use std::{
collections::{HashMap, HashSet},
convert::TryFrom,
hash::{Hash, Hasher},
};
use educe::Educe;
use oid::ObjectIdentifier;
use enum_as_inner::EnumAsInner;
#[cfg(feature = "chumsky")]
use chumsky::{prelude::*, text::digits};
#[cfg(feature = "chumsky")]
use itertools::Itertools;
#[cfg(feature = "chumsky")]
use ariadne::{Color, Fmt, Label, Report, ReportKind, Source};
#[cfg(feature = "serde")]
use serde::{de::SeqAccess, ser::SerializeSeq, Deserialize, Deserializer, Serialize, Serializer};
#[cfg(feature = "diff")]
use diff::Diff;
#[cfg(feature = "chumsky")]
#[derive(Debug)]
pub struct ChumskyError {
pub description: String,
pub source: String,
pub errors: Vec<chumsky::error::Simple<char>>,
}
#[cfg(feature = "chumsky")]
impl std::fmt::Display for ChumskyError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for e in &self.errors {
let msg = format!(
"While parsing {}: {}{}, expected {}",
self.description,
if e.found().is_some() {
"Unexpected token"
} else {
"Unexpected end of input"
},
if let Some(label) = e.label() {
format!(" while parsing {}", label)
} else {
String::new()
},
if e.expected().len() == 0 {
"end of input".to_string()
} else {
e.expected()
.map(|expected| match expected {
Some(expected) => expected.to_string(),
None => "end of input".to_string(),
})
.collect::<Vec<_>>()
.join(", ")
},
);
let report = Report::build(ReportKind::Error, e.span())
.with_code(3)
.with_message(msg)
.with_label(
Label::new(e.span())
.with_message(format!(
"Unexpected {}",
e.found()
.map(|c| format!("token {}", c.fg(Color::Red)))
.unwrap_or_else(|| "end of input".to_string())
))
.with_color(Color::Red),
);
let report = match e.reason() {
chumsky::error::SimpleReason::Unclosed { span, delimiter } => report.with_label(
Label::new(span.clone())
.with_message(format!(
"Unclosed delimiter {}",
delimiter.fg(Color::Yellow)
))
.with_color(Color::Yellow),
),
chumsky::error::SimpleReason::Unexpected => report,
chumsky::error::SimpleReason::Custom(msg) => report.with_label(
Label::new(e.span())
.with_message(format!("{}", msg.fg(Color::Yellow)))
.with_color(Color::Yellow),
),
};
let mut s: Vec<u8> = Vec::new();
report
.finish()
.write(Source::from(&self.source), &mut s)
.map_err(|_| <std::fmt::Error as std::default::Default>::default())?;
let s = std::str::from_utf8(&s).expect("Expected ariadne to generate valid UTF-8");
write!(f, "{}", s)?;
}
Ok(())
}
}
#[cfg(feature = "chumsky")]
impl std::error::Error for ChumskyError {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
None
}
}
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct RootDSE {
pub supported_ldap_version: String,
pub supported_controls: Vec<ObjectIdentifier>,
pub supported_extensions: Vec<ObjectIdentifier>,
pub supported_features: Vec<ObjectIdentifier>,
pub supported_sasl_mechanisms: Vec<String>,
pub config_context: String,
pub naming_contexts: Vec<String>,
pub subschema_subentry: String,
}
impl std::fmt::Debug for RootDSE {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
f.debug_struct("RootDSE")
.field("supported_ldap_version", &self.supported_ldap_version)
.field(
"supported_controls",
&self
.supported_controls
.iter()
.map(|x| x.into())
.collect::<Vec<String>>(),
)
.field(
"supported_extensions",
&self
.supported_extensions
.iter()
.map(|x| x.into())
.collect::<Vec<String>>(),
)
.field(
"supported_features",
&self
.supported_features
.iter()
.map(|x| x.into())
.collect::<Vec<String>>(),
)
.field("supported_sasl_mechanisms", &self.supported_sasl_mechanisms)
.field("config_context", &self.config_context)
.field("naming_contexts", &self.naming_contexts)
.field("subschema_subentry", &self.subschema_subentry)
.finish()
}
}
#[cfg(feature = "chumsky")]
pub fn oid_parser() -> impl Parser<char, ObjectIdentifier, Error = Simple<char>> {
digits(10).separated_by(just('.')).try_map(|x, span| {
x.into_iter()
.join(".")
.try_into()
.map_err(|e| Simple::custom(span, format!("{:?}", e)))
})
}
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct KeyString(pub String);
impl std::fmt::Display for KeyString {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
std::fmt::Display::fmt(&self.0, f)?;
Ok(())
}
}
impl KeyString {
pub fn describes_case_insensitive_match(&self) -> bool {
match self {
KeyString(s) if s == "objectIdentifierMatch" => true,
KeyString(s) if s == "caseIgnoreMatch" => true,
KeyString(s) if s == "caseIgnoreListMatch" => true,
KeyString(s) if s == "caseIgnoreIA5Match" => true,
KeyString(s) if s == "caseIgnoreListSubstringsMatch" => true,
KeyString(s) if s == "caseIgnoreSubstringsMatch" => true,
KeyString(s) if s == "caseIgnoreOrderingMatch" => true,
KeyString(s) if s == "caseIgnoreIA5SubstringsMatch" => true,
_ => false,
}
}
pub fn to_lowercase(&self) -> KeyString {
let KeyString(s) = self;
KeyString(s.to_lowercase())
}
}
impl TryFrom<KeyStringOrOID> for KeyString {
type Error = ();
fn try_from(value: KeyStringOrOID) -> Result<Self, Self::Error> {
match value {
KeyStringOrOID::KeyString(ks) => Ok(ks),
KeyStringOrOID::OID(_) => Err(()),
}
}
}
impl TryFrom<&KeyStringOrOID> for KeyString {
type Error = ();
fn try_from(value: &KeyStringOrOID) -> Result<Self, Self::Error> {
match value {
KeyStringOrOID::KeyString(ks) => Ok(ks.to_owned()),
KeyStringOrOID::OID(_) => Err(()),
}
}
}
#[cfg(feature = "chumsky")]
pub fn keystring_parser() -> impl Parser<char, KeyString, Error = Simple<char>> {
filter(|c: &char| c.is_ascii_alphabetic())
.chain(filter(|c: &char| c.is_ascii_alphanumeric() || *c == '-' || *c == ';').repeated())
.collect::<String>()
.map(KeyString)
}
#[cfg(feature = "chumsky")]
pub fn quoted_keystring_parser() -> impl Parser<char, KeyString, Error = Simple<char>> {
keystring_parser().delimited_by(just('\''), just('\''))
}
pub fn hash_oid<H: Hasher>(s: &ObjectIdentifier, state: &mut H) {
Hash::hash(&format!("{s:?}"), state);
}
#[derive(Clone, Debug, EnumAsInner, Educe)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[educe(PartialEq, Eq, Hash)]
pub enum KeyStringOrOID {
#[cfg_attr(feature = "serde", serde(rename = "key_string"))]
KeyString(KeyString),
#[cfg_attr(feature = "serde", serde(rename = "oid"))]
OID(#[educe(Hash(method = "hash_oid"))] ObjectIdentifier),
}
impl PartialOrd for KeyStringOrOID {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for KeyStringOrOID {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
match (self, other) {
(KeyStringOrOID::KeyString(s1), KeyStringOrOID::KeyString(s2)) => s1.cmp(s2),
(KeyStringOrOID::KeyString(_), KeyStringOrOID::OID(_)) => std::cmp::Ordering::Less,
(KeyStringOrOID::OID(_), KeyStringOrOID::KeyString(_)) => std::cmp::Ordering::Greater,
(KeyStringOrOID::OID(oid1), KeyStringOrOID::OID(oid2)) => {
let s1: String = oid1.into();
let s2: String = oid2.into();
s1.cmp(&s2)
}
}
}
}
impl std::fmt::Display for KeyStringOrOID {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match &self {
Self::KeyString(s) => {
std::fmt::Display::fmt(s, f)?;
Ok(())
}
Self::OID(oid) => {
let string_oid: String = oid.clone().into();
std::fmt::Display::fmt(&string_oid, f)?;
Ok(())
}
}
}
}
#[cfg(feature = "chumsky")]
impl TryFrom<&str> for KeyStringOrOID {
type Error = ChumskyError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
(keystring_or_oid_parser().then_ignore(chumsky::primitive::end()))
.parse(value)
.map_err(|e| ChumskyError {
description: "keystring or OID".to_string(),
source: value.to_string(),
errors: e,
})
}
}
#[cfg(feature = "chumsky")]
impl TryFrom<String> for KeyStringOrOID {
type Error = ChumskyError;
fn try_from(value: String) -> Result<Self, Self::Error> {
(keystring_or_oid_parser().then_ignore(chumsky::primitive::end()))
.parse(value.to_owned())
.map_err(|e| ChumskyError {
description: "keystring or OID".to_string(),
source: value.to_string(),
errors: e,
})
}
}
#[cfg(feature = "chumsky")]
impl TryFrom<&String> for KeyStringOrOID {
type Error = ChumskyError;
fn try_from(value: &String) -> Result<Self, Self::Error> {
(keystring_or_oid_parser().then_ignore(chumsky::primitive::end()))
.parse(value.to_owned())
.map_err(|e| ChumskyError {
description: "keystring or OID".to_string(),
source: value.to_string(),
errors: e,
})
}
}
#[cfg(feature = "chumsky")]
impl std::str::FromStr for KeyStringOrOID {
type Err = ChumskyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
(keystring_or_oid_parser().then_ignore(chumsky::primitive::end()))
.parse(s.to_owned())
.map_err(|e| ChumskyError {
description: "keystring or OID".to_string(),
source: s.to_string(),
errors: e,
})
}
}
impl From<&KeyStringOrOID> for KeyStringOrOID {
fn from(value: &KeyStringOrOID) -> Self {
value.to_owned()
}
}
impl From<KeyString> for KeyStringOrOID {
fn from(value: KeyString) -> Self {
KeyStringOrOID::KeyString(value)
}
}
impl From<&KeyString> for KeyStringOrOID {
fn from(value: &KeyString) -> Self {
KeyStringOrOID::KeyString(value.to_owned())
}
}
impl From<ObjectIdentifier> for KeyStringOrOID {
fn from(value: ObjectIdentifier) -> Self {
KeyStringOrOID::OID(value)
}
}
impl From<&ObjectIdentifier> for KeyStringOrOID {
fn from(value: &ObjectIdentifier) -> Self {
KeyStringOrOID::OID(value.to_owned())
}
}
impl TryFrom<KeyStringOrOID> for ObjectIdentifier {
type Error = ();
fn try_from(value: KeyStringOrOID) -> Result<Self, Self::Error> {
match value {
KeyStringOrOID::OID(oid) => Ok(oid),
KeyStringOrOID::KeyString(_) => Err(()),
}
}
}
impl TryFrom<&KeyStringOrOID> for ObjectIdentifier {
type Error = ();
fn try_from(value: &KeyStringOrOID) -> Result<Self, Self::Error> {
match value {
KeyStringOrOID::OID(oid) => Ok(oid.to_owned()),
KeyStringOrOID::KeyString(_) => Err(()),
}
}
}
#[cfg(feature = "chumsky")]
pub fn keystring_or_oid_parser() -> impl Parser<char, KeyStringOrOID, Error = Simple<char>> {
keystring_parser()
.map(KeyStringOrOID::KeyString)
.or(oid_parser().map(KeyStringOrOID::OID))
}
#[derive(Clone, Educe)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[educe(PartialEq, Eq, Hash)]
pub struct OIDWithLength {
#[educe(Hash(method = "hash_oid"))]
pub oid: ObjectIdentifier,
pub length: Option<usize>,
}
impl From<OIDWithLength> for ObjectIdentifier {
fn from(value: OIDWithLength) -> Self {
value.oid
}
}
impl From<&OIDWithLength> for ObjectIdentifier {
fn from(value: &OIDWithLength) -> Self {
value.oid.to_owned()
}
}
impl std::fmt::Debug for OIDWithLength {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let string_oid: String = self.oid.clone().into();
f.debug_struct("OIDWithLength")
.field("oid", &string_oid)
.field("length", &self.length)
.finish()
}
}
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct RelativeDistinguishedName {
#[cfg_attr(
feature = "serde",
serde(serialize_with = "serialize_rdn", deserialize_with = "deserialize_rdn")
)]
pub attributes: Vec<(KeyStringOrOID, Vec<u8>)>,
}
impl std::fmt::Display for RelativeDistinguishedName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let mut first = true;
for (k, v) in &self.attributes {
if !first {
write!(f, "+")?;
} else {
first = false;
}
write!(f, "{}", k)?;
write!(f, "=")?;
if let Ok(s) = std::str::from_utf8(v) {
write!(f, "{}", s)?;
} else {
write!(f, "#{}", hex::encode(v))?;
}
}
Ok(())
}
}
#[cfg(feature = "chumsky")]
impl TryFrom<&str> for RelativeDistinguishedName {
type Error = ChumskyError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
(rdn_parser().then_ignore(chumsky::primitive::end()))
.parse(value)
.map_err(|e| ChumskyError {
description: "relative distinguished name".to_string(),
source: value.to_string(),
errors: e,
})
}
}
#[cfg(feature = "chumsky")]
impl TryFrom<String> for RelativeDistinguishedName {
type Error = ChumskyError;
fn try_from(value: String) -> Result<Self, Self::Error> {
(rdn_parser().then_ignore(chumsky::primitive::end()))
.parse(value.to_owned())
.map_err(|e| ChumskyError {
description: "relative distinguished name".to_string(),
source: value.to_string(),
errors: e,
})
}
}
#[cfg(feature = "chumsky")]
impl std::str::FromStr for RelativeDistinguishedName {
type Err = ChumskyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
(rdn_parser().then_ignore(chumsky::primitive::end()))
.parse(s)
.map_err(|e| ChumskyError {
description: "relative distinguished name".to_string(),
source: s.to_string(),
errors: e,
})
}
}
impl From<RelativeDistinguishedName> for String {
fn from(rdn: RelativeDistinguishedName) -> Self {
rdn.to_string()
}
}
#[cfg(feature = "serde")]
pub fn serialize_rdn<S>(xs: &[(KeyStringOrOID, Vec<u8>)], s: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut seq = s.serialize_seq(Some(xs.len()))?;
for e @ (k, v) in xs.iter() {
if let Ok(s) = std::str::from_utf8(v) {
seq.serialize_element(&(k, s))?;
} else {
seq.serialize_element(e)?;
}
}
seq.end()
}
#[cfg(feature = "serde")]
pub fn deserialize_rdn<'de, D>(d: D) -> Result<Vec<(KeyStringOrOID, Vec<u8>)>, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(untagged)]
enum StringOrBytes {
String(String),
Bytes(Vec<u8>),
}
struct RDNVisitor;
impl<'de> serde::de::Visitor<'de> for RDNVisitor {
type Value = Vec<(KeyStringOrOID, StringOrBytes)>;
fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(formatter, "an array of tuples of attribute name and attribute value (either a string or a sequence of integers)")
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where
A: SeqAccess<'de>,
{
let mut result = Vec::new();
while let Some(e) = seq.next_element()? {
result.push(e);
}
Ok(result)
}
}
let parse_result = d.deserialize_seq(RDNVisitor)?;
let mut results = Vec::new();
for (ref k, ref v) in parse_result {
match v {
StringOrBytes::String(s) => {
results.push((k.to_owned(), s.as_bytes().to_vec()));
}
StringOrBytes::Bytes(b) => results.push((k.to_owned(), b.to_vec())),
}
}
Ok(results)
}
#[cfg(feature = "chumsky")]
pub fn hex_byte_parser() -> impl Parser<char, u8, Error = Simple<char>> {
filter(|c: &char| c.is_ascii_hexdigit())
.repeated()
.exactly(2)
.collect::<String>()
.try_map(|ds, span| {
hex::decode(ds.as_bytes()).map_err(|e| Simple::custom(span, format!("{:?}", e)))
})
.map(|v: Vec<u8>| v.first().unwrap().to_owned())
}
#[cfg(feature = "chumsky")]
pub fn rdn_attribute_binary_value_parser() -> impl Parser<char, Vec<u8>, Error = Simple<char>> {
just('#').ignore_then(hex_byte_parser().repeated())
}
#[cfg(feature = "chumsky")]
pub fn rdn_attribute_string_value_parser() -> impl Parser<char, Vec<u8>, Error = Simple<char>> {
none_of(",+\"\\<>;")
.or(just('\\').ignore_then(one_of(" ,+\"\\<>;")))
.or(just('\\').ignore_then(hex_byte_parser().map(|s| s as char)))
.repeated()
.collect::<String>()
.map(|s| s.as_bytes().to_vec())
}
#[cfg(feature = "chumsky")]
pub fn rdn_attribute_value_parser() -> impl Parser<char, Vec<u8>, Error = Simple<char>> {
rdn_attribute_binary_value_parser().or(rdn_attribute_string_value_parser())
}
#[cfg(feature = "chumsky")]
pub fn rdn_parser() -> impl Parser<char, RelativeDistinguishedName, Error = Simple<char>> {
keystring_or_oid_parser()
.then(just('=').ignore_then(rdn_attribute_value_parser()))
.separated_by(just('+'))
.at_least(1)
.map(|attributes| RelativeDistinguishedName { attributes })
}
#[derive(Debug, PartialEq, Eq, Clone, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DistinguishedName {
pub rdns: Vec<RelativeDistinguishedName>,
}
impl DistinguishedName {
pub fn is_empty(&self) -> bool {
self.rdns.is_empty()
}
pub fn parent(&self) -> Option<DistinguishedName> {
if self.is_empty() {
None
} else {
Some(DistinguishedName {
rdns: self.rdns.iter().skip(1).cloned().collect(),
})
}
}
pub fn is_ancestor_of(&self, other: &DistinguishedName) -> bool {
let mut it = self.rdns.iter().rev();
let mut other_it = other.rdns.iter().rev();
loop {
let e = it.next();
let other_e = other_it.next();
match (e, other_e) {
(None, None) => {
return false;
}
(Some(_), None) => {
return false;
}
(None, Some(_)) => {
return true;
}
(Some(e), Some(other_e)) => {
if e != other_e {
return false;
}
}
}
}
}
pub fn add_suffix(&self, other: &DistinguishedName) -> DistinguishedName {
DistinguishedName {
rdns: [self.rdns.to_vec(), other.rdns.to_vec()].concat(),
}
}
pub fn strip_suffix(&self, other: &DistinguishedName) -> Option<DistinguishedName> {
if !other.is_ancestor_of(self) {
None
} else {
let self_len = self.rdns.len();
let other_len = other.rdns.len();
Some(DistinguishedName {
rdns: self.rdns.split_at(self_len - other_len).0.to_vec(),
})
}
}
}
#[cfg(feature = "chumsky")]
impl TryFrom<&str> for DistinguishedName {
type Error = ChumskyError;
fn try_from(value: &str) -> Result<Self, Self::Error> {
(dn_parser().then_ignore(chumsky::primitive::end()))
.parse(value)
.map_err(|e| ChumskyError {
description: "distinguished name".to_string(),
source: value.to_string(),
errors: e,
})
}
}
#[cfg(feature = "chumsky")]
impl TryFrom<String> for DistinguishedName {
type Error = ChumskyError;
fn try_from(value: String) -> Result<Self, Self::Error> {
(dn_parser().then_ignore(chumsky::primitive::end()))
.parse(value.to_owned())
.map_err(|e| ChumskyError {
description: "distinguished name".to_string(),
source: value.to_string(),
errors: e,
})
}
}
#[cfg(feature = "chumsky")]
impl std::str::FromStr for DistinguishedName {
type Err = ChumskyError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
(dn_parser().then_ignore(chumsky::primitive::end()))
.parse(s)
.map_err(|e| ChumskyError {
description: "distinguished name".to_string(),
source: s.to_string(),
errors: e,
})
}
}
impl From<DistinguishedName> for String {
fn from(dn: DistinguishedName) -> Self {
dn.to_string()
}
}
impl std::fmt::Display for DistinguishedName {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
let mut first = true;
for rdn in &self.rdns {
if !first {
write!(f, ",")?;
} else {
first = false;
}
write!(f, "{}", rdn)?;
}
Ok(())
}
}
impl PartialOrd for DistinguishedName {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for DistinguishedName {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.rdns
.iter()
.rev()
.zip(other.rdns.iter().rev())
.map(|(a, b)| a.cmp(b))
.fold(std::cmp::Ordering::Equal, |acc, e| acc.then(e))
.then(self.rdns.len().cmp(&other.rdns.len()))
}
}
#[cfg(feature = "chumsky")]
pub fn dn_parser() -> impl Parser<char, DistinguishedName, Error = Simple<char>> {
rdn_parser()
.separated_by(just(','))
.map(|rdns| DistinguishedName { rdns })
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[cfg_attr(feature = "diff", derive(Diff))]
#[cfg_attr(feature = "diff", diff(attr(#[derive(Debug, Serialize, Deserialize)] #[allow(missing_docs)])))]
pub struct LDAPEntry {
pub dn: String,
pub attrs: HashMap<String, Vec<String>>,
pub bin_attrs: HashMap<String, Vec<Vec<u8>>>,
}
impl LDAPEntry {
pub fn combined_attrs(&self) -> Vec<(Vec<u8>, HashSet<Vec<u8>>)> {
let mut result: HashMap<Vec<u8>, HashSet<Vec<u8>>> = HashMap::new();
for (attr_name, attr_values) in &self.attrs {
let attr_name = attr_name.as_bytes().to_vec();
let attr_values = attr_values.iter().map(|x| x.as_bytes().to_vec()).collect();
if let Some(values) = result.get_mut(&attr_name) {
values.extend(attr_values);
} else {
result.insert(attr_name, attr_values);
}
}
for (attr_name, attr_values) in &self.bin_attrs {
let attr_name = attr_name.as_bytes().to_vec();
let attr_values = attr_values.iter().map(|x| x.to_vec()).collect();
if let Some(values) = result.get_mut(&attr_name) {
values.extend(attr_values);
} else {
result.insert(attr_name, attr_values);
}
}
result.into_iter().collect()
}
}
#[cfg(feature = "ldap3")]
impl From<ldap3::SearchEntry> for LDAPEntry {
fn from(entry: ldap3::SearchEntry) -> Self {
Self {
dn: entry.dn,
attrs: entry.attrs,
bin_attrs: entry.bin_attrs,
}
}
}
#[cfg(feature = "ldap3")]
impl From<LDAPEntry> for ldap3::SearchEntry {
fn from(entry: LDAPEntry) -> Self {
Self {
dn: entry.dn,
attrs: entry.attrs,
bin_attrs: entry.bin_attrs,
}
}
}
#[derive(Debug, Clone, EnumAsInner)]
pub enum LDAPOperation {
Add(LDAPEntry),
Delete {
dn: String,
},
Modify {
dn: String,
mods: Vec<ldap3::Mod<String>>,
bin_mods: Vec<ldap3::Mod<Vec<u8>>>,
},
}
impl LDAPOperation {
#[cfg(feature = "chumsky")]
pub fn operation_apply_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
match (self, other) {
(
LDAPOperation::Add(entry1 @ LDAPEntry { .. }),
LDAPOperation::Add(entry2 @ LDAPEntry { .. }),
) => {
let parsed_dn1: Result<DistinguishedName, _> =
dn_parser().parse(entry1.dn.to_owned());
let parsed_dn2: Result<DistinguishedName, _> =
dn_parser().parse(entry2.dn.to_owned());
if let (Ok(parsed_dn1), Ok(parsed_dn2)) = (parsed_dn1, parsed_dn2) {
Some(parsed_dn1.cmp(&parsed_dn2))
} else {
None
}
}
(op1 @ LDAPOperation::Delete { .. }, op2 @ LDAPOperation::Delete { .. }) => {
let parsed_dn1: Result<DistinguishedName, _> =
dn_parser().parse(op1.as_delete().unwrap().to_owned());
let parsed_dn2: Result<DistinguishedName, _> =
dn_parser().parse(op2.as_delete().unwrap().to_owned());
if let (Ok(parsed_dn1), Ok(parsed_dn2)) = (parsed_dn1, parsed_dn2) {
Some(parsed_dn1.cmp(&parsed_dn2))
} else {
None
}
}
_ => None,
}
}
}
#[cfg(test)]
mod test {
use super::*;
use pretty_assertions::assert_eq;
#[test]
fn test_parse_oid() {
assert!(oid_parser().parse("1.2.3.4").is_ok());
}
#[test]
fn test_parse_oid_value() {
assert_eq!(
oid_parser().parse("1.2.3.4"),
Ok("1.2.3.4".to_string().try_into().unwrap())
);
}
#[test]
fn test_dn_parser_empty_dn() {
assert_eq!(
dn_parser().parse(""),
Ok(DistinguishedName { rdns: vec![] })
)
}
#[test]
fn test_dn_parser_single_rdn_single_string_attribute() {
assert_eq!(
dn_parser().parse("cn=Foobar"),
Ok(DistinguishedName {
rdns: vec![RelativeDistinguishedName {
attributes: vec![(
KeyStringOrOID::KeyString(KeyString("cn".to_string())),
"Foobar".as_bytes().to_vec()
)]
}]
})
)
}
#[test]
fn test_dn_parser_single_rdn_single_string_attribute_with_escaped_comma() {
assert_eq!(
dn_parser().parse("cn=Foo\\,bar"),
Ok(DistinguishedName {
rdns: vec![RelativeDistinguishedName {
attributes: vec![(
KeyStringOrOID::KeyString(KeyString("cn".to_string())),
"Foo,bar".as_bytes().to_vec()
)]
}]
})
)
}
#[test]
fn test_dn_parser_single_rdn_single_binary_attribute() {
assert_eq!(
dn_parser().parse("cn=#466f6f626172"),
Ok(DistinguishedName {
rdns: vec![RelativeDistinguishedName {
attributes: vec![(
KeyStringOrOID::KeyString(KeyString("cn".to_string())),
"Foobar".as_bytes().to_vec()
)]
}]
})
)
}
#[test]
fn test_dn_parser_single_rdn_multiple_string_attributes() {
assert_eq!(
dn_parser().parse("cn=Foo\\,bar+uid=foobar"),
Ok(DistinguishedName {
rdns: vec![RelativeDistinguishedName {
attributes: vec![
(
KeyStringOrOID::KeyString(KeyString("cn".to_string())),
"Foo,bar".as_bytes().to_vec()
),
(
KeyStringOrOID::KeyString(KeyString("uid".to_string())),
"foobar".as_bytes().to_vec()
),
]
}]
})
)
}
#[test]
fn test_dn_parser_multiple_rdns() {
assert_eq!(
dn_parser().parse("cn=Foo\\,bar,uid=foobar"),
Ok(DistinguishedName {
rdns: vec![
RelativeDistinguishedName {
attributes: vec![(
KeyStringOrOID::KeyString(KeyString("cn".to_string())),
"Foo,bar".as_bytes().to_vec()
)]
},
RelativeDistinguishedName {
attributes: vec![(
KeyStringOrOID::KeyString(KeyString("uid".to_string())),
"foobar".as_bytes().to_vec()
)]
},
]
})
)
}
#[test]
fn test_dn_cmp() {
assert_eq!(
DistinguishedName { rdns: vec![] }.cmp(&DistinguishedName {
rdns: vec![RelativeDistinguishedName {
attributes: vec![(
KeyStringOrOID::KeyString(KeyString("cn".to_string())),
"Foo,bar".as_bytes().to_vec()
)]
}]
}),
std::cmp::Ordering::Less
)
}
#[test]
fn test_serialize_json_oid() -> Result<(), Box<dyn std::error::Error>> {
let oid: ObjectIdentifier = "1.2.3.4".to_string().try_into().unwrap();
let result = serde_json::to_string(&oid)?;
assert_eq!(result, "\"1.2.3.4\"".to_string());
Ok(())
}
#[test]
fn test_deserialize_json_oid() -> Result<(), Box<dyn std::error::Error>> {
let expected: ObjectIdentifier = "1.2.3.4".to_string().try_into().unwrap();
let result: ObjectIdentifier = serde_json::from_str("\"1.2.3.4\"")?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_serialize_json_keystring() -> Result<(), Box<dyn std::error::Error>> {
let ks: KeyString = KeyString("foo".to_string());
let result = serde_json::to_string(&ks)?;
assert_eq!(result, "\"foo\"".to_string());
Ok(())
}
#[test]
fn test_deserialize_json_keystring() -> Result<(), Box<dyn std::error::Error>> {
let expected: KeyString = KeyString("foo".to_string());
let result: KeyString = serde_json::from_str("\"foo\"")?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_serialize_json_keystring_or_oid_keystring() -> Result<(), Box<dyn std::error::Error>> {
let ks: KeyStringOrOID = KeyStringOrOID::KeyString(KeyString("foo".to_string()));
let result = serde_json::to_string(&ks)?;
assert_eq!(result, "{\"key_string\":\"foo\"}".to_string());
Ok(())
}
#[test]
fn test_deserialize_json_keystring_or_oid_keystring() -> Result<(), Box<dyn std::error::Error>>
{
let expected: KeyStringOrOID = KeyStringOrOID::KeyString(KeyString("foo".to_string()));
let result: KeyStringOrOID = serde_json::from_str("{\"key_string\":\"foo\"}")?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_serialize_json_keystring_or_oid_oid() -> Result<(), Box<dyn std::error::Error>> {
let ks: KeyStringOrOID = KeyStringOrOID::OID("1.2.3.4".to_string().try_into().unwrap());
let result = serde_json::to_string(&ks)?;
assert_eq!(result, "{\"oid\":\"1.2.3.4\"}".to_string());
Ok(())
}
#[test]
fn test_deserialize_json_keystring_or_oid_oid() -> Result<(), Box<dyn std::error::Error>> {
let expected: KeyStringOrOID =
KeyStringOrOID::OID("1.2.3.4".to_string().try_into().unwrap());
let result: KeyStringOrOID = serde_json::from_str("{\"oid\":\"1.2.3.4\"}")?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_serialize_json_rdn() -> Result<(), Box<dyn std::error::Error>> {
let rdn: RelativeDistinguishedName = RelativeDistinguishedName {
attributes: vec![(
KeyStringOrOID::KeyString(KeyString("cn".to_string())),
"Foobar".as_bytes().to_vec(),
)],
};
let result = serde_json::to_string(&rdn)?;
assert_eq!(
result,
"{\"attributes\":[[{\"key_string\":\"cn\"},\"Foobar\"]]}".to_string()
);
Ok(())
}
#[test]
fn test_deserialize_json_rdn_string() -> Result<(), Box<dyn std::error::Error>> {
let expected: RelativeDistinguishedName = RelativeDistinguishedName {
attributes: vec![(
KeyStringOrOID::KeyString(KeyString("cn".to_string())),
"Foobar".as_bytes().to_vec(),
)],
};
let result: RelativeDistinguishedName =
serde_json::from_str("{\"attributes\":[[{\"key_string\":\"cn\"},\"Foobar\"]]}")?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_deserialize_json_rdn_integers() -> Result<(), Box<dyn std::error::Error>> {
let expected: RelativeDistinguishedName = RelativeDistinguishedName {
attributes: vec![(
KeyStringOrOID::KeyString(KeyString("cn".to_string())),
"Foobar".as_bytes().to_vec(),
)],
};
let result: RelativeDistinguishedName = serde_json::from_str(
"{\"attributes\":[[{\"key_string\":\"cn\"},[70, 111, 111, 98, 97, 114]]]}",
)?;
assert_eq!(result, expected);
Ok(())
}
#[test]
fn test_serialize_json_dn() -> Result<(), Box<dyn std::error::Error>> {
let dn: DistinguishedName = DistinguishedName {
rdns: vec![RelativeDistinguishedName {
attributes: vec![
(
KeyStringOrOID::KeyString(KeyString("cn".to_string())),
"Foo,bar".as_bytes().to_vec(),
),
(
KeyStringOrOID::KeyString(KeyString("uid".to_string())),
"foobar".as_bytes().to_vec(),
),
],
}],
};
let result = serde_json::to_string(&dn)?;
assert_eq!(
result,
"{\"rdns\":[{\"attributes\":[[{\"key_string\":\"cn\"},\"Foo,bar\"],[{\"key_string\":\"uid\"},\"foobar\"]]}]}".to_string()
);
Ok(())
}
#[test]
fn test_deserialize_json_dn() -> Result<(), Box<dyn std::error::Error>> {
let expected: DistinguishedName = DistinguishedName {
rdns: vec![RelativeDistinguishedName {
attributes: vec![
(
KeyStringOrOID::KeyString(KeyString("cn".to_string())),
"Foo,bar".as_bytes().to_vec(),
),
(
KeyStringOrOID::KeyString(KeyString("uid".to_string())),
"foobar".as_bytes().to_vec(),
),
],
}],
};
let result : DistinguishedName = serde_json::from_str("{\"rdns\":[{\"attributes\":[[{\"key_string\":\"cn\"},\"Foo,bar\"],[{\"key_string\":\"uid\"},\"foobar\"]]}]}")?;
assert_eq!(result, expected);
Ok(())
}
}