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