kanidmd_lib/idm/
application.rs

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