Skip to main content

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