ldap_types/
basic.rs

1//! Contains al the basic LDAP types
2use std::{
3    collections::{HashMap, HashSet},
4    convert::TryFrom,
5    hash::{Hash, Hasher},
6};
7
8use educe::Educe;
9use oid::ObjectIdentifier;
10
11use enum_as_inner::EnumAsInner;
12
13#[cfg(feature = "chumsky")]
14use chumsky::{prelude::*, text::digits};
15
16#[cfg(feature = "chumsky")]
17use itertools::Itertools;
18
19#[cfg(feature = "chumsky")]
20use ariadne::{Color, Fmt, Label, Report, ReportKind, Source};
21
22#[cfg(feature = "serde")]
23use serde::{de::SeqAccess, ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer};
24
25#[cfg(feature = "diff")]
26use diff::Diff;
27
28/// a wrapped error in case parsing fails to get proper error output
29/// the chumsky errors themselves lack Display and std::error::Error
30/// implementations
31#[cfg(feature = "chumsky")]
32#[derive(Debug)]
33pub struct ChumskyError {
34    /// description of the object we were trying to parse
35    pub description: String,
36    /// source string for parsing
37    pub source: String,
38    /// errors encountered during parsing
39    pub errors: Vec<chumsky::error::Simple<char>>,
40}
41
42#[cfg(feature = "chumsky")]
43impl std::fmt::Display for ChumskyError {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        for e in &self.errors {
46            let msg = format!(
47                "While parsing {}: {}{}, expected {}",
48                self.description,
49                if e.found().is_some() {
50                    "Unexpected token"
51                } else {
52                    "Unexpected end of input"
53                },
54                if let Some(label) = e.label() {
55                    format!(" while parsing {}", label)
56                } else {
57                    String::new()
58                },
59                if e.expected().len() == 0 {
60                    "end of input".to_string()
61                } else {
62                    e.expected()
63                        .map(|expected| match expected {
64                            Some(expected) => expected.to_string(),
65                            None => "end of input".to_string(),
66                        })
67                        .collect::<Vec<_>>()
68                        .join(", ")
69                },
70            );
71
72            let report = Report::build(ReportKind::Error, e.span())
73                .with_code(3)
74                .with_message(msg)
75                .with_label(
76                    Label::new(e.span())
77                        .with_message(format!(
78                            "Unexpected {}",
79                            e.found()
80                                .map(|c| format!("token {}", c.fg(Color::Red)))
81                                .unwrap_or_else(|| "end of input".to_string())
82                        ))
83                        .with_color(Color::Red),
84                );
85
86            let report = match e.reason() {
87                chumsky::error::SimpleReason::Unclosed { span, delimiter } => report.with_label(
88                    Label::new(span.clone())
89                        .with_message(format!(
90                            "Unclosed delimiter {}",
91                            delimiter.fg(Color::Yellow)
92                        ))
93                        .with_color(Color::Yellow),
94                ),
95                chumsky::error::SimpleReason::Unexpected => report,
96                chumsky::error::SimpleReason::Custom(msg) => report.with_label(
97                    Label::new(e.span())
98                        .with_message(format!("{}", msg.fg(Color::Yellow)))
99                        .with_color(Color::Yellow),
100                ),
101            };
102
103            let mut s: Vec<u8> = Vec::new();
104            report
105                .finish()
106                .write(Source::from(&self.source), &mut s)
107                .map_err(|_| <std::fmt::Error as std::default::Default>::default())?;
108            let s = std::str::from_utf8(&s).expect("Expected ariadne to generate valid UTF-8");
109            write!(f, "{}", s)?;
110        }
111        Ok(())
112    }
113}
114
115#[cfg(feature = "chumsky")]
116impl std::error::Error for ChumskyError {
117    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
118        None
119    }
120}
121
122/// represents the object to request from an LDAP server to figure out which
123/// features,... it supports
124///
125/// <https://ldapwiki.com/wiki/RootDSE>
126///
127/// <https://ldapwiki.com/wiki/LDAP%20Extensions%20and%20Controls%20Listing>
128#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
129pub struct RootDSE {
130    /// version of the LDAP protocol the server supports
131    pub supported_ldap_version: String,
132    /// LDAP controls the server supports
133    ///
134    /// <https://ldapwiki.com/wiki/SupportedControl>
135    pub supported_controls: Vec<ObjectIdentifier>,
136    /// LDAP extensions the server supports
137    ///
138    /// <https://ldapwiki.com/wiki/SupportedExtension>
139    pub supported_extensions: Vec<ObjectIdentifier>,
140    /// LDAP features the server supports
141    ///
142    /// <https://ldapwiki.com/wiki/SupportedFeatures>
143    pub supported_features: Vec<ObjectIdentifier>,
144    /// SASL mechanisms the server supports for authentication
145    ///
146    /// <https://ldapwiki.com/wiki/SupportedSASLMechanisms>
147    pub supported_sasl_mechanisms: Vec<String>,
148    /// the DN of the config context on this server
149    ///
150    /// this is where the LDAP server configuration lives
151    pub config_context: String,
152    /// the DNs of naming contexts on this server
153    ///
154    /// each of these is essentially the root of a tree where the actual data
155    /// on the server lives
156    ///
157    /// <https://ldapwiki.com/wiki/NamingContext>
158    pub naming_contexts: Vec<String>,
159    /// the DN of the subschema subentry
160    ///
161    /// this is essentially where the LDAP schema elements this server supports
162    /// can be retrieved
163    ///
164    /// <https://ldapwiki.com/wiki/SubschemaSubentry>
165    pub subschema_subentry: String,
166}
167
168impl std::fmt::Debug for RootDSE {
169    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
170        f.debug_struct("RootDSE")
171            .field("supported_ldap_version", &self.supported_ldap_version)
172            .field(
173                "supported_controls",
174                &self
175                    .supported_controls
176                    .iter()
177                    .map(|x| x.into())
178                    .collect::<Vec<String>>(),
179            )
180            .field(
181                "supported_extensions",
182                &self
183                    .supported_extensions
184                    .iter()
185                    .map(|x| x.into())
186                    .collect::<Vec<String>>(),
187            )
188            .field(
189                "supported_features",
190                &self
191                    .supported_features
192                    .iter()
193                    .map(|x| x.into())
194                    .collect::<Vec<String>>(),
195            )
196            .field("supported_sasl_mechanisms", &self.supported_sasl_mechanisms)
197            .field("config_context", &self.config_context)
198            .field("naming_contexts", &self.naming_contexts)
199            .field("subschema_subentry", &self.subschema_subentry)
200            .finish()
201    }
202}
203
204/// chumsky parser for [oid::ObjectIdentifier]
205#[cfg(feature = "chumsky")]
206pub fn oid_parser() -> impl Parser<char, ObjectIdentifier, Error = Simple<char>> {
207    digits(10).separated_by(just('.')).try_map(|x, span| {
208        x.into_iter()
209            .join(".")
210            .try_into()
211            .map_err(|e| Simple::custom(span, format!("{:?}", e)))
212    })
213}
214
215/// a key string is a string limited to the characters that are safe to use
216/// in a key context, e.g. a relative distinguished name, without encoding
217#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Hash)]
218#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
219pub struct KeyString(pub String);
220
221impl std::fmt::Display for KeyString {
222    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
223        std::fmt::Display::fmt(&self.0, f)?;
224        Ok(())
225    }
226}
227
228impl KeyString {
229    /// this is a quick and dirty helper method to determine if this KeyString
230    /// describes one of the standard case insensitive matches
231    ///
232    /// not perfect but it is useful when trying to figure out how string LDAP
233    /// attributes need to be compared
234    pub fn describes_case_insensitive_match(&self) -> bool {
235        match self {
236            KeyString(s) if s == "objectIdentifierMatch" => true,
237            KeyString(s) if s == "caseIgnoreMatch" => true,
238            KeyString(s) if s == "caseIgnoreListMatch" => true,
239            KeyString(s) if s == "caseIgnoreIA5Match" => true,
240            KeyString(s) if s == "caseIgnoreListSubstringsMatch" => true,
241            KeyString(s) if s == "caseIgnoreSubstringsMatch" => true,
242            KeyString(s) if s == "caseIgnoreOrderingMatch" => true,
243            KeyString(s) if s == "caseIgnoreIA5SubstringsMatch" => true,
244            _ => false,
245        }
246    }
247
248    /// converts the KeyString to lowercase
249    pub fn to_lowercase(&self) -> KeyString {
250        let KeyString(s) = self;
251        KeyString(s.to_lowercase())
252    }
253}
254
255impl TryFrom<KeyStringOrOID> for KeyString {
256    type Error = ();
257
258    fn try_from(value: KeyStringOrOID) -> Result<Self, Self::Error> {
259        match value {
260            KeyStringOrOID::KeyString(ks) => Ok(ks),
261            KeyStringOrOID::OID(_) => Err(()),
262        }
263    }
264}
265
266impl TryFrom<&KeyStringOrOID> for KeyString {
267    type Error = ();
268
269    fn try_from(value: &KeyStringOrOID) -> Result<Self, Self::Error> {
270        match value {
271            KeyStringOrOID::KeyString(ks) => Ok(ks.to_owned()),
272            KeyStringOrOID::OID(_) => Err(()),
273        }
274    }
275}
276
277/// parses a [KeyString]
278#[cfg(feature = "chumsky")]
279pub fn keystring_parser() -> impl Parser<char, KeyString, Error = Simple<char>> {
280    filter(|c: &char| c.is_ascii_alphabetic())
281        .chain(filter(|c: &char| c.is_ascii_alphanumeric() || *c == '-' || *c == ';').repeated())
282        .collect::<String>()
283        .map(KeyString)
284}
285
286/// parses a [KeyString] in locations where it is single-quoted
287#[cfg(feature = "chumsky")]
288pub fn quoted_keystring_parser() -> impl Parser<char, KeyString, Error = Simple<char>> {
289    keystring_parser().delimited_by(just('\''), just('\''))
290}
291
292/// hash function for ObjectIdentifier based on string representation
293/// since ObjectIdentifier does not implement Hash
294pub fn hash_oid<H: Hasher>(s: &ObjectIdentifier, state: &mut H) {
295    Hash::hash(&format!("{s:?}"), state);
296}
297
298/// LDAP allows the use of either a keystring or an OID in many locations,
299/// e.g. in DNs or in the schema
300#[derive(Clone, Debug, EnumAsInner, Educe)]
301#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
302#[educe(PartialEq, Eq, Hash)]
303pub enum KeyStringOrOID {
304    /// this represents a [KeyString]
305    #[cfg_attr(feature = "serde", serde(rename = "key_string"))]
306    KeyString(KeyString),
307    /// this reprents an [ObjectIdentifier]
308    #[cfg_attr(feature = "serde", serde(rename = "oid"))]
309    OID(#[educe(Hash(method = "hash_oid"))] ObjectIdentifier),
310}
311
312impl PartialOrd for KeyStringOrOID {
313    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
314        Some(self.cmp(other))
315    }
316}
317
318impl Ord for KeyStringOrOID {
319    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
320        match (self, other) {
321            (KeyStringOrOID::KeyString(s1), KeyStringOrOID::KeyString(s2)) => s1.cmp(s2),
322            (KeyStringOrOID::KeyString(_), KeyStringOrOID::OID(_)) => std::cmp::Ordering::Less,
323            (KeyStringOrOID::OID(_), KeyStringOrOID::KeyString(_)) => std::cmp::Ordering::Greater,
324            (KeyStringOrOID::OID(oid1), KeyStringOrOID::OID(oid2)) => {
325                let s1: String = oid1.into();
326                let s2: String = oid2.into();
327                s1.cmp(&s2)
328            }
329        }
330    }
331}
332
333impl std::fmt::Display for KeyStringOrOID {
334    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
335        match &self {
336            Self::KeyString(s) => {
337                std::fmt::Display::fmt(s, f)?;
338                Ok(())
339            }
340            Self::OID(oid) => {
341                let string_oid: String = oid.clone().into();
342                std::fmt::Display::fmt(&string_oid, f)?;
343                Ok(())
344            }
345        }
346    }
347}
348
349#[cfg(feature = "chumsky")]
350impl TryFrom<&str> for KeyStringOrOID {
351    type Error = ChumskyError;
352    fn try_from(value: &str) -> Result<Self, Self::Error> {
353        (keystring_or_oid_parser().then_ignore(chumsky::primitive::end()))
354            .parse(value)
355            .map_err(|e| ChumskyError {
356                description: "keystring or OID".to_string(),
357                source: value.to_string(),
358                errors: e,
359            })
360    }
361}
362
363#[cfg(feature = "chumsky")]
364impl TryFrom<String> for KeyStringOrOID {
365    type Error = ChumskyError;
366    fn try_from(value: String) -> Result<Self, Self::Error> {
367        (keystring_or_oid_parser().then_ignore(chumsky::primitive::end()))
368            .parse(value.to_owned())
369            .map_err(|e| ChumskyError {
370                description: "keystring or OID".to_string(),
371                source: value.to_string(),
372                errors: e,
373            })
374    }
375}
376
377#[cfg(feature = "chumsky")]
378impl TryFrom<&String> for KeyStringOrOID {
379    type Error = ChumskyError;
380    fn try_from(value: &String) -> Result<Self, Self::Error> {
381        (keystring_or_oid_parser().then_ignore(chumsky::primitive::end()))
382            .parse(value.to_owned())
383            .map_err(|e| ChumskyError {
384                description: "keystring or OID".to_string(),
385                source: value.to_string(),
386                errors: e,
387            })
388    }
389}
390
391#[cfg(feature = "chumsky")]
392impl std::str::FromStr for KeyStringOrOID {
393    type Err = ChumskyError;
394
395    fn from_str(s: &str) -> Result<Self, Self::Err> {
396        (keystring_or_oid_parser().then_ignore(chumsky::primitive::end()))
397            .parse(s.to_owned())
398            .map_err(|e| ChumskyError {
399                description: "keystring or OID".to_string(),
400                source: s.to_string(),
401                errors: e,
402            })
403    }
404}
405
406impl From<&KeyStringOrOID> for KeyStringOrOID {
407    fn from(value: &KeyStringOrOID) -> Self {
408        value.to_owned()
409    }
410}
411
412impl From<KeyString> for KeyStringOrOID {
413    fn from(value: KeyString) -> Self {
414        KeyStringOrOID::KeyString(value)
415    }
416}
417
418impl From<&KeyString> for KeyStringOrOID {
419    fn from(value: &KeyString) -> Self {
420        KeyStringOrOID::KeyString(value.to_owned())
421    }
422}
423
424impl From<ObjectIdentifier> for KeyStringOrOID {
425    fn from(value: ObjectIdentifier) -> Self {
426        KeyStringOrOID::OID(value)
427    }
428}
429
430impl From<&ObjectIdentifier> for KeyStringOrOID {
431    fn from(value: &ObjectIdentifier) -> Self {
432        KeyStringOrOID::OID(value.to_owned())
433    }
434}
435
436impl TryFrom<KeyStringOrOID> for ObjectIdentifier {
437    type Error = ();
438
439    fn try_from(value: KeyStringOrOID) -> Result<Self, Self::Error> {
440        match value {
441            KeyStringOrOID::OID(oid) => Ok(oid),
442            KeyStringOrOID::KeyString(_) => Err(()),
443        }
444    }
445}
446
447impl TryFrom<&KeyStringOrOID> for ObjectIdentifier {
448    type Error = ();
449
450    fn try_from(value: &KeyStringOrOID) -> Result<Self, Self::Error> {
451        match value {
452            KeyStringOrOID::OID(oid) => Ok(oid.to_owned()),
453            KeyStringOrOID::KeyString(_) => Err(()),
454        }
455    }
456}
457
458/// parses either a [KeyString] or an [ObjectIdentifier]
459#[cfg(feature = "chumsky")]
460pub fn keystring_or_oid_parser() -> impl Parser<char, KeyStringOrOID, Error = Simple<char>> {
461    keystring_parser()
462        .map(KeyStringOrOID::KeyString)
463        .or(oid_parser().map(KeyStringOrOID::OID))
464}
465
466/// in some locations LDAP allows OIDs with an optional length specifier
467/// to describe attribute types with a length limit
468#[derive(Clone, Educe)]
469#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
470#[educe(PartialEq, Eq, Hash)]
471pub struct OIDWithLength {
472    /// the [ObjectIdentifier]
473    #[educe(Hash(method = "hash_oid"))]
474    pub oid: ObjectIdentifier,
475    /// the optional maximum length of the value
476    pub length: Option<usize>,
477}
478
479impl From<OIDWithLength> for ObjectIdentifier {
480    fn from(value: OIDWithLength) -> Self {
481        value.oid
482    }
483}
484
485impl From<&OIDWithLength> for ObjectIdentifier {
486    fn from(value: &OIDWithLength) -> Self {
487        value.oid.to_owned()
488    }
489}
490
491impl std::fmt::Debug for OIDWithLength {
492    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
493        let string_oid: String = self.oid.clone().into();
494        f.debug_struct("OIDWithLength")
495            .field("oid", &string_oid)
496            .field("length", &self.length)
497            .finish()
498    }
499}
500
501/// a relative distinguished name is one of the components of a distinguished name
502/// usually a single pair of a keystring or an OID along with its attribute value
503/// but it can also be a plus sign separated string of several such pairs
504///
505/// <https://ldapwiki.com/wiki/Relative%20Distinguished%20Name>
506#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
507#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
508pub struct RelativeDistinguishedName {
509    /// the attributes of the RDN
510    #[cfg_attr(
511        feature = "serde",
512        serde(serialize_with = "serialize_rdn", deserialize_with = "deserialize_rdn")
513    )]
514    pub attributes: Vec<(KeyStringOrOID, Vec<u8>)>,
515}
516
517impl std::fmt::Display for RelativeDistinguishedName {
518    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
519        let mut first = true;
520        for (k, v) in &self.attributes {
521            if !first {
522                write!(f, "+")?;
523            } else {
524                first = false;
525            }
526            write!(f, "{}", k)?;
527            write!(f, "=")?;
528            if let Ok(s) = std::str::from_utf8(v) {
529                write!(f, "{}", s)?;
530            } else {
531                write!(f, "#{}", hex::encode(v))?;
532            }
533        }
534        Ok(())
535    }
536}
537
538#[cfg(feature = "chumsky")]
539impl TryFrom<&str> for RelativeDistinguishedName {
540    type Error = ChumskyError;
541
542    fn try_from(value: &str) -> Result<Self, Self::Error> {
543        (rdn_parser().then_ignore(chumsky::primitive::end()))
544            .parse(value)
545            .map_err(|e| ChumskyError {
546                description: "relative distinguished name".to_string(),
547                source: value.to_string(),
548                errors: e,
549            })
550    }
551}
552
553#[cfg(feature = "chumsky")]
554impl TryFrom<String> for RelativeDistinguishedName {
555    type Error = ChumskyError;
556
557    fn try_from(value: String) -> Result<Self, Self::Error> {
558        (rdn_parser().then_ignore(chumsky::primitive::end()))
559            .parse(value.to_owned())
560            .map_err(|e| ChumskyError {
561                description: "relative distinguished name".to_string(),
562                source: value.to_string(),
563                errors: e,
564            })
565    }
566}
567
568#[cfg(feature = "chumsky")]
569impl std::str::FromStr for RelativeDistinguishedName {
570    type Err = ChumskyError;
571
572    fn from_str(s: &str) -> Result<Self, Self::Err> {
573        (rdn_parser().then_ignore(chumsky::primitive::end()))
574            .parse(s)
575            .map_err(|e| ChumskyError {
576                description: "relative distinguished name".to_string(),
577                source: s.to_string(),
578                errors: e,
579            })
580    }
581}
582
583impl From<RelativeDistinguishedName> for String {
584    fn from(rdn: RelativeDistinguishedName) -> Self {
585        rdn.to_string()
586    }
587}
588
589/// serialize RDN attribute values as string if possible
590/// falling back to array of numbers of necessary
591#[cfg(feature = "serde")]
592pub fn serialize_rdn<S>(xs: &[(KeyStringOrOID, Vec<u8>)], s: S) -> Result<S::Ok, S::Error>
593where
594    S: Serializer,
595{
596    let mut seq = s.serialize_seq(Some(xs.len()))?;
597    for e @ (k, v) in xs.iter() {
598        if let Ok(s) = std::str::from_utf8(v) {
599            seq.serialize_element(&(k, s))?;
600        } else {
601            seq.serialize_element(e)?;
602        }
603    }
604    seq.end()
605}
606
607/// parses an RDN with attribute values being represented either as a string or an array of integers
608#[cfg(feature = "serde")]
609pub fn deserialize_rdn<'de, D>(d: D) -> Result<Vec<(KeyStringOrOID, Vec<u8>)>, D::Error>
610where
611    D: Deserializer<'de>,
612{
613    /// untagged union to allow deserializing attribute values as either string or bytes
614    #[derive(Deserialize)]
615    #[serde(untagged)]
616    enum StringOrBytes {
617        /// string attribute value
618        String(String),
619        /// bytes attribute value
620        Bytes(Vec<u8>),
621    }
622
623    /// visitor to deserialize RDNs in deserialize_rdn
624    struct RDNVisitor;
625
626    impl<'de> serde::de::Visitor<'de> for RDNVisitor {
627        type Value = Vec<(KeyStringOrOID, StringOrBytes)>;
628
629        fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
630            write!(formatter, "an array of tuples of attribute name and attribute value (either a string or a sequence of integers)")
631        }
632
633        fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
634        where
635            A: SeqAccess<'de>,
636        {
637            let mut result = Vec::new();
638            while let Some(e) = seq.next_element()? {
639                result.push(e);
640            }
641            Ok(result)
642        }
643    }
644
645    let parse_result = d.deserialize_seq(RDNVisitor)?;
646    let mut results = Vec::new();
647    for (ref k, ref v) in parse_result {
648        match v {
649            StringOrBytes::String(s) => {
650                results.push((k.to_owned(), s.as_bytes().to_vec()));
651            }
652            StringOrBytes::Bytes(b) => results.push((k.to_owned(), b.to_vec())),
653        }
654    }
655    Ok(results)
656}
657
658/// parses a series of hex-encoded bytes (always even number of hex digits)
659#[cfg(feature = "chumsky")]
660pub fn hex_byte_parser() -> impl Parser<char, u8, Error = Simple<char>> {
661    filter(|c: &char| c.is_ascii_hexdigit())
662        .repeated()
663        .exactly(2)
664        .collect::<String>()
665        .try_map(|ds, span| {
666            hex::decode(ds.as_bytes()).map_err(|e| Simple::custom(span, format!("{:?}", e)))
667        })
668        .map(|v: Vec<u8>| v.first().unwrap().to_owned())
669}
670
671/// parses a hex-encoded binary attribute value in an RDN
672#[cfg(feature = "chumsky")]
673pub fn rdn_attribute_binary_value_parser() -> impl Parser<char, Vec<u8>, Error = Simple<char>> {
674    just('#').ignore_then(hex_byte_parser().repeated())
675}
676
677/// parses a plain string attribute value in an RDN
678#[cfg(feature = "chumsky")]
679pub fn rdn_attribute_string_value_parser() -> impl Parser<char, Vec<u8>, Error = Simple<char>> {
680    none_of(",+\"\\<>;")
681        .or(just('\\').ignore_then(one_of(" ,+\"\\<>;")))
682        .or(just('\\').ignore_then(hex_byte_parser().map(|s| s as char)))
683        .repeated()
684        .collect::<String>()
685        .map(|s| s.as_bytes().to_vec())
686}
687
688/// parses either a binary or a plain attribute value in an RDN
689#[cfg(feature = "chumsky")]
690pub fn rdn_attribute_value_parser() -> impl Parser<char, Vec<u8>, Error = Simple<char>> {
691    rdn_attribute_binary_value_parser().or(rdn_attribute_string_value_parser())
692}
693
694/// parses a [RelativeDistinguishedName]
695#[cfg(feature = "chumsky")]
696pub fn rdn_parser() -> impl Parser<char, RelativeDistinguishedName, Error = Simple<char>> {
697    keystring_or_oid_parser()
698        .then(just('=').ignore_then(rdn_attribute_value_parser()))
699        .separated_by(just('+'))
700        .at_least(1)
701        .map(|attributes| RelativeDistinguishedName { attributes })
702}
703
704/// a distinguished name is a unique identifier for an entry within the LDAP tree,
705/// it is comprised of a comma-separated ordered list of [RelativeDistinguishedName]
706/// components
707///
708/// <https://ldapwiki.com/wiki/Distinguished%20Names>
709#[derive(Debug, PartialEq, Eq, Clone, Hash)]
710#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
711pub struct DistinguishedName {
712    /// the RDN components of the DN
713    pub rdns: Vec<RelativeDistinguishedName>,
714}
715
716impl DistinguishedName {
717    /// returns true if this is the empty DN
718    pub fn is_empty(&self) -> bool {
719        self.rdns.is_empty()
720    }
721    /// returns the DN for the parent object in the LDAP hierarchy unless this is
722    /// already the empty DN
723    pub fn parent(&self) -> Option<DistinguishedName> {
724        if self.is_empty() {
725            None
726        } else {
727            Some(DistinguishedName {
728                rdns: self.rdns.iter().skip(1).cloned().collect(),
729            })
730        }
731    }
732
733    /// checks if the current DN is an ancestor (parent, parent of parent,...)
734    /// of the given other DN
735    ///
736    /// it does return false if both DNs are identical
737    pub fn is_ancestor_of(&self, other: &DistinguishedName) -> bool {
738        let mut it = self.rdns.iter().rev();
739        let mut other_it = other.rdns.iter().rev();
740        loop {
741            let e = it.next();
742            let other_e = other_it.next();
743            match (e, other_e) {
744                (None, None) => {
745                    // both DNs are identical
746                    return false;
747                }
748                (Some(_), None) => {
749                    // self is longer, can not be an ancestor
750                    return false;
751                }
752                (None, Some(_)) => {
753                    // so far we have not gotten a false and self is longer,
754                    // so other must be an ancestor
755                    return true;
756                }
757                (Some(e), Some(other_e)) => {
758                    if e != other_e {
759                        // different RDNs in the same position mean self
760                        // can not be an ancestor of other (or vice versa)
761                        return false;
762                    }
763                    // identical RDNs in this position mean we can advance
764                    // the loop
765                }
766            }
767        }
768    }
769
770    /// add suffix DN to this DN (e.g. the base DN)
771    pub fn add_suffix(&self, other: &DistinguishedName) -> DistinguishedName {
772        DistinguishedName {
773            rdns: [self.rdns.to_vec(), other.rdns.to_vec()].concat(),
774        }
775    }
776
777    /// remove a suffix DN from this DN (e.g. the base DN)
778    pub fn strip_suffix(&self, other: &DistinguishedName) -> Option<DistinguishedName> {
779        if !other.is_ancestor_of(self) {
780            None
781        } else {
782            let self_len = self.rdns.len();
783            let other_len = other.rdns.len();
784            Some(DistinguishedName {
785                rdns: self.rdns.split_at(self_len - other_len).0.to_vec(),
786            })
787        }
788    }
789}
790
791#[cfg(feature = "chumsky")]
792impl TryFrom<&str> for DistinguishedName {
793    type Error = ChumskyError;
794
795    fn try_from(value: &str) -> Result<Self, Self::Error> {
796        (dn_parser().then_ignore(chumsky::primitive::end()))
797            .parse(value)
798            .map_err(|e| ChumskyError {
799                description: "distinguished name".to_string(),
800                source: value.to_string(),
801                errors: e,
802            })
803    }
804}
805
806#[cfg(feature = "chumsky")]
807impl TryFrom<String> for DistinguishedName {
808    type Error = ChumskyError;
809
810    fn try_from(value: String) -> Result<Self, Self::Error> {
811        (dn_parser().then_ignore(chumsky::primitive::end()))
812            .parse(value.to_owned())
813            .map_err(|e| ChumskyError {
814                description: "distinguished name".to_string(),
815                source: value.to_string(),
816                errors: e,
817            })
818    }
819}
820
821#[cfg(feature = "chumsky")]
822impl std::str::FromStr for DistinguishedName {
823    type Err = ChumskyError;
824
825    fn from_str(s: &str) -> Result<Self, Self::Err> {
826        (dn_parser().then_ignore(chumsky::primitive::end()))
827            .parse(s)
828            .map_err(|e| ChumskyError {
829                description: "distinguished name".to_string(),
830                source: s.to_string(),
831                errors: e,
832            })
833    }
834}
835
836impl From<DistinguishedName> for String {
837    fn from(dn: DistinguishedName) -> Self {
838        dn.to_string()
839    }
840}
841
842impl std::fmt::Display for DistinguishedName {
843    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
844        let mut first = true;
845        for rdn in &self.rdns {
846            if !first {
847                write!(f, ",")?;
848            } else {
849                first = false;
850            }
851            write!(f, "{}", rdn)?;
852        }
853        Ok(())
854    }
855}
856
857impl PartialOrd for DistinguishedName {
858    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
859        Some(self.cmp(other))
860    }
861}
862
863impl Ord for DistinguishedName {
864    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
865        self.rdns
866            .iter()
867            .rev()
868            .zip(other.rdns.iter().rev())
869            .map(|(a, b)| a.cmp(b))
870            .fold(std::cmp::Ordering::Equal, |acc, e| acc.then(e))
871            .then(self.rdns.len().cmp(&other.rdns.len()))
872    }
873}
874
875/// parses a [DistinguishedName]
876#[cfg(feature = "chumsky")]
877pub fn dn_parser() -> impl Parser<char, DistinguishedName, Error = Simple<char>> {
878    rdn_parser()
879        .separated_by(just(','))
880        .map(|rdns| DistinguishedName { rdns })
881}
882
883/// represents an object in the LDAP tree
884/// we would use ldap3::SearchEntry but then we would not be able to derive Diff
885/// easily
886#[derive(Debug, Clone, PartialEq, Eq)]
887#[cfg_attr(feature = "diff", derive(Diff))]
888#[cfg_attr(feature = "diff", diff(attr(#[derive(Debug)] #[allow(missing_docs)])))]
889#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
890pub struct LDAPEntry {
891    /// the DN of the entry
892    pub dn: String,
893    /// the textual attributes of the entry
894    pub attrs: HashMap<String, Vec<String>>,
895    /// the binary attributes of the entry
896    pub bin_attrs: HashMap<String, Vec<Vec<u8>>>,
897}
898
899impl LDAPEntry {
900    /// return the combined attributes from attrs and bin_attrs for use in e.g. the [ldap3::Ldap::add] method
901    pub fn combined_attrs(&self) -> Vec<(Vec<u8>, HashSet<Vec<u8>>)> {
902        let mut result: HashMap<Vec<u8>, HashSet<Vec<u8>>> = HashMap::new();
903        for (attr_name, attr_values) in &self.attrs {
904            let attr_name = attr_name.as_bytes().to_vec();
905            let attr_values = attr_values.iter().map(|x| x.as_bytes().to_vec()).collect();
906            if let Some(values) = result.get_mut(&attr_name) {
907                values.extend(attr_values);
908            } else {
909                result.insert(attr_name, attr_values);
910            }
911        }
912        for (attr_name, attr_values) in &self.bin_attrs {
913            let attr_name = attr_name.as_bytes().to_vec();
914            let attr_values = attr_values.iter().map(|x| x.to_vec()).collect();
915            if let Some(values) = result.get_mut(&attr_name) {
916                values.extend(attr_values);
917            } else {
918                result.insert(attr_name, attr_values);
919            }
920        }
921        result.into_iter().collect()
922    }
923}
924
925#[cfg(feature = "ldap3")]
926impl From<ldap3::SearchEntry> for LDAPEntry {
927    fn from(entry: ldap3::SearchEntry) -> Self {
928        Self {
929            dn: entry.dn,
930            attrs: entry.attrs,
931            bin_attrs: entry.bin_attrs,
932        }
933    }
934}
935
936#[cfg(feature = "ldap3")]
937impl From<LDAPEntry> for ldap3::SearchEntry {
938    fn from(entry: LDAPEntry) -> Self {
939        Self {
940            dn: entry.dn,
941            attrs: entry.attrs,
942            bin_attrs: entry.bin_attrs,
943        }
944    }
945}
946
947/// an operation to perform to turn one LDAP object into another.
948/// we purposefully only include operations here that operate without
949/// moving the object to a different DN
950#[derive(Debug, Clone, EnumAsInner)]
951#[cfg(feature = "ldap3")]
952pub enum LDAPOperation {
953    /// add a new entry
954    Add(LDAPEntry),
955    /// delete an existing entry
956    Delete {
957        /// the DN of the entry to delete
958        dn: String,
959    },
960    /// modify attributes of an existing entry
961    Modify {
962        /// the DN of the entry to modify
963        dn: String,
964        /// the modifications to textual attributes to perform
965        mods: Vec<ldap3::Mod<String>>,
966        /// the modifications to binary attributes to perform
967        bin_mods: Vec<ldap3::Mod<Vec<u8>>>,
968    },
969}
970
971#[cfg(feature = "ldap3")]
972impl LDAPOperation {
973    /// Used to order operations so parents are added first and children deleted first
974    #[cfg(feature = "chumsky")]
975    pub fn operation_apply_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
976        match (self, other) {
977            (
978                LDAPOperation::Add(entry1 @ LDAPEntry { .. }),
979                LDAPOperation::Add(entry2 @ LDAPEntry { .. }),
980            ) => {
981                let parsed_dn1: Result<DistinguishedName, _> =
982                    dn_parser().parse(entry1.dn.to_owned());
983                let parsed_dn2: Result<DistinguishedName, _> =
984                    dn_parser().parse(entry2.dn.to_owned());
985                if let (Ok(parsed_dn1), Ok(parsed_dn2)) = (parsed_dn1, parsed_dn2) {
986                    Some(parsed_dn1.cmp(&parsed_dn2))
987                } else {
988                    None
989                }
990            }
991            (op1 @ LDAPOperation::Delete { .. }, op2 @ LDAPOperation::Delete { .. }) => {
992                let parsed_dn1: Result<DistinguishedName, _> =
993                    dn_parser().parse(op1.as_delete().unwrap().to_owned());
994                let parsed_dn2: Result<DistinguishedName, _> =
995                    dn_parser().parse(op2.as_delete().unwrap().to_owned());
996                if let (Ok(parsed_dn1), Ok(parsed_dn2)) = (parsed_dn1, parsed_dn2) {
997                    Some(parsed_dn1.cmp(&parsed_dn2))
998                } else {
999                    None
1000                }
1001            }
1002            _ => None,
1003        }
1004    }
1005}
1006
1007#[cfg(test)]
1008mod test {
1009    use super::*;
1010    use pretty_assertions::assert_eq;
1011
1012    #[cfg(feature = "chumsky")]
1013    #[test]
1014    fn test_parse_oid() {
1015        assert!(oid_parser().parse("1.2.3.4").is_ok());
1016    }
1017
1018    #[cfg(feature = "chumsky")]
1019    #[test]
1020    fn test_parse_oid_value() {
1021        assert_eq!(
1022            oid_parser().parse("1.2.3.4"),
1023            Ok("1.2.3.4".to_string().try_into().unwrap())
1024        );
1025    }
1026
1027    #[cfg(feature = "chumsky")]
1028    #[test]
1029    fn test_dn_parser_empty_dn() {
1030        assert_eq!(
1031            dn_parser().parse(""),
1032            Ok(DistinguishedName { rdns: vec![] })
1033        )
1034    }
1035
1036    #[cfg(feature = "chumsky")]
1037    #[test]
1038    fn test_dn_parser_single_rdn_single_string_attribute() {
1039        assert_eq!(
1040            dn_parser().parse("cn=Foobar"),
1041            Ok(DistinguishedName {
1042                rdns: vec![RelativeDistinguishedName {
1043                    attributes: vec![(
1044                        KeyStringOrOID::KeyString(KeyString("cn".to_string())),
1045                        "Foobar".as_bytes().to_vec()
1046                    )]
1047                }]
1048            })
1049        )
1050    }
1051
1052    #[cfg(feature = "chumsky")]
1053    #[test]
1054    fn test_dn_parser_single_rdn_single_string_attribute_with_escaped_comma() {
1055        assert_eq!(
1056            dn_parser().parse("cn=Foo\\,bar"),
1057            Ok(DistinguishedName {
1058                rdns: vec![RelativeDistinguishedName {
1059                    attributes: vec![(
1060                        KeyStringOrOID::KeyString(KeyString("cn".to_string())),
1061                        "Foo,bar".as_bytes().to_vec()
1062                    )]
1063                }]
1064            })
1065        )
1066    }
1067
1068    #[cfg(feature = "chumsky")]
1069    #[test]
1070    fn test_dn_parser_single_rdn_single_binary_attribute() {
1071        assert_eq!(
1072            dn_parser().parse("cn=#466f6f626172"),
1073            Ok(DistinguishedName {
1074                rdns: vec![RelativeDistinguishedName {
1075                    attributes: vec![(
1076                        KeyStringOrOID::KeyString(KeyString("cn".to_string())),
1077                        "Foobar".as_bytes().to_vec()
1078                    )]
1079                }]
1080            })
1081        )
1082    }
1083
1084    #[cfg(feature = "chumsky")]
1085    #[test]
1086    fn test_dn_parser_single_rdn_multiple_string_attributes() {
1087        assert_eq!(
1088            dn_parser().parse("cn=Foo\\,bar+uid=foobar"),
1089            Ok(DistinguishedName {
1090                rdns: vec![RelativeDistinguishedName {
1091                    attributes: vec![
1092                        (
1093                            KeyStringOrOID::KeyString(KeyString("cn".to_string())),
1094                            "Foo,bar".as_bytes().to_vec()
1095                        ),
1096                        (
1097                            KeyStringOrOID::KeyString(KeyString("uid".to_string())),
1098                            "foobar".as_bytes().to_vec()
1099                        ),
1100                    ]
1101                }]
1102            })
1103        )
1104    }
1105
1106    #[cfg(feature = "chumsky")]
1107    #[test]
1108    fn test_dn_parser_multiple_rdns() {
1109        assert_eq!(
1110            dn_parser().parse("cn=Foo\\,bar,uid=foobar"),
1111            Ok(DistinguishedName {
1112                rdns: vec![
1113                    RelativeDistinguishedName {
1114                        attributes: vec![(
1115                            KeyStringOrOID::KeyString(KeyString("cn".to_string())),
1116                            "Foo,bar".as_bytes().to_vec()
1117                        )]
1118                    },
1119                    RelativeDistinguishedName {
1120                        attributes: vec![(
1121                            KeyStringOrOID::KeyString(KeyString("uid".to_string())),
1122                            "foobar".as_bytes().to_vec()
1123                        )]
1124                    },
1125                ]
1126            })
1127        )
1128    }
1129
1130    #[test]
1131    fn test_dn_cmp() {
1132        assert_eq!(
1133            DistinguishedName { rdns: vec![] }.cmp(&DistinguishedName {
1134                rdns: vec![RelativeDistinguishedName {
1135                    attributes: vec![(
1136                        KeyStringOrOID::KeyString(KeyString("cn".to_string())),
1137                        "Foo,bar".as_bytes().to_vec()
1138                    )]
1139                }]
1140            }),
1141            std::cmp::Ordering::Less
1142        )
1143    }
1144
1145    #[cfg(feature = "serde")]
1146    #[test]
1147    fn test_serialize_json_oid() -> Result<(), Box<dyn std::error::Error>> {
1148        let oid: ObjectIdentifier = "1.2.3.4".to_string().try_into().unwrap();
1149        let result = serde_json::to_string(&oid)?;
1150        assert_eq!(result, "\"1.2.3.4\"".to_string());
1151        Ok(())
1152    }
1153
1154    #[cfg(feature = "serde")]
1155    #[test]
1156    fn test_deserialize_json_oid() -> Result<(), Box<dyn std::error::Error>> {
1157        let expected: ObjectIdentifier = "1.2.3.4".to_string().try_into().unwrap();
1158        let result: ObjectIdentifier = serde_json::from_str("\"1.2.3.4\"")?;
1159        assert_eq!(result, expected);
1160        Ok(())
1161    }
1162
1163    #[cfg(feature = "serde")]
1164    #[test]
1165    fn test_serialize_json_keystring() -> Result<(), Box<dyn std::error::Error>> {
1166        let ks: KeyString = KeyString("foo".to_string());
1167        let result = serde_json::to_string(&ks)?;
1168        assert_eq!(result, "\"foo\"".to_string());
1169        Ok(())
1170    }
1171
1172    #[cfg(feature = "serde")]
1173    #[test]
1174    fn test_deserialize_json_keystring() -> Result<(), Box<dyn std::error::Error>> {
1175        let expected: KeyString = KeyString("foo".to_string());
1176        let result: KeyString = serde_json::from_str("\"foo\"")?;
1177        assert_eq!(result, expected);
1178        Ok(())
1179    }
1180
1181    #[cfg(feature = "serde")]
1182    #[test]
1183    fn test_serialize_json_keystring_or_oid_keystring() -> Result<(), Box<dyn std::error::Error>> {
1184        let ks: KeyStringOrOID = KeyStringOrOID::KeyString(KeyString("foo".to_string()));
1185        let result = serde_json::to_string(&ks)?;
1186        assert_eq!(result, "{\"key_string\":\"foo\"}".to_string());
1187        Ok(())
1188    }
1189
1190    #[cfg(feature = "serde")]
1191    #[test]
1192    fn test_deserialize_json_keystring_or_oid_keystring() -> Result<(), Box<dyn std::error::Error>>
1193    {
1194        let expected: KeyStringOrOID = KeyStringOrOID::KeyString(KeyString("foo".to_string()));
1195        let result: KeyStringOrOID = serde_json::from_str("{\"key_string\":\"foo\"}")?;
1196        assert_eq!(result, expected);
1197        Ok(())
1198    }
1199
1200    #[cfg(feature = "serde")]
1201    #[test]
1202    fn test_serialize_json_keystring_or_oid_oid() -> Result<(), Box<dyn std::error::Error>> {
1203        let ks: KeyStringOrOID = KeyStringOrOID::OID("1.2.3.4".to_string().try_into().unwrap());
1204        let result = serde_json::to_string(&ks)?;
1205        assert_eq!(result, "{\"oid\":\"1.2.3.4\"}".to_string());
1206        Ok(())
1207    }
1208
1209    #[cfg(feature = "serde")]
1210    #[test]
1211    fn test_deserialize_json_keystring_or_oid_oid() -> Result<(), Box<dyn std::error::Error>> {
1212        let expected: KeyStringOrOID =
1213            KeyStringOrOID::OID("1.2.3.4".to_string().try_into().unwrap());
1214        let result: KeyStringOrOID = serde_json::from_str("{\"oid\":\"1.2.3.4\"}")?;
1215        assert_eq!(result, expected);
1216        Ok(())
1217    }
1218
1219    #[cfg(feature = "serde")]
1220    #[test]
1221    fn test_serialize_json_rdn() -> Result<(), Box<dyn std::error::Error>> {
1222        let rdn: RelativeDistinguishedName = RelativeDistinguishedName {
1223            attributes: vec![(
1224                KeyStringOrOID::KeyString(KeyString("cn".to_string())),
1225                "Foobar".as_bytes().to_vec(),
1226            )],
1227        };
1228        let result = serde_json::to_string(&rdn)?;
1229        assert_eq!(
1230            result,
1231            "{\"attributes\":[[{\"key_string\":\"cn\"},\"Foobar\"]]}".to_string()
1232        );
1233        Ok(())
1234    }
1235
1236    #[cfg(feature = "serde")]
1237    #[test]
1238    fn test_deserialize_json_rdn_string() -> Result<(), Box<dyn std::error::Error>> {
1239        let expected: RelativeDistinguishedName = RelativeDistinguishedName {
1240            attributes: vec![(
1241                KeyStringOrOID::KeyString(KeyString("cn".to_string())),
1242                "Foobar".as_bytes().to_vec(),
1243            )],
1244        };
1245        let result: RelativeDistinguishedName =
1246            serde_json::from_str("{\"attributes\":[[{\"key_string\":\"cn\"},\"Foobar\"]]}")?;
1247        assert_eq!(result, expected);
1248        Ok(())
1249    }
1250
1251    #[cfg(feature = "serde")]
1252    #[test]
1253    fn test_deserialize_json_rdn_integers() -> Result<(), Box<dyn std::error::Error>> {
1254        let expected: RelativeDistinguishedName = RelativeDistinguishedName {
1255            attributes: vec![(
1256                KeyStringOrOID::KeyString(KeyString("cn".to_string())),
1257                "Foobar".as_bytes().to_vec(),
1258            )],
1259        };
1260        let result: RelativeDistinguishedName = serde_json::from_str(
1261            "{\"attributes\":[[{\"key_string\":\"cn\"},[70, 111, 111, 98, 97, 114]]]}",
1262        )?;
1263        assert_eq!(result, expected);
1264        Ok(())
1265    }
1266
1267    #[cfg(feature = "serde")]
1268    #[test]
1269    fn test_serialize_json_dn() -> Result<(), Box<dyn std::error::Error>> {
1270        let dn: DistinguishedName = DistinguishedName {
1271            rdns: vec![RelativeDistinguishedName {
1272                attributes: vec![
1273                    (
1274                        KeyStringOrOID::KeyString(KeyString("cn".to_string())),
1275                        "Foo,bar".as_bytes().to_vec(),
1276                    ),
1277                    (
1278                        KeyStringOrOID::KeyString(KeyString("uid".to_string())),
1279                        "foobar".as_bytes().to_vec(),
1280                    ),
1281                ],
1282            }],
1283        };
1284        let result = serde_json::to_string(&dn)?;
1285        assert_eq!(
1286            result,
1287            "{\"rdns\":[{\"attributes\":[[{\"key_string\":\"cn\"},\"Foo,bar\"],[{\"key_string\":\"uid\"},\"foobar\"]]}]}".to_string()
1288        );
1289        Ok(())
1290    }
1291
1292    #[cfg(feature = "serde")]
1293    #[test]
1294    fn test_deserialize_json_dn() -> Result<(), Box<dyn std::error::Error>> {
1295        let expected: DistinguishedName = DistinguishedName {
1296            rdns: vec![RelativeDistinguishedName {
1297                attributes: vec![
1298                    (
1299                        KeyStringOrOID::KeyString(KeyString("cn".to_string())),
1300                        "Foo,bar".as_bytes().to_vec(),
1301                    ),
1302                    (
1303                        KeyStringOrOID::KeyString(KeyString("uid".to_string())),
1304                        "foobar".as_bytes().to_vec(),
1305                    ),
1306                ],
1307            }],
1308        };
1309        let result : DistinguishedName = serde_json::from_str("{\"rdns\":[{\"attributes\":[[{\"key_string\":\"cn\"},\"Foo,bar\"],[{\"key_string\":\"uid\"},\"foobar\"]]}]}")?;
1310        assert_eq!(result, expected);
1311        Ok(())
1312    }
1313}