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 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 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 self.qs_write
276 .impersonate_modify(
277 &filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(ev.target))),
279 &filter_all!(f_eq(Attribute::Uuid, PartialValue::Uuid(ev.target))),
281 &modlist,
282 &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!(f_eq(Attribute::Uuid, PartialValue::Uuid(target))),
305 &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 #[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 #[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 {
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 {
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 #[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(>e, ct)
608 .expect("failed to generate new api token");
609
610 trace!(?api_token);
611
612 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 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 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 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 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}