ldap-acis 0.1.1

LDAP Access Control Instructions (ACI) system built on acls-rs
Documentation
//! ACI policy manager and authorization result.

use super::{Aci, BindRule};
use crate::entry::LdapEntry;
use crate::operation::{LdapOperation, LdapPermission};
use acls_rs::prelude::*;

/// ACI policy manager.
#[derive(Debug, Default)]
pub struct AciPolicy {
    acis: Vec<Aci>,
}

impl AciPolicy {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn add_aci(&mut self, aci: Aci) {
        self.acis.push(aci);
    }

    pub fn acis(&self) -> &[Aci] {
        &self.acis
    }

    pub fn compile(&self) -> crate::cache::CachedAciPolicy {
        crate::cache::CachedAciPolicy::new(&self.acis)
    }

    pub fn compile_with_cache_size(&self, cache_size: usize) -> crate::cache::CachedAciPolicy {
        crate::cache::CachedAciPolicy::with_cache_size(&self.acis, cache_size)
    }

    /// Authorize an LDAP operation.
    pub fn authorize(
        &self,
        operation: &LdapOperation,
        target_entry: &LdapEntry,
        user_dn: Option<&str>,
        user_entry: Option<&LdapEntry>,
    ) -> AuthorizationResult {
        let mut grants = PermissionSet::new();
        let mut denials = PermissionSet::new();

        for aci in &self.acis {
            let target_matches = aci.applies_to_target(operation, target_entry);

            if target_matches {
                let bind_matches = aci.bind_rule.matches(user_dn, user_entry, &target_entry.dn);

                if bind_matches {
                    let perm = LdapPermission::new(operation.operation_type, &operation.target_dn)
                        .to_atomic();
                    if aci.grant {
                        grants.extend([perm]);
                    } else {
                        denials.extend([perm]);
                    }
                } else if !aci.grant {
                    if let BindRule::Not(ref inner) = aci.bind_rule {
                        match **inner {
                            BindRule::GroupDn(_) | BindRule::UserDn(_) => {
                                let perm = LdapPermission::new(
                                    operation.operation_type,
                                    &operation.target_dn,
                                )
                                .to_atomic();
                                grants.extend([perm]);
                            }
                            _ => {}
                        }
                    }
                }
            }
        }

        let gd_pair = GrantDenialPair::new(grants, denials);
        let effective = gd_pair.effective_permissions();

        let authorized = effective.contains(&operation.to_permission().to_atomic());

        AuthorizationResult {
            authorized,
            grants: gd_pair.grants,
            denials: gd_pair.denials,
            effective,
        }
    }
}

/// Result of an authorization check.
#[derive(Debug, Clone, PartialEq)]
pub struct AuthorizationResult {
    pub authorized: bool,
    pub grants: PermissionSet,
    pub denials: PermissionSet,
    pub effective: PermissionSet,
}

impl AuthorizationResult {
    pub fn cached(authorized: bool) -> Self {
        Self {
            authorized,
            grants: PermissionSet::new(),
            denials: PermissionSet::new(),
            effective: PermissionSet::new(),
        }
    }
}

#[cfg(test)]
mod tests {
    use super::super::{AciBuilder, BindRule, Scope};
    use super::*;
    use crate::operation::OperationType;

    #[test]
    fn test_self_read_aci() {
        let aci = AciBuilder::new("self-read")
            .target_dn("ou=people,dc=example,dc=com")
            .target_scope(Scope::OneLevel)
            .permission(OperationType::Read)
            .bind_rule(BindRule::SelfUser)
            .build();

        let entry = LdapEntry::new("uid=alice,ou=people,dc=example,dc=com");
        let operation =
            LdapOperation::new(OperationType::Read, "uid=alice,ou=people,dc=example,dc=com");

        assert!(aci.applies_to(
            &operation,
            &entry,
            Some("uid=alice,ou=people,dc=example,dc=com"),
            None
        ));
        assert!(!aci.applies_to(
            &operation,
            &entry,
            Some("uid=bob,ou=people,dc=example,dc=com"),
            None
        ));
    }

    #[test]
    fn test_aci_policy() {
        let mut policy = AciPolicy::new();

        policy.add_aci(
            AciBuilder::new("read-people")
                .target_dn("ou=people,dc=example,dc=com")
                .target_scope(Scope::OneLevel)
                .permission(OperationType::Read)
                .bind_rule(BindRule::Authenticated)
                .build(),
        );

        let entry = LdapEntry::new("uid=alice,ou=people,dc=example,dc=com");
        let operation =
            LdapOperation::new(OperationType::Read, "uid=alice,ou=people,dc=example,dc=com");

        let result = policy.authorize(
            &operation,
            &entry,
            Some("uid=bob,ou=people,dc=example,dc=com"),
            None,
        );
        assert!(result.authorized);
    }
}