ldap-acis 0.1.1

LDAP Access Control Instructions (ACI) system built on acls-rs
Documentation
//! The core ACI struct and its accessor/matching methods.

use super::{AttributeSet, BindRule, DnScope, Scope, TargetAttrFilter, TargetFilter};
use crate::entry::LdapEntry;
use crate::operation::{LdapOperation, LdapPermission, OperationType};
use acls_rs::prelude::*;

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

/// An LDAP Access Control Instruction.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub struct Aci {
    pub(crate) name: String,
    pub(crate) target_dn: Option<String>,
    pub(crate) scope: Scope,
    pub(crate) target_filter: TargetFilter,
    pub(crate) target_attributes: Vec<String>,
    pub(crate) target_attributes_excluded: bool,
    pub(crate) target_attr_filters: Vec<TargetAttrFilter>,
    pub(crate) target_from: Option<String>,
    pub(crate) target_to: Option<String>,
    pub(crate) permissions: Vec<OperationType>,
    pub(crate) bind_rule: BindRule,
    pub(crate) grant: bool,
}

impl Aci {
    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn target_dn(&self) -> Option<&str> {
        self.target_dn.as_deref()
    }

    pub fn scope(&self) -> Scope {
        self.scope
    }

    pub fn target_filter(&self) -> &TargetFilter {
        &self.target_filter
    }

    pub fn target_attributes(&self) -> &[String] {
        &self.target_attributes
    }

    pub fn target_attributes_excluded(&self) -> bool {
        self.target_attributes_excluded
    }

    pub fn target_attr_filters(&self) -> &[TargetAttrFilter] {
        &self.target_attr_filters
    }

    pub fn target_from(&self) -> Option<&str> {
        self.target_from.as_deref()
    }

    pub fn target_to(&self) -> Option<&str> {
        self.target_to.as_deref()
    }

    pub fn permissions(&self) -> &[OperationType] {
        &self.permissions
    }

    pub fn bind_rule(&self) -> &BindRule {
        &self.bind_rule
    }

    pub fn grant(&self) -> bool {
        self.grant
    }

    /// Compute the `AttributeSet` from this ACI's target attribute fields.
    pub fn attribute_set(&self) -> AttributeSet {
        AttributeSet::from_vec(&self.target_attributes, self.target_attributes_excluded)
    }

    /// Compute the `DnScope` representing this ACI's target region.
    pub fn dn_scope(&self) -> DnScope {
        DnScope::new(self.target_dn.clone(), self.scope)
    }

    /// Compute the `OperationSet` from this ACI's permission list.
    pub fn operation_set(&self) -> crate::operation::OperationSet {
        crate::operation::OperationSet::from(self.permissions.as_slice())
    }

    /// Check if this ACI's target criteria (DN, filter, attributes) match the operation,
    /// without checking the bind rule.
    pub fn applies_to_target(&self, operation: &LdapOperation, target_entry: &LdapEntry) -> bool {
        if !self.operation_set().contains(operation.operation_type) {
            return false;
        }

        if !self.target_filter.matches(target_entry) {
            return false;
        }

        if !self.dn_scope().contains_dn(&target_entry.dn) {
            return false;
        }

        if !self.target_attributes.is_empty() {
            let attr_set = self.attribute_set();
            if operation.attributes.is_empty() {
                if attr_set.is_excluded() {
                    return false;
                }
            } else if attr_set.is_excluded() {
                if !operation
                    .attributes
                    .iter()
                    .any(|a| attr_set.contains_attr(a))
                {
                    return false;
                }
            } else if !operation
                .attributes
                .iter()
                .all(|a| attr_set.contains_attr(a))
            {
                return false;
            }
        }

        true
    }

    /// Check if this ACI applies to a given operation.
    pub fn applies_to(
        &self,
        operation: &LdapOperation,
        target_entry: &LdapEntry,
        user_dn: Option<&str>,
        user_entry: Option<&LdapEntry>,
    ) -> bool {
        if !self.applies_to_target(operation, target_entry) {
            return false;
        }

        self.bind_rule
            .matches(user_dn, user_entry, &target_entry.dn)
    }

    /// Convert to acls-rs permission.
    pub fn to_permission(&self, operation: &LdapOperation) -> Option<AtomicPermission> {
        if self.grant {
            Some(LdapPermission::new(operation.operation_type, &operation.target_dn).to_atomic())
        } else {
            None
        }
    }
}