1use std::collections::BTreeSet;
5use std::iter;
6use std::str::FromStr;
7
8use compact_jwt::JwsCompact;
9use itertools::Itertools;
10use kanidm_proto::constants::*;
11use kanidm_proto::internal::{ApiToken, UserAuthToken};
12use ldap3_proto::simple::*;
13use regex::{Regex, RegexBuilder};
14use std::net::IpAddr;
15use tracing::trace;
16use uuid::Uuid;
17
18use crate::event::SearchEvent;
19use crate::idm::event::{LdapApplicationAuthEvent, LdapAuthEvent, LdapTokenAuthEvent};
20use crate::idm::server::{IdmServer, IdmServerAuthTransaction, IdmServerTransaction};
21use crate::prelude::*;
22
23#[allow(clippy::large_enum_variant)]
26pub enum LdapResponseState {
27 Unbind,
28 Disconnect(LdapMsg),
29 Bind(LdapBoundToken, LdapMsg),
30 Respond(LdapMsg),
31 MultiPartResponse(Vec<LdapMsg>),
32 BindMultiPartResponse(LdapBoundToken, Vec<LdapMsg>),
33}
34
35#[derive(Debug, Clone, PartialEq, Eq)]
36pub enum LdapSession {
37 UnixBind(Uuid),
40 UserAuthToken(UserAuthToken),
41 ApiToken(ApiToken),
42 ApplicationPasswordBind(Uuid, Uuid),
43}
44
45#[derive(Debug, Clone)]
46pub struct LdapBoundToken {
47 pub spn: String,
49 pub session_id: Uuid,
50 pub effective_session: LdapSession,
57}
58
59pub struct LdapServer {
60 rootdse: LdapSearchResultEntry,
61 basedn: String,
62 dnre: Regex,
63 binddnre: Regex,
64 max_queryable_attrs: usize,
65}
66
67#[derive(Debug)]
68enum LdapBindTarget {
69 Account(Uuid),
70 ApiToken,
71 Application(String, Uuid),
72}
73
74impl LdapServer {
75 pub async fn new(idms: &IdmServer) -> Result<Self, OperationError> {
76 let mut idms_prox_read = idms.proxy_read().await?;
78 let domain_entry = idms_prox_read
81 .qs_read
82 .internal_search_uuid(UUID_DOMAIN_INFO)?;
83
84 let max_queryable_attrs = domain_entry
86 .get_ava_single_uint32(Attribute::LdapMaxQueryableAttrs)
87 .map(|u| u as usize)
88 .unwrap_or(DEFAULT_LDAP_MAXIMUM_QUERYABLE_ATTRIBUTES);
89
90 let basedn = domain_entry
91 .get_ava_single_iutf8(Attribute::DomainLdapBasedn)
92 .map(|s| s.to_string())
93 .or_else(|| {
94 domain_entry
95 .get_ava_single_iname(Attribute::DomainName)
96 .map(ldap_domain_to_dc)
97 })
98 .ok_or(OperationError::InvalidEntryState)?;
99
100 let dnre = RegexBuilder::new(
114 format!("^((?P<attr>[^=,]+)=(?P<val>[^=,]+),)?(app=(?P<app>[^=,]+),)?({basedn})$")
115 .as_str(),
116 )
117 .swap_greed(true)
118 .build()
119 .map_err(|_| OperationError::InvalidEntryState)?;
120
121 let binddnre = Regex::new(
122 format!("^((([^=,]+)=)?(?P<val>[^=,]+))(,app=(?P<app>[^=,]+))?(,{basedn})?$").as_str(),
123 )
124 .map_err(|_| OperationError::InvalidEntryState)?;
125
126 let rootdse = LdapSearchResultEntry {
127 dn: "".to_string(),
128 attributes: vec![
129 LdapPartialAttribute {
130 atype: ATTR_OBJECTCLASS.to_string(),
131 vals: vec!["top".as_bytes().to_vec()],
132 },
133 LdapPartialAttribute {
134 atype: "vendorname".to_string(),
135 vals: vec!["Kanidm Project".as_bytes().to_vec()],
136 },
137 LdapPartialAttribute {
138 atype: "vendorversion".to_string(),
139 vals: vec![env!("CARGO_PKG_VERSION").as_bytes().to_vec()],
140 },
141 LdapPartialAttribute {
142 atype: "supportedldapversion".to_string(),
143 vals: vec!["3".as_bytes().to_vec()],
144 },
145 LdapPartialAttribute {
146 atype: "supportedextension".to_string(),
147 vals: vec!["1.3.6.1.4.1.4203.1.11.3".as_bytes().to_vec()],
148 },
149 LdapPartialAttribute {
150 atype: "supportedfeatures".to_string(),
151 vals: vec!["1.3.6.1.4.1.4203.1.5.1".as_bytes().to_vec()],
152 },
153 LdapPartialAttribute {
154 atype: "defaultnamingcontext".to_string(),
155 vals: vec![basedn.as_bytes().to_vec()],
156 },
157 ],
158 };
159
160 Ok(LdapServer {
161 rootdse,
162 basedn,
163 dnre,
164 binddnre,
165 max_queryable_attrs,
166 })
167 }
168
169 #[instrument(level = "debug", skip_all)]
170 async fn do_search(
171 &self,
172 idms: &IdmServer,
173 sr: &SearchRequest,
174 uat: &LdapBoundToken,
175 source: Source,
176 ) -> Result<Vec<LdapMsg>, OperationError> {
178 admin_info!("Attempt LDAP Search for {}", uat.spn);
179 if sr.base.is_empty() && sr.scope == LdapSearchScope::Base {
181 admin_info!("LDAP Search success - RootDSE");
182 Ok(vec![
183 sr.gen_result_entry(self.rootdse.clone()),
184 sr.gen_success(),
185 ])
186 } else {
187 let (opt_attr, opt_value) = match self.dnre.captures(sr.base.as_str()) {
193 Some(caps) => (
194 caps.name("attr").map(|v| v.as_str().to_string()),
195 caps.name("val").map(|v| v.as_str().to_string()),
196 ),
197 None => {
198 request_error!("LDAP Search failure - invalid basedn");
199 return Err(OperationError::InvalidRequestState);
200 }
201 };
202
203 let req_dn = match (opt_attr, opt_value) {
204 (Some(a), Some(v)) => Some((a, v)),
205 (None, None) => None,
206 _ => {
207 request_error!("LDAP Search failure - invalid rdn");
208 return Err(OperationError::InvalidRequestState);
209 }
210 };
211
212 trace!(rdn = ?req_dn);
213
214 let ext_filter = match (&sr.scope, req_dn) {
217 (LdapSearchScope::Children, Some(_r)) | (LdapSearchScope::OneLevel, Some(_r)) => {
221 return Ok(vec![sr.gen_success()])
222 }
223 (LdapSearchScope::Children, None) | (LdapSearchScope::OneLevel, None) => {
224 Some(LdapFilter::Not(Box::new(LdapFilter::Equality(
226 Attribute::Uuid.to_string(),
227 STR_UUID_DOMAIN_INFO.to_string(),
228 ))))
229 }
230 (LdapSearchScope::Base, Some((a, v)))
233 | (LdapSearchScope::Subtree, Some((a, v))) => Some(LdapFilter::Equality(a, v)),
234 (LdapSearchScope::Base, None) => {
235 Some(LdapFilter::Equality(
237 Attribute::Uuid.to_string(),
238 STR_UUID_DOMAIN_INFO.to_string(),
239 ))
240 }
241 (LdapSearchScope::Subtree, None) => {
242 None
244 }
245 };
246
247 let mut no_attrs = false;
248 let mut all_attrs = false;
249 let mut all_op_attrs = false;
250
251 let attrs_len = sr.attrs.len();
252 if sr.attrs.is_empty() {
253 all_attrs = true;
255 } else if attrs_len < self.max_queryable_attrs {
256 sr.attrs.iter().for_each(|a| {
257 if a == "*" {
258 all_attrs = true;
259 } else if a == "+" {
260 all_attrs = true;
263 all_op_attrs = true;
264 } else if a == "1.1" {
265 if sr.attrs.len() == 1 {
275 no_attrs = true;
276 }
277 }
278 })
279 } else {
280 admin_error!(
281 "Too many LDAP attributes requested. Maximum allowed is {}, while your search query had {}",
282 self.max_queryable_attrs, attrs_len
283 );
284 return Err(OperationError::ResourceLimit);
285 }
286
287 let (k_attrs, l_attrs) = if no_attrs {
289 (None, Vec::with_capacity(0))
291 } else if all_op_attrs {
292 (None, ldap_all_vattrs())
294 } else if all_attrs {
295 let req_attrs: Vec<String> = sr
298 .attrs
299 .iter()
300 .filter_map(|a| {
301 let a_lower = a.to_lowercase();
302
303 if ldap_vattr_map(&a_lower).is_some() {
304 Some(a_lower)
305 } else {
306 None
307 }
308 })
309 .collect();
310
311 (None, req_attrs)
312 } else {
313 let req_attrs: Vec<String> = sr
315 .attrs
316 .iter()
317 .filter_map(|a| {
318 if a == "*" || a == "+" || a == "1.1" {
319 None
320 } else {
321 Some(a.to_lowercase())
322 }
323 })
324 .collect();
325 let mapped_attrs: BTreeSet<_> = req_attrs
328 .iter()
329 .map(|a| Attribute::from(ldap_vattr_map(a).unwrap_or(a)))
330 .collect();
331
332 (Some(mapped_attrs), req_attrs)
333 };
334 let k_attrs = k_attrs.map(|ka| ka.into_iter().sorted().dedup().collect());
336 let l_attrs = l_attrs.into_iter().sorted().dedup().collect::<Vec<_>>();
337
338 debug!(attr = ?l_attrs, "LDAP Search Request LDAP Attrs");
339 debug!(attr = ?k_attrs, "LDAP Search Request Mapped Attrs");
340
341 let ct = duration_from_epoch_now();
342 let mut idm_read = idms.proxy_read().await?;
343 let lfilter = match ext_filter {
347 Some(ext) => LdapFilter::And(vec![
348 sr.filter.clone(),
349 ext,
350 LdapFilter::Not(Box::new(LdapFilter::Or(vec![
351 LdapFilter::Equality(Attribute::Class.to_string(), "classtype".to_string()),
352 LdapFilter::Equality(
353 Attribute::Class.to_string(),
354 "attributetype".to_string(),
355 ),
356 LdapFilter::Equality(
357 Attribute::Class.to_string(),
358 "access_control_profile".to_string(),
359 ),
360 ]))),
361 ]),
362 None => LdapFilter::And(vec![
363 sr.filter.clone(),
364 LdapFilter::Not(Box::new(LdapFilter::Or(vec![
365 LdapFilter::Equality(Attribute::Class.to_string(), "classtype".to_string()),
366 LdapFilter::Equality(
367 Attribute::Class.to_string(),
368 "attributetype".to_string(),
369 ),
370 LdapFilter::Equality(
371 Attribute::Class.to_string(),
372 "access_control_profile".to_string(),
373 ),
374 ]))),
375 ]),
376 };
377
378 debug!(filter = ?lfilter, "LDAP Search Filter");
379
380 let ident = idm_read
384 .validate_ldap_session(&uat.effective_session, source, ct)
385 .map_err(|e| {
386 admin_error!("Invalid identity: {:?}", e);
387 e
388 })?;
389 let se = SearchEvent::new_ext_impersonate_uuid(
390 &mut idm_read.qs_read,
391 ident,
392 &lfilter,
393 k_attrs,
394 )
395 .map_err(|e| {
396 admin_error!("failed to create search event -> {:?}", e);
397 e
398 })?;
399
400 let res = idm_read.qs_read.search_ext(&se).map_err(|e| {
401 admin_error!("search failure {:?}", e);
402 e
403 })?;
404
405 let lres: Result<Vec<_>, _> = res
409 .into_iter()
410 .map(|e| {
411 e.to_ldap(
412 &mut idm_read.qs_read,
413 self.basedn.as_str(),
414 all_attrs,
415 &l_attrs,
416 )
417 .map(|r| sr.gen_result_entry(r))
419 })
420 .chain(iter::once(Ok(sr.gen_success())))
421 .collect();
422
423 let lres = lres.map_err(|e| {
424 admin_error!("entry resolve failure {:?}", e);
425 e
426 })?;
427
428 admin_info!(
429 nentries = %lres.len(),
430 "LDAP Search Success -> number of entries"
431 );
432
433 Ok(lres)
434 }
435 }
436
437 async fn do_bind(
438 &self,
439 idms: &IdmServer,
440 dn: &str,
441 pw: &str,
442 ) -> Result<Option<LdapBoundToken>, OperationError> {
443 security_info!(
444 "Attempt LDAP Bind for {}",
445 if dn.is_empty() { "(empty dn)" } else { dn }
446 );
447 let ct = duration_from_epoch_now();
448
449 let mut idm_auth = idms.auth().await?;
450 let target = self.bind_target_from_bind_dn(&mut idm_auth, dn, pw).await?;
451
452 let result = match target {
453 LdapBindTarget::Account(uuid) => {
454 let lae = LdapAuthEvent::from_parts(uuid, pw.to_string())?;
455 idm_auth.auth_ldap(&lae, ct).await?
456 }
457 LdapBindTarget::ApiToken => {
458 let jwsc = JwsCompact::from_str(pw).map_err(|err| {
459 error!(?err, "Invalid JwsCompact supplied as authentication token.");
460 OperationError::NotAuthenticated
461 })?;
462
463 let lae = LdapTokenAuthEvent::from_parts(jwsc)?;
464 idm_auth.token_auth_ldap(&lae, ct).await?
465 }
466 LdapBindTarget::Application(ref app_name, usr_uuid) => {
467 let lae =
468 LdapApplicationAuthEvent::new(app_name.as_str(), usr_uuid, pw.to_string())?;
469 idm_auth.application_auth_ldap(&lae, ct).await?
470 }
471 };
472
473 idm_auth.commit()?;
474
475 if result.is_some() {
476 security_info!(
477 "✅ LDAP Bind success for {} -> {:?}",
478 if dn.is_empty() { "(empty dn)" } else { dn },
479 target
480 );
481 } else {
482 security_info!(
483 "❌ LDAP Bind failure for {} -> {:?}",
484 if dn.is_empty() { "(empty dn)" } else { dn },
485 target
486 );
487 }
488
489 Ok(result)
490 }
491
492 #[instrument(level = "debug", skip_all)]
493 async fn do_compare(
494 &self,
495 idms: &IdmServer,
496 cr: &CompareRequest,
497 uat: &LdapBoundToken,
498 source: Source,
499 ) -> Result<Vec<LdapMsg>, OperationError> {
500 admin_info!("Attempt LDAP CompareRequest for {}", uat.spn);
501
502 let (opt_attr, opt_value) = match self.dnre.captures(cr.entry.as_str()) {
503 Some(caps) => (
504 caps.name("attr").map(|v| v.as_str().to_string()),
505 caps.name("val").map(|v| v.as_str().to_string()),
506 ),
507 None => {
508 request_error!("LDAP Search failure - invalid basedn");
509 return Err(OperationError::InvalidRequestState);
510 }
511 };
512
513 let ext_filter = match (opt_attr, opt_value) {
514 (Some(a), Some(v)) => LdapFilter::Equality(a, v),
515 _ => {
516 request_error!("LDAP Search failure - invalid rdn");
517 return Err(OperationError::InvalidRequestState);
518 }
519 };
520
521 let ct = duration_from_epoch_now();
522 let mut idm_read = idms.proxy_read().await?;
523 let lfilter = LdapFilter::And(vec![
527 ext_filter.clone(),
528 LdapFilter::Equality(cr.atype.clone(), cr.val.clone()),
529 LdapFilter::Not(Box::new(LdapFilter::Or(vec![
530 LdapFilter::Equality(Attribute::Class.to_string(), "classtype".to_string()),
531 LdapFilter::Equality(Attribute::Class.to_string(), "attributetype".to_string()),
532 LdapFilter::Equality(
533 Attribute::Class.to_string(),
534 "access_control_profile".to_string(),
535 ),
536 ]))),
537 ]);
538
539 admin_info!(filter = ?lfilter, "LDAP Compare Filter");
540
541 let ident = idm_read
543 .validate_ldap_session(&uat.effective_session, source, ct)
544 .map_err(|e| {
545 admin_error!("Invalid identity: {:?}", e);
546 e
547 })?;
548
549 let f = Filter::from_ldap_ro(&ident, &lfilter, &mut idm_read.qs_read)?;
550 let filter_orig = f
551 .validate(idm_read.qs_read.get_schema())
552 .map_err(OperationError::SchemaViolation)?;
553 let filter = filter_orig.clone().into_ignore_hidden();
554
555 let ee = ExistsEvent {
556 ident: ident.clone(),
557 filter,
558 filter_orig,
559 };
560
561 let res = idm_read.qs_read.exists(&ee).map_err(|e| {
562 admin_error!("call to exists failure {:?}", e);
563 e
564 })?;
565
566 if res {
567 admin_info!("LDAP Compare -> True");
568 return Ok(vec![cr.gen_compare_true()]);
569 }
570
571 let lfilter = LdapFilter::And(vec![
573 ext_filter,
574 LdapFilter::Not(Box::new(LdapFilter::Or(vec![
575 LdapFilter::Equality(Attribute::Class.to_string(), "classtype".to_string()),
576 LdapFilter::Equality(Attribute::Class.to_string(), "attributetype".to_string()),
577 LdapFilter::Equality(
578 Attribute::Class.to_string(),
579 "access_control_profile".to_string(),
580 ),
581 ]))),
582 ]);
583 let f = Filter::from_ldap_ro(&ident, &lfilter, &mut idm_read.qs_read)?;
584 let filter_orig = f
585 .validate(idm_read.qs_read.get_schema())
586 .map_err(OperationError::SchemaViolation)?;
587 let filter = filter_orig.clone().into_ignore_hidden();
588 let ee = ExistsEvent {
589 ident,
590 filter,
591 filter_orig,
592 };
593
594 let res = idm_read.qs_read.exists(&ee).map_err(|e| {
595 admin_error!("call to exists failure {:?}", e);
596 e
597 })?;
598
599 if res {
600 admin_info!("LDAP Compare -> False");
601 return Ok(vec![cr.gen_compare_false()]);
602 }
603
604 Ok(vec![
605 cr.gen_error(LdapResultCode::NoSuchObject, "".to_string())
606 ])
607 }
608
609 pub async fn do_op(
610 &self,
611 idms: &IdmServer,
612 server_op: ServerOps,
613 uat: Option<LdapBoundToken>,
614 ip_addr: IpAddr,
615 eventid: Uuid,
616 ) -> Result<LdapResponseState, OperationError> {
617 let source = Source::Ldaps(ip_addr);
618
619 match server_op {
620 ServerOps::SimpleBind(sbr) => self
621 .do_bind(idms, sbr.dn.as_str(), sbr.pw.as_str())
622 .await
623 .map(|r| match r {
624 Some(lbt) => LdapResponseState::Bind(lbt, sbr.gen_success()),
625 None => LdapResponseState::Respond(sbr.gen_invalid_cred()),
626 })
627 .or_else(|e| {
628 let (rc, msg) = operationerr_to_ldapresultcode(e);
629 Ok(LdapResponseState::Respond(sbr.gen_error(rc, msg)))
630 }),
631 ServerOps::Search(sr) => match uat {
632 Some(u) => self
633 .do_search(idms, &sr, &u, source)
634 .await
635 .map(LdapResponseState::MultiPartResponse)
636 .or_else(|e| {
637 let (rc, msg) = operationerr_to_ldapresultcode(e);
638 Ok(LdapResponseState::Respond(sr.gen_error(rc, msg)))
639 }),
640 None => {
641 let lbt = match self.do_bind(idms, "", "").await {
644 Ok(Some(lbt)) => lbt,
645 Ok(None) => {
646 return Ok(LdapResponseState::Respond(
647 sr.gen_error(LdapResultCode::InvalidCredentials, "".to_string()),
648 ))
649 }
650 Err(e) => {
651 let (rc, msg) = operationerr_to_ldapresultcode(e);
652 return Ok(LdapResponseState::Respond(sr.gen_error(rc, msg)));
653 }
654 };
655 self.do_search(idms, &sr, &lbt, Source::Internal)
657 .await
658 .map(|r| LdapResponseState::BindMultiPartResponse(lbt, r))
659 .or_else(|e| {
660 let (rc, msg) = operationerr_to_ldapresultcode(e);
661 Ok(LdapResponseState::Respond(sr.gen_error(rc, msg)))
662 })
663 }
664 },
665 ServerOps::Unbind(_) => {
666 Ok(LdapResponseState::Unbind)
668 }
669 ServerOps::Compare(cr) => match uat {
670 Some(u) => self
671 .do_compare(idms, &cr, &u, source)
672 .await
673 .map(LdapResponseState::MultiPartResponse)
674 .or_else(|e| {
675 let (rc, msg) = operationerr_to_ldapresultcode(e);
676 Ok(LdapResponseState::Respond(cr.gen_error(rc, msg)))
677 }),
678 None => {
679 let lbt = match self.do_bind(idms, "", "").await {
682 Ok(Some(lbt)) => lbt,
683 Ok(None) => {
684 return Ok(LdapResponseState::Respond(
685 cr.gen_error(LdapResultCode::InvalidCredentials, "".to_string()),
686 ))
687 }
688 Err(e) => {
689 let (rc, msg) = operationerr_to_ldapresultcode(e);
690 return Ok(LdapResponseState::Respond(cr.gen_error(rc, msg)));
691 }
692 };
693 self.do_compare(idms, &cr, &lbt, Source::Internal)
695 .await
696 .map(|r| LdapResponseState::BindMultiPartResponse(lbt, r))
697 .or_else(|e| {
698 let (rc, msg) = operationerr_to_ldapresultcode(e);
699 Ok(LdapResponseState::Respond(cr.gen_error(rc, msg)))
700 })
701 }
702 },
703 ServerOps::Whoami(wr) => match uat {
704 Some(u) => Ok(LdapResponseState::Respond(
705 wr.gen_success(format!("u: {}", u.spn).as_str()),
706 )),
707 None => Ok(LdapResponseState::Respond(
708 wr.gen_operror(format!("Unbound Connection {eventid}").as_str()),
709 )),
710 },
711 } }
713
714 async fn bind_target_from_bind_dn(
715 &self,
716 idm_auth: &mut IdmServerAuthTransaction<'_>,
717 dn: &str,
718 pw: &str,
719 ) -> Result<LdapBindTarget, OperationError> {
720 if dn.is_empty() {
721 if pw.is_empty() {
722 return Ok(LdapBindTarget::Account(UUID_ANONYMOUS));
723 } else {
724 return Ok(LdapBindTarget::ApiToken);
726 }
727 } else if dn == "dn=token" {
728 return Ok(LdapBindTarget::ApiToken);
733 }
734
735 if let Some(captures) = self.binddnre.captures(dn) {
736 if let Some(usr) = captures.name("val") {
737 let usr = usr.as_str();
738
739 if usr.is_empty() {
740 error!("Failed to parse user name from bind DN, it is empty (capture group is {:#?})", captures.name("val"));
741 return Err(OperationError::NoMatchingEntries);
742 }
743
744 let usr_uuid = idm_auth.qs_read.name_to_uuid(usr).map_err(|e| {
745 error!(err = ?e, ?usr, "Error resolving rdn to target");
746 e
747 })?;
748
749 if let Some(app) = captures.name("app") {
750 let app = app.as_str();
751
752 if app.is_empty() {
753 error!("Failed to parse application name from bind DN, it is empty (capture group is {:#?})", captures.name("app"));
754 return Err(OperationError::NoMatchingEntries);
755 }
756
757 return Ok(LdapBindTarget::Application(app.to_string(), usr_uuid));
758 }
759
760 return Ok(LdapBindTarget::Account(usr_uuid));
761 }
762 }
763
764 error!(
765 binddn = ?dn,
766 "Failed to parse bind DN - check the basedn and app attribute if present are correct. Examples: name=tobias,app=lounge,{} OR name=ellie,{} OR name=claire,app=table OR name=william ", self.basedn, self.basedn
767 );
768
769 Err(OperationError::NoMatchingEntries)
770 }
771}
772
773fn ldap_domain_to_dc(input: &str) -> String {
774 let mut output: String = String::new();
775 input.split('.').for_each(|dc| {
776 output.push_str("dc=");
777 output.push_str(dc);
778 #[allow(clippy::single_char_pattern, clippy::single_char_add_str)]
779 output.push_str(",");
780 });
781 output.pop();
783 output
784}
785
786fn operationerr_to_ldapresultcode(e: OperationError) -> (LdapResultCode, String) {
787 match e {
788 OperationError::InvalidRequestState => {
789 (LdapResultCode::ConstraintViolation, "".to_string())
790 }
791 OperationError::InvalidAttributeName(s) | OperationError::InvalidAttribute(s) => {
792 (LdapResultCode::InvalidAttributeSyntax, s)
793 }
794 OperationError::SchemaViolation(se) => {
795 (LdapResultCode::UnwillingToPerform, format!("{se:?}"))
796 }
797 e => (LdapResultCode::Other, format!("{e:?}")),
798 }
799}
800
801#[inline]
802pub(crate) fn ldap_all_vattrs() -> Vec<String> {
803 vec![
804 ATTR_CN.to_string(),
805 ATTR_EMAIL.to_string(),
806 ATTR_LDAP_EMAIL_ADDRESS.to_string(),
807 LDAP_ATTR_DN.to_string(),
808 LDAP_ATTR_EMAIL_ALTERNATIVE.to_string(),
809 LDAP_ATTR_EMAIL_PRIMARY.to_string(),
810 LDAP_ATTR_ENTRYDN.to_string(),
811 LDAP_ATTR_ENTRYUUID.to_string(),
812 LDAP_ATTR_KEYS.to_string(),
813 LDAP_ATTR_MAIL_ALTERNATIVE.to_string(),
814 LDAP_ATTR_MAIL_PRIMARY.to_string(),
815 ATTR_OBJECTCLASS.to_string(),
816 ATTR_LDAP_SSHPUBLICKEY.to_string(),
817 ATTR_UIDNUMBER.to_string(),
818 ATTR_UID.to_string(),
819 ATTR_GECOS.to_string(),
820 ATTR_HOME_DIRECTORY.to_string(),
821 ATTR_PWD_CHANGED_TIME.to_string(),
822 ]
823}
824
825#[inline]
826pub(crate) fn ldap_vattr_map(input: &str) -> Option<&str> {
827 match input {
834 ATTR_CN | ATTR_UID | LDAP_ATTR_ENTRYDN | LDAP_ATTR_DN => Some(ATTR_NAME),
840 ATTR_GECOS => Some(ATTR_DISPLAYNAME),
841 ATTR_EMAIL => Some(ATTR_MAIL),
842 ATTR_LDAP_EMAIL_ADDRESS => Some(ATTR_MAIL),
843 LDAP_ATTR_EMAIL_ALTERNATIVE => Some(ATTR_MAIL),
844 LDAP_ATTR_EMAIL_PRIMARY => Some(ATTR_MAIL),
845 LDAP_ATTR_ENTRYUUID => Some(ATTR_UUID),
846 LDAP_ATTR_KEYS => Some(ATTR_SSH_PUBLICKEY),
847 LDAP_ATTR_MAIL_ALTERNATIVE => Some(ATTR_MAIL),
848 LDAP_ATTR_MAIL_PRIMARY => Some(ATTR_MAIL),
849 ATTR_OBJECTCLASS => Some(ATTR_CLASS),
850 ATTR_LDAP_SSHPUBLICKEY => Some(ATTR_SSH_PUBLICKEY), ATTR_UIDNUMBER => Some(ATTR_GIDNUMBER), ATTR_HOME_DIRECTORY => Some(ATTR_UUID),
853 LDAP_ATTR_PWD_CHANGED_TIME => Some(ATTR_PWD_CHANGED_TIME),
854 _ => None,
855 }
856}
857
858#[inline]
859pub(crate) fn ldap_attr_filter_map(input: &str) -> Attribute {
860 let a_lower = input.to_lowercase();
861 Attribute::from(ldap_vattr_map(&a_lower).unwrap_or(a_lower.as_str()))
862}
863
864#[cfg(test)]
865mod tests {
866 use crate::prelude::*;
867
868 use compact_jwt::{dangernoverify::JwsDangerReleaseWithoutVerify, JwsVerifier};
869 use hashbrown::HashSet;
870 use kanidm_proto::internal::ApiToken;
871 use ldap3_proto::proto::{
872 LdapFilter, LdapMsg, LdapOp, LdapResultCode, LdapSearchScope, LdapSubstringFilter,
873 };
874 use ldap3_proto::simple::*;
875
876 use super::{LdapServer, LdapSession};
877 use crate::idm::application::GenerateApplicationPasswordEvent;
878 use crate::idm::event::{LdapApplicationAuthEvent, UnixPasswordChangeEvent};
879 use crate::idm::serviceaccount::GenerateApiTokenEvent;
880
881 const TEST_PASSWORD: &str = "ntaoeuntnaoeuhraohuercahu😍";
882
883 #[idm_test]
884 async fn test_ldap_simple_bind(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
885 let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
886
887 let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
888 let me_posix = ModifyEvent::new_internal_invalid(
890 filter!(f_eq(Attribute::Name, PartialValue::new_iname("admin"))),
891 ModifyList::new_list(vec![
892 Modify::Present(Attribute::Class, EntryClass::PosixAccount.into()),
893 Modify::Present(Attribute::GidNumber, Value::new_uint32(2001)),
894 ]),
895 );
896 assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok());
897
898 let pce = UnixPasswordChangeEvent::new_internal(UUID_ADMIN, TEST_PASSWORD);
899
900 assert!(idms_prox_write.set_unix_account_password(&pce).is_ok());
901 assert!(idms_prox_write.commit().is_ok()); let admin_t = ldaps
906 .do_bind(idms, "admin", TEST_PASSWORD)
907 .await
908 .unwrap()
909 .unwrap();
910 assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
911 let admin_t = ldaps
912 .do_bind(idms, "admin@example.com", TEST_PASSWORD)
913 .await
914 .unwrap()
915 .unwrap();
916 assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
917
918 let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
921 let disallow_unix_pw_flag = ModifyEvent::new_internal_invalid(
922 filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_DOMAIN_INFO))),
923 ModifyList::new_purge_and_set(Attribute::LdapAllowUnixPwBind, Value::Bool(false)),
924 );
925 assert!(idms_prox_write
926 .qs_write
927 .modify(&disallow_unix_pw_flag)
928 .is_ok());
929 assert!(idms_prox_write.commit().is_ok());
930 let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
931 assert_eq!(
932 anon_t.effective_session,
933 LdapSession::UnixBind(UUID_ANONYMOUS)
934 );
935 assert!(
936 ldaps.do_bind(idms, "", "test").await.unwrap_err() == OperationError::NotAuthenticated
937 );
938 let admin_t = ldaps.do_bind(idms, "admin", TEST_PASSWORD).await.unwrap();
939 assert!(admin_t.is_none());
940
941 let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
943 let allow_unix_pw_flag = ModifyEvent::new_internal_invalid(
944 filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_DOMAIN_INFO))),
945 ModifyList::new_purge_and_set(Attribute::LdapAllowUnixPwBind, Value::Bool(true)),
946 );
947 assert!(idms_prox_write.qs_write.modify(&allow_unix_pw_flag).is_ok());
948 assert!(idms_prox_write.commit().is_ok());
949
950 let admin_t = ldaps
952 .do_bind(idms, "admin", TEST_PASSWORD)
953 .await
954 .unwrap()
955 .unwrap();
956 assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
957 let admin_t = ldaps
958 .do_bind(idms, "admin@example.com", TEST_PASSWORD)
959 .await
960 .unwrap()
961 .unwrap();
962 assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
963 let admin_t = ldaps
964 .do_bind(idms, STR_UUID_ADMIN, TEST_PASSWORD)
965 .await
966 .unwrap()
967 .unwrap();
968 assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
969 let admin_t = ldaps
970 .do_bind(idms, "name=admin,dc=example,dc=com", TEST_PASSWORD)
971 .await
972 .unwrap()
973 .unwrap();
974 assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
975 let admin_t = ldaps
976 .do_bind(
977 idms,
978 "spn=admin@example.com,dc=example,dc=com",
979 TEST_PASSWORD,
980 )
981 .await
982 .unwrap()
983 .unwrap();
984 assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
985 let admin_t = ldaps
986 .do_bind(
987 idms,
988 format!("uuid={STR_UUID_ADMIN},dc=example,dc=com").as_str(),
989 TEST_PASSWORD,
990 )
991 .await
992 .unwrap()
993 .unwrap();
994 assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
995
996 let admin_t = ldaps
997 .do_bind(idms, "name=admin", TEST_PASSWORD)
998 .await
999 .unwrap()
1000 .unwrap();
1001 assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
1002 let admin_t = ldaps
1003 .do_bind(idms, "spn=admin@example.com", TEST_PASSWORD)
1004 .await
1005 .unwrap()
1006 .unwrap();
1007 assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
1008 let admin_t = ldaps
1009 .do_bind(
1010 idms,
1011 format!("uuid={STR_UUID_ADMIN}").as_str(),
1012 TEST_PASSWORD,
1013 )
1014 .await
1015 .unwrap()
1016 .unwrap();
1017 assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
1018
1019 let admin_t = ldaps
1020 .do_bind(idms, "admin,dc=example,dc=com", TEST_PASSWORD)
1021 .await
1022 .unwrap()
1023 .unwrap();
1024 assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
1025 let admin_t = ldaps
1026 .do_bind(idms, "admin@example.com,dc=example,dc=com", TEST_PASSWORD)
1027 .await
1028 .unwrap()
1029 .unwrap();
1030 assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
1031 let admin_t = ldaps
1032 .do_bind(
1033 idms,
1034 format!("{STR_UUID_ADMIN},dc=example,dc=com").as_str(),
1035 TEST_PASSWORD,
1036 )
1037 .await
1038 .unwrap()
1039 .unwrap();
1040 assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
1041
1042 assert!(ldaps
1044 .do_bind(idms, "admin", "test")
1045 .await
1046 .unwrap()
1047 .is_none());
1048
1049 assert!(ldaps
1051 .do_bind(
1052 idms,
1053 "spn=admin@example.com,dc=clownshoes,dc=example,dc=com",
1054 TEST_PASSWORD
1055 )
1056 .await
1057 .is_err());
1058 assert!(ldaps
1059 .do_bind(
1060 idms,
1061 "spn=claire@example.com,dc=example,dc=com",
1062 TEST_PASSWORD
1063 )
1064 .await
1065 .is_err());
1066 assert!(ldaps
1067 .do_bind(idms, ",dc=example,dc=com", TEST_PASSWORD)
1068 .await
1069 .is_err());
1070 assert!(ldaps
1071 .do_bind(idms, "dc=example,dc=com", TEST_PASSWORD)
1072 .await
1073 .is_err());
1074
1075 assert!(ldaps.do_bind(idms, "claire", "test").await.is_err());
1076 }
1077
1078 #[idm_test]
1079 async fn test_ldap_application_dnre(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
1080 let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
1081
1082 let testdn = format!("app=app1,{0}", ldaps.basedn);
1083 let captures = ldaps.dnre.captures(testdn.as_str()).unwrap();
1084 assert!(captures.name("app").is_some());
1085 assert!(captures.name("attr").is_none());
1086 assert!(captures.name("val").is_none());
1087
1088 let testdn = format!("uid=foo,app=app1,{0}", ldaps.basedn);
1089 let captures = ldaps.dnre.captures(testdn.as_str()).unwrap();
1090 assert!(captures.name("app").is_some());
1091 assert!(captures.name("attr").is_some());
1092 assert!(captures.name("val").is_some());
1093
1094 let testdn = format!("uid=foo,{0}", ldaps.basedn);
1095 let captures = ldaps.dnre.captures(testdn.as_str()).unwrap();
1096 assert!(captures.name("app").is_none());
1097 assert!(captures.name("attr").is_some());
1098 assert!(captures.name("val").is_some());
1099 }
1100
1101 #[idm_test]
1102 async fn test_ldap_application_search(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
1103 let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
1104
1105 let usr_uuid = Uuid::new_v4();
1106 let grp_uuid = Uuid::new_v4();
1107 let app_uuid = Uuid::new_v4();
1108 let app_name = "testapp1";
1109
1110 {
1112 let e1 = entry_init!(
1113 (Attribute::Class, EntryClass::Object.to_value()),
1114 (Attribute::Class, EntryClass::Account.to_value()),
1115 (Attribute::Class, EntryClass::Person.to_value()),
1116 (Attribute::Name, Value::new_iname("testperson1")),
1117 (Attribute::Uuid, Value::Uuid(usr_uuid)),
1118 (Attribute::Description, Value::new_utf8s("testperson1")),
1119 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
1120 );
1121
1122 let e2 = entry_init!(
1123 (Attribute::Class, EntryClass::Object.to_value()),
1124 (Attribute::Class, EntryClass::Group.to_value()),
1125 (Attribute::Name, Value::new_iname("testgroup1")),
1126 (Attribute::Uuid, Value::Uuid(grp_uuid))
1127 );
1128
1129 let e3 = entry_init!(
1130 (Attribute::Class, EntryClass::Object.to_value()),
1131 (Attribute::Class, EntryClass::Account.to_value()),
1132 (Attribute::Class, EntryClass::ServiceAccount.to_value()),
1133 (Attribute::Class, EntryClass::Application.to_value()),
1134 (Attribute::DisplayName, Value::new_utf8s("Application")),
1135 (Attribute::Name, Value::new_iname(app_name)),
1136 (Attribute::Uuid, Value::Uuid(app_uuid)),
1137 (Attribute::LinkedGroup, Value::Refer(grp_uuid))
1138 );
1139
1140 let ct = duration_from_epoch_now();
1141 let mut server_txn = idms.proxy_write(ct).await.unwrap();
1142 assert!(server_txn
1143 .qs_write
1144 .internal_create(vec![e1, e2, e3])
1145 .and_then(|_| server_txn.commit())
1146 .is_ok());
1147 }
1148
1149 let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
1151 assert_eq!(
1152 anon_t.effective_session,
1153 LdapSession::UnixBind(UUID_ANONYMOUS)
1154 );
1155
1156 let sr = SearchRequest {
1158 msgid: 1,
1159 base: format!("app={app_name},dc=example,dc=com"),
1160 scope: LdapSearchScope::Subtree,
1161 filter: LdapFilter::Present(Attribute::ObjectClass.to_string()),
1162 attrs: vec!["*".to_string()],
1163 };
1164
1165 let r1 = ldaps
1166 .do_search(idms, &sr, &anon_t, Source::Internal)
1167 .await
1168 .unwrap();
1169
1170 let sr = SearchRequest {
1171 msgid: 1,
1172 base: "dc=example,dc=com".to_string(),
1173 scope: LdapSearchScope::Subtree,
1174 filter: LdapFilter::Present(Attribute::ObjectClass.to_string()),
1175 attrs: vec!["*".to_string()],
1176 };
1177
1178 let r2 = ldaps
1179 .do_search(idms, &sr, &anon_t, Source::Internal)
1180 .await
1181 .unwrap();
1182 assert!(!r1.is_empty());
1183 assert_eq!(r1.len(), r2.len());
1184 }
1185
1186 #[idm_test]
1187 async fn test_ldap_spn_search(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
1188 let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
1189
1190 let usr_uuid = Uuid::new_v4();
1191 let usr_name = "panko";
1192
1193 {
1195 let e1: Entry<EntryInit, EntryNew> = entry_init!(
1196 (Attribute::Class, EntryClass::Object.to_value()),
1197 (Attribute::Class, EntryClass::Account.to_value()),
1198 (Attribute::Class, EntryClass::PosixAccount.to_value()),
1199 (Attribute::Class, EntryClass::Person.to_value()),
1200 (Attribute::Name, Value::new_iname(usr_name)),
1201 (Attribute::Uuid, Value::Uuid(usr_uuid)),
1202 (Attribute::DisplayName, Value::new_utf8s(usr_name))
1203 );
1204
1205 let ct = duration_from_epoch_now();
1206 let mut server_txn = idms.proxy_write(ct).await.unwrap();
1207
1208 server_txn
1210 .qs_write
1211 .internal_modify_uuid(
1212 UUID_IDM_UNIX_AUTHENTICATION_READ,
1213 &ModifyList::new_append(Attribute::Member, Value::Refer(UUID_ANONYMOUS)),
1214 )
1215 .expect("Unable to modify UNIX_AUTHENTICATION_READ group");
1216
1217 assert!(server_txn
1218 .qs_write
1219 .internal_create(vec![e1])
1220 .and_then(|_| server_txn.commit())
1221 .is_ok());
1222 }
1223
1224 let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
1226 assert_eq!(
1227 anon_t.effective_session,
1228 LdapSession::UnixBind(UUID_ANONYMOUS)
1229 );
1230
1231 let sr = SearchRequest {
1233 msgid: 1,
1234 base: "dc=example,dc=com".to_string(),
1235 scope: LdapSearchScope::Subtree,
1236 filter: LdapFilter::Or(vec![
1237 LdapFilter::Equality(Attribute::Name.to_string(), usr_name.to_string()),
1238 LdapFilter::Equality(Attribute::Spn.to_string(), usr_name.to_string()),
1239 ]),
1240 attrs: vec!["*".to_string()],
1241 };
1242
1243 let result = ldaps
1244 .do_search(idms, &sr, &anon_t, Source::Internal)
1245 .await
1246 .map(|r| {
1247 r.into_iter()
1248 .filter(|r| matches!(r.op, LdapOp::SearchResultEntry(_)))
1249 .collect::<Vec<_>>()
1250 })
1251 .unwrap();
1252
1253 assert!(!result.is_empty());
1254
1255 let sr = SearchRequest {
1256 msgid: 1,
1257 base: "dc=example,dc=com".to_string(),
1258 scope: LdapSearchScope::Subtree,
1259 filter: LdapFilter::And(vec![
1260 LdapFilter::Equality(Attribute::Name.to_string(), usr_name.to_string()),
1261 LdapFilter::Equality(Attribute::Spn.to_string(), usr_name.to_string()),
1262 ]),
1263 attrs: vec!["*".to_string()],
1264 };
1265
1266 let empty_result = ldaps
1267 .do_search(idms, &sr, &anon_t, Source::Internal)
1268 .await
1269 .map(|r| {
1270 r.into_iter()
1271 .filter(|r| matches!(r.op, LdapOp::SearchResultEntry(_)))
1272 .collect::<Vec<_>>()
1273 })
1274 .unwrap();
1275
1276 assert!(empty_result.is_empty());
1277 }
1278
1279 #[idm_test]
1280 async fn test_ldap_application_bind(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
1281 let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
1282
1283 let usr_uuid = Uuid::new_v4();
1284 let grp_uuid = Uuid::new_v4();
1285 let app_uuid = Uuid::new_v4();
1286
1287 {
1289 let e1 = entry_init!(
1290 (Attribute::Class, EntryClass::Object.to_value()),
1291 (Attribute::Class, EntryClass::Account.to_value()),
1292 (Attribute::Class, EntryClass::Person.to_value()),
1293 (Attribute::Name, Value::new_iname("testperson1")),
1294 (Attribute::Uuid, Value::Uuid(usr_uuid)),
1295 (Attribute::Description, Value::new_utf8s("testperson1")),
1296 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
1297 );
1298
1299 let e2 = entry_init!(
1300 (Attribute::Class, EntryClass::Object.to_value()),
1301 (Attribute::Class, EntryClass::Group.to_value()),
1302 (Attribute::Name, Value::new_iname("testgroup1")),
1303 (Attribute::Uuid, Value::Uuid(grp_uuid))
1304 );
1305
1306 let e3 = entry_init!(
1307 (Attribute::Class, EntryClass::Object.to_value()),
1308 (Attribute::Class, EntryClass::Account.to_value()),
1309 (Attribute::Class, EntryClass::ServiceAccount.to_value()),
1310 (Attribute::Class, EntryClass::Application.to_value()),
1311 (Attribute::DisplayName, Value::new_utf8s("Application")),
1312 (Attribute::Name, Value::new_iname("testapp1")),
1313 (Attribute::Uuid, Value::Uuid(app_uuid)),
1314 (Attribute::LinkedGroup, Value::Refer(grp_uuid))
1315 );
1316
1317 let ct = duration_from_epoch_now();
1318 let mut server_txn = idms.proxy_write(ct).await.unwrap();
1319 assert!(server_txn
1320 .qs_write
1321 .internal_create(vec![e1, e2, e3])
1322 .and_then(|_| server_txn.commit())
1323 .is_ok());
1324 }
1325
1326 let res = ldaps
1328 .do_bind(idms, "spn=testperson1,app=testapp1,dc=example,dc=com", "")
1329 .await;
1330 assert!(res.is_ok());
1331 assert!(res.unwrap().is_none());
1332
1333 {
1334 let ml = ModifyList::new_append(Attribute::Member, Value::Refer(usr_uuid));
1335 let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
1336 assert!(idms_prox_write
1337 .qs_write
1338 .internal_modify_uuid(grp_uuid, &ml)
1339 .is_ok());
1340 assert!(idms_prox_write.commit().is_ok());
1341 }
1342
1343 let res = ldaps
1345 .do_bind(idms, "spn=testperson1,app=testapp1,dc=example,dc=com", "")
1346 .await;
1347 assert!(res.is_ok());
1348 assert!(res.unwrap().is_none());
1349
1350 let pass1: String;
1351 let pass2: String;
1352 let pass3: String;
1353 {
1354 let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
1355
1356 let ev = GenerateApplicationPasswordEvent::new_internal(
1357 usr_uuid,
1358 app_uuid,
1359 "apppwd1".to_string(),
1360 );
1361 (pass1, _) = idms_prox_write
1362 .generate_application_password(&ev)
1363 .expect("Failed to generate application password");
1364
1365 let ev = GenerateApplicationPasswordEvent::new_internal(
1366 usr_uuid,
1367 app_uuid,
1368 "apppwd2".to_string(),
1369 );
1370 (pass2, _) = idms_prox_write
1371 .generate_application_password(&ev)
1372 .expect("Failed to generate application password");
1373
1374 assert!(idms_prox_write.commit().is_ok());
1375
1376 let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
1378 let ev = GenerateApplicationPasswordEvent::new_internal(
1379 usr_uuid,
1380 app_uuid,
1381 "apppwd2".to_string(),
1382 );
1383 (pass3, _) = idms_prox_write
1384 .generate_application_password(&ev)
1385 .expect("Failed to generate application password");
1386 assert!(idms_prox_write.commit().is_ok());
1387 }
1388
1389 let res = ldaps
1391 .do_bind(
1392 idms,
1393 "spn=testperson1,app=testapp1,dc=example,dc=com",
1394 pass1.as_str(),
1395 )
1396 .await;
1397 assert!(res.is_ok());
1398 assert!(res.unwrap().is_some());
1399
1400 let res = ldaps
1402 .do_bind(
1403 idms,
1404 "spn=testperson1,app=testapp1,dc=example,dc=com",
1405 pass2.as_str(),
1406 )
1407 .await;
1408 assert!(res.is_ok());
1409 assert!(res.unwrap().is_none());
1410
1411 let res = ldaps
1413 .do_bind(
1414 idms,
1415 "spn=testperson1,app=testapp1,dc=example,dc=com",
1416 pass3.as_str(),
1417 )
1418 .await;
1419 assert!(res.is_ok());
1420 assert!(res.unwrap().is_some());
1421
1422 let res = ldaps
1424 .do_bind(
1425 idms,
1426 "spn=testperson1,app=testapp1,dc=example,dc=com",
1427 "FOO",
1428 )
1429 .await;
1430 assert!(res.is_ok());
1431 assert!(res.unwrap().is_none());
1432 }
1433
1434 #[idm_test]
1435 async fn test_ldap_application_linked_group(
1436 idms: &IdmServer,
1437 _idms_delayed: &IdmServerDelayed,
1438 ) {
1439 let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
1440
1441 let usr_uuid = Uuid::new_v4();
1442 let usr_name = "testuser1";
1443
1444 let grp1_uuid = Uuid::new_v4();
1445 let grp1_name = "testgroup1";
1446 let grp2_uuid = Uuid::new_v4();
1447 let grp2_name = "testgroup2";
1448
1449 let app1_uuid = Uuid::new_v4();
1450 let app1_name = "testapp1";
1451 let app2_uuid = Uuid::new_v4();
1452 let app2_name = "testapp2";
1453
1454 {
1456 let e1 = entry_init!(
1457 (Attribute::Class, EntryClass::Object.to_value()),
1458 (Attribute::Class, EntryClass::Account.to_value()),
1459 (Attribute::Class, EntryClass::Person.to_value()),
1460 (Attribute::Name, Value::new_iname(usr_name)),
1461 (Attribute::Uuid, Value::Uuid(usr_uuid)),
1462 (Attribute::Description, Value::new_utf8s(usr_name)),
1463 (Attribute::DisplayName, Value::new_utf8s(usr_name))
1464 );
1465
1466 let e2 = entry_init!(
1467 (Attribute::Class, EntryClass::Object.to_value()),
1468 (Attribute::Class, EntryClass::Group.to_value()),
1469 (Attribute::Name, Value::new_iname(grp1_name)),
1470 (Attribute::Uuid, Value::Uuid(grp1_uuid)),
1471 (Attribute::Member, Value::Refer(usr_uuid))
1472 );
1473
1474 let e3 = entry_init!(
1475 (Attribute::Class, EntryClass::Object.to_value()),
1476 (Attribute::Class, EntryClass::Group.to_value()),
1477 (Attribute::Name, Value::new_iname(grp2_name)),
1478 (Attribute::Uuid, Value::Uuid(grp2_uuid))
1479 );
1480
1481 let e4 = entry_init!(
1482 (Attribute::Class, EntryClass::Object.to_value()),
1483 (Attribute::Class, EntryClass::Account.to_value()),
1484 (Attribute::Class, EntryClass::ServiceAccount.to_value()),
1485 (Attribute::Class, EntryClass::Application.to_value()),
1486 (Attribute::DisplayName, Value::new_utf8s("Application")),
1487 (Attribute::Name, Value::new_iname(app1_name)),
1488 (Attribute::Uuid, Value::Uuid(app1_uuid)),
1489 (Attribute::LinkedGroup, Value::Refer(grp1_uuid))
1490 );
1491
1492 let e5 = entry_init!(
1493 (Attribute::Class, EntryClass::Object.to_value()),
1494 (Attribute::Class, EntryClass::Account.to_value()),
1495 (Attribute::Class, EntryClass::ServiceAccount.to_value()),
1496 (Attribute::Class, EntryClass::Application.to_value()),
1497 (Attribute::DisplayName, Value::new_utf8s("Application")),
1498 (Attribute::Name, Value::new_iname(app2_name)),
1499 (Attribute::Uuid, Value::Uuid(app2_uuid)),
1500 (Attribute::LinkedGroup, Value::Refer(grp2_uuid))
1501 );
1502
1503 let ct = duration_from_epoch_now();
1504 let mut server_txn = idms.proxy_write(ct).await.unwrap();
1505 assert!(server_txn
1506 .qs_write
1507 .internal_create(vec![e1, e2, e3, e4, e5])
1508 .and_then(|_| server_txn.commit())
1509 .is_ok());
1510 }
1511
1512 let pass_app1: String;
1513 let pass_app2: String;
1514 {
1515 let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
1516
1517 let ev = GenerateApplicationPasswordEvent::new_internal(
1518 usr_uuid,
1519 app1_uuid,
1520 "label".to_string(),
1521 );
1522 (pass_app1, _) = idms_prox_write
1523 .generate_application_password(&ev)
1524 .expect("Failed to generate application password");
1525
1526 let ev = GenerateApplicationPasswordEvent::new_internal(
1529 usr_uuid,
1530 app2_uuid,
1531 "label".to_string(),
1532 );
1533 (pass_app2, _) = idms_prox_write
1534 .generate_application_password(&ev)
1535 .expect("Failed to generate application password");
1536
1537 assert!(idms_prox_write.commit().is_ok());
1538 }
1539
1540 let res = ldaps
1542 .do_bind(
1543 idms,
1544 format!("spn={usr_name},app={app1_name},dc=example,dc=com").as_str(),
1545 pass_app1.as_str(),
1546 )
1547 .await;
1548 assert!(res.is_ok());
1549 assert!(res.unwrap().is_some());
1550
1551 let res = ldaps
1553 .do_bind(
1554 idms,
1555 format!("spn={usr_name},app={app2_name},dc=example,dc=com").as_str(),
1556 pass_app2.as_str(),
1557 )
1558 .await;
1559 assert!(res.is_ok());
1560 assert!(res.unwrap().is_none());
1561
1562 {
1564 let ml = ModifyList::new_append(Attribute::Member, Value::Refer(usr_uuid));
1565 let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
1566 assert!(idms_prox_write
1567 .qs_write
1568 .internal_modify_uuid(grp2_uuid, &ml)
1569 .is_ok());
1570 assert!(idms_prox_write.commit().is_ok());
1571 }
1572
1573 let res = ldaps
1575 .do_bind(
1576 idms,
1577 format!("spn={usr_name},app={app2_name},dc=example,dc=com").as_str(),
1578 pass_app2.as_str(),
1579 )
1580 .await;
1581 assert!(res.is_ok());
1582 assert!(res.unwrap().is_some());
1583
1584 let res = ldaps
1586 .do_bind(
1587 idms,
1588 format!("spn={usr_name},app={app1_name},dc=example,dc=com").as_str(),
1589 pass_app2.as_str(),
1590 )
1591 .await;
1592 assert!(res.is_ok());
1593 assert!(res.unwrap().is_none());
1594
1595 {
1597 let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
1598 let de = DeleteEvent::new_internal_invalid(filter!(f_eq(
1599 Attribute::Uuid,
1600 PartialValue::Uuid(app2_uuid)
1601 )));
1602 assert!(idms_prox_write.qs_write.delete(&de).is_ok());
1603 assert!(idms_prox_write.commit().is_ok());
1604 }
1605
1606 let res = ldaps
1607 .do_bind(
1608 idms,
1609 format!("spn={usr_name},app={app2_name},dc=example,dc=com").as_str(),
1610 pass_app2.as_str(),
1611 )
1612 .await;
1613 assert!(res.is_err());
1614 }
1615
1616 const TEST_CURRENT_TIME: u64 = 6000;
1620 const TEST_NOT_YET_VALID_TIME: u64 = TEST_CURRENT_TIME - 240;
1621 const TEST_VALID_FROM_TIME: u64 = TEST_CURRENT_TIME - 120;
1622 const TEST_EXPIRE_TIME: u64 = TEST_CURRENT_TIME + 120;
1623 const TEST_AFTER_EXPIRY: u64 = TEST_CURRENT_TIME + 240;
1624
1625 async fn set_account_valid_time(idms: &IdmServer, acct: Uuid) {
1626 let mut idms_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
1627
1628 let v_valid_from = Value::new_datetime_epoch(Duration::from_secs(TEST_VALID_FROM_TIME));
1629 let v_expire = Value::new_datetime_epoch(Duration::from_secs(TEST_EXPIRE_TIME));
1630
1631 let me = ModifyEvent::new_internal_invalid(
1632 filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(acct))),
1633 ModifyList::new_list(vec![
1634 Modify::Present(Attribute::AccountExpire, v_expire),
1635 Modify::Present(Attribute::AccountValidFrom, v_valid_from),
1636 ]),
1637 );
1638 assert!(idms_write.qs_write.modify(&me).is_ok());
1639 idms_write.commit().expect("Must not fail");
1640 }
1641
1642 #[idm_test]
1643 async fn test_ldap_application_valid_from_expire(
1644 idms: &IdmServer,
1645 _idms_delayed: &IdmServerDelayed,
1646 ) {
1647 let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
1648
1649 let usr_uuid = Uuid::new_v4();
1650 let usr_name = "testuser1";
1651
1652 let grp1_uuid = Uuid::new_v4();
1653 let grp1_name = "testgroup1";
1654
1655 let app1_uuid = Uuid::new_v4();
1656 let app1_name = "testapp1";
1657
1658 let pass_app1: String;
1659
1660 {
1662 let e1 = entry_init!(
1663 (Attribute::Class, EntryClass::Object.to_value()),
1664 (Attribute::Class, EntryClass::Account.to_value()),
1665 (Attribute::Class, EntryClass::Person.to_value()),
1666 (Attribute::Name, Value::new_iname(usr_name)),
1667 (Attribute::Uuid, Value::Uuid(usr_uuid)),
1668 (Attribute::Description, Value::new_utf8s(usr_name)),
1669 (Attribute::DisplayName, Value::new_utf8s(usr_name))
1670 );
1671
1672 let e2 = entry_init!(
1673 (Attribute::Class, EntryClass::Object.to_value()),
1674 (Attribute::Class, EntryClass::Group.to_value()),
1675 (Attribute::Name, Value::new_iname(grp1_name)),
1676 (Attribute::Uuid, Value::Uuid(grp1_uuid)),
1677 (Attribute::Member, Value::Refer(usr_uuid))
1678 );
1679
1680 let e3 = entry_init!(
1681 (Attribute::Class, EntryClass::Object.to_value()),
1682 (Attribute::Class, EntryClass::Account.to_value()),
1683 (Attribute::Class, EntryClass::ServiceAccount.to_value()),
1684 (Attribute::Class, EntryClass::Application.to_value()),
1685 (Attribute::DisplayName, Value::new_utf8s("Application")),
1686 (Attribute::Name, Value::new_iname(app1_name)),
1687 (Attribute::Uuid, Value::Uuid(app1_uuid)),
1688 (Attribute::LinkedGroup, Value::Refer(grp1_uuid))
1689 );
1690
1691 let ct = duration_from_epoch_now();
1692 let mut server_txn = idms.proxy_write(ct).await.unwrap();
1693 assert!(server_txn
1694 .qs_write
1695 .internal_create(vec![e1, e2, e3])
1696 .and_then(|_| server_txn.commit())
1697 .is_ok());
1698
1699 let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
1700
1701 let ev = GenerateApplicationPasswordEvent::new_internal(
1702 usr_uuid,
1703 app1_uuid,
1704 "label".to_string(),
1705 );
1706 (pass_app1, _) = idms_prox_write
1707 .generate_application_password(&ev)
1708 .expect("Failed to generate application password");
1709
1710 assert!(idms_prox_write.commit().is_ok());
1711 }
1712
1713 let res = ldaps
1715 .do_bind(
1716 idms,
1717 format!("spn={usr_name},app={app1_name},dc=example,dc=com").as_str(),
1718 pass_app1.as_str(),
1719 )
1720 .await;
1721 assert!(res.is_ok());
1722 assert!(res.unwrap().is_some());
1723
1724 set_account_valid_time(idms, usr_uuid).await;
1728
1729 let time_low = Duration::from_secs(TEST_NOT_YET_VALID_TIME);
1730 let time = Duration::from_secs(TEST_CURRENT_TIME);
1731 let time_high = Duration::from_secs(TEST_AFTER_EXPIRY);
1732
1733 let mut idms_auth = idms.auth().await.unwrap();
1734 let lae = LdapApplicationAuthEvent::new(app1_name, usr_uuid, pass_app1)
1735 .expect("Failed to build auth event");
1736
1737 let r1 = idms_auth
1738 .application_auth_ldap(&lae, time_low)
1739 .await
1740 .expect_err("Authentication succeeded");
1741 assert_eq!(r1, OperationError::SessionExpired);
1742
1743 let r1 = idms_auth
1744 .application_auth_ldap(&lae, time)
1745 .await
1746 .expect("Failed auth");
1747 assert!(r1.is_some());
1748
1749 let r1 = idms_auth
1750 .application_auth_ldap(&lae, time_high)
1751 .await
1752 .expect_err("Authentication succeeded");
1753 assert_eq!(r1, OperationError::SessionExpired);
1754 }
1755
1756 macro_rules! assert_entry_contains {
1757 (
1758 $entry:expr,
1759 $dn:expr,
1760 $($item:expr),*
1761 ) => {{
1762 assert_eq!($entry.dn, $dn);
1763 let mut attrs = HashSet::new();
1765 for a in $entry.attributes.iter() {
1766 for v in a.vals.iter() {
1767 attrs.insert((a.atype.as_str(), v.as_slice()));
1768 }
1769 };
1770 info!(?attrs);
1771 $(
1772 warn!("{}", $item.0);
1773 assert!(attrs.contains(&(
1774 $item.0.as_ref(), $item.1.as_bytes()
1775 )));
1776 )*
1777
1778 }};
1779 }
1780
1781 #[idm_test]
1782 async fn test_ldap_virtual_attribute_generation(
1783 idms: &IdmServer,
1784 _idms_delayed: &IdmServerDelayed,
1785 ) {
1786 let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
1787
1788 let ssh_ed25519 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeGW1P6Pc2rPq0XqbRaDKBcXZUPRklo0L1EyR30CwoP william@amethyst";
1789
1790 {
1792 let e1 = entry_init!(
1793 (Attribute::Class, EntryClass::Object.to_value()),
1794 (Attribute::Class, EntryClass::Person.to_value()),
1795 (Attribute::Class, EntryClass::Account.to_value()),
1796 (Attribute::Class, EntryClass::PosixAccount.to_value()),
1797 (Attribute::Name, Value::new_iname("testperson1")),
1798 (
1799 Attribute::Uuid,
1800 Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1801 ),
1802 (Attribute::Description, Value::new_utf8s("testperson1")),
1803 (Attribute::DisplayName, Value::new_utf8s("testperson1")),
1804 (Attribute::GidNumber, Value::new_uint32(12345)),
1805 (Attribute::LoginShell, Value::new_iutf8("/bin/zsh")),
1806 (
1807 Attribute::SshPublicKey,
1808 Value::new_sshkey_str("test", ssh_ed25519).expect("Invalid ssh key")
1809 )
1810 );
1811
1812 let mut server_txn = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
1813
1814 server_txn
1816 .qs_write
1817 .internal_modify_uuid(
1818 UUID_IDM_UNIX_AUTHENTICATION_READ,
1819 &ModifyList::new_append(Attribute::Member, Value::Refer(UUID_ANONYMOUS)),
1820 )
1821 .expect("Unable to modify UNIX_AUTHENTICATION_READ group");
1822
1823 let ce = CreateEvent::new_internal(vec![e1]);
1824 assert!(server_txn
1825 .qs_write
1826 .create(&ce)
1827 .and_then(|_| server_txn.commit())
1828 .is_ok());
1829 }
1830
1831 let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
1833 assert_eq!(
1834 anon_t.effective_session,
1835 LdapSession::UnixBind(UUID_ANONYMOUS)
1836 );
1837
1838 let sr = SearchRequest {
1840 msgid: 1,
1841 base: "dc=example,dc=com".to_string(),
1842 scope: LdapSearchScope::Subtree,
1843 filter: LdapFilter::Equality(Attribute::Name.to_string(), "testperson1".to_string()),
1844 attrs: vec!["*".to_string()],
1845 };
1846 let r1 = ldaps
1847 .do_search(idms, &sr, &anon_t, Source::Internal)
1848 .await
1849 .unwrap();
1850
1851 assert_eq!(r1.len(), 2);
1853 match &r1[0].op {
1854 LdapOp::SearchResultEntry(lsre) => {
1855 assert_entry_contains!(
1856 lsre,
1857 "spn=testperson1@example.com,dc=example,dc=com",
1858 (Attribute::Class, EntryClass::Object.to_string()),
1859 (Attribute::Class, EntryClass::Person.to_string()),
1860 (Attribute::Class, EntryClass::Account.to_string()),
1861 (Attribute::Class, EntryClass::PosixAccount.to_string()),
1862 (Attribute::DisplayName, "testperson1"),
1863 (Attribute::Name, "testperson1"),
1864 (Attribute::GidNumber, "12345"),
1865 (Attribute::LoginShell, "/bin/zsh"),
1866 (Attribute::SshPublicKey, ssh_ed25519),
1867 (Attribute::Uuid, "cc8e95b4-c24f-4d68-ba54-8bed76f63930")
1868 );
1869 }
1870 _ => panic!("Oh no"),
1871 };
1872
1873 let sr = SearchRequest {
1875 msgid: 1,
1876 base: "dc=example,dc=com".to_string(),
1877 scope: LdapSearchScope::Subtree,
1878 filter: LdapFilter::Equality(Attribute::Name.to_string(), "testperson1".to_string()),
1879 attrs: vec!["+".to_string()],
1880 };
1881 let r1 = ldaps
1882 .do_search(idms, &sr, &anon_t, Source::Internal)
1883 .await
1884 .unwrap();
1885
1886 assert_eq!(r1.len(), 2);
1888 match &r1[0].op {
1889 LdapOp::SearchResultEntry(lsre) => {
1890 assert_entry_contains!(
1891 lsre,
1892 "spn=testperson1@example.com,dc=example,dc=com",
1893 (Attribute::ObjectClass, EntryClass::Object.as_ref()),
1894 (Attribute::ObjectClass, EntryClass::Person.as_ref()),
1895 (Attribute::ObjectClass, EntryClass::Account.as_ref()),
1896 (Attribute::ObjectClass, EntryClass::PosixAccount.as_ref()),
1897 (Attribute::DisplayName, "testperson1"),
1898 (Attribute::Name, "testperson1"),
1899 (Attribute::GidNumber, "12345"),
1900 (Attribute::LoginShell, "/bin/zsh"),
1901 (Attribute::SshPublicKey, ssh_ed25519),
1902 (Attribute::EntryUuid, "cc8e95b4-c24f-4d68-ba54-8bed76f63930"),
1903 (
1904 Attribute::HomeDirectory,
1905 "/home/cc8e95b4-c24f-4d68-ba54-8bed76f63930"
1906 ),
1907 (
1908 Attribute::EntryDn,
1909 "spn=testperson1@example.com,dc=example,dc=com"
1910 ),
1911 (Attribute::UidNumber, "12345"),
1912 (Attribute::Cn, "testperson1"),
1913 (Attribute::LdapKeys, ssh_ed25519)
1914 );
1915 }
1916 _ => panic!("Oh no"),
1917 };
1918
1919 let sr = SearchRequest {
1921 msgid: 1,
1922 base: "dc=example,dc=com".to_string(),
1923 scope: LdapSearchScope::Subtree,
1924 filter: LdapFilter::Equality(Attribute::Name.to_string(), "testperson1".to_string()),
1925 attrs: vec![
1926 LDAP_ATTR_NAME.to_string(),
1927 Attribute::EntryDn.to_string(),
1928 ATTR_LDAP_KEYS.to_string(),
1929 Attribute::UidNumber.to_string(),
1930 ],
1931 };
1932 let r1 = ldaps
1933 .do_search(idms, &sr, &anon_t, Source::Internal)
1934 .await
1935 .unwrap();
1936
1937 assert_eq!(r1.len(), 2);
1939 match &r1[0].op {
1940 LdapOp::SearchResultEntry(lsre) => {
1941 assert_entry_contains!(
1942 lsre,
1943 "spn=testperson1@example.com,dc=example,dc=com",
1944 (Attribute::Name, "testperson1"),
1945 (
1946 Attribute::EntryDn,
1947 "spn=testperson1@example.com,dc=example,dc=com"
1948 ),
1949 (Attribute::UidNumber, "12345"),
1950 (Attribute::LdapKeys, ssh_ed25519)
1951 );
1952 }
1953 _ => panic!("Oh no"),
1954 };
1955 }
1956
1957 #[idm_test]
1958 async fn test_ldap_token_privilege_granting(
1959 idms: &IdmServer,
1960 _idms_delayed: &IdmServerDelayed,
1961 ) {
1962 let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
1964
1965 let sr = SearchRequest {
1967 msgid: 1,
1968 base: "dc=example,dc=com".to_string(),
1969 scope: LdapSearchScope::Subtree,
1970 filter: LdapFilter::Equality(Attribute::Name.to_string(), "testperson1".to_string()),
1971 attrs: vec![
1972 LDAP_ATTR_NAME,
1973 LDAP_ATTR_MAIL,
1974 LDAP_ATTR_MAIL_PRIMARY,
1975 LDAP_ATTR_MAIL_ALTERNATIVE,
1976 LDAP_ATTR_EMAIL_PRIMARY,
1977 LDAP_ATTR_EMAIL_ALTERNATIVE,
1978 ]
1979 .into_iter()
1980 .map(|s| s.to_string())
1981 .collect(),
1982 };
1983
1984 let sa_uuid = uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
1985
1986 let apitoken = {
1989 let e1 = entry_init!(
1992 (Attribute::Class, EntryClass::Object.to_value()),
1993 (Attribute::Class, EntryClass::ServiceAccount.to_value()),
1994 (Attribute::Class, EntryClass::Account.to_value()),
1995 (Attribute::Uuid, Value::Uuid(sa_uuid)),
1996 (Attribute::Name, Value::new_iname("service_permission_test")),
1997 (
1998 Attribute::DisplayName,
1999 Value::new_utf8s("service_permission_test")
2000 )
2001 );
2002
2003 let e2 = entry_init!(
2005 (Attribute::Class, EntryClass::Object.to_value()),
2006 (Attribute::Class, EntryClass::Person.to_value()),
2007 (Attribute::Class, EntryClass::Account.to_value()),
2008 (Attribute::Class, EntryClass::PosixAccount.to_value()),
2009 (Attribute::Name, Value::new_iname("testperson1")),
2010 (
2011 Attribute::Mail,
2012 Value::EmailAddress("testperson1@example.com".to_string(), true)
2013 ),
2014 (
2015 Attribute::Mail,
2016 Value::EmailAddress("testperson1.alternative@example.com".to_string(), false)
2017 ),
2018 (Attribute::Description, Value::new_utf8s("testperson1")),
2019 (Attribute::DisplayName, Value::new_utf8s("testperson1")),
2020 (Attribute::GidNumber, Value::new_uint32(12345)),
2021 (Attribute::LoginShell, Value::new_iutf8("/bin/zsh"))
2022 );
2023
2024 let ct = duration_from_epoch_now();
2025
2026 let mut server_txn = idms.proxy_write(ct).await.unwrap();
2027
2028 let ce = CreateEvent::new_internal(vec![e1, e2]);
2029 assert!(server_txn.qs_write.create(&ce).is_ok());
2030
2031 server_txn
2033 .qs_write
2034 .internal_modify_uuid(
2035 UUID_IDM_ACCOUNT_MAIL_READ,
2036 &ModifyList::new_append(Attribute::Member, Value::Refer(sa_uuid)),
2037 )
2038 .expect("Unable to modify UNIX_AUTHENTICATION_READ group");
2039
2040 server_txn
2042 .qs_write
2043 .internal_modify_uuid(
2044 UUID_IDM_UNIX_AUTHENTICATION_READ,
2045 &ModifyList::new_append(Attribute::Member, Value::Refer(UUID_ANONYMOUS)),
2046 )
2047 .expect("Unable to modify UNIX_AUTHENTICATION_READ group");
2048
2049 let gte = GenerateApiTokenEvent::new_internal(sa_uuid, "TestToken", None);
2053
2054 let apitoken = server_txn
2055 .service_account_generate_api_token(>e, ct)
2056 .expect("Failed to create new apitoken");
2057
2058 assert!(server_txn.commit().is_ok());
2059
2060 apitoken
2061 };
2062
2063 let anon_lbt = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
2068 assert_eq!(
2069 anon_lbt.effective_session,
2070 LdapSession::UnixBind(UUID_ANONYMOUS)
2071 );
2072
2073 let r1 = ldaps
2074 .do_search(idms, &sr, &anon_lbt, Source::Internal)
2075 .await
2076 .unwrap();
2077 assert_eq!(r1.len(), 2);
2078 match &r1[0].op {
2079 LdapOp::SearchResultEntry(lsre) => {
2080 assert_entry_contains!(
2081 lsre,
2082 "spn=testperson1@example.com,dc=example,dc=com",
2083 (Attribute::Name, "testperson1")
2084 );
2085 }
2086 _ => panic!("Oh no"),
2087 };
2088
2089 let jws_verifier = JwsDangerReleaseWithoutVerify::default();
2091
2092 let apitoken_inner = jws_verifier
2093 .verify(&apitoken)
2094 .unwrap()
2095 .from_json::<ApiToken>()
2096 .unwrap();
2097
2098 let sa_lbt = ldaps
2100 .do_bind(idms, "dn=token", &apitoken.to_string())
2101 .await
2102 .unwrap()
2103 .unwrap();
2104 assert_eq!(
2105 sa_lbt.effective_session,
2106 LdapSession::ApiToken(apitoken_inner.clone())
2107 );
2108
2109 let sa_lbt = ldaps
2111 .do_bind(idms, "", &apitoken.to_string())
2112 .await
2113 .unwrap()
2114 .unwrap();
2115 assert_eq!(
2116 sa_lbt.effective_session,
2117 LdapSession::ApiToken(apitoken_inner)
2118 );
2119
2120 let r1 = ldaps
2122 .do_search(idms, &sr, &sa_lbt, Source::Internal)
2123 .await
2124 .unwrap();
2125 assert_eq!(r1.len(), 2);
2126 match &r1[0].op {
2127 LdapOp::SearchResultEntry(lsre) => {
2128 assert_entry_contains!(
2129 lsre,
2130 "spn=testperson1@example.com,dc=example,dc=com",
2131 (Attribute::Name, "testperson1"),
2132 (Attribute::Mail, "testperson1@example.com"),
2133 (Attribute::Mail, "testperson1.alternative@example.com"),
2134 (LDAP_ATTR_MAIL_PRIMARY, "testperson1@example.com"),
2135 (
2136 LDAP_ATTR_MAIL_ALTERNATIVE,
2137 "testperson1.alternative@example.com"
2138 ),
2139 (LDAP_ATTR_EMAIL_PRIMARY, "testperson1@example.com"),
2140 (
2141 LDAP_ATTR_EMAIL_ALTERNATIVE,
2142 "testperson1.alternative@example.com"
2143 )
2144 );
2145 }
2146 _ => panic!("Oh no"),
2147 };
2148
2149 let sr = SearchRequest {
2152 msgid: 2,
2153 base: "dc=example,dc=com".to_string(),
2154 scope: LdapSearchScope::Subtree,
2155 filter: LdapFilter::And(vec![
2156 LdapFilter::Equality(Attribute::Class.to_string(), "posixAccount".to_string()),
2157 LdapFilter::Substring(
2158 LDAP_ATTR_MAIL.to_string(),
2159 LdapSubstringFilter {
2160 initial: None,
2161 any: vec![],
2162 final_: Some("@example.com".to_string()),
2163 },
2164 ),
2165 ]),
2166 attrs: vec![
2167 LDAP_ATTR_NAME,
2168 LDAP_ATTR_MAIL,
2169 LDAP_ATTR_MAIL_PRIMARY,
2170 LDAP_ATTR_MAIL_ALTERNATIVE,
2171 ]
2172 .into_iter()
2173 .map(|s| s.to_string())
2174 .collect(),
2175 };
2176
2177 let r1 = ldaps
2178 .do_search(idms, &sr, &sa_lbt, Source::Internal)
2179 .await
2180 .unwrap();
2181
2182 assert_eq!(r1.len(), 2);
2183 match &r1[0].op {
2184 LdapOp::SearchResultEntry(lsre) => {
2185 assert_entry_contains!(
2186 lsre,
2187 "spn=testperson1@example.com,dc=example,dc=com",
2188 (Attribute::Name, "testperson1"),
2189 (Attribute::Mail, "testperson1@example.com"),
2190 (Attribute::Mail, "testperson1.alternative@example.com"),
2191 (LDAP_ATTR_MAIL_PRIMARY, "testperson1@example.com"),
2192 (
2193 LDAP_ATTR_MAIL_ALTERNATIVE,
2194 "testperson1.alternative@example.com"
2195 )
2196 );
2197 }
2198 _ => panic!("Oh no"),
2199 };
2200 }
2201
2202 #[idm_test]
2203 async fn test_ldap_virtual_attribute_with_all_attr_search(
2204 idms: &IdmServer,
2205 _idms_delayed: &IdmServerDelayed,
2206 ) {
2207 let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
2208
2209 let acct_uuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
2210
2211 {
2213 let e1 = entry_init!(
2214 (Attribute::Class, EntryClass::Person.to_value()),
2215 (Attribute::Class, EntryClass::Account.to_value()),
2216 (Attribute::Class, EntryClass::PosixAccount.to_value()),
2217 (Attribute::Name, Value::new_iname("testperson1")),
2218 (Attribute::Uuid, Value::Uuid(acct_uuid)),
2219 (Attribute::Description, Value::new_utf8s("testperson1")),
2220 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
2221 );
2222
2223 let mut server_txn = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2224
2225 server_txn
2227 .qs_write
2228 .internal_modify_uuid(
2229 UUID_IDM_UNIX_AUTHENTICATION_READ,
2230 &ModifyList::new_append(Attribute::Member, Value::Refer(UUID_ANONYMOUS)),
2231 )
2232 .expect("Unable to modify UNIX_AUTHENTICATION_READ group");
2233
2234 assert!(server_txn
2235 .qs_write
2236 .internal_create(vec![e1])
2237 .and_then(|_| server_txn.commit())
2238 .is_ok());
2239 }
2240
2241 let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
2243 assert_eq!(
2244 anon_t.effective_session,
2245 LdapSession::UnixBind(UUID_ANONYMOUS)
2246 );
2247
2248 let sr = SearchRequest {
2250 msgid: 1,
2251 base: "dc=example,dc=com".to_string(),
2252 scope: LdapSearchScope::Subtree,
2253 filter: LdapFilter::Equality(Attribute::Name.to_string(), "testperson1".to_string()),
2254 attrs: vec![
2255 "*".to_string(),
2256 LDAP_ATTR_NAME.to_string(),
2258 Attribute::EntryUuid.to_string(),
2260 ],
2261 };
2262 let r1 = ldaps
2263 .do_search(idms, &sr, &anon_t, Source::Internal)
2264 .await
2265 .unwrap();
2266
2267 assert_eq!(r1.len(), 2);
2269 match &r1[0].op {
2270 LdapOp::SearchResultEntry(lsre) => {
2271 assert_entry_contains!(
2272 lsre,
2273 "spn=testperson1@example.com,dc=example,dc=com",
2274 (Attribute::Name, "testperson1"),
2275 (Attribute::DisplayName, "testperson1"),
2276 (Attribute::Uuid, "cc8e95b4-c24f-4d68-ba54-8bed76f63930"),
2277 (Attribute::EntryUuid, "cc8e95b4-c24f-4d68-ba54-8bed76f63930")
2278 );
2279 }
2280 _ => panic!("Oh no"),
2281 };
2282 }
2283
2284 #[idm_test]
2286 async fn test_ldap_one_dot_one_attribute(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
2287 let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
2288
2289 let acct_uuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
2290
2291 {
2293 let e1 = entry_init!(
2294 (Attribute::Class, EntryClass::Person.to_value()),
2295 (Attribute::Class, EntryClass::Account.to_value()),
2296 (Attribute::Class, EntryClass::PosixAccount.to_value()),
2297 (Attribute::Name, Value::new_iname("testperson1")),
2298 (Attribute::Uuid, Value::Uuid(acct_uuid)),
2299 (Attribute::Description, Value::new_utf8s("testperson1")),
2300 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
2301 );
2302
2303 let mut server_txn = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2304
2305 server_txn
2307 .qs_write
2308 .internal_modify_uuid(
2309 UUID_IDM_UNIX_AUTHENTICATION_READ,
2310 &ModifyList::new_append(Attribute::Member, Value::Refer(UUID_ANONYMOUS)),
2311 )
2312 .expect("Unable to modify UNIX_AUTHENTICATION_READ group");
2313
2314 assert!(server_txn
2315 .qs_write
2316 .internal_create(vec![e1])
2317 .and_then(|_| server_txn.commit())
2318 .is_ok());
2319 }
2320
2321 let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
2323 assert_eq!(
2324 anon_t.effective_session,
2325 LdapSession::UnixBind(UUID_ANONYMOUS)
2326 );
2327
2328 let sr = SearchRequest {
2330 msgid: 1,
2331 base: "dc=example,dc=com".to_string(),
2332 scope: LdapSearchScope::Subtree,
2333 filter: LdapFilter::Equality(Attribute::Name.to_string(), "testperson1".to_string()),
2334 attrs: vec!["1.1".to_string()],
2335 };
2336 let r1 = ldaps
2337 .do_search(idms, &sr, &anon_t, Source::Internal)
2338 .await
2339 .unwrap();
2340
2341 assert_eq!(r1.len(), 2);
2343 match &r1[0].op {
2344 LdapOp::SearchResultEntry(lsre) => {
2345 assert_eq!(
2346 lsre.dn.as_str(),
2347 "spn=testperson1@example.com,dc=example,dc=com"
2348 );
2349 assert!(lsre.attributes.is_empty());
2350 }
2351 _ => panic!("Oh no"),
2352 };
2353
2354 let sr = SearchRequest {
2356 msgid: 1,
2357 base: "dc=example,dc=com".to_string(),
2358 scope: LdapSearchScope::Subtree,
2359 filter: LdapFilter::Equality(Attribute::Name.to_string(), "testperson1".to_string()),
2360 attrs: vec![
2361 "1.1".to_string(),
2362 Attribute::EntryUuid.to_string(),
2364 ],
2365 };
2366 let r1 = ldaps
2367 .do_search(idms, &sr, &anon_t, Source::Internal)
2368 .await
2369 .unwrap();
2370
2371 assert_eq!(r1.len(), 2);
2373 match &r1[0].op {
2374 LdapOp::SearchResultEntry(lsre) => {
2375 assert_entry_contains!(
2376 lsre,
2377 "spn=testperson1@example.com,dc=example,dc=com",
2378 (Attribute::EntryUuid, "cc8e95b4-c24f-4d68-ba54-8bed76f63930")
2379 );
2380 }
2381 _ => panic!("Oh no"),
2382 };
2383 }
2384
2385 #[idm_test]
2386 async fn test_ldap_rootdse_basedn_change(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
2387 let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
2388
2389 let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
2390 assert_eq!(
2391 anon_t.effective_session,
2392 LdapSession::UnixBind(UUID_ANONYMOUS)
2393 );
2394
2395 let sr = SearchRequest {
2396 msgid: 1,
2397 base: "".to_string(),
2398 scope: LdapSearchScope::Base,
2399 filter: LdapFilter::Present(Attribute::ObjectClass.to_string()),
2400 attrs: vec!["*".to_string()],
2401 };
2402 let r1 = ldaps
2403 .do_search(idms, &sr, &anon_t, Source::Internal)
2404 .await
2405 .unwrap();
2406
2407 trace!(?r1);
2408
2409 assert_eq!(r1.len(), 2);
2411 match &r1[0].op {
2412 LdapOp::SearchResultEntry(lsre) => {
2413 assert_entry_contains!(
2414 lsre,
2415 "",
2416 (Attribute::ObjectClass, "top"),
2417 ("vendorname", "Kanidm Project"),
2418 ("supportedldapversion", "3"),
2419 ("defaultnamingcontext", "dc=example,dc=com")
2420 );
2421 }
2422 _ => panic!("Oh no"),
2423 };
2424
2425 drop(ldaps);
2426
2427 let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2430 let me_posix = ModifyEvent::new_internal_invalid(
2432 filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_DOMAIN_INFO))),
2433 ModifyList::new_purge_and_set(
2434 Attribute::DomainLdapBasedn,
2435 Value::new_iutf8("o=kanidmproject"),
2436 ),
2437 );
2438 assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok());
2439
2440 assert!(idms_prox_write.commit().is_ok());
2441
2442 let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
2444
2445 let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
2446 assert_eq!(
2447 anon_t.effective_session,
2448 LdapSession::UnixBind(UUID_ANONYMOUS)
2449 );
2450
2451 let sr = SearchRequest {
2452 msgid: 1,
2453 base: "".to_string(),
2454 scope: LdapSearchScope::Base,
2455 filter: LdapFilter::Present(Attribute::ObjectClass.to_string()),
2456 attrs: vec!["*".to_string()],
2457 };
2458 let r1 = ldaps
2459 .do_search(idms, &sr, &anon_t, Source::Internal)
2460 .await
2461 .unwrap();
2462
2463 trace!(?r1);
2464
2465 assert_eq!(r1.len(), 2);
2467 match &r1[0].op {
2468 LdapOp::SearchResultEntry(lsre) => {
2469 assert_entry_contains!(
2470 lsre,
2471 "",
2472 (Attribute::ObjectClass, "top"),
2473 ("vendorname", "Kanidm Project"),
2474 ("supportedldapversion", "3"),
2475 ("defaultnamingcontext", "o=kanidmproject")
2476 );
2477 }
2478 _ => panic!("Oh no"),
2479 };
2480 }
2481
2482 #[idm_test]
2483 async fn test_ldap_sssd_compat(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
2484 let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
2485
2486 let acct_uuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
2487
2488 {
2490 let e1 = entry_init!(
2491 (Attribute::Class, EntryClass::Person.to_value()),
2492 (Attribute::Class, EntryClass::Account.to_value()),
2493 (Attribute::Class, EntryClass::PosixAccount.to_value()),
2494 (Attribute::Name, Value::new_iname("testperson1")),
2495 (Attribute::Uuid, Value::Uuid(acct_uuid)),
2496 (Attribute::GidNumber, Value::Uint32(12345)),
2497 (Attribute::Description, Value::new_utf8s("testperson1")),
2498 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
2499 );
2500
2501 let mut server_txn = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2502
2503 server_txn
2505 .qs_write
2506 .internal_modify_uuid(
2507 UUID_IDM_UNIX_AUTHENTICATION_READ,
2508 &ModifyList::new_append(Attribute::Member, Value::Refer(UUID_ANONYMOUS)),
2509 )
2510 .expect("Unable to modify UNIX_AUTHENTICATION_READ group");
2511
2512 assert!(server_txn
2513 .qs_write
2514 .internal_create(vec![e1])
2515 .and_then(|_| server_txn.commit())
2516 .is_ok());
2517 }
2518
2519 let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
2521 assert_eq!(
2522 anon_t.effective_session,
2523 LdapSession::UnixBind(UUID_ANONYMOUS)
2524 );
2525
2526 let sr = SearchRequest {
2528 msgid: 1,
2529 base: "dc=example,dc=com".to_string(),
2530 scope: LdapSearchScope::Subtree,
2531 filter: LdapFilter::And(vec![
2532 LdapFilter::Equality(Attribute::Class.to_string(), "sudohost".to_string()),
2533 LdapFilter::Substring(
2534 Attribute::SudoHost.to_string(),
2535 LdapSubstringFilter {
2536 initial: Some("a".to_string()),
2537 any: vec!["x".to_string()],
2538 final_: Some("z".to_string()),
2539 },
2540 ),
2541 ]),
2542 attrs: vec![
2543 "*".to_string(),
2544 LDAP_ATTR_NAME.to_string(),
2546 Attribute::EntryUuid.to_string(),
2548 ],
2549 };
2550 let r1 = ldaps
2551 .do_search(idms, &sr, &anon_t, Source::Internal)
2552 .await
2553 .unwrap();
2554
2555 assert_eq!(r1.len(), 1);
2557
2558 let sr = SearchRequest {
2561 msgid: 1,
2562 base: "dc=example,dc=com".to_string(),
2563 scope: LdapSearchScope::Subtree,
2564 filter: LdapFilter::Equality(Attribute::Name.to_string(), "testperson1".to_string()),
2565 attrs: vec![
2566 "uid".to_string(),
2567 "uidNumber".to_string(),
2568 "gidNumber".to_string(),
2569 "gecos".to_string(),
2570 "cn".to_string(),
2571 "entryuuid".to_string(),
2572 ],
2573 };
2574 let r1 = ldaps
2575 .do_search(idms, &sr, &anon_t, Source::Internal)
2576 .await
2577 .unwrap();
2578
2579 trace!(?r1);
2580
2581 assert_eq!(r1.len(), 2);
2583 match &r1[0].op {
2584 LdapOp::SearchResultEntry(lsre) => {
2585 assert_entry_contains!(
2586 lsre,
2587 "spn=testperson1@example.com,dc=example,dc=com",
2588 (Attribute::Uid, "testperson1"),
2589 (Attribute::Cn, "testperson1"),
2590 (Attribute::Gecos, "testperson1"),
2591 (Attribute::UidNumber, "12345"),
2592 (Attribute::GidNumber, "12345"),
2593 (Attribute::EntryUuid, "cc8e95b4-c24f-4d68-ba54-8bed76f63930")
2594 );
2595 }
2596 _ => panic!("Oh no"),
2597 };
2598 }
2599
2600 #[idm_test]
2601 async fn test_ldap_compare_request(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
2602 let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
2603
2604 {
2606 let acct_uuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
2607
2608 let e1 = entry_init!(
2609 (Attribute::Class, EntryClass::Person.to_value()),
2610 (Attribute::Class, EntryClass::Account.to_value()),
2611 (Attribute::Class, EntryClass::PosixAccount.to_value()),
2612 (Attribute::Name, Value::new_iname("testperson1")),
2613 (Attribute::Uuid, Value::Uuid(acct_uuid)),
2614 (Attribute::GidNumber, Value::Uint32(12345)),
2615 (Attribute::Description, Value::new_utf8s("testperson1")),
2616 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
2617 );
2618
2619 let mut server_txn = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2620
2621 server_txn
2623 .qs_write
2624 .internal_modify_uuid(
2625 UUID_IDM_UNIX_AUTHENTICATION_READ,
2626 &ModifyList::new_append(Attribute::Member, Value::Refer(UUID_ANONYMOUS)),
2627 )
2628 .expect("Unable to modify UNIX_AUTHENTICATION_READ group");
2629
2630 assert!(server_txn
2631 .qs_write
2632 .internal_create(vec![e1])
2633 .and_then(|_| server_txn.commit())
2634 .is_ok());
2635 }
2636
2637 let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
2639 assert_eq!(
2640 anon_t.effective_session,
2641 LdapSession::UnixBind(UUID_ANONYMOUS)
2642 );
2643
2644 #[track_caller]
2645 fn assert_compare_result(r: &[LdapMsg], code: &LdapResultCode) {
2646 assert_eq!(r.len(), 1);
2647 match &r[0].op {
2648 LdapOp::CompareResult(lcr) => {
2649 assert_eq!(&lcr.code, code);
2650 }
2651 _ => panic!("Oh no"),
2652 };
2653 }
2654
2655 let cr = CompareRequest {
2656 msgid: 1,
2657 entry: "name=testperson1,dc=example,dc=com".to_string(),
2658 atype: Attribute::Name.to_string(),
2659 val: "testperson1".to_string(),
2660 };
2661
2662 assert_compare_result(
2663 &ldaps
2664 .do_compare(idms, &cr, &anon_t, Source::Internal)
2665 .await
2666 .unwrap(),
2667 &LdapResultCode::CompareTrue,
2668 );
2669
2670 let cr = CompareRequest {
2671 msgid: 1,
2672 entry: "name=testperson1,dc=example,dc=com".to_string(),
2673 atype: Attribute::GidNumber.to_string(),
2674 val: "12345".to_string(),
2675 };
2676
2677 assert_compare_result(
2678 &ldaps
2679 .do_compare(idms, &cr, &anon_t, Source::Internal)
2680 .await
2681 .unwrap(),
2682 &LdapResultCode::CompareTrue,
2683 );
2684
2685 let cr = CompareRequest {
2686 msgid: 1,
2687 entry: "name=testperson1,dc=example,dc=com".to_string(),
2688 atype: Attribute::Name.to_string(),
2689 val: "other".to_string(),
2690 };
2691 assert_compare_result(
2692 &ldaps
2693 .do_compare(idms, &cr, &anon_t, Source::Internal)
2694 .await
2695 .unwrap(),
2696 &LdapResultCode::CompareFalse,
2697 );
2698
2699 let cr = CompareRequest {
2700 msgid: 1,
2701 entry: "name=other,dc=example,dc=com".to_string(),
2702 atype: Attribute::Name.to_string(),
2703 val: "other".to_string(),
2704 };
2705 assert_compare_result(
2706 &ldaps
2707 .do_compare(idms, &cr, &anon_t, Source::Internal)
2708 .await
2709 .unwrap(),
2710 &LdapResultCode::NoSuchObject,
2711 );
2712
2713 let cr = CompareRequest {
2714 msgid: 1,
2715 entry: "invalidentry".to_string(),
2716 atype: Attribute::Name.to_string(),
2717 val: "other".to_string(),
2718 };
2719 assert!(&ldaps
2720 .do_compare(idms, &cr, &anon_t, Source::Internal)
2721 .await
2722 .is_err());
2723
2724 let cr = CompareRequest {
2725 msgid: 1,
2726 entry: "name=other,dc=example,dc=com".to_string(),
2727 atype: "invalid".to_string(),
2728 val: "other".to_string(),
2729 };
2730 assert_eq!(
2731 &ldaps
2732 .do_compare(idms, &cr, &anon_t, Source::Internal)
2733 .await
2734 .unwrap_err(),
2735 &OperationError::InvalidAttributeName("invalid".to_string()),
2736 );
2737 }
2738
2739 #[idm_test]
2740 async fn test_ldap_maximum_queryable_attributes(
2741 idms: &IdmServer,
2742 _idms_delayed: &IdmServerDelayed,
2743 ) {
2744 let mut server_txn = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2747
2748 let set_ldap_maximum_queryable_attrs = ModifyEvent::new_internal_invalid(
2749 filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_DOMAIN_INFO))),
2750 ModifyList::new_purge_and_set(Attribute::LdapMaxQueryableAttrs, Value::Uint32(2)),
2751 );
2752 assert!(server_txn
2753 .qs_write
2754 .modify(&set_ldap_maximum_queryable_attrs)
2755 .and_then(|_| server_txn.commit())
2756 .is_ok());
2757
2758 let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
2759
2760 let usr_uuid = Uuid::new_v4();
2761 let grp_uuid = Uuid::new_v4();
2762 let app_uuid = Uuid::new_v4();
2763 let app_name = "testapp1";
2764
2765 {
2767 let e1 = entry_init!(
2768 (Attribute::Class, EntryClass::Object.to_value()),
2769 (Attribute::Class, EntryClass::Account.to_value()),
2770 (Attribute::Class, EntryClass::Person.to_value()),
2771 (Attribute::Name, Value::new_iname("testperson1")),
2772 (Attribute::Uuid, Value::Uuid(usr_uuid)),
2773 (Attribute::Description, Value::new_utf8s("testperson1")),
2774 (Attribute::DisplayName, Value::new_utf8s("testperson1"))
2775 );
2776
2777 let e2 = entry_init!(
2778 (Attribute::Class, EntryClass::Object.to_value()),
2779 (Attribute::Class, EntryClass::Group.to_value()),
2780 (Attribute::Name, Value::new_iname("testgroup1")),
2781 (Attribute::Uuid, Value::Uuid(grp_uuid))
2782 );
2783
2784 let e3 = entry_init!(
2785 (Attribute::Class, EntryClass::Object.to_value()),
2786 (Attribute::Class, EntryClass::Account.to_value()),
2787 (Attribute::Class, EntryClass::ServiceAccount.to_value()),
2788 (Attribute::Class, EntryClass::Application.to_value()),
2789 (Attribute::DisplayName, Value::new_utf8s("Application")),
2790 (Attribute::Name, Value::new_iname(app_name)),
2791 (Attribute::Uuid, Value::Uuid(app_uuid)),
2792 (Attribute::LinkedGroup, Value::Refer(grp_uuid))
2793 );
2794
2795 let ct = duration_from_epoch_now();
2796 let mut server_txn = idms.proxy_write(ct).await.unwrap();
2797 assert!(server_txn
2798 .qs_write
2799 .internal_create(vec![e1, e2, e3])
2800 .and_then(|_| server_txn.commit())
2801 .is_ok());
2802 }
2803
2804 let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
2806 assert_eq!(
2807 anon_t.effective_session,
2808 LdapSession::UnixBind(UUID_ANONYMOUS)
2809 );
2810
2811 let invalid_search = SearchRequest {
2812 msgid: 1,
2813 base: "dc=example,dc=com".to_string(),
2814 scope: LdapSearchScope::Subtree,
2815 filter: LdapFilter::Present(Attribute::ObjectClass.to_string()),
2816 attrs: vec![
2817 "objectClass".to_string(),
2818 "cn".to_string(),
2819 "givenName".to_string(),
2820 ],
2821 };
2822
2823 let valid_search = SearchRequest {
2824 msgid: 1,
2825 base: "dc=example,dc=com".to_string(),
2826 scope: LdapSearchScope::Subtree,
2827 filter: LdapFilter::Present(Attribute::ObjectClass.to_string()),
2828 attrs: vec!["objectClass: person".to_string()],
2829 };
2830
2831 let invalid_res: Result<Vec<LdapMsg>, OperationError> = ldaps
2832 .do_search(idms, &invalid_search, &anon_t, Source::Internal)
2833 .await;
2834
2835 let valid_res: Result<Vec<LdapMsg>, OperationError> = ldaps
2836 .do_search(idms, &valid_search, &anon_t, Source::Internal)
2837 .await;
2838
2839 assert_eq!(invalid_res, Err(OperationError::ResourceLimit));
2840 assert!(valid_res.is_ok());
2841 }
2842
2843 #[idm_test]
2844 async fn test_ldap_distinct_attributes(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
2846 let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
2847
2848 let anon_t = ldaps
2850 .do_bind(idms, "", "")
2851 .await
2852 .expect("failed to connect to ldap")
2853 .expect("Failed to get token");
2854 assert_eq!(
2855 anon_t.effective_session,
2856 LdapSession::UnixBind(UUID_ANONYMOUS)
2857 );
2858
2859 let valid_search = SearchRequest {
2860 msgid: 1,
2861 base: "dc=example,dc=com".to_string(),
2862 scope: LdapSearchScope::Subtree,
2863 filter: LdapFilter::Present(Attribute::ObjectClass.to_string()),
2864 attrs: vec![
2865 "objectClass: person".to_string(),
2866 "uuid".to_string(),
2867 "uuid".to_string(),
2868 ],
2869 };
2870
2871 let valid_res = ldaps
2872 .do_search(idms, &valid_search, &anon_t, Source::Internal)
2873 .await
2874 .expect("Search failed");
2875 let response_zero = valid_res.into_iter().next().expect("No results");
2876 let LdapOp::SearchResultEntry(searchresult) = response_zero.op else {
2877 panic!("invalid result!");
2878 };
2879 assert!(searchresult.attributes.len() == 1)
2880 }
2881}