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 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 #[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 #[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 {
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 {
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 #[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(>e, ct)
537 .expect("failed to generate new api token");
538
539 trace!(?api_token);
540
541 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 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 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 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 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}