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