kanidmd_lib/idm/
ldap.rs

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