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 it is not possible to create an application without the linked group attribute
259    #[idm_test]
260    async fn test_idm_application_no_linked_group(
261        idms: &IdmServer,
262        _idms_delayed: &mut IdmServerDelayed,
263    ) {
264        let ct = Duration::from_secs(TEST_CURRENT_TIME);
265        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
266
267        let test_entry_uuid = Uuid::new_v4();
268
269        let e1 = entry_init!(
270            (Attribute::Class, EntryClass::Object.to_value()),
271            (Attribute::Class, EntryClass::Account.to_value()),
272            (Attribute::Class, EntryClass::ServiceAccount.to_value()),
273            (Attribute::Class, EntryClass::Application.to_value()),
274            (Attribute::DisplayName, Value::new_utf8s("Application")),
275            (Attribute::Name, Value::new_iname("test_app_name")),
276            (Attribute::Uuid, Value::Uuid(test_entry_uuid)),
277            (Attribute::Description, Value::new_utf8s("test_app_desc")),
278            (
279                Attribute::DisplayName,
280                Value::new_utf8s("test_app_dispname")
281            )
282        );
283
284        let ce = CreateEvent::new_internal(vec![e1]);
285        let cr = idms_prox_write.qs_write.create(&ce);
286        assert!(cr.is_err());
287    }
288
289    // Tests creating an application with a real linked group attribute
290    #[idm_test]
291    async fn test_idm_application_linked_group(
292        idms: &IdmServer,
293        _idms_delayed: &mut IdmServerDelayed,
294    ) {
295        let test_entry_name = "test_app_name";
296        let test_entry_uuid = Uuid::new_v4();
297        let test_grp_name = "testgroup1";
298        let test_grp_uuid = Uuid::new_v4();
299
300        {
301            let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
302
303            let e1 = entry_init!(
304                (Attribute::Class, EntryClass::Object.to_value()),
305                (Attribute::Class, EntryClass::Group.to_value()),
306                (Attribute::Name, Value::new_iname(test_grp_name)),
307                (Attribute::Uuid, Value::Uuid(test_grp_uuid))
308            );
309
310            let e2 = entry_init!(
311                (Attribute::Class, EntryClass::Object.to_value()),
312                (Attribute::Class, EntryClass::Account.to_value()),
313                (Attribute::Class, EntryClass::ServiceAccount.to_value()),
314                (Attribute::Class, EntryClass::Application.to_value()),
315                (Attribute::Name, Value::new_iname(test_entry_name)),
316                (Attribute::Uuid, Value::Uuid(test_entry_uuid)),
317                (Attribute::Description, Value::new_utf8s("test_app_desc")),
318                (
319                    Attribute::DisplayName,
320                    Value::new_utf8s("test_app_dispname")
321                ),
322                (Attribute::LinkedGroup, Value::Refer(test_grp_uuid))
323            );
324
325            let ce = CreateEvent::new_internal(vec![e1, e2]);
326            let cr = idms_prox_write.qs_write.create(&ce);
327            assert!(cr.is_ok());
328
329            let cr = idms_prox_write.qs_write.commit();
330            assert!(cr.is_ok());
331        }
332
333        {
334            let mut idms_prox_read = idms.proxy_read().await.unwrap();
335            let app = idms_prox_read
336                .qs_read
337                .internal_search_uuid(test_entry_uuid)
338                .and_then(|entry| {
339                    Application::try_from_entry_ro(&entry, &mut idms_prox_read.qs_read)
340                })
341                .map_err(|e| {
342                    trace!("Error: {:?}", e);
343                    e
344                });
345            assert!(app.is_ok());
346
347            let app = app.unwrap();
348            assert_eq!(app.name, "test_app_name");
349            assert_eq!(app.uuid, test_entry_uuid);
350            assert_eq!(app.linked_group, test_grp_uuid);
351        }
352
353        // Test reference integrity. An attempt to remove a linked group blocks
354        // the group being deleted
355        {
356            let de = DeleteEvent::new_internal_invalid(filter!(f_eq(
357                Attribute::Uuid,
358                PartialValue::Uuid(test_grp_uuid)
359            )));
360            let mut idms_proxy_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
361            assert!(idms_proxy_write.qs_write.delete(&de).is_err());
362        }
363
364        {
365            let de = DeleteEvent::new_internal_invalid(filter!(f_eq(
366                Attribute::Uuid,
367                PartialValue::Uuid(test_entry_uuid)
368            )));
369            let mut idms_proxy_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
370            assert!(idms_proxy_write.qs_write.delete(&de).is_ok());
371            assert!(idms_proxy_write.qs_write.commit().is_ok());
372        }
373
374        {
375            let de = DeleteEvent::new_internal_invalid(filter!(f_eq(
376                Attribute::Uuid,
377                PartialValue::Uuid(test_grp_uuid)
378            )));
379            let mut idms_proxy_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
380            assert!(idms_proxy_write.qs_write.delete(&de).is_ok());
381            assert!(idms_proxy_write.qs_write.commit().is_ok());
382        }
383    }
384
385    #[idm_test]
386    async fn test_idm_application_delete(idms: &IdmServer, _idms_delayed: &mut IdmServerDelayed) {
387        let test_usr_name = "testuser1";
388        let test_usr_uuid = Uuid::new_v4();
389        let test_app_name = "testapp1";
390        let test_app_uuid = Uuid::new_v4();
391        let test_grp_name = "testgroup1";
392        let test_grp_uuid = Uuid::new_v4();
393
394        {
395            let ct = duration_from_epoch_now();
396            let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
397
398            let e1 = entry_init!(
399                (Attribute::Class, EntryClass::Object.to_value()),
400                (Attribute::Class, EntryClass::Account.to_value()),
401                (Attribute::Class, EntryClass::Person.to_value()),
402                (Attribute::Name, Value::new_iname(test_usr_name)),
403                (Attribute::Uuid, Value::Uuid(test_usr_uuid)),
404                (Attribute::Description, Value::new_utf8s(test_usr_name)),
405                (Attribute::DisplayName, Value::new_utf8s(test_usr_name))
406            );
407
408            let e2 = entry_init!(
409                (Attribute::Class, EntryClass::Object.to_value()),
410                (Attribute::Class, EntryClass::Group.to_value()),
411                (Attribute::Name, Value::new_iname(test_grp_name)),
412                (Attribute::Uuid, Value::Uuid(test_grp_uuid)),
413                (Attribute::Member, Value::Refer(test_usr_uuid))
414            );
415
416            let e3 = entry_init!(
417                (Attribute::Class, EntryClass::Object.to_value()),
418                (Attribute::Class, EntryClass::Account.to_value()),
419                (Attribute::Class, EntryClass::ServiceAccount.to_value()),
420                (Attribute::Class, EntryClass::Application.to_value()),
421                (Attribute::DisplayName, Value::new_utf8s("Application")),
422                (Attribute::Name, Value::new_iname(test_app_name)),
423                (Attribute::Uuid, Value::Uuid(test_app_uuid)),
424                (Attribute::LinkedGroup, Value::Refer(test_grp_uuid))
425            );
426
427            let ce = CreateEvent::new_internal(vec![e1, e2, e3]);
428            let cr = idms_prox_write.qs_write.create(&ce);
429            assert!(cr.is_ok());
430
431            let ev = GenerateApplicationPasswordEvent {
432                ident: Identity::from_internal(),
433                target: test_usr_uuid,
434                application: test_app_uuid,
435                label: "label".to_string(),
436            };
437            idms_prox_write
438                .generate_application_password(&ev)
439                .expect("Failed to create application password");
440
441            let cr = idms_prox_write.qs_write.commit();
442            assert!(cr.is_ok());
443        }
444
445        {
446            let mut idms_prox_read = idms.proxy_read().await.unwrap();
447            let account = idms_prox_read
448                .qs_read
449                .internal_search_uuid(test_usr_uuid)
450                .and_then(|entry| Account::try_from_entry_ro(&entry, &mut idms_prox_read.qs_read))
451                .map_err(|e| {
452                    trace!("Error: {:?}", e);
453                    e
454                })
455                .expect("Failed to search for account");
456
457            assert!(account.apps_pwds.values().count() > 0);
458        }
459
460        // Test reference integrity. If app is removed linked application passwords must go
461        {
462            let de = DeleteEvent::new_internal_invalid(filter!(f_eq(
463                Attribute::Uuid,
464                PartialValue::Uuid(test_app_uuid)
465            )));
466            let mut idms_proxy_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
467            assert!(idms_proxy_write.qs_write.delete(&de).is_ok());
468            assert!(idms_proxy_write.qs_write.commit().is_ok());
469        }
470
471        {
472            let mut idms_prox_read = idms.proxy_read().await.unwrap();
473            assert!(idms_prox_read
474                .qs_read
475                .internal_search_uuid(test_app_uuid)
476                .is_err());
477        }
478
479        {
480            let mut idms_prox_read = idms.proxy_read().await.unwrap();
481            let account = idms_prox_read
482                .qs_read
483                .internal_search_uuid(test_usr_uuid)
484                .and_then(|entry| Account::try_from_entry_ro(&entry, &mut idms_prox_read.qs_read))
485                .map_err(|e| {
486                    trace!("Error: {:?}", e);
487                    e
488                })
489                .expect("Failed to search for account");
490
491            assert_eq!(account.apps_pwds.values().count(), 0);
492        }
493    }
494
495    // Test apitoken for application entries
496    #[idm_test]
497    async fn test_idm_application_api_token(
498        idms: &IdmServer,
499        _idms_delayed: &mut IdmServerDelayed,
500    ) {
501        let ct = Duration::from_secs(TEST_CURRENT_TIME);
502        let past_grc = Duration::from_secs(TEST_CURRENT_TIME + 1) + AUTH_TOKEN_GRACE_WINDOW;
503        let exp = Duration::from_secs(TEST_CURRENT_TIME + 6000);
504        let post_exp = Duration::from_secs(TEST_CURRENT_TIME + 6010);
505        let mut idms_prox_write = idms.proxy_write(ct).await.unwrap();
506
507        let test_entry_uuid = Uuid::new_v4();
508        let test_group_uuid = Uuid::new_v4();
509
510        let e1 = entry_init!(
511            (Attribute::Class, EntryClass::Object.to_value()),
512            (Attribute::Class, EntryClass::Group.to_value()),
513            (Attribute::Name, Value::new_iname("test_group")),
514            (Attribute::Uuid, Value::Uuid(test_group_uuid))
515        );
516
517        let e2 = entry_init!(
518            (Attribute::Class, EntryClass::Object.to_value()),
519            (Attribute::Class, EntryClass::ServiceAccount.to_value()),
520            (Attribute::Class, EntryClass::Account.to_value()),
521            (Attribute::Class, EntryClass::Application.to_value()),
522            (Attribute::DisplayName, Value::new_utf8s("Application")),
523            (Attribute::Name, Value::new_iname("test_app_name")),
524            (Attribute::Uuid, Value::Uuid(test_entry_uuid)),
525            (Attribute::Description, Value::new_utf8s("test_app_desc")),
526            (Attribute::LinkedGroup, Value::Refer(test_group_uuid))
527        );
528
529        let ce = CreateEvent::new_internal(vec![e1, e2]);
530        let cr = idms_prox_write.qs_write.create(&ce);
531        assert!(cr.is_ok());
532
533        let gte = GenerateApiTokenEvent::new_internal(test_entry_uuid, "TestToken", Some(exp));
534
535        let api_token = idms_prox_write
536            .service_account_generate_api_token(&gte, ct)
537            .expect("failed to generate new api token");
538
539        trace!(?api_token);
540
541        // Deserialise it.
542        let jws_verifier = JwsDangerReleaseWithoutVerify::default();
543
544        let apitoken_inner = jws_verifier
545            .verify(&api_token)
546            .unwrap()
547            .from_json::<ProtoApiToken>()
548            .unwrap();
549
550        let ident = idms_prox_write
551            .validate_client_auth_info_to_ident(api_token.clone().into(), ct)
552            .expect("Unable to verify api token.");
553
554        assert_eq!(ident.get_uuid(), Some(test_entry_uuid));
555
556        // Check the expiry
557        assert!(
558            idms_prox_write
559                .validate_client_auth_info_to_ident(api_token.clone().into(), post_exp)
560                .expect_err("Should not succeed")
561                == OperationError::SessionExpired
562        );
563
564        // Delete session
565        let dte =
566            DestroyApiTokenEvent::new_internal(apitoken_inner.account_id, apitoken_inner.token_id);
567        assert!(idms_prox_write
568            .service_account_destroy_api_token(&dte)
569            .is_ok());
570
571        // Within gracewindow?
572        // This is okay, because we are within the gracewindow.
573        let ident = idms_prox_write
574            .validate_client_auth_info_to_ident(api_token.clone().into(), ct)
575            .expect("Unable to verify api token.");
576        assert_eq!(ident.get_uuid(), Some(test_entry_uuid));
577
578        // Past gracewindow?
579        assert!(
580            idms_prox_write
581                .validate_client_auth_info_to_ident(api_token.clone().into(), past_grc)
582                .expect_err("Should not succeed")
583                == OperationError::SessionExpired
584        );
585
586        assert!(idms_prox_write.commit().is_ok());
587    }
588}