ldap-acis 0.2.1

LDAP Access Control Instructions (ACI) system built on acls-rs
Documentation
//! LDAP directory entries and attributes.

use std::collections::HashMap;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// An attribute in an LDAP entry.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LdapAttribute {
    pub name: String,
    pub values: Vec<String>,
}

impl LdapAttribute {
    pub fn new(name: impl Into<String>, values: Vec<String>) -> Self {
        Self {
            name: name.into(),
            values,
        }
    }

    pub fn single(name: impl Into<String>, value: impl Into<String>) -> Self {
        Self {
            name: name.into(),
            values: vec![value.into()],
        }
    }
}

/// An LDAP directory entry.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct LdapEntry {
    pub dn: String,
    pub object_classes: Vec<String>,
    pub attributes: HashMap<String, Vec<String>>,
}

impl LdapEntry {
    pub fn new(dn: impl Into<String>) -> Self {
        Self {
            dn: dn.into(),
            object_classes: Vec::new(),
            attributes: HashMap::new(),
        }
    }

    pub fn with_object_class(mut self, oc: impl Into<String>) -> Self {
        self.object_classes.push(oc.into().to_lowercase());
        self
    }

    pub fn with_attribute(mut self, name: impl Into<String>, values: Vec<String>) -> Self {
        self.attributes.insert(name.into().to_lowercase(), values);
        self
    }

    pub fn with_single_attr(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
        self.attributes
            .insert(name.into().to_lowercase(), vec![value.into()]);
        self
    }

    /// Get attribute values (case-insensitive per RFC 4512)
    pub fn get_attr(&self, name: &str) -> Option<&Vec<String>> {
        self.attributes.get(&name.to_lowercase())
    }

    /// Check if entry has an object class (case-insensitive per RFC 4512)
    pub fn has_object_class(&self, oc: &str) -> bool {
        self.object_classes
            .iter()
            .any(|c| c.eq_ignore_ascii_case(oc))
    }

    /// Check if DN matches a pattern (simple glob-style, case-insensitive per RFC 4514)
    pub fn dn_matches(&self, pattern: &str) -> bool {
        crate::dn::matches_dn_pattern(&self.dn, pattern)
    }

    /// Extract organizational unit from DN
    pub fn get_ou(&self) -> Option<String> {
        for part in self.dn.split(',') {
            if let Some(ou) = part.trim().strip_prefix("ou=") {
                return Some(ou.to_string());
            }
        }
        None
    }

    /// Extract uid from DN
    pub fn get_uid(&self) -> Option<String> {
        for part in self.dn.split(',') {
            if let Some(uid) = part.trim().strip_prefix("uid=") {
                return Some(uid.to_string());
            }
        }
        None
    }
}