kanidmd_lib/idm/
application.rs

1use super::ldap::{LdapBoundToken, LdapSession};
2use crate::idm::account::Account;
3use crate::idm::event::LdapApplicationAuthEvent;
4use crate::idm::server::{IdmServerAuthTransaction, IdmServerTransaction};
5use crate::prelude::*;
6use concread::cowcell::*;
7use hashbrown::HashMap;
8use kanidm_proto::internal::OperationError;
9use std::sync::Arc;
10use uuid::Uuid;
11
12#[derive(Clone)]
13pub(crate) struct Application {
14    pub uuid: Uuid,
15    pub name: String,
16    pub linked_group: Uuid,
17}
18
19impl Application {
20    #[cfg(test)]
21    pub(crate) fn try_from_entry_ro(
22        value: &Entry<EntrySealed, EntryCommitted>,
23        _qs: &mut QueryServerReadTransaction,
24    ) -> Result<Self, OperationError> {
25        if !value.attribute_equality(Attribute::Class, &EntryClass::Application.to_partialvalue()) {
26            return Err(OperationError::MissingClass(ENTRYCLASS_APPLICATION.into()));
27        }
28
29        let uuid = value.get_uuid();
30
31        let name = value
32            .get_ava_single_iname(Attribute::Name)
33            .map(|s| s.to_string())
34            .ok_or_else(|| OperationError::MissingAttribute(Attribute::Name))?;
35
36        let linked_group = value
37            .get_ava_single_refer(Attribute::LinkedGroup)
38            .ok_or_else(|| OperationError::MissingAttribute(Attribute::LinkedGroup))?;
39
40        Ok(Application {
41            name,
42            uuid,
43            linked_group,
44        })
45    }
46}
47
48#[derive(Clone)]
49struct LdapApplicationsInner {
50    set: HashMap<String, Application>,
51}
52
53pub struct LdapApplications {
54    inner: CowCell<LdapApplicationsInner>,
55}
56
57pub struct LdapApplicationsReadTransaction {
58    inner: CowCellReadTxn<LdapApplicationsInner>,
59}
60
61pub struct LdapApplicationsWriteTransaction<'a> {
62    inner: CowCellWriteTxn<'a, LdapApplicationsInner>,
63}
64
65impl LdapApplicationsWriteTransaction<'_> {
66    pub fn reload(&mut self, value: Vec<Arc<EntrySealedCommitted>>) -> Result<(), OperationError> {
67        let app_set: Result<HashMap<_, _>, _> = value
68            .into_iter()
69            .map(|ent| {
70                if !ent.attribute_equality(Attribute::Class, &EntryClass::Application.into()) {
71                    error!("Missing class application");
72                    return Err(OperationError::InvalidEntryState);
73                }
74
75                let uuid = ent.get_uuid();
76                let name = ent
77                    .get_ava_single_iname(Attribute::Name)
78                    .map(str::to_string)
79                    .ok_or(OperationError::InvalidValueState)?;
80
81                let linked_group = ent
82                    .get_ava_single_refer(Attribute::LinkedGroup)
83                    .ok_or(OperationError::InvalidValueState)?;
84
85                let app = Application {
86                    uuid,
87                    name: name.clone(),
88                    linked_group,
89                };
90
91                Ok((name, app))
92            })
93            .collect();
94
95        let new_inner = LdapApplicationsInner { set: app_set? };
96        self.inner.replace(new_inner);
97
98        Ok(())
99    }
100
101    pub fn commit(self) {
102        self.inner.commit();
103    }
104}
105
106impl LdapApplications {
107    pub fn read(&self) -> LdapApplicationsReadTransaction {
108        LdapApplicationsReadTransaction {
109            inner: self.inner.read(),
110        }
111    }
112
113    pub fn write(&self) -> LdapApplicationsWriteTransaction {
114        LdapApplicationsWriteTransaction {
115            inner: self.inner.write(),
116        }
117    }
118}
119
120impl TryFrom<Vec<Arc<EntrySealedCommitted>>> for LdapApplications {
121    type Error = OperationError;
122
123    fn try_from(value: Vec<Arc<EntrySealedCommitted>>) -> Result<Self, Self::Error> {
124        let apps = LdapApplications {
125            inner: CowCell::new(LdapApplicationsInner {
126                set: HashMap::new(),
127            }),
128        };
129
130        let mut apps_wr = apps.write();
131        apps_wr.reload(value)?;
132        apps_wr.commit();
133        Ok(apps)
134    }
135}
136
137impl IdmServerAuthTransaction<'_> {
138    pub async fn application_auth_ldap(
139        &mut self,
140        lae: &LdapApplicationAuthEvent,
141        ct: Duration,
142    ) -> Result<Option<LdapBoundToken>, OperationError> {
143        let usr_entry = self.get_qs_txn().internal_search_uuid(lae.target)?;
144
145        let account: Account =
146            Account::try_from_entry_ro(&usr_entry, &mut self.qs_read).map_err(|e| {
147                error!("Failed to search account {:?}", e);
148                e
149            })?;
150
151        if account.is_anonymous() {
152            return Err(OperationError::InvalidUuid);
153        }
154
155        if !account.is_within_valid_time(ct) {
156            security_info!("Account has expired or is not yet valid, not allowing to proceed");
157            return Err(OperationError::SessionExpired);
158        }
159
160        let application = self
161            .applications
162            .inner
163            .set
164            .get(&lae.application)
165            .ok_or_else(|| {
166                info!("Application {:?} not found", lae.application);
167                OperationError::NoMatchingEntries
168            })?;
169
170        // Check linked group membership
171        let is_memberof = usr_entry
172            .get_ava_refer(Attribute::MemberOf)
173            .map(|member_of_set| member_of_set.contains(&application.linked_group))
174            .unwrap_or_default();
175
176        if !is_memberof {
177            debug!(
178                "User {:?} not member of application {}:{:?} linked group {:?}",
179                account.uuid, application.name, application.uuid, application.linked_group,
180            );
181            return Ok(None);
182        }
183
184        match account.verify_application_password(application, lae.cleartext.as_str())? {
185            Some(_) => {
186                let session_id = Uuid::new_v4();
187                security_info!(
188                    "Starting session {} for {} {} with application {}:{:?}",
189                    session_id,
190                    account.spn,
191                    account.uuid,
192                    application.name,
193                    application.uuid,
194                );
195
196                Ok(Some(LdapBoundToken {
197                    spn: account.spn,
198                    session_id,
199                    effective_session: LdapSession::UnixBind(account.uuid),
200                }))
201            }
202            None => {
203                security_info!("Account does not have a configured application password.");
204                Ok(None)
205            }
206        }
207    }
208}
209
210#[derive(Debug)]
211pub struct GenerateApplicationPasswordEvent {
212    pub ident: Identity,
213    pub target: Uuid,
214    pub application: Uuid,
215    pub label: String,
216}
217
218impl GenerateApplicationPasswordEvent {
219    pub fn from_parts(
220        ident: Identity,
221        target: Uuid,
222        application: Uuid,
223        label: String,
224    ) -> Result<Self, OperationError> {
225        Ok(GenerateApplicationPasswordEvent {
226            ident,
227            target,
228            application,
229            label,
230        })
231    }
232
233    pub fn new_internal(target: Uuid, application: Uuid, label: String) -> Self {
234        GenerateApplicationPasswordEvent {
235            ident: Identity::from_internal(),
236            target,
237            application,
238            label,
239        }
240    }
241}
242
243#[cfg(test)]
244mod tests {
245    use crate::event::CreateEvent;
246    use crate::idm::account::Account;
247    use crate::idm::application::Application;
248    use crate::idm::application::GenerateApplicationPasswordEvent;
249    use crate::idm::server::IdmServerTransaction;
250    use crate::idm::serviceaccount::{DestroyApiTokenEvent, GenerateApiTokenEvent};
251    use crate::prelude::*;
252    use compact_jwt::{dangernoverify::JwsDangerReleaseWithoutVerify, JwsVerifier};
253    use kanidm_proto::internal::ApiToken as ProtoApiToken;
254    use std::time::Duration;
255
256    const TEST_CURRENT_TIME: u64 = 6000;
257
258    // Tests that only the correct combinations of [Account, Person, Application and
259    // ServiceAccount] classes are allowed.
260    #[idm_test]
261    async fn test_idm_application_excludes(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
262        let ct = Duration::from_secs(TEST_CURRENT_TIME);
263        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
264
265        // ServiceAccount, Application and Person not allowed together
266        let test_grp_name = "testgroup1";
267        let test_grp_uuid = Uuid::new_v4();
268        let e1 = entry_init!(
269            (Attribute::Class, EntryClass::Object.to_value()),
270            (Attribute::Class, EntryClass::Group.to_value()),
271            (Attribute::Name, Value::new_iname(test_grp_name)),
272            (Attribute::Uuid, Value::Uuid(test_grp_uuid))
273        );
274        let test_entry_uuid = Uuid::new_v4();
275        let e2 = entry_init!(
276            (Attribute::Class, EntryClass::Object.to_value()),
277            (Attribute::Class, EntryClass::Account.to_value()),
278            (Attribute::Class, EntryClass::ServiceAccount.to_value()),
279            (Attribute::Class, EntryClass::Application.to_value()),
280            (Attribute::Class, EntryClass::Person.to_value()),
281            (Attribute::Name, Value::new_iname("test_app_name")),
282            (Attribute::Uuid, Value::Uuid(test_entry_uuid)),
283            (Attribute::Description, Value::new_utf8s("test_app_desc")),
284            (
285                Attribute::DisplayName,
286                Value::new_utf8s("test_app_dispname")
287            ),
288            (Attribute::LinkedGroup, Value::Refer(test_grp_uuid))
289        );
290        let ce = CreateEvent::new_internal(vec![e1, e2]);
291        let cr = idms_prox_write.qs_write.create(&ce);
292        assert!(cr.is_err());
293
294        // Application and Person not allowed together
295        let test_grp_name = "testgroup1";
296        let test_grp_uuid = Uuid::new_v4();
297        let e1 = entry_init!(
298            (Attribute::Class, EntryClass::Object.to_value()),
299            (Attribute::Class, EntryClass::Group.to_value()),
300            (Attribute::Name, Value::new_iname(test_grp_name)),
301            (Attribute::Uuid, Value::Uuid(test_grp_uuid))
302        );
303        let test_entry_uuid = Uuid::new_v4();
304        let e2 = entry_init!(
305            (Attribute::Class, EntryClass::Object.to_value()),
306            (Attribute::Class, EntryClass::Account.to_value()),
307            (Attribute::Class, EntryClass::Application.to_value()),
308            (Attribute::Class, EntryClass::Person.to_value()),
309            (Attribute::Name, Value::new_iname("test_app_name")),
310            (Attribute::Uuid, Value::Uuid(test_entry_uuid)),
311            (Attribute::Description, Value::new_utf8s("test_app_desc")),
312            (
313                Attribute::DisplayName,
314                Value::new_utf8s("test_app_dispname")
315            ),
316            (Attribute::LinkedGroup, Value::Refer(test_grp_uuid))
317        );
318        let ce = CreateEvent::new_internal(vec![e1, e2]);
319        let cr = idms_prox_write.qs_write.create(&ce);
320        assert!(cr.is_err());
321
322        // Supplements not satisfied, Application supplements ServiceAccount
323        let test_grp_name = "testgroup1";
324        let test_grp_uuid = Uuid::new_v4();
325        let e1 = entry_init!(
326            (Attribute::Class, EntryClass::Object.to_value()),
327            (Attribute::Class, EntryClass::Group.to_value()),
328            (Attribute::Name, Value::new_iname(test_grp_name)),
329            (Attribute::Uuid, Value::Uuid(test_grp_uuid))
330        );
331        let test_entry_uuid = Uuid::new_v4();
332        let e2 = entry_init!(
333            (Attribute::Class, EntryClass::Object.to_value()),
334            (Attribute::Class, EntryClass::Account.to_value()),
335            (Attribute::Class, EntryClass::Application.to_value()),
336            (Attribute::Name, Value::new_iname("test_app_name")),
337            (Attribute::Uuid, Value::Uuid(test_entry_uuid)),
338            (Attribute::Description, Value::new_utf8s("test_app_desc")),
339            (Attribute::LinkedGroup, Value::Refer(test_grp_uuid))
340        );
341        let ce = CreateEvent::new_internal(vec![e1, e2]);
342        let cr = idms_prox_write.qs_write.create(&ce);
343        assert!(cr.is_err());
344
345        // Supplements not satisfied, Application supplements ServiceAccount
346        let test_grp_name = "testgroup1";
347        let test_grp_uuid = Uuid::new_v4();
348        let e1 = entry_init!(
349            (Attribute::Class, EntryClass::Object.to_value()),
350            (Attribute::Class, EntryClass::Group.to_value()),
351            (Attribute::Name, Value::new_iname(test_grp_name)),
352            (Attribute::Uuid, Value::Uuid(test_grp_uuid))
353        );
354        let test_entry_uuid = Uuid::new_v4();
355        let e2 = entry_init!(
356            (Attribute::Class, EntryClass::Object.to_value()),
357            (Attribute::Class, EntryClass::Application.to_value()),
358            (Attribute::Name, Value::new_iname("test_app_name")),
359            (Attribute::Uuid, Value::Uuid(test_entry_uuid)),
360            (Attribute::Description, Value::new_utf8s("test_app_desc")),
361            (Attribute::LinkedGroup, Value::Refer(test_grp_uuid))
362        );
363        let ce = CreateEvent::new_internal(vec![e1, e2]);
364        let cr = idms_prox_write.qs_write.create(&ce);
365        assert!(cr.is_err());
366
367        // Supplements satisfied, Application supplements ServiceAccount
368        let test_grp_name = "testgroup1";
369        let test_grp_uuid = Uuid::new_v4();
370        let e1 = entry_init!(
371            (Attribute::Class, EntryClass::Object.to_value()),
372            (Attribute::Class, EntryClass::Group.to_value()),
373            (Attribute::Name, Value::new_iname(test_grp_name)),
374            (Attribute::Uuid, Value::Uuid(test_grp_uuid))
375        );
376        let test_entry_uuid = Uuid::new_v4();
377        let e2 = entry_init!(
378            (Attribute::Class, EntryClass::Object.to_value()),
379            (Attribute::Class, EntryClass::Application.to_value()),
380            (Attribute::Class, EntryClass::ServiceAccount.to_value()),
381            (Attribute::Name, Value::new_iname("test_app_name")),
382            (Attribute::Uuid, Value::Uuid(test_entry_uuid)),
383            (Attribute::Description, Value::new_utf8s("test_app_desc")),
384            (Attribute::LinkedGroup, Value::Refer(test_grp_uuid))
385        );
386        let ce = CreateEvent::new_internal(vec![e1, e2]);
387        let cr = idms_prox_write.qs_write.create(&ce);
388        assert!(cr.is_ok());
389    }
390
391    // Tests it is not possible to create an application without the linked group attribute
392    #[idm_test]
393    async fn test_idm_application_no_linked_group(
394        idms: &IdmServer,
395        _idms_delayed: &mut IdmServerDelayed,
396    ) {
397        let ct = Duration::from_secs(TEST_CURRENT_TIME);
398        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
399
400        let test_entry_uuid = Uuid::new_v4();
401
402        let e1 = entry_init!(
403            (Attribute::Class, EntryClass::Object.to_value()),
404            (Attribute::Class, EntryClass::Account.to_value()),
405            (Attribute::Class, EntryClass::ServiceAccount.to_value()),
406            (Attribute::Class, EntryClass::Application.to_value()),
407            (Attribute::Name, Value::new_iname("test_app_name")),
408            (Attribute::Uuid, Value::Uuid(test_entry_uuid)),
409            (Attribute::Description, Value::new_utf8s("test_app_desc")),
410            (
411                Attribute::DisplayName,
412                Value::new_utf8s("test_app_dispname")
413            )
414        );
415
416        let ce = CreateEvent::new_internal(vec![e1]);
417        let cr = idms_prox_write.qs_write.create(&ce);
418        assert!(cr.is_err());
419    }
420
421    // Tests creating an application with a real linked group attribute
422    #[idm_test]
423    async fn test_idm_application_linked_group(
424        idms: &IdmServer,
425        _idms_delayed: &mut IdmServerDelayed,
426    ) {
427        let test_entry_name = "test_app_name";
428        let test_entry_uuid = Uuid::new_v4();
429        let test_grp_name = "testgroup1";
430        let test_grp_uuid = Uuid::new_v4();
431
432        {
433            let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
434
435            let e1 = entry_init!(
436                (Attribute::Class, EntryClass::Object.to_value()),
437                (Attribute::Class, EntryClass::Group.to_value()),
438                (Attribute::Name, Value::new_iname(test_grp_name)),
439                (Attribute::Uuid, Value::Uuid(test_grp_uuid))
440            );
441
442            let e2 = entry_init!(
443                (Attribute::Class, EntryClass::Object.to_value()),
444                (Attribute::Class, EntryClass::Account.to_value()),
445                (Attribute::Class, EntryClass::ServiceAccount.to_value()),
446                (Attribute::Class, EntryClass::Application.to_value()),
447                (Attribute::Name, Value::new_iname(test_entry_name)),
448                (Attribute::Uuid, Value::Uuid(test_entry_uuid)),
449                (Attribute::Description, Value::new_utf8s("test_app_desc")),
450                (
451                    Attribute::DisplayName,
452                    Value::new_utf8s("test_app_dispname")
453                ),
454                (Attribute::LinkedGroup, Value::Refer(test_grp_uuid))
455            );
456
457            let ce = CreateEvent::new_internal(vec![e1, e2]);
458            let cr = idms_prox_write.qs_write.create(&ce);
459            assert!(cr.is_ok());
460
461            let cr = idms_prox_write.qs_write.commit();
462            assert!(cr.is_ok());
463        }
464
465        {
466            let mut idms_prox_read = idms.proxy_read().await.unwrap();
467            let app = idms_prox_read
468                .qs_read
469                .internal_search_uuid(test_entry_uuid)
470                .and_then(|entry| {
471                    Application::try_from_entry_ro(&entry, &mut idms_prox_read.qs_read)
472                })
473                .map_err(|e| {
474                    trace!("Error: {:?}", e);
475                    e
476                });
477            assert!(app.is_ok());
478
479            let app = app.unwrap();
480            assert_eq!(app.name, "test_app_name");
481            assert_eq!(app.uuid, test_entry_uuid);
482            assert_eq!(app.linked_group, test_grp_uuid);
483        }
484
485        // Test reference integrity. An attempt to remove a linked group blocks
486        // the group being deleted
487        {
488            let de = DeleteEvent::new_internal_invalid(filter!(f_eq(
489                Attribute::Uuid,
490                PartialValue::Uuid(test_grp_uuid)
491            )));
492            let mut idms_proxy_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
493            assert!(idms_proxy_write.qs_write.delete(&de).is_err());
494        }
495
496        {
497            let de = DeleteEvent::new_internal_invalid(filter!(f_eq(
498                Attribute::Uuid,
499                PartialValue::Uuid(test_entry_uuid)
500            )));
501            let mut idms_proxy_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
502            assert!(idms_proxy_write.qs_write.delete(&de).is_ok());
503            assert!(idms_proxy_write.qs_write.commit().is_ok());
504        }
505
506        {
507            let de = DeleteEvent::new_internal_invalid(filter!(f_eq(
508                Attribute::Uuid,
509                PartialValue::Uuid(test_grp_uuid)
510            )));
511            let mut idms_proxy_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
512            assert!(idms_proxy_write.qs_write.delete(&de).is_ok());
513            assert!(idms_proxy_write.qs_write.commit().is_ok());
514        }
515    }
516
517    #[idm_test]
518    async fn test_idm_application_delete(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
519        let test_usr_name = "testuser1";
520        let test_usr_uuid = Uuid::new_v4();
521        let test_app_name = "testapp1";
522        let test_app_uuid = Uuid::new_v4();
523        let test_grp_name = "testgroup1";
524        let test_grp_uuid = Uuid::new_v4();
525
526        {
527            let ct = duration_from_epoch_now();
528            let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
529
530            let e1 = entry_init!(
531                (Attribute::Class, EntryClass::Object.to_value()),
532                (Attribute::Class, EntryClass::Account.to_value()),
533                (Attribute::Class, EntryClass::Person.to_value()),
534                (Attribute::Name, Value::new_iname(test_usr_name)),
535                (Attribute::Uuid, Value::Uuid(test_usr_uuid)),
536                (Attribute::Description, Value::new_utf8s(test_usr_name)),
537                (Attribute::DisplayName, Value::new_utf8s(test_usr_name))
538            );
539
540            let e2 = entry_init!(
541                (Attribute::Class, EntryClass::Object.to_value()),
542                (Attribute::Class, EntryClass::Group.to_value()),
543                (Attribute::Name, Value::new_iname(test_grp_name)),
544                (Attribute::Uuid, Value::Uuid(test_grp_uuid)),
545                (Attribute::Member, Value::Refer(test_usr_uuid))
546            );
547
548            let e3 = entry_init!(
549                (Attribute::Class, EntryClass::Object.to_value()),
550                (Attribute::Class, EntryClass::ServiceAccount.to_value()),
551                (Attribute::Class, EntryClass::Application.to_value()),
552                (Attribute::Name, Value::new_iname(test_app_name)),
553                (Attribute::Uuid, Value::Uuid(test_app_uuid)),
554                (Attribute::LinkedGroup, Value::Refer(test_grp_uuid))
555            );
556
557            let ce = CreateEvent::new_internal(vec![e1, e2, e3]);
558            let cr = idms_prox_write.qs_write.create(&ce);
559            assert!(cr.is_ok());
560
561            let ev = GenerateApplicationPasswordEvent {
562                ident: Identity::from_internal(),
563                target: test_usr_uuid,
564                application: test_app_uuid,
565                label: "label".to_string(),
566            };
567            idms_prox_write
568                .generate_application_password(&ev)
569                .expect("Failed to create application password");
570
571            let cr = idms_prox_write.qs_write.commit();
572            assert!(cr.is_ok());
573        }
574
575        {
576            let mut idms_prox_read = idms.proxy_read().await.unwrap();
577            let account = idms_prox_read
578                .qs_read
579                .internal_search_uuid(test_usr_uuid)
580                .and_then(|entry| Account::try_from_entry_ro(&entry, &mut idms_prox_read.qs_read))
581                .map_err(|e| {
582                    trace!("Error: {:?}", e);
583                    e
584                })
585                .expect("Failed to search for account");
586
587            assert!(account.apps_pwds.values().count() > 0);
588        }
589
590        // Test reference integrity. If app is removed linked application passwords must go
591        {
592            let de = DeleteEvent::new_internal_invalid(filter!(f_eq(
593                Attribute::Uuid,
594                PartialValue::Uuid(test_app_uuid)
595            )));
596            let mut idms_proxy_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
597            assert!(idms_proxy_write.qs_write.delete(&de).is_ok());
598            assert!(idms_proxy_write.qs_write.commit().is_ok());
599        }
600
601        {
602            let mut idms_prox_read = idms.proxy_read().await.unwrap();
603            assert!(idms_prox_read
604                .qs_read
605                .internal_search_uuid(test_app_uuid)
606                .is_err());
607        }
608
609        {
610            let mut idms_prox_read = idms.proxy_read().await.unwrap();
611            let account = idms_prox_read
612                .qs_read
613                .internal_search_uuid(test_usr_uuid)
614                .and_then(|entry| Account::try_from_entry_ro(&entry, &mut idms_prox_read.qs_read))
615                .map_err(|e| {
616                    trace!("Error: {:?}", e);
617                    e
618                })
619                .expect("Failed to search for account");
620
621            assert_eq!(account.apps_pwds.values().count(), 0);
622        }
623    }
624
625    // Test apitoken for application entries
626    #[idm_test]
627    async fn test_idm_application_api_token(
628        idms: &IdmServer,
629        _idms_delayed: &mut IdmServerDelayed,
630    ) {
631        let ct = Duration::from_secs(TEST_CURRENT_TIME);
632        let past_grc = Duration::from_secs(TEST_CURRENT_TIME + 1) + AUTH_TOKEN_GRACE_WINDOW;
633        let exp = Duration::from_secs(TEST_CURRENT_TIME + 6000);
634        let post_exp = Duration::from_secs(TEST_CURRENT_TIME + 6010);
635        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
636
637        let test_entry_uuid = Uuid::new_v4();
638        let test_group_uuid = Uuid::new_v4();
639
640        let e1 = entry_init!(
641            (Attribute::Class, EntryClass::Object.to_value()),
642            (Attribute::Class, EntryClass::Group.to_value()),
643            (Attribute::Name, Value::new_iname("test_group")),
644            (Attribute::Uuid, Value::Uuid(test_group_uuid))
645        );
646
647        let e2 = entry_init!(
648            (Attribute::Class, EntryClass::Object.to_value()),
649            (Attribute::Class, EntryClass::ServiceAccount.to_value()),
650            (Attribute::Class, EntryClass::Application.to_value()),
651            (Attribute::Name, Value::new_iname("test_app_name")),
652            (Attribute::Uuid, Value::Uuid(test_entry_uuid)),
653            (Attribute::Description, Value::new_utf8s("test_app_desc")),
654            (Attribute::LinkedGroup, Value::Refer(test_group_uuid))
655        );
656
657        let ce = CreateEvent::new_internal(vec![e1, e2]);
658        let cr = idms_prox_write.qs_write.create(&ce);
659        assert!(cr.is_ok());
660
661        let gte = GenerateApiTokenEvent::new_internal(test_entry_uuid, "TestToken", Some(exp));
662
663        let api_token = idms_prox_write
664            .service_account_generate_api_token(&gte, ct)
665            .expect("failed to generate new api token");
666
667        trace!(?api_token);
668
669        // Deserialise it.
670        let jws_verifier = JwsDangerReleaseWithoutVerify::default();
671
672        let apitoken_inner = jws_verifier
673            .verify(&api_token)
674            .unwrap()
675            .from_json::<ProtoApiToken>()
676            .unwrap();
677
678        let ident = idms_prox_write
679            .validate_client_auth_info_to_ident(api_token.clone().into(), ct)
680            .expect("Unable to verify api token.");
681
682        assert_eq!(ident.get_uuid(), Some(test_entry_uuid));
683
684        // Check the expiry
685        assert!(
686            idms_prox_write
687                .validate_client_auth_info_to_ident(api_token.clone().into(), post_exp)
688                .expect_err("Should not succeed")
689                == OperationError::SessionExpired
690        );
691
692        // Delete session
693        let dte =
694            DestroyApiTokenEvent::new_internal(apitoken_inner.account_id, apitoken_inner.token_id);
695        assert!(idms_prox_write
696            .service_account_destroy_api_token(&dte)
697            .is_ok());
698
699        // Within gracewindow?
700        // This is okay, because we are within the gracewindow.
701        let ident = idms_prox_write
702            .validate_client_auth_info_to_ident(api_token.clone().into(), ct)
703            .expect("Unable to verify api token.");
704        assert_eq!(ident.get_uuid(), Some(test_entry_uuid));
705
706        // Past gracewindow?
707        assert!(
708            idms_prox_write
709                .validate_client_auth_info_to_ident(api_token.clone().into(), past_grc)
710                .expect_err("Should not succeed")
711                == OperationError::SessionExpired
712        );
713
714        assert!(idms_prox_write.commit().is_ok());
715    }
716}