kanidmd_lib/idm/
ldap.rs

1//! LDAP specific operations handling components. This is where LDAP operations
2//! are sent to for processing.
3
4use 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// Clippy doesn't like Bind here. But proto needs unboxed ldapmsg,
23// and ldapboundtoken is moved. Really, it's not too bad, every message here is pretty sucky.
24#[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    // Maps through and provides anon read, but allows us to check the validity
37    // of the account still.
38    UnixBind(Uuid),
39    UserAuthToken(UserAuthToken),
40    ApiToken(ApiToken),
41    ApplicationPasswordBind(Uuid, Uuid),
42}
43
44#[derive(Debug, Clone)]
45pub struct LdapBoundToken {
46    // Used to help ID the user doing the action, makes logging nicer.
47    pub spn: String,
48    pub session_id: Uuid,
49    // This is the effective session permission. This is generated from either:
50    // * A valid anonymous bind
51    // * A valid unix pw bind
52    // * A valid ApiToken
53    // In a way, this is a stepping stone to an "ident" but allows us to check
54    // the session is still "valid" depending on it's origin.
55    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 ct = duration_from_epoch_now();
76        let mut idms_prox_read = idms.proxy_read().await?;
77        // This is the rootdse path.
78        // get the domain_info item
79        let domain_entry = idms_prox_read
80            .qs_read
81            .internal_search_uuid(UUID_DOMAIN_INFO)?;
82
83        // Get the maximum number of queryable attributes from the domain entry
84        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        // It is necessary to swap greed to avoid the first group "<attr>=<val>" matching the
100        // next group "app=<app>", son one can use "app=app1,dc=test,dc=net" as search base:
101        // Greedy (app=app1,dc=test,dc=net):
102        //     Match 1      - app=app1,dc=test,dc=net
103        //     Group 1      - app=app1,
104        //     Group <attr> - app
105        //     Group <val>  - app1
106        //     Group 6      - dc=test,dc=net
107        // Ungreedy (app=app1,dc=test,dc=net):
108        //     Match 1      - app=app1,dc=test,dc=net
109        //     Group 4      - app=app1,
110        //     Group <app>  - app1
111        //     Group 6      - dc=test,dc=net
112        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        // eventid: &Uuid,
176    ) -> Result<Vec<LdapMsg>, OperationError> {
177        admin_info!("Attempt LDAP Search for {}", uat.spn);
178        // If the request is "", Base, Present(Attribute::ObjectClass.into()), [], then we want the rootdse.
179        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            // We want something else apparently. Need to do some more work ...
187            // Parse the operation and make sure it's sane before we start the txn.
188
189            // This scoping returns an extra filter component.
190
191            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            // Map the Some(a,v) to ...?
214
215            let ext_filter = match (&sr.scope, req_dn) {
216                // OneLevel and Child searches are **very** similar for us because child
217                // is a "subtree search excluding base". Because we don't have a tree structure at
218                // all, this is the same as a one level (all children of base excluding base).
219                (LdapSearchScope::Children, Some(_r)) | (LdapSearchScope::OneLevel, Some(_r)) => {
220                    return Ok(vec![sr.gen_success()])
221                }
222                (LdapSearchScope::Children, None) | (LdapSearchScope::OneLevel, None) => {
223                    // exclude domain_info
224                    Some(LdapFilter::Not(Box::new(LdapFilter::Equality(
225                        Attribute::Uuid.to_string(),
226                        STR_UUID_DOMAIN_INFO.to_string(),
227                    ))))
228                }
229                // because we request a specific DN, these are the same since we want the same
230                // entry.
231                (LdapSearchScope::Base, Some((a, v)))
232                | (LdapSearchScope::Subtree, Some((a, v))) => Some(LdapFilter::Equality(a, v)),
233                (LdapSearchScope::Base, None) => {
234                    // domain_info
235                    Some(LdapFilter::Equality(
236                        Attribute::Uuid.to_string(),
237                        STR_UUID_DOMAIN_INFO.to_string(),
238                    ))
239                }
240                (LdapSearchScope::Subtree, None) => {
241                    // No filter changes needed.
242                    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                // If [], then "all" attrs
253                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                        // This forces the BE to get all the attrs so we can
260                        // map all vattrs.
261                        all_attrs = true;
262                        all_op_attrs = true;
263                    } else if a == "1.1" {
264                        /*
265                         *  ref https://www.rfc-editor.org/rfc/rfc4511#section-4.5.1.8
266                         *
267                         *  A list containing only the OID "1.1" indicates that no
268                         *  attributes are to be returned. If "1.1" is provided with other
269                         *  attributeSelector values, the "1.1" attributeSelector is
270                         *  ignored. This OID was chosen because it does not (and can not)
271                         *  correspond to any attribute in use.
272                         */
273                        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            // We need to retain this to know what the client requested.
287            let (k_attrs, l_attrs) = if no_attrs {
288                // Request no attributes and no mapped attributes.
289                (None, Vec::with_capacity(0))
290            } else if all_op_attrs {
291                // We need all attrs, and we do a full v_attr map.
292                (None, ldap_all_vattrs())
293            } else if all_attrs {
294                // We are already getting all attrs, but if there are any virtual attrs
295                // we need them in our request as well.
296                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                // What the client requested, in LDAP forms.
313                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                // This is what the client requested, but mapped to kanidm forms.
325                // NOTE: All req_attrs are lowercase at this point.
326                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            // Now start the txn - we need it for resolving filter components.
340
341            // join the filter, with ext_filter
342            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            // Build the event, with the permissions from effective_session
377            //
378            // ! Remember, searchEvent wraps to ignore hidden for us.
379            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            // These have already been fully reduced (access controls applied),
402            // so we can just transform the values and open palm slam them into
403            // the result structure.
404            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                    // if okay, wrap in a ldap msg.
414                    .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        // Now start the txn - we need it for resolving filter components.
520
521        // join the filter, with ext_filter
522        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        // Build the event, with the permissions from effective_session
538        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        // we need to check if the entry exists at all (without the ava).
568        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                    // Search can occur without a bind, so bind first.
638                    // This is per section 4 of RFC 4513 (https://www.rfc-editor.org/rfc/rfc4513#section-4).
639                    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                    // If okay, do the search.
652                    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                // No need to notify on unbind (per rfc4511)
663                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                    // Compare can occur without a bind, so bind first.
676                    // This is per section 4 of RFC 4513 (https://www.rfc-editor.org/rfc/rfc4513#section-4).
677                    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                    // If okay, do the compare.
690                    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        } // end match server op
708    }
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                // This is the path to access api-token logins.
721                return Ok(LdapBindTarget::ApiToken);
722            }
723        } else if dn == "dn=token" {
724            // Is the passed dn requesting token auth?
725            // We use dn= here since these are attr=value, and dn is a phantom so it will
726            // never be present or match a real value. We also make it an ava so that clients
727            // that over-zealously validate dn syntax are happy.
728            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            binddn = ?dn,
762            "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
763        );
764
765        Err(OperationError::NoMatchingEntries)
766    }
767}
768
769fn ldap_domain_to_dc(input: &str) -> String {
770    let mut output: String = String::new();
771    input.split('.').for_each(|dc| {
772        output.push_str("dc=");
773        output.push_str(dc);
774        #[allow(clippy::single_char_pattern, clippy::single_char_add_str)]
775        output.push_str(",");
776    });
777    // Remove the last ','
778    output.pop();
779    output
780}
781
782fn operationerr_to_ldapresultcode(e: OperationError) -> (LdapResultCode, String) {
783    match e {
784        OperationError::InvalidRequestState => {
785            (LdapResultCode::ConstraintViolation, "".to_string())
786        }
787        OperationError::InvalidAttributeName(s) | OperationError::InvalidAttribute(s) => {
788            (LdapResultCode::InvalidAttributeSyntax, s)
789        }
790        OperationError::SchemaViolation(se) => {
791            (LdapResultCode::UnwillingToPerform, format!("{se:?}"))
792        }
793        e => (LdapResultCode::Other, format!("{e:?}")),
794    }
795}
796
797#[inline]
798pub(crate) fn ldap_all_vattrs() -> Vec<String> {
799    vec![
800        ATTR_CN.to_string(),
801        ATTR_EMAIL.to_string(),
802        ATTR_LDAP_EMAIL_ADDRESS.to_string(),
803        LDAP_ATTR_DN.to_string(),
804        LDAP_ATTR_EMAIL_ALTERNATIVE.to_string(),
805        LDAP_ATTR_EMAIL_PRIMARY.to_string(),
806        LDAP_ATTR_ENTRYDN.to_string(),
807        LDAP_ATTR_ENTRYUUID.to_string(),
808        LDAP_ATTR_KEYS.to_string(),
809        LDAP_ATTR_MAIL_ALTERNATIVE.to_string(),
810        LDAP_ATTR_MAIL_PRIMARY.to_string(),
811        ATTR_OBJECTCLASS.to_string(),
812        ATTR_LDAP_SSHPUBLICKEY.to_string(),
813        ATTR_UIDNUMBER.to_string(),
814        ATTR_UID.to_string(),
815        ATTR_GECOS.to_string(),
816        ATTR_HOME_DIRECTORY.to_string(),
817    ]
818}
819
820#[inline]
821pub(crate) fn ldap_vattr_map(input: &str) -> Option<&str> {
822    // ⚠️  WARNING ⚠️
823    // If you modify this list you MUST add these values to
824    // corresponding phantom attributes in the schema to prevent
825    // incorrect future or duplicate usage.
826    //
827    //   LDAP NAME     KANI ATTR SOURCE NAME
828    match input {
829        // EntryDN and DN have special handling in to_ldap in Entry. However, we
830        // need to map them to "name" so that if the user has requested dn/entrydn
831        // only, then we still requested at least one attribute from the backend
832        // allowing the access control tests to take place. Otherwise no entries
833        // would be returned.
834        ATTR_CN | ATTR_UID | LDAP_ATTR_ENTRYDN | LDAP_ATTR_DN => Some(ATTR_NAME),
835        ATTR_GECOS => Some(ATTR_DISPLAYNAME),
836        ATTR_EMAIL => Some(ATTR_MAIL),
837        ATTR_LDAP_EMAIL_ADDRESS => Some(ATTR_MAIL),
838        LDAP_ATTR_EMAIL_ALTERNATIVE => Some(ATTR_MAIL),
839        LDAP_ATTR_EMAIL_PRIMARY => Some(ATTR_MAIL),
840        LDAP_ATTR_ENTRYUUID => Some(ATTR_UUID),
841        LDAP_ATTR_KEYS => Some(ATTR_SSH_PUBLICKEY),
842        LDAP_ATTR_MAIL_ALTERNATIVE => Some(ATTR_MAIL),
843        LDAP_ATTR_MAIL_PRIMARY => Some(ATTR_MAIL),
844        ATTR_OBJECTCLASS => Some(ATTR_CLASS),
845        ATTR_LDAP_SSHPUBLICKEY => Some(ATTR_SSH_PUBLICKEY), // no-underscore -> underscore
846        ATTR_UIDNUMBER => Some(ATTR_GIDNUMBER),             // yes this is intentional
847        ATTR_HOME_DIRECTORY => Some(ATTR_UUID),
848        _ => None,
849    }
850}
851
852#[inline]
853pub(crate) fn ldap_attr_filter_map(input: &str) -> Attribute {
854    let a_lower = input.to_lowercase();
855    Attribute::from(ldap_vattr_map(&a_lower).unwrap_or(a_lower.as_str()))
856}
857
858#[cfg(test)]
859mod tests {
860    use crate::prelude::*;
861
862    use compact_jwt::{dangernoverify::JwsDangerReleaseWithoutVerify, JwsVerifier};
863    use hashbrown::HashSet;
864    use kanidm_proto::internal::ApiToken;
865    use ldap3_proto::proto::{
866        LdapFilter, LdapMsg, LdapOp, LdapResultCode, LdapSearchScope, LdapSubstringFilter,
867    };
868    use ldap3_proto::simple::*;
869
870    use super::{LdapServer, LdapSession};
871    use crate::idm::application::GenerateApplicationPasswordEvent;
872    use crate::idm::event::{LdapApplicationAuthEvent, UnixPasswordChangeEvent};
873    use crate::idm::serviceaccount::GenerateApiTokenEvent;
874
875    const TEST_PASSWORD: &str = "ntaoeuntnaoeuhraohuercahu😍";
876
877    #[idm_test]
878    async fn test_ldap_simple_bind(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
879        let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
880
881        let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
882        // make the admin a valid posix account
883        let me_posix = ModifyEvent::new_internal_invalid(
884            filter!(f_eq(Attribute::Name, PartialValue::new_iname("admin"))),
885            ModifyList::new_list(vec![
886                Modify::Present(Attribute::Class, EntryClass::PosixAccount.into()),
887                Modify::Present(Attribute::GidNumber, Value::new_uint32(2001)),
888            ]),
889        );
890        assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok());
891
892        let pce = UnixPasswordChangeEvent::new_internal(UUID_ADMIN, TEST_PASSWORD);
893
894        assert!(idms_prox_write.set_unix_account_password(&pce).is_ok());
895        assert!(idms_prox_write.commit().is_ok()); // Committing all configs
896
897        // default UNIX_PW bind (default is set to true)
898        // Hence allows all unix binds
899        let admin_t = ldaps
900            .do_bind(idms, "admin", TEST_PASSWORD)
901            .await
902            .unwrap()
903            .unwrap();
904        assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
905        let admin_t = ldaps
906            .do_bind(idms, "admin@example.com", TEST_PASSWORD)
907            .await
908            .unwrap()
909            .unwrap();
910        assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
911
912        // Setting UNIX_PW_BIND flag to false:
913        // Hence all of the below authentication will fail (asserts are still satisfied)
914        let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
915        let disallow_unix_pw_flag = ModifyEvent::new_internal_invalid(
916            filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_DOMAIN_INFO))),
917            ModifyList::new_purge_and_set(Attribute::LdapAllowUnixPwBind, Value::Bool(false)),
918        );
919        assert!(idms_prox_write
920            .qs_write
921            .modify(&disallow_unix_pw_flag)
922            .is_ok());
923        assert!(idms_prox_write.commit().is_ok());
924        let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
925        assert_eq!(
926            anon_t.effective_session,
927            LdapSession::UnixBind(UUID_ANONYMOUS)
928        );
929        assert!(
930            ldaps.do_bind(idms, "", "test").await.unwrap_err() == OperationError::NotAuthenticated
931        );
932        let admin_t = ldaps.do_bind(idms, "admin", TEST_PASSWORD).await.unwrap();
933        assert!(admin_t.is_none());
934
935        // Setting UNIX_PW_BIND flag to true :
936        let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
937        let allow_unix_pw_flag = ModifyEvent::new_internal_invalid(
938            filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_DOMAIN_INFO))),
939            ModifyList::new_purge_and_set(Attribute::LdapAllowUnixPwBind, Value::Bool(true)),
940        );
941        assert!(idms_prox_write.qs_write.modify(&allow_unix_pw_flag).is_ok());
942        assert!(idms_prox_write.commit().is_ok());
943
944        // Now test the admin and various DN's
945        let admin_t = ldaps
946            .do_bind(idms, "admin", TEST_PASSWORD)
947            .await
948            .unwrap()
949            .unwrap();
950        assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
951        let admin_t = ldaps
952            .do_bind(idms, "admin@example.com", 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, STR_UUID_ADMIN, 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, "name=admin,dc=example,dc=com", 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(
971                idms,
972                "spn=admin@example.com,dc=example,dc=com",
973                TEST_PASSWORD,
974            )
975            .await
976            .unwrap()
977            .unwrap();
978        assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
979        let admin_t = ldaps
980            .do_bind(
981                idms,
982                format!("uuid={STR_UUID_ADMIN},dc=example,dc=com").as_str(),
983                TEST_PASSWORD,
984            )
985            .await
986            .unwrap()
987            .unwrap();
988        assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
989
990        let admin_t = ldaps
991            .do_bind(idms, "name=admin", TEST_PASSWORD)
992            .await
993            .unwrap()
994            .unwrap();
995        assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
996        let admin_t = ldaps
997            .do_bind(idms, "spn=admin@example.com", 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(
1004                idms,
1005                format!("uuid={STR_UUID_ADMIN}").as_str(),
1006                TEST_PASSWORD,
1007            )
1008            .await
1009            .unwrap()
1010            .unwrap();
1011        assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
1012
1013        let admin_t = ldaps
1014            .do_bind(idms, "admin,dc=example,dc=com", TEST_PASSWORD)
1015            .await
1016            .unwrap()
1017            .unwrap();
1018        assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
1019        let admin_t = ldaps
1020            .do_bind(idms, "admin@example.com,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(
1027                idms,
1028                format!("{STR_UUID_ADMIN},dc=example,dc=com").as_str(),
1029                TEST_PASSWORD,
1030            )
1031            .await
1032            .unwrap()
1033            .unwrap();
1034        assert_eq!(admin_t.effective_session, LdapSession::UnixBind(UUID_ADMIN));
1035
1036        // Bad password, check last to prevent softlocking of the admin account.
1037        assert!(ldaps
1038            .do_bind(idms, "admin", "test")
1039            .await
1040            .unwrap()
1041            .is_none());
1042
1043        // Non-existent and invalid DNs
1044        assert!(ldaps
1045            .do_bind(
1046                idms,
1047                "spn=admin@example.com,dc=clownshoes,dc=example,dc=com",
1048                TEST_PASSWORD
1049            )
1050            .await
1051            .is_err());
1052        assert!(ldaps
1053            .do_bind(
1054                idms,
1055                "spn=claire@example.com,dc=example,dc=com",
1056                TEST_PASSWORD
1057            )
1058            .await
1059            .is_err());
1060        assert!(ldaps
1061            .do_bind(idms, ",dc=example,dc=com", TEST_PASSWORD)
1062            .await
1063            .is_err());
1064        assert!(ldaps
1065            .do_bind(idms, "dc=example,dc=com", TEST_PASSWORD)
1066            .await
1067            .is_err());
1068
1069        assert!(ldaps.do_bind(idms, "claire", "test").await.is_err());
1070    }
1071
1072    #[idm_test]
1073    async fn test_ldap_application_dnre(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
1074        let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
1075
1076        let testdn = format!("app=app1,{0}", ldaps.basedn);
1077        let captures = ldaps.dnre.captures(testdn.as_str()).unwrap();
1078        assert!(captures.name("app").is_some());
1079        assert!(captures.name("attr").is_none());
1080        assert!(captures.name("val").is_none());
1081
1082        let testdn = format!("uid=foo,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_some());
1086        assert!(captures.name("val").is_some());
1087
1088        let testdn = format!("uid=foo,{0}", ldaps.basedn);
1089        let captures = ldaps.dnre.captures(testdn.as_str()).unwrap();
1090        assert!(captures.name("app").is_none());
1091        assert!(captures.name("attr").is_some());
1092        assert!(captures.name("val").is_some());
1093    }
1094
1095    #[idm_test]
1096    async fn test_ldap_application_search(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
1097        let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
1098
1099        let usr_uuid = Uuid::new_v4();
1100        let grp_uuid = Uuid::new_v4();
1101        let app_uuid = Uuid::new_v4();
1102        let app_name = "testapp1";
1103
1104        // Setup person, group and application
1105        {
1106            let e1 = entry_init!(
1107                (Attribute::Class, EntryClass::Object.to_value()),
1108                (Attribute::Class, EntryClass::Account.to_value()),
1109                (Attribute::Class, EntryClass::Person.to_value()),
1110                (Attribute::Name, Value::new_iname("testperson1")),
1111                (Attribute::Uuid, Value::Uuid(usr_uuid)),
1112                (Attribute::Description, Value::new_utf8s("testperson1")),
1113                (Attribute::DisplayName, Value::new_utf8s("testperson1"))
1114            );
1115
1116            let e2 = entry_init!(
1117                (Attribute::Class, EntryClass::Object.to_value()),
1118                (Attribute::Class, EntryClass::Group.to_value()),
1119                (Attribute::Name, Value::new_iname("testgroup1")),
1120                (Attribute::Uuid, Value::Uuid(grp_uuid))
1121            );
1122
1123            let e3 = entry_init!(
1124                (Attribute::Class, EntryClass::Object.to_value()),
1125                (Attribute::Class, EntryClass::Account.to_value()),
1126                (Attribute::Class, EntryClass::ServiceAccount.to_value()),
1127                (Attribute::Class, EntryClass::Application.to_value()),
1128                (Attribute::DisplayName, Value::new_utf8s("Application")),
1129                (Attribute::Name, Value::new_iname(app_name)),
1130                (Attribute::Uuid, Value::Uuid(app_uuid)),
1131                (Attribute::LinkedGroup, Value::Refer(grp_uuid))
1132            );
1133
1134            let ct = duration_from_epoch_now();
1135            let mut server_txn = idms.proxy_write(ct).await.unwrap();
1136            assert!(server_txn
1137                .qs_write
1138                .internal_create(vec![e1, e2, e3])
1139                .and_then(|_| server_txn.commit())
1140                .is_ok());
1141        }
1142
1143        // Setup the anonymous login
1144        let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
1145        assert_eq!(
1146            anon_t.effective_session,
1147            LdapSession::UnixBind(UUID_ANONYMOUS)
1148        );
1149
1150        // Searches under application base DN must show same content
1151        let sr = SearchRequest {
1152            msgid: 1,
1153            base: format!("app={app_name},dc=example,dc=com"),
1154            scope: LdapSearchScope::Subtree,
1155            filter: LdapFilter::Present(Attribute::ObjectClass.to_string()),
1156            attrs: vec!["*".to_string()],
1157        };
1158
1159        let r1 = ldaps
1160            .do_search(idms, &sr, &anon_t, Source::Internal)
1161            .await
1162            .unwrap();
1163
1164        let sr = SearchRequest {
1165            msgid: 1,
1166            base: "dc=example,dc=com".to_string(),
1167            scope: LdapSearchScope::Subtree,
1168            filter: LdapFilter::Present(Attribute::ObjectClass.to_string()),
1169            attrs: vec!["*".to_string()],
1170        };
1171
1172        let r2 = ldaps
1173            .do_search(idms, &sr, &anon_t, Source::Internal)
1174            .await
1175            .unwrap();
1176        assert!(!r1.is_empty());
1177        assert_eq!(r1.len(), r2.len());
1178    }
1179
1180    #[idm_test]
1181    async fn test_ldap_spn_search(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
1182        let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
1183
1184        let usr_uuid = Uuid::new_v4();
1185        let usr_name = "panko";
1186
1187        // Setup person, group and application
1188        {
1189            let e1: Entry<EntryInit, EntryNew> = entry_init!(
1190                (Attribute::Class, EntryClass::Object.to_value()),
1191                (Attribute::Class, EntryClass::Account.to_value()),
1192                (Attribute::Class, EntryClass::PosixAccount.to_value()),
1193                (Attribute::Class, EntryClass::Person.to_value()),
1194                (Attribute::Name, Value::new_iname(usr_name)),
1195                (Attribute::Uuid, Value::Uuid(usr_uuid)),
1196                (Attribute::DisplayName, Value::new_utf8s(usr_name))
1197            );
1198
1199            let ct = duration_from_epoch_now();
1200            let mut server_txn = idms.proxy_write(ct).await.unwrap();
1201
1202            // Add anonymous to the needed permission groups.
1203            server_txn
1204                .qs_write
1205                .internal_modify_uuid(
1206                    UUID_IDM_UNIX_AUTHENTICATION_READ,
1207                    &ModifyList::new_append(Attribute::Member, Value::Refer(UUID_ANONYMOUS)),
1208                )
1209                .expect("Unable to modify UNIX_AUTHENTICATION_READ group");
1210
1211            assert!(server_txn
1212                .qs_write
1213                .internal_create(vec![e1])
1214                .and_then(|_| server_txn.commit())
1215                .is_ok());
1216        }
1217
1218        // Setup the anonymous login
1219        let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
1220        assert_eq!(
1221            anon_t.effective_session,
1222            LdapSession::UnixBind(UUID_ANONYMOUS)
1223        );
1224
1225        // Searching a malformed spn shouldn't cause the query to fail
1226        let sr = SearchRequest {
1227            msgid: 1,
1228            base: "dc=example,dc=com".to_string(),
1229            scope: LdapSearchScope::Subtree,
1230            filter: LdapFilter::Or(vec![
1231                LdapFilter::Equality(Attribute::Name.to_string(), usr_name.to_string()),
1232                LdapFilter::Equality(Attribute::Spn.to_string(), usr_name.to_string()),
1233            ]),
1234            attrs: vec!["*".to_string()],
1235        };
1236
1237        let result = ldaps
1238            .do_search(idms, &sr, &anon_t, Source::Internal)
1239            .await
1240            .map(|r| {
1241                r.into_iter()
1242                    .filter(|r| matches!(r.op, LdapOp::SearchResultEntry(_)))
1243                    .collect::<Vec<_>>()
1244            })
1245            .unwrap();
1246
1247        assert!(!result.is_empty());
1248
1249        let sr = SearchRequest {
1250            msgid: 1,
1251            base: "dc=example,dc=com".to_string(),
1252            scope: LdapSearchScope::Subtree,
1253            filter: LdapFilter::And(vec![
1254                LdapFilter::Equality(Attribute::Name.to_string(), usr_name.to_string()),
1255                LdapFilter::Equality(Attribute::Spn.to_string(), usr_name.to_string()),
1256            ]),
1257            attrs: vec!["*".to_string()],
1258        };
1259
1260        let empty_result = ldaps
1261            .do_search(idms, &sr, &anon_t, Source::Internal)
1262            .await
1263            .map(|r| {
1264                r.into_iter()
1265                    .filter(|r| matches!(r.op, LdapOp::SearchResultEntry(_)))
1266                    .collect::<Vec<_>>()
1267            })
1268            .unwrap();
1269
1270        assert!(empty_result.is_empty());
1271    }
1272
1273    #[idm_test]
1274    async fn test_ldap_application_bind(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
1275        let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
1276
1277        let usr_uuid = Uuid::new_v4();
1278        let grp_uuid = Uuid::new_v4();
1279        let app_uuid = Uuid::new_v4();
1280
1281        // Setup person, group and application
1282        {
1283            let e1 = entry_init!(
1284                (Attribute::Class, EntryClass::Object.to_value()),
1285                (Attribute::Class, EntryClass::Account.to_value()),
1286                (Attribute::Class, EntryClass::Person.to_value()),
1287                (Attribute::Name, Value::new_iname("testperson1")),
1288                (Attribute::Uuid, Value::Uuid(usr_uuid)),
1289                (Attribute::Description, Value::new_utf8s("testperson1")),
1290                (Attribute::DisplayName, Value::new_utf8s("testperson1"))
1291            );
1292
1293            let e2 = entry_init!(
1294                (Attribute::Class, EntryClass::Object.to_value()),
1295                (Attribute::Class, EntryClass::Group.to_value()),
1296                (Attribute::Name, Value::new_iname("testgroup1")),
1297                (Attribute::Uuid, Value::Uuid(grp_uuid))
1298            );
1299
1300            let e3 = entry_init!(
1301                (Attribute::Class, EntryClass::Object.to_value()),
1302                (Attribute::Class, EntryClass::Account.to_value()),
1303                (Attribute::Class, EntryClass::ServiceAccount.to_value()),
1304                (Attribute::Class, EntryClass::Application.to_value()),
1305                (Attribute::DisplayName, Value::new_utf8s("Application")),
1306                (Attribute::Name, Value::new_iname("testapp1")),
1307                (Attribute::Uuid, Value::Uuid(app_uuid)),
1308                (Attribute::LinkedGroup, Value::Refer(grp_uuid))
1309            );
1310
1311            let ct = duration_from_epoch_now();
1312            let mut server_txn = idms.proxy_write(ct).await.unwrap();
1313            assert!(server_txn
1314                .qs_write
1315                .internal_create(vec![e1, e2, e3])
1316                .and_then(|_| server_txn.commit())
1317                .is_ok());
1318        }
1319
1320        // No session, user not member of linked group
1321        let res = ldaps
1322            .do_bind(idms, "spn=testperson1,app=testapp1,dc=example,dc=com", "")
1323            .await;
1324        assert!(res.is_ok());
1325        assert!(res.unwrap().is_none());
1326
1327        {
1328            let ml = ModifyList::new_append(Attribute::Member, Value::Refer(usr_uuid));
1329            let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
1330            assert!(idms_prox_write
1331                .qs_write
1332                .internal_modify_uuid(grp_uuid, &ml)
1333                .is_ok());
1334            assert!(idms_prox_write.commit().is_ok());
1335        }
1336
1337        // No session, user does not have app password for testapp1
1338        let res = ldaps
1339            .do_bind(idms, "spn=testperson1,app=testapp1,dc=example,dc=com", "")
1340            .await;
1341        assert!(res.is_ok());
1342        assert!(res.unwrap().is_none());
1343
1344        let pass1: String;
1345        let pass2: String;
1346        let pass3: String;
1347        {
1348            let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
1349
1350            let ev = GenerateApplicationPasswordEvent::new_internal(
1351                usr_uuid,
1352                app_uuid,
1353                "apppwd1".to_string(),
1354            );
1355            (pass1, _) = idms_prox_write
1356                .generate_application_password(&ev)
1357                .expect("Failed to generate application password");
1358
1359            let ev = GenerateApplicationPasswordEvent::new_internal(
1360                usr_uuid,
1361                app_uuid,
1362                "apppwd2".to_string(),
1363            );
1364            (pass2, _) = idms_prox_write
1365                .generate_application_password(&ev)
1366                .expect("Failed to generate application password");
1367
1368            assert!(idms_prox_write.commit().is_ok());
1369
1370            // Application password overwritten on duplicated label
1371            let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
1372            let ev = GenerateApplicationPasswordEvent::new_internal(
1373                usr_uuid,
1374                app_uuid,
1375                "apppwd2".to_string(),
1376            );
1377            (pass3, _) = idms_prox_write
1378                .generate_application_password(&ev)
1379                .expect("Failed to generate application password");
1380            assert!(idms_prox_write.commit().is_ok());
1381        }
1382
1383        // Got session, app password valid
1384        let res = ldaps
1385            .do_bind(
1386                idms,
1387                "spn=testperson1,app=testapp1,dc=example,dc=com",
1388                pass1.as_str(),
1389            )
1390            .await;
1391        assert!(res.is_ok());
1392        assert!(res.unwrap().is_some());
1393
1394        // No session, app password overwritten
1395        let res = ldaps
1396            .do_bind(
1397                idms,
1398                "spn=testperson1,app=testapp1,dc=example,dc=com",
1399                pass2.as_str(),
1400            )
1401            .await;
1402        assert!(res.is_ok());
1403        assert!(res.unwrap().is_none());
1404
1405        // Got session, app password overwritten
1406        let res = ldaps
1407            .do_bind(
1408                idms,
1409                "spn=testperson1,app=testapp1,dc=example,dc=com",
1410                pass3.as_str(),
1411            )
1412            .await;
1413        assert!(res.is_ok());
1414        assert!(res.unwrap().is_some());
1415
1416        // No session, invalid app password
1417        let res = ldaps
1418            .do_bind(
1419                idms,
1420                "spn=testperson1,app=testapp1,dc=example,dc=com",
1421                "FOO",
1422            )
1423            .await;
1424        assert!(res.is_ok());
1425        assert!(res.unwrap().is_none());
1426    }
1427
1428    #[idm_test]
1429    async fn test_ldap_application_linked_group(
1430        idms: &IdmServer,
1431        _idms_delayed: &IdmServerDelayed,
1432    ) {
1433        let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
1434
1435        let usr_uuid = Uuid::new_v4();
1436        let usr_name = "testuser1";
1437
1438        let grp1_uuid = Uuid::new_v4();
1439        let grp1_name = "testgroup1";
1440        let grp2_uuid = Uuid::new_v4();
1441        let grp2_name = "testgroup2";
1442
1443        let app1_uuid = Uuid::new_v4();
1444        let app1_name = "testapp1";
1445        let app2_uuid = Uuid::new_v4();
1446        let app2_name = "testapp2";
1447
1448        // Setup person, groups and applications
1449        {
1450            let e1 = entry_init!(
1451                (Attribute::Class, EntryClass::Object.to_value()),
1452                (Attribute::Class, EntryClass::Account.to_value()),
1453                (Attribute::Class, EntryClass::Person.to_value()),
1454                (Attribute::Name, Value::new_iname(usr_name)),
1455                (Attribute::Uuid, Value::Uuid(usr_uuid)),
1456                (Attribute::Description, Value::new_utf8s(usr_name)),
1457                (Attribute::DisplayName, Value::new_utf8s(usr_name))
1458            );
1459
1460            let e2 = entry_init!(
1461                (Attribute::Class, EntryClass::Object.to_value()),
1462                (Attribute::Class, EntryClass::Group.to_value()),
1463                (Attribute::Name, Value::new_iname(grp1_name)),
1464                (Attribute::Uuid, Value::Uuid(grp1_uuid)),
1465                (Attribute::Member, Value::Refer(usr_uuid))
1466            );
1467
1468            let e3 = entry_init!(
1469                (Attribute::Class, EntryClass::Object.to_value()),
1470                (Attribute::Class, EntryClass::Group.to_value()),
1471                (Attribute::Name, Value::new_iname(grp2_name)),
1472                (Attribute::Uuid, Value::Uuid(grp2_uuid))
1473            );
1474
1475            let e4 = entry_init!(
1476                (Attribute::Class, EntryClass::Object.to_value()),
1477                (Attribute::Class, EntryClass::Account.to_value()),
1478                (Attribute::Class, EntryClass::ServiceAccount.to_value()),
1479                (Attribute::Class, EntryClass::Application.to_value()),
1480                (Attribute::DisplayName, Value::new_utf8s("Application")),
1481                (Attribute::Name, Value::new_iname(app1_name)),
1482                (Attribute::Uuid, Value::Uuid(app1_uuid)),
1483                (Attribute::LinkedGroup, Value::Refer(grp1_uuid))
1484            );
1485
1486            let e5 = entry_init!(
1487                (Attribute::Class, EntryClass::Object.to_value()),
1488                (Attribute::Class, EntryClass::Account.to_value()),
1489                (Attribute::Class, EntryClass::ServiceAccount.to_value()),
1490                (Attribute::Class, EntryClass::Application.to_value()),
1491                (Attribute::DisplayName, Value::new_utf8s("Application")),
1492                (Attribute::Name, Value::new_iname(app2_name)),
1493                (Attribute::Uuid, Value::Uuid(app2_uuid)),
1494                (Attribute::LinkedGroup, Value::Refer(grp2_uuid))
1495            );
1496
1497            let ct = duration_from_epoch_now();
1498            let mut server_txn = idms.proxy_write(ct).await.unwrap();
1499            assert!(server_txn
1500                .qs_write
1501                .internal_create(vec![e1, e2, e3, e4, e5])
1502                .and_then(|_| server_txn.commit())
1503                .is_ok());
1504        }
1505
1506        let pass_app1: String;
1507        let pass_app2: String;
1508        {
1509            let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
1510
1511            let ev = GenerateApplicationPasswordEvent::new_internal(
1512                usr_uuid,
1513                app1_uuid,
1514                "label".to_string(),
1515            );
1516            (pass_app1, _) = idms_prox_write
1517                .generate_application_password(&ev)
1518                .expect("Failed to generate application password");
1519
1520            // It is possible to generate an application password even if the
1521            // user is not member of the linked group
1522            let ev = GenerateApplicationPasswordEvent::new_internal(
1523                usr_uuid,
1524                app2_uuid,
1525                "label".to_string(),
1526            );
1527            (pass_app2, _) = idms_prox_write
1528                .generate_application_password(&ev)
1529                .expect("Failed to generate application password");
1530
1531            assert!(idms_prox_write.commit().is_ok());
1532        }
1533
1534        // Got session, app password valid
1535        let res = ldaps
1536            .do_bind(
1537                idms,
1538                format!("spn={usr_name},app={app1_name},dc=example,dc=com").as_str(),
1539                pass_app1.as_str(),
1540            )
1541            .await;
1542        assert!(res.is_ok());
1543        assert!(res.unwrap().is_some());
1544
1545        // No session, not member
1546        let res = ldaps
1547            .do_bind(
1548                idms,
1549                format!("spn={usr_name},app={app2_name},dc=example,dc=com").as_str(),
1550                pass_app2.as_str(),
1551            )
1552            .await;
1553        assert!(res.is_ok());
1554        assert!(res.unwrap().is_none());
1555
1556        // Add user to grp2
1557        {
1558            let ml = ModifyList::new_append(Attribute::Member, Value::Refer(usr_uuid));
1559            let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
1560            assert!(idms_prox_write
1561                .qs_write
1562                .internal_modify_uuid(grp2_uuid, &ml)
1563                .is_ok());
1564            assert!(idms_prox_write.commit().is_ok());
1565        }
1566
1567        // Got session, app password valid
1568        let res = ldaps
1569            .do_bind(
1570                idms,
1571                format!("spn={usr_name},app={app2_name},dc=example,dc=com").as_str(),
1572                pass_app2.as_str(),
1573            )
1574            .await;
1575        assert!(res.is_ok());
1576        assert!(res.unwrap().is_some());
1577
1578        // No session, wrong app
1579        let res = ldaps
1580            .do_bind(
1581                idms,
1582                format!("spn={usr_name},app={app1_name},dc=example,dc=com").as_str(),
1583                pass_app2.as_str(),
1584            )
1585            .await;
1586        assert!(res.is_ok());
1587        assert!(res.unwrap().is_none());
1588
1589        // Bind error, app not exists
1590        {
1591            let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
1592            let de = DeleteEvent::new_internal_invalid(filter!(f_eq(
1593                Attribute::Uuid,
1594                PartialValue::Uuid(app2_uuid)
1595            )));
1596            assert!(idms_prox_write.qs_write.delete(&de).is_ok());
1597            assert!(idms_prox_write.commit().is_ok());
1598        }
1599
1600        let res = ldaps
1601            .do_bind(
1602                idms,
1603                format!("spn={usr_name},app={app2_name},dc=example,dc=com").as_str(),
1604                pass_app2.as_str(),
1605            )
1606            .await;
1607        assert!(res.is_err());
1608    }
1609
1610    // For testing the timeouts
1611    // We need times on this scale
1612    //    not yet valid <-> valid from time <-> current_time <-> expire time <-> expired
1613    const TEST_CURRENT_TIME: u64 = 6000;
1614    const TEST_NOT_YET_VALID_TIME: u64 = TEST_CURRENT_TIME - 240;
1615    const TEST_VALID_FROM_TIME: u64 = TEST_CURRENT_TIME - 120;
1616    const TEST_EXPIRE_TIME: u64 = TEST_CURRENT_TIME + 120;
1617    const TEST_AFTER_EXPIRY: u64 = TEST_CURRENT_TIME + 240;
1618
1619    async fn set_account_valid_time(idms: &IdmServer, acct: Uuid) {
1620        let mut idms_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
1621
1622        let v_valid_from = Value::new_datetime_epoch(Duration::from_secs(TEST_VALID_FROM_TIME));
1623        let v_expire = Value::new_datetime_epoch(Duration::from_secs(TEST_EXPIRE_TIME));
1624
1625        let me = ModifyEvent::new_internal_invalid(
1626            filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(acct))),
1627            ModifyList::new_list(vec![
1628                Modify::Present(Attribute::AccountExpire, v_expire),
1629                Modify::Present(Attribute::AccountValidFrom, v_valid_from),
1630            ]),
1631        );
1632        assert!(idms_write.qs_write.modify(&me).is_ok());
1633        idms_write.commit().expect("Must not fail");
1634    }
1635
1636    #[idm_test]
1637    async fn test_ldap_application_valid_from_expire(
1638        idms: &IdmServer,
1639        _idms_delayed: &IdmServerDelayed,
1640    ) {
1641        let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
1642
1643        let usr_uuid = Uuid::new_v4();
1644        let usr_name = "testuser1";
1645
1646        let grp1_uuid = Uuid::new_v4();
1647        let grp1_name = "testgroup1";
1648
1649        let app1_uuid = Uuid::new_v4();
1650        let app1_name = "testapp1";
1651
1652        let pass_app1: String;
1653
1654        // Setup person, group, application and app password
1655        {
1656            let e1 = entry_init!(
1657                (Attribute::Class, EntryClass::Object.to_value()),
1658                (Attribute::Class, EntryClass::Account.to_value()),
1659                (Attribute::Class, EntryClass::Person.to_value()),
1660                (Attribute::Name, Value::new_iname(usr_name)),
1661                (Attribute::Uuid, Value::Uuid(usr_uuid)),
1662                (Attribute::Description, Value::new_utf8s(usr_name)),
1663                (Attribute::DisplayName, Value::new_utf8s(usr_name))
1664            );
1665
1666            let e2 = entry_init!(
1667                (Attribute::Class, EntryClass::Object.to_value()),
1668                (Attribute::Class, EntryClass::Group.to_value()),
1669                (Attribute::Name, Value::new_iname(grp1_name)),
1670                (Attribute::Uuid, Value::Uuid(grp1_uuid)),
1671                (Attribute::Member, Value::Refer(usr_uuid))
1672            );
1673
1674            let e3 = entry_init!(
1675                (Attribute::Class, EntryClass::Object.to_value()),
1676                (Attribute::Class, EntryClass::Account.to_value()),
1677                (Attribute::Class, EntryClass::ServiceAccount.to_value()),
1678                (Attribute::Class, EntryClass::Application.to_value()),
1679                (Attribute::DisplayName, Value::new_utf8s("Application")),
1680                (Attribute::Name, Value::new_iname(app1_name)),
1681                (Attribute::Uuid, Value::Uuid(app1_uuid)),
1682                (Attribute::LinkedGroup, Value::Refer(grp1_uuid))
1683            );
1684
1685            let ct = duration_from_epoch_now();
1686            let mut server_txn = idms.proxy_write(ct).await.unwrap();
1687            assert!(server_txn
1688                .qs_write
1689                .internal_create(vec![e1, e2, e3])
1690                .and_then(|_| server_txn.commit())
1691                .is_ok());
1692
1693            let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
1694
1695            let ev = GenerateApplicationPasswordEvent::new_internal(
1696                usr_uuid,
1697                app1_uuid,
1698                "label".to_string(),
1699            );
1700            (pass_app1, _) = idms_prox_write
1701                .generate_application_password(&ev)
1702                .expect("Failed to generate application password");
1703
1704            assert!(idms_prox_write.commit().is_ok());
1705        }
1706
1707        // Got session, app password valid
1708        let res = ldaps
1709            .do_bind(
1710                idms,
1711                format!("spn={usr_name},app={app1_name},dc=example,dc=com").as_str(),
1712                pass_app1.as_str(),
1713            )
1714            .await;
1715        assert!(res.is_ok());
1716        assert!(res.unwrap().is_some());
1717
1718        // Any account that is not yet valid / expired can't auth.
1719        // Set the valid bounds high/low
1720        // TEST_VALID_FROM_TIME/TEST_EXPIRE_TIME
1721        set_account_valid_time(idms, usr_uuid).await;
1722
1723        let time_low = Duration::from_secs(TEST_NOT_YET_VALID_TIME);
1724        let time = Duration::from_secs(TEST_CURRENT_TIME);
1725        let time_high = Duration::from_secs(TEST_AFTER_EXPIRY);
1726
1727        let mut idms_auth = idms.auth().await.unwrap();
1728        let lae = LdapApplicationAuthEvent::new(app1_name, usr_uuid, pass_app1)
1729            .expect("Failed to build auth event");
1730
1731        let r1 = idms_auth
1732            .application_auth_ldap(&lae, time_low)
1733            .await
1734            .expect_err("Authentication succeeded");
1735        assert_eq!(r1, OperationError::SessionExpired);
1736
1737        let r1 = idms_auth
1738            .application_auth_ldap(&lae, time)
1739            .await
1740            .expect("Failed auth");
1741        assert!(r1.is_some());
1742
1743        let r1 = idms_auth
1744            .application_auth_ldap(&lae, time_high)
1745            .await
1746            .expect_err("Authentication succeeded");
1747        assert_eq!(r1, OperationError::SessionExpired);
1748    }
1749
1750    macro_rules! assert_entry_contains {
1751        (
1752            $entry:expr,
1753            $dn:expr,
1754            $($item:expr),*
1755        ) => {{
1756            assert_eq!($entry.dn, $dn);
1757            // Build a set from the attrs.
1758            let mut attrs = HashSet::new();
1759            for a in $entry.attributes.iter() {
1760                for v in a.vals.iter() {
1761                    attrs.insert((a.atype.as_str(), v.as_slice()));
1762                }
1763            };
1764            info!(?attrs);
1765            $(
1766                warn!("{}", $item.0);
1767                assert!(attrs.contains(&(
1768                    $item.0.as_ref(), $item.1.as_bytes()
1769                )));
1770            )*
1771
1772        }};
1773    }
1774
1775    #[idm_test]
1776    async fn test_ldap_virtual_attribute_generation(
1777        idms: &IdmServer,
1778        _idms_delayed: &IdmServerDelayed,
1779    ) {
1780        let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
1781
1782        let ssh_ed25519 = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIAeGW1P6Pc2rPq0XqbRaDKBcXZUPRklo0L1EyR30CwoP william@amethyst";
1783
1784        // Setup a user we want to check.
1785        {
1786            let e1 = entry_init!(
1787                (Attribute::Class, EntryClass::Object.to_value()),
1788                (Attribute::Class, EntryClass::Person.to_value()),
1789                (Attribute::Class, EntryClass::Account.to_value()),
1790                (Attribute::Class, EntryClass::PosixAccount.to_value()),
1791                (Attribute::Name, Value::new_iname("testperson1")),
1792                (
1793                    Attribute::Uuid,
1794                    Value::Uuid(uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930"))
1795                ),
1796                (Attribute::Description, Value::new_utf8s("testperson1")),
1797                (Attribute::DisplayName, Value::new_utf8s("testperson1")),
1798                (Attribute::GidNumber, Value::new_uint32(12345)),
1799                (Attribute::LoginShell, Value::new_iutf8("/bin/zsh")),
1800                (
1801                    Attribute::SshPublicKey,
1802                    Value::new_sshkey_str("test", ssh_ed25519).expect("Invalid ssh key")
1803                )
1804            );
1805
1806            let mut server_txn = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
1807
1808            // Add anonymous to the needed permission groups.
1809            server_txn
1810                .qs_write
1811                .internal_modify_uuid(
1812                    UUID_IDM_UNIX_AUTHENTICATION_READ,
1813                    &ModifyList::new_append(Attribute::Member, Value::Refer(UUID_ANONYMOUS)),
1814                )
1815                .expect("Unable to modify UNIX_AUTHENTICATION_READ group");
1816
1817            let ce = CreateEvent::new_internal(vec![e1]);
1818            assert!(server_txn
1819                .qs_write
1820                .create(&ce)
1821                .and_then(|_| server_txn.commit())
1822                .is_ok());
1823        }
1824
1825        // Setup the anonymous login.
1826        let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
1827        assert_eq!(
1828            anon_t.effective_session,
1829            LdapSession::UnixBind(UUID_ANONYMOUS)
1830        );
1831
1832        // Check that when we request *, we get default list.
1833        let sr = SearchRequest {
1834            msgid: 1,
1835            base: "dc=example,dc=com".to_string(),
1836            scope: LdapSearchScope::Subtree,
1837            filter: LdapFilter::Equality(Attribute::Name.to_string(), "testperson1".to_string()),
1838            attrs: vec!["*".to_string()],
1839        };
1840        let r1 = ldaps
1841            .do_search(idms, &sr, &anon_t, Source::Internal)
1842            .await
1843            .unwrap();
1844
1845        // The result, and the ldap proto success msg.
1846        assert_eq!(r1.len(), 2);
1847        match &r1[0].op {
1848            LdapOp::SearchResultEntry(lsre) => {
1849                assert_entry_contains!(
1850                    lsre,
1851                    "spn=testperson1@example.com,dc=example,dc=com",
1852                    (Attribute::Class, EntryClass::Object.to_string()),
1853                    (Attribute::Class, EntryClass::Person.to_string()),
1854                    (Attribute::Class, EntryClass::Account.to_string()),
1855                    (Attribute::Class, EntryClass::PosixAccount.to_string()),
1856                    (Attribute::DisplayName, "testperson1"),
1857                    (Attribute::Name, "testperson1"),
1858                    (Attribute::GidNumber, "12345"),
1859                    (Attribute::LoginShell, "/bin/zsh"),
1860                    (Attribute::SshPublicKey, ssh_ed25519),
1861                    (Attribute::Uuid, "cc8e95b4-c24f-4d68-ba54-8bed76f63930")
1862                );
1863            }
1864            _ => panic!("Oh no"),
1865        };
1866
1867        // Check that when we request +, we get all attrs and the vattrs
1868        let sr = SearchRequest {
1869            msgid: 1,
1870            base: "dc=example,dc=com".to_string(),
1871            scope: LdapSearchScope::Subtree,
1872            filter: LdapFilter::Equality(Attribute::Name.to_string(), "testperson1".to_string()),
1873            attrs: vec!["+".to_string()],
1874        };
1875        let r1 = ldaps
1876            .do_search(idms, &sr, &anon_t, Source::Internal)
1877            .await
1878            .unwrap();
1879
1880        // The result, and the ldap proto success msg.
1881        assert_eq!(r1.len(), 2);
1882        match &r1[0].op {
1883            LdapOp::SearchResultEntry(lsre) => {
1884                assert_entry_contains!(
1885                    lsre,
1886                    "spn=testperson1@example.com,dc=example,dc=com",
1887                    (Attribute::ObjectClass, EntryClass::Object.as_ref()),
1888                    (Attribute::ObjectClass, EntryClass::Person.as_ref()),
1889                    (Attribute::ObjectClass, EntryClass::Account.as_ref()),
1890                    (Attribute::ObjectClass, EntryClass::PosixAccount.as_ref()),
1891                    (Attribute::DisplayName, "testperson1"),
1892                    (Attribute::Name, "testperson1"),
1893                    (Attribute::GidNumber, "12345"),
1894                    (Attribute::LoginShell, "/bin/zsh"),
1895                    (Attribute::SshPublicKey, ssh_ed25519),
1896                    (Attribute::EntryUuid, "cc8e95b4-c24f-4d68-ba54-8bed76f63930"),
1897                    (
1898                        Attribute::HomeDirectory,
1899                        "/home/cc8e95b4-c24f-4d68-ba54-8bed76f63930"
1900                    ),
1901                    (
1902                        Attribute::EntryDn,
1903                        "spn=testperson1@example.com,dc=example,dc=com"
1904                    ),
1905                    (Attribute::UidNumber, "12345"),
1906                    (Attribute::Cn, "testperson1"),
1907                    (Attribute::LdapKeys, ssh_ed25519)
1908                );
1909            }
1910            _ => panic!("Oh no"),
1911        };
1912
1913        // Check that when we request an attr by name, we get all of them correctly.
1914        let sr = SearchRequest {
1915            msgid: 1,
1916            base: "dc=example,dc=com".to_string(),
1917            scope: LdapSearchScope::Subtree,
1918            filter: LdapFilter::Equality(Attribute::Name.to_string(), "testperson1".to_string()),
1919            attrs: vec![
1920                LDAP_ATTR_NAME.to_string(),
1921                Attribute::EntryDn.to_string(),
1922                ATTR_LDAP_KEYS.to_string(),
1923                Attribute::UidNumber.to_string(),
1924            ],
1925        };
1926        let r1 = ldaps
1927            .do_search(idms, &sr, &anon_t, Source::Internal)
1928            .await
1929            .unwrap();
1930
1931        // The result, and the ldap proto success msg.
1932        assert_eq!(r1.len(), 2);
1933        match &r1[0].op {
1934            LdapOp::SearchResultEntry(lsre) => {
1935                assert_entry_contains!(
1936                    lsre,
1937                    "spn=testperson1@example.com,dc=example,dc=com",
1938                    (Attribute::Name, "testperson1"),
1939                    (
1940                        Attribute::EntryDn,
1941                        "spn=testperson1@example.com,dc=example,dc=com"
1942                    ),
1943                    (Attribute::UidNumber, "12345"),
1944                    (Attribute::LdapKeys, ssh_ed25519)
1945                );
1946            }
1947            _ => panic!("Oh no"),
1948        };
1949    }
1950
1951    #[idm_test]
1952    async fn test_ldap_token_privilege_granting(
1953        idms: &IdmServer,
1954        _idms_delayed: &IdmServerDelayed,
1955    ) {
1956        // Setup the ldap server
1957        let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
1958
1959        // Prebuild the search req we'll be using this test.
1960        let sr = SearchRequest {
1961            msgid: 1,
1962            base: "dc=example,dc=com".to_string(),
1963            scope: LdapSearchScope::Subtree,
1964            filter: LdapFilter::Equality(Attribute::Name.to_string(), "testperson1".to_string()),
1965            attrs: vec![
1966                LDAP_ATTR_NAME,
1967                LDAP_ATTR_MAIL,
1968                LDAP_ATTR_MAIL_PRIMARY,
1969                LDAP_ATTR_MAIL_ALTERNATIVE,
1970                LDAP_ATTR_EMAIL_PRIMARY,
1971                LDAP_ATTR_EMAIL_ALTERNATIVE,
1972            ]
1973            .into_iter()
1974            .map(|s| s.to_string())
1975            .collect(),
1976        };
1977
1978        let sa_uuid = uuid::uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
1979
1980        // Configure the user account that will have the tokens issued.
1981        // Should be a SERVICE account.
1982        let apitoken = {
1983            // Create a service account,
1984
1985            let e1 = entry_init!(
1986                (Attribute::Class, EntryClass::Object.to_value()),
1987                (Attribute::Class, EntryClass::ServiceAccount.to_value()),
1988                (Attribute::Class, EntryClass::Account.to_value()),
1989                (Attribute::Uuid, Value::Uuid(sa_uuid)),
1990                (Attribute::Name, Value::new_iname("service_permission_test")),
1991                (
1992                    Attribute::DisplayName,
1993                    Value::new_utf8s("service_permission_test")
1994                )
1995            );
1996
1997            // Setup a person with an email
1998            let e2 = entry_init!(
1999                (Attribute::Class, EntryClass::Object.to_value()),
2000                (Attribute::Class, EntryClass::Person.to_value()),
2001                (Attribute::Class, EntryClass::Account.to_value()),
2002                (Attribute::Class, EntryClass::PosixAccount.to_value()),
2003                (Attribute::Name, Value::new_iname("testperson1")),
2004                (
2005                    Attribute::Mail,
2006                    Value::EmailAddress("testperson1@example.com".to_string(), true)
2007                ),
2008                (
2009                    Attribute::Mail,
2010                    Value::EmailAddress("testperson1.alternative@example.com".to_string(), false)
2011                ),
2012                (Attribute::Description, Value::new_utf8s("testperson1")),
2013                (Attribute::DisplayName, Value::new_utf8s("testperson1")),
2014                (Attribute::GidNumber, Value::new_uint32(12345)),
2015                (Attribute::LoginShell, Value::new_iutf8("/bin/zsh"))
2016            );
2017
2018            let ct = duration_from_epoch_now();
2019
2020            let mut server_txn = idms.proxy_write(ct).await.unwrap();
2021
2022            let ce = CreateEvent::new_internal(vec![e1, e2]);
2023            assert!(server_txn.qs_write.create(&ce).is_ok());
2024
2025            // Setup an access control for the service account to view mail attrs.
2026            server_txn
2027                .qs_write
2028                .internal_modify_uuid(
2029                    UUID_IDM_ACCOUNT_MAIL_READ,
2030                    &ModifyList::new_append(Attribute::Member, Value::Refer(sa_uuid)),
2031                )
2032                .expect("Unable to modify UNIX_AUTHENTICATION_READ group");
2033
2034            // Allow anonymous to read basic posix attrs.
2035            server_txn
2036                .qs_write
2037                .internal_modify_uuid(
2038                    UUID_IDM_UNIX_AUTHENTICATION_READ,
2039                    &ModifyList::new_append(Attribute::Member, Value::Refer(UUID_ANONYMOUS)),
2040                )
2041                .expect("Unable to modify UNIX_AUTHENTICATION_READ group");
2042
2043            // Issue a token
2044            // make it purpose = ldap <- currently purpose isn't supported,
2045            // it's an idea for future.
2046            let gte = GenerateApiTokenEvent::new_internal(sa_uuid, "TestToken", None);
2047
2048            let apitoken = server_txn
2049                .service_account_generate_api_token(&gte, ct)
2050                .expect("Failed to create new apitoken");
2051
2052            assert!(server_txn.commit().is_ok());
2053
2054            apitoken
2055        };
2056
2057        // assert the token fails on non-ldap events token-xchg <- currently
2058        // we don't have purpose so this isn't tested.
2059
2060        // Bind with anonymous, search and show mail attr isn't accessible.
2061        let anon_lbt = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
2062        assert_eq!(
2063            anon_lbt.effective_session,
2064            LdapSession::UnixBind(UUID_ANONYMOUS)
2065        );
2066
2067        let r1 = ldaps
2068            .do_search(idms, &sr, &anon_lbt, Source::Internal)
2069            .await
2070            .unwrap();
2071        assert_eq!(r1.len(), 2);
2072        match &r1[0].op {
2073            LdapOp::SearchResultEntry(lsre) => {
2074                assert_entry_contains!(
2075                    lsre,
2076                    "spn=testperson1@example.com,dc=example,dc=com",
2077                    (Attribute::Name, "testperson1")
2078                );
2079            }
2080            _ => panic!("Oh no"),
2081        };
2082
2083        // Inspect the token to get its uuid out.
2084        let jws_verifier = JwsDangerReleaseWithoutVerify::default();
2085
2086        let apitoken_inner = jws_verifier
2087            .verify(&apitoken)
2088            .unwrap()
2089            .from_json::<ApiToken>()
2090            .unwrap();
2091
2092        // Bind using the token as a DN
2093        let sa_lbt = ldaps
2094            .do_bind(idms, "dn=token", &apitoken.to_string())
2095            .await
2096            .unwrap()
2097            .unwrap();
2098        assert_eq!(
2099            sa_lbt.effective_session,
2100            LdapSession::ApiToken(apitoken_inner.clone())
2101        );
2102
2103        // Bind using the token as a pw
2104        let sa_lbt = ldaps
2105            .do_bind(idms, "", &apitoken.to_string())
2106            .await
2107            .unwrap()
2108            .unwrap();
2109        assert_eq!(
2110            sa_lbt.effective_session,
2111            LdapSession::ApiToken(apitoken_inner)
2112        );
2113
2114        // Search and retrieve mail that's now accessible.
2115        let r1 = ldaps
2116            .do_search(idms, &sr, &sa_lbt, Source::Internal)
2117            .await
2118            .unwrap();
2119        assert_eq!(r1.len(), 2);
2120        match &r1[0].op {
2121            LdapOp::SearchResultEntry(lsre) => {
2122                assert_entry_contains!(
2123                    lsre,
2124                    "spn=testperson1@example.com,dc=example,dc=com",
2125                    (Attribute::Name, "testperson1"),
2126                    (Attribute::Mail, "testperson1@example.com"),
2127                    (Attribute::Mail, "testperson1.alternative@example.com"),
2128                    (LDAP_ATTR_MAIL_PRIMARY, "testperson1@example.com"),
2129                    (
2130                        LDAP_ATTR_MAIL_ALTERNATIVE,
2131                        "testperson1.alternative@example.com"
2132                    ),
2133                    (LDAP_ATTR_EMAIL_PRIMARY, "testperson1@example.com"),
2134                    (
2135                        LDAP_ATTR_EMAIL_ALTERNATIVE,
2136                        "testperson1.alternative@example.com"
2137                    )
2138                );
2139            }
2140            _ => panic!("Oh no"),
2141        };
2142
2143        // ======= test with a substring search
2144
2145        let sr = SearchRequest {
2146            msgid: 2,
2147            base: "dc=example,dc=com".to_string(),
2148            scope: LdapSearchScope::Subtree,
2149            filter: LdapFilter::And(vec![
2150                LdapFilter::Equality(Attribute::Class.to_string(), "posixAccount".to_string()),
2151                LdapFilter::Substring(
2152                    LDAP_ATTR_MAIL.to_string(),
2153                    LdapSubstringFilter {
2154                        initial: None,
2155                        any: vec![],
2156                        final_: Some("@example.com".to_string()),
2157                    },
2158                ),
2159            ]),
2160            attrs: vec![
2161                LDAP_ATTR_NAME,
2162                LDAP_ATTR_MAIL,
2163                LDAP_ATTR_MAIL_PRIMARY,
2164                LDAP_ATTR_MAIL_ALTERNATIVE,
2165            ]
2166            .into_iter()
2167            .map(|s| s.to_string())
2168            .collect(),
2169        };
2170
2171        let r1 = ldaps
2172            .do_search(idms, &sr, &sa_lbt, Source::Internal)
2173            .await
2174            .unwrap();
2175
2176        assert_eq!(r1.len(), 2);
2177        match &r1[0].op {
2178            LdapOp::SearchResultEntry(lsre) => {
2179                assert_entry_contains!(
2180                    lsre,
2181                    "spn=testperson1@example.com,dc=example,dc=com",
2182                    (Attribute::Name, "testperson1"),
2183                    (Attribute::Mail, "testperson1@example.com"),
2184                    (Attribute::Mail, "testperson1.alternative@example.com"),
2185                    (LDAP_ATTR_MAIL_PRIMARY, "testperson1@example.com"),
2186                    (
2187                        LDAP_ATTR_MAIL_ALTERNATIVE,
2188                        "testperson1.alternative@example.com"
2189                    )
2190                );
2191            }
2192            _ => panic!("Oh no"),
2193        };
2194    }
2195
2196    #[idm_test]
2197    async fn test_ldap_virtual_attribute_with_all_attr_search(
2198        idms: &IdmServer,
2199        _idms_delayed: &IdmServerDelayed,
2200    ) {
2201        let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
2202
2203        let acct_uuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
2204
2205        // Setup a user we want to check.
2206        {
2207            let e1 = entry_init!(
2208                (Attribute::Class, EntryClass::Person.to_value()),
2209                (Attribute::Class, EntryClass::Account.to_value()),
2210                (Attribute::Class, EntryClass::PosixAccount.to_value()),
2211                (Attribute::Name, Value::new_iname("testperson1")),
2212                (Attribute::Uuid, Value::Uuid(acct_uuid)),
2213                (Attribute::Description, Value::new_utf8s("testperson1")),
2214                (Attribute::DisplayName, Value::new_utf8s("testperson1"))
2215            );
2216
2217            let mut server_txn = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2218
2219            // Add anonymous to the needed permission groups.
2220            server_txn
2221                .qs_write
2222                .internal_modify_uuid(
2223                    UUID_IDM_UNIX_AUTHENTICATION_READ,
2224                    &ModifyList::new_append(Attribute::Member, Value::Refer(UUID_ANONYMOUS)),
2225                )
2226                .expect("Unable to modify UNIX_AUTHENTICATION_READ group");
2227
2228            assert!(server_txn
2229                .qs_write
2230                .internal_create(vec![e1])
2231                .and_then(|_| server_txn.commit())
2232                .is_ok());
2233        }
2234
2235        // Setup the anonymous login.
2236        let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
2237        assert_eq!(
2238            anon_t.effective_session,
2239            LdapSession::UnixBind(UUID_ANONYMOUS)
2240        );
2241
2242        // Check that when we request a virtual attr by name *and* all_attrs we get all the requested values.
2243        let sr = SearchRequest {
2244            msgid: 1,
2245            base: "dc=example,dc=com".to_string(),
2246            scope: LdapSearchScope::Subtree,
2247            filter: LdapFilter::Equality(Attribute::Name.to_string(), "testperson1".to_string()),
2248            attrs: vec![
2249                "*".to_string(),
2250                // Already being returned
2251                LDAP_ATTR_NAME.to_string(),
2252                // This is a virtual attribute
2253                Attribute::EntryUuid.to_string(),
2254            ],
2255        };
2256        let r1 = ldaps
2257            .do_search(idms, &sr, &anon_t, Source::Internal)
2258            .await
2259            .unwrap();
2260
2261        // The result, and the ldap proto success msg.
2262        assert_eq!(r1.len(), 2);
2263        match &r1[0].op {
2264            LdapOp::SearchResultEntry(lsre) => {
2265                assert_entry_contains!(
2266                    lsre,
2267                    "spn=testperson1@example.com,dc=example,dc=com",
2268                    (Attribute::Name, "testperson1"),
2269                    (Attribute::DisplayName, "testperson1"),
2270                    (Attribute::Uuid, "cc8e95b4-c24f-4d68-ba54-8bed76f63930"),
2271                    (Attribute::EntryUuid, "cc8e95b4-c24f-4d68-ba54-8bed76f63930")
2272                );
2273            }
2274            _ => panic!("Oh no"),
2275        };
2276    }
2277
2278    // Test behaviour of the 1.1 attribute.
2279    #[idm_test]
2280    async fn test_ldap_one_dot_one_attribute(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
2281        let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
2282
2283        let acct_uuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
2284
2285        // Setup a user we want to check.
2286        {
2287            let e1 = entry_init!(
2288                (Attribute::Class, EntryClass::Person.to_value()),
2289                (Attribute::Class, EntryClass::Account.to_value()),
2290                (Attribute::Class, EntryClass::PosixAccount.to_value()),
2291                (Attribute::Name, Value::new_iname("testperson1")),
2292                (Attribute::Uuid, Value::Uuid(acct_uuid)),
2293                (Attribute::Description, Value::new_utf8s("testperson1")),
2294                (Attribute::DisplayName, Value::new_utf8s("testperson1"))
2295            );
2296
2297            let mut server_txn = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2298
2299            // Add anonymous to the needed permission groups.
2300            server_txn
2301                .qs_write
2302                .internal_modify_uuid(
2303                    UUID_IDM_UNIX_AUTHENTICATION_READ,
2304                    &ModifyList::new_append(Attribute::Member, Value::Refer(UUID_ANONYMOUS)),
2305                )
2306                .expect("Unable to modify UNIX_AUTHENTICATION_READ group");
2307
2308            assert!(server_txn
2309                .qs_write
2310                .internal_create(vec![e1])
2311                .and_then(|_| server_txn.commit())
2312                .is_ok());
2313        }
2314
2315        // Setup the anonymous login.
2316        let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
2317        assert_eq!(
2318            anon_t.effective_session,
2319            LdapSession::UnixBind(UUID_ANONYMOUS)
2320        );
2321
2322        // If we request only 1.1, we get no attributes.
2323        let sr = SearchRequest {
2324            msgid: 1,
2325            base: "dc=example,dc=com".to_string(),
2326            scope: LdapSearchScope::Subtree,
2327            filter: LdapFilter::Equality(Attribute::Name.to_string(), "testperson1".to_string()),
2328            attrs: vec!["1.1".to_string()],
2329        };
2330        let r1 = ldaps
2331            .do_search(idms, &sr, &anon_t, Source::Internal)
2332            .await
2333            .unwrap();
2334
2335        // The result, and the ldap proto success msg.
2336        assert_eq!(r1.len(), 2);
2337        match &r1[0].op {
2338            LdapOp::SearchResultEntry(lsre) => {
2339                assert_eq!(
2340                    lsre.dn.as_str(),
2341                    "spn=testperson1@example.com,dc=example,dc=com"
2342                );
2343                assert!(lsre.attributes.is_empty());
2344            }
2345            _ => panic!("Oh no"),
2346        };
2347
2348        // If we request 1.1 and another attr, 1.1 is IGNORED.
2349        let sr = SearchRequest {
2350            msgid: 1,
2351            base: "dc=example,dc=com".to_string(),
2352            scope: LdapSearchScope::Subtree,
2353            filter: LdapFilter::Equality(Attribute::Name.to_string(), "testperson1".to_string()),
2354            attrs: vec![
2355                "1.1".to_string(),
2356                // This should be present.
2357                Attribute::EntryUuid.to_string(),
2358            ],
2359        };
2360        let r1 = ldaps
2361            .do_search(idms, &sr, &anon_t, Source::Internal)
2362            .await
2363            .unwrap();
2364
2365        // The result, and the ldap proto success msg.
2366        assert_eq!(r1.len(), 2);
2367        match &r1[0].op {
2368            LdapOp::SearchResultEntry(lsre) => {
2369                assert_entry_contains!(
2370                    lsre,
2371                    "spn=testperson1@example.com,dc=example,dc=com",
2372                    (Attribute::EntryUuid, "cc8e95b4-c24f-4d68-ba54-8bed76f63930")
2373                );
2374            }
2375            _ => panic!("Oh no"),
2376        };
2377    }
2378
2379    #[idm_test]
2380    async fn test_ldap_rootdse_basedn_change(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
2381        let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
2382
2383        let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
2384        assert_eq!(
2385            anon_t.effective_session,
2386            LdapSession::UnixBind(UUID_ANONYMOUS)
2387        );
2388
2389        let sr = SearchRequest {
2390            msgid: 1,
2391            base: "".to_string(),
2392            scope: LdapSearchScope::Base,
2393            filter: LdapFilter::Present(Attribute::ObjectClass.to_string()),
2394            attrs: vec!["*".to_string()],
2395        };
2396        let r1 = ldaps
2397            .do_search(idms, &sr, &anon_t, Source::Internal)
2398            .await
2399            .unwrap();
2400
2401        trace!(?r1);
2402
2403        // The result, and the ldap proto success msg.
2404        assert_eq!(r1.len(), 2);
2405        match &r1[0].op {
2406            LdapOp::SearchResultEntry(lsre) => {
2407                assert_entry_contains!(
2408                    lsre,
2409                    "",
2410                    (Attribute::ObjectClass, "top"),
2411                    ("vendorname", "Kanidm Project"),
2412                    ("supportedldapversion", "3"),
2413                    ("defaultnamingcontext", "dc=example,dc=com")
2414                );
2415            }
2416            _ => panic!("Oh no"),
2417        };
2418
2419        drop(ldaps);
2420
2421        // Change the domain basedn
2422
2423        let mut idms_prox_write = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2424        // make the admin a valid posix account
2425        let me_posix = ModifyEvent::new_internal_invalid(
2426            filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_DOMAIN_INFO))),
2427            ModifyList::new_purge_and_set(
2428                Attribute::DomainLdapBasedn,
2429                Value::new_iutf8("o=kanidmproject"),
2430            ),
2431        );
2432        assert!(idms_prox_write.qs_write.modify(&me_posix).is_ok());
2433
2434        assert!(idms_prox_write.commit().is_ok());
2435
2436        // Now re-test
2437        let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
2438
2439        let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
2440        assert_eq!(
2441            anon_t.effective_session,
2442            LdapSession::UnixBind(UUID_ANONYMOUS)
2443        );
2444
2445        let sr = SearchRequest {
2446            msgid: 1,
2447            base: "".to_string(),
2448            scope: LdapSearchScope::Base,
2449            filter: LdapFilter::Present(Attribute::ObjectClass.to_string()),
2450            attrs: vec!["*".to_string()],
2451        };
2452        let r1 = ldaps
2453            .do_search(idms, &sr, &anon_t, Source::Internal)
2454            .await
2455            .unwrap();
2456
2457        trace!(?r1);
2458
2459        // The result, and the ldap proto success msg.
2460        assert_eq!(r1.len(), 2);
2461        match &r1[0].op {
2462            LdapOp::SearchResultEntry(lsre) => {
2463                assert_entry_contains!(
2464                    lsre,
2465                    "",
2466                    (Attribute::ObjectClass, "top"),
2467                    ("vendorname", "Kanidm Project"),
2468                    ("supportedldapversion", "3"),
2469                    ("defaultnamingcontext", "o=kanidmproject")
2470                );
2471            }
2472            _ => panic!("Oh no"),
2473        };
2474    }
2475
2476    #[idm_test]
2477    async fn test_ldap_sssd_compat(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
2478        let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
2479
2480        let acct_uuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
2481
2482        // Setup a user we want to check.
2483        {
2484            let e1 = entry_init!(
2485                (Attribute::Class, EntryClass::Person.to_value()),
2486                (Attribute::Class, EntryClass::Account.to_value()),
2487                (Attribute::Class, EntryClass::PosixAccount.to_value()),
2488                (Attribute::Name, Value::new_iname("testperson1")),
2489                (Attribute::Uuid, Value::Uuid(acct_uuid)),
2490                (Attribute::GidNumber, Value::Uint32(12345)),
2491                (Attribute::Description, Value::new_utf8s("testperson1")),
2492                (Attribute::DisplayName, Value::new_utf8s("testperson1"))
2493            );
2494
2495            let mut server_txn = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2496
2497            // Add anonymous to the needed permission groups.
2498            server_txn
2499                .qs_write
2500                .internal_modify_uuid(
2501                    UUID_IDM_UNIX_AUTHENTICATION_READ,
2502                    &ModifyList::new_append(Attribute::Member, Value::Refer(UUID_ANONYMOUS)),
2503                )
2504                .expect("Unable to modify UNIX_AUTHENTICATION_READ group");
2505
2506            assert!(server_txn
2507                .qs_write
2508                .internal_create(vec![e1])
2509                .and_then(|_| server_txn.commit())
2510                .is_ok());
2511        }
2512
2513        // Setup the anonymous login.
2514        let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
2515        assert_eq!(
2516            anon_t.effective_session,
2517            LdapSession::UnixBind(UUID_ANONYMOUS)
2518        );
2519
2520        // SSSD tries to just search for silly attrs all the time. We ignore them.
2521        let sr = SearchRequest {
2522            msgid: 1,
2523            base: "dc=example,dc=com".to_string(),
2524            scope: LdapSearchScope::Subtree,
2525            filter: LdapFilter::And(vec![
2526                LdapFilter::Equality(Attribute::Class.to_string(), "sudohost".to_string()),
2527                LdapFilter::Substring(
2528                    Attribute::SudoHost.to_string(),
2529                    LdapSubstringFilter {
2530                        initial: Some("a".to_string()),
2531                        any: vec!["x".to_string()],
2532                        final_: Some("z".to_string()),
2533                    },
2534                ),
2535            ]),
2536            attrs: vec![
2537                "*".to_string(),
2538                // Already being returned
2539                LDAP_ATTR_NAME.to_string(),
2540                // This is a virtual attribute
2541                Attribute::EntryUuid.to_string(),
2542            ],
2543        };
2544        let r1 = ldaps
2545            .do_search(idms, &sr, &anon_t, Source::Internal)
2546            .await
2547            .unwrap();
2548
2549        // Empty results and ldap proto success msg.
2550        assert_eq!(r1.len(), 1);
2551
2552        // Second search
2553
2554        let sr = SearchRequest {
2555            msgid: 1,
2556            base: "dc=example,dc=com".to_string(),
2557            scope: LdapSearchScope::Subtree,
2558            filter: LdapFilter::Equality(Attribute::Name.to_string(), "testperson1".to_string()),
2559            attrs: vec![
2560                "uid".to_string(),
2561                "uidNumber".to_string(),
2562                "gidNumber".to_string(),
2563                "gecos".to_string(),
2564                "cn".to_string(),
2565                "entryuuid".to_string(),
2566            ],
2567        };
2568        let r1 = ldaps
2569            .do_search(idms, &sr, &anon_t, Source::Internal)
2570            .await
2571            .unwrap();
2572
2573        trace!(?r1);
2574
2575        // The result, and the ldap proto success msg.
2576        assert_eq!(r1.len(), 2);
2577        match &r1[0].op {
2578            LdapOp::SearchResultEntry(lsre) => {
2579                assert_entry_contains!(
2580                    lsre,
2581                    "spn=testperson1@example.com,dc=example,dc=com",
2582                    (Attribute::Uid, "testperson1"),
2583                    (Attribute::Cn, "testperson1"),
2584                    (Attribute::Gecos, "testperson1"),
2585                    (Attribute::UidNumber, "12345"),
2586                    (Attribute::GidNumber, "12345"),
2587                    (Attribute::EntryUuid, "cc8e95b4-c24f-4d68-ba54-8bed76f63930")
2588                );
2589            }
2590            _ => panic!("Oh no"),
2591        };
2592    }
2593
2594    #[idm_test]
2595    async fn test_ldap_compare_request(idms: &IdmServer, _idms_delayed: &IdmServerDelayed) {
2596        let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
2597
2598        // Setup a user we want to check.
2599        {
2600            let acct_uuid = uuid!("cc8e95b4-c24f-4d68-ba54-8bed76f63930");
2601
2602            let e1 = entry_init!(
2603                (Attribute::Class, EntryClass::Person.to_value()),
2604                (Attribute::Class, EntryClass::Account.to_value()),
2605                (Attribute::Class, EntryClass::PosixAccount.to_value()),
2606                (Attribute::Name, Value::new_iname("testperson1")),
2607                (Attribute::Uuid, Value::Uuid(acct_uuid)),
2608                (Attribute::GidNumber, Value::Uint32(12345)),
2609                (Attribute::Description, Value::new_utf8s("testperson1")),
2610                (Attribute::DisplayName, Value::new_utf8s("testperson1"))
2611            );
2612
2613            let mut server_txn = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2614
2615            // Add anonymous to the needed permission groups.
2616            server_txn
2617                .qs_write
2618                .internal_modify_uuid(
2619                    UUID_IDM_UNIX_AUTHENTICATION_READ,
2620                    &ModifyList::new_append(Attribute::Member, Value::Refer(UUID_ANONYMOUS)),
2621                )
2622                .expect("Unable to modify UNIX_AUTHENTICATION_READ group");
2623
2624            assert!(server_txn
2625                .qs_write
2626                .internal_create(vec![e1])
2627                .and_then(|_| server_txn.commit())
2628                .is_ok());
2629        }
2630
2631        // Setup the anonymous login.
2632        let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
2633        assert_eq!(
2634            anon_t.effective_session,
2635            LdapSession::UnixBind(UUID_ANONYMOUS)
2636        );
2637
2638        #[track_caller]
2639        fn assert_compare_result(r: &[LdapMsg], code: &LdapResultCode) {
2640            assert_eq!(r.len(), 1);
2641            match &r[0].op {
2642                LdapOp::CompareResult(lcr) => {
2643                    assert_eq!(&lcr.code, code);
2644                }
2645                _ => panic!("Oh no"),
2646            };
2647        }
2648
2649        let cr = CompareRequest {
2650            msgid: 1,
2651            entry: "name=testperson1,dc=example,dc=com".to_string(),
2652            atype: Attribute::Name.to_string(),
2653            val: "testperson1".to_string(),
2654        };
2655
2656        assert_compare_result(
2657            &ldaps
2658                .do_compare(idms, &cr, &anon_t, Source::Internal)
2659                .await
2660                .unwrap(),
2661            &LdapResultCode::CompareTrue,
2662        );
2663
2664        let cr = CompareRequest {
2665            msgid: 1,
2666            entry: "name=testperson1,dc=example,dc=com".to_string(),
2667            atype: Attribute::GidNumber.to_string(),
2668            val: "12345".to_string(),
2669        };
2670
2671        assert_compare_result(
2672            &ldaps
2673                .do_compare(idms, &cr, &anon_t, Source::Internal)
2674                .await
2675                .unwrap(),
2676            &LdapResultCode::CompareTrue,
2677        );
2678
2679        let cr = CompareRequest {
2680            msgid: 1,
2681            entry: "name=testperson1,dc=example,dc=com".to_string(),
2682            atype: Attribute::Name.to_string(),
2683            val: "other".to_string(),
2684        };
2685        assert_compare_result(
2686            &ldaps
2687                .do_compare(idms, &cr, &anon_t, Source::Internal)
2688                .await
2689                .unwrap(),
2690            &LdapResultCode::CompareFalse,
2691        );
2692
2693        let cr = CompareRequest {
2694            msgid: 1,
2695            entry: "name=other,dc=example,dc=com".to_string(),
2696            atype: Attribute::Name.to_string(),
2697            val: "other".to_string(),
2698        };
2699        assert_compare_result(
2700            &ldaps
2701                .do_compare(idms, &cr, &anon_t, Source::Internal)
2702                .await
2703                .unwrap(),
2704            &LdapResultCode::NoSuchObject,
2705        );
2706
2707        let cr = CompareRequest {
2708            msgid: 1,
2709            entry: "invalidentry".to_string(),
2710            atype: Attribute::Name.to_string(),
2711            val: "other".to_string(),
2712        };
2713        assert!(&ldaps
2714            .do_compare(idms, &cr, &anon_t, Source::Internal)
2715            .await
2716            .is_err());
2717
2718        let cr = CompareRequest {
2719            msgid: 1,
2720            entry: "name=other,dc=example,dc=com".to_string(),
2721            atype: "invalid".to_string(),
2722            val: "other".to_string(),
2723        };
2724        assert_eq!(
2725            &ldaps
2726                .do_compare(idms, &cr, &anon_t, Source::Internal)
2727                .await
2728                .unwrap_err(),
2729            &OperationError::InvalidAttributeName("invalid".to_string()),
2730        );
2731    }
2732
2733    #[idm_test]
2734    async fn test_ldap_maximum_queryable_attributes(
2735        idms: &IdmServer,
2736        _idms_delayed: &IdmServerDelayed,
2737    ) {
2738        // Set the max queryable attrs to 2
2739
2740        let mut server_txn = idms.proxy_write(duration_from_epoch_now()).await.unwrap();
2741
2742        let set_ldap_maximum_queryable_attrs = ModifyEvent::new_internal_invalid(
2743            filter!(f_eq(Attribute::Uuid, PartialValue::Uuid(UUID_DOMAIN_INFO))),
2744            ModifyList::new_purge_and_set(Attribute::LdapMaxQueryableAttrs, Value::Uint32(2)),
2745        );
2746        assert!(server_txn
2747            .qs_write
2748            .modify(&set_ldap_maximum_queryable_attrs)
2749            .and_then(|_| server_txn.commit())
2750            .is_ok());
2751
2752        let ldaps = LdapServer::new(idms).await.expect("failed to start ldap");
2753
2754        let usr_uuid = Uuid::new_v4();
2755        let grp_uuid = Uuid::new_v4();
2756        let app_uuid = Uuid::new_v4();
2757        let app_name = "testapp1";
2758
2759        // Setup person, group and application
2760        {
2761            let e1 = entry_init!(
2762                (Attribute::Class, EntryClass::Object.to_value()),
2763                (Attribute::Class, EntryClass::Account.to_value()),
2764                (Attribute::Class, EntryClass::Person.to_value()),
2765                (Attribute::Name, Value::new_iname("testperson1")),
2766                (Attribute::Uuid, Value::Uuid(usr_uuid)),
2767                (Attribute::Description, Value::new_utf8s("testperson1")),
2768                (Attribute::DisplayName, Value::new_utf8s("testperson1"))
2769            );
2770
2771            let e2 = entry_init!(
2772                (Attribute::Class, EntryClass::Object.to_value()),
2773                (Attribute::Class, EntryClass::Group.to_value()),
2774                (Attribute::Name, Value::new_iname("testgroup1")),
2775                (Attribute::Uuid, Value::Uuid(grp_uuid))
2776            );
2777
2778            let e3 = entry_init!(
2779                (Attribute::Class, EntryClass::Object.to_value()),
2780                (Attribute::Class, EntryClass::Account.to_value()),
2781                (Attribute::Class, EntryClass::ServiceAccount.to_value()),
2782                (Attribute::Class, EntryClass::Application.to_value()),
2783                (Attribute::DisplayName, Value::new_utf8s("Application")),
2784                (Attribute::Name, Value::new_iname(app_name)),
2785                (Attribute::Uuid, Value::Uuid(app_uuid)),
2786                (Attribute::LinkedGroup, Value::Refer(grp_uuid))
2787            );
2788
2789            let ct = duration_from_epoch_now();
2790            let mut server_txn = idms.proxy_write(ct).await.unwrap();
2791            assert!(server_txn
2792                .qs_write
2793                .internal_create(vec![e1, e2, e3])
2794                .and_then(|_| server_txn.commit())
2795                .is_ok());
2796        }
2797
2798        // Setup the anonymous login
2799        let anon_t = ldaps.do_bind(idms, "", "").await.unwrap().unwrap();
2800        assert_eq!(
2801            anon_t.effective_session,
2802            LdapSession::UnixBind(UUID_ANONYMOUS)
2803        );
2804
2805        let invalid_search = SearchRequest {
2806            msgid: 1,
2807            base: "dc=example,dc=com".to_string(),
2808            scope: LdapSearchScope::Subtree,
2809            filter: LdapFilter::Present(Attribute::ObjectClass.to_string()),
2810            attrs: vec![
2811                "objectClass".to_string(),
2812                "cn".to_string(),
2813                "givenName".to_string(),
2814            ],
2815        };
2816
2817        let valid_search = SearchRequest {
2818            msgid: 1,
2819            base: "dc=example,dc=com".to_string(),
2820            scope: LdapSearchScope::Subtree,
2821            filter: LdapFilter::Present(Attribute::ObjectClass.to_string()),
2822            attrs: vec!["objectClass: person".to_string()],
2823        };
2824
2825        let invalid_res: Result<Vec<LdapMsg>, OperationError> = ldaps
2826            .do_search(idms, &invalid_search, &anon_t, Source::Internal)
2827            .await;
2828
2829        let valid_res: Result<Vec<LdapMsg>, OperationError> = ldaps
2830            .do_search(idms, &valid_search, &anon_t, Source::Internal)
2831            .await;
2832
2833        assert_eq!(invalid_res, Err(OperationError::ResourceLimit));
2834        assert!(valid_res.is_ok());
2835    }
2836}