use super::Scope;
use crate::dn::{is_descendant_or_self, is_direct_child};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct DnScope {
pub dn: Option<String>,
pub scope: Scope,
}
impl DnScope {
pub fn new(dn: Option<String>, scope: Scope) -> Self {
match dn {
None => Self {
dn: None,
scope: Scope::Subtree,
},
Some(d) => Self { dn: Some(d), scope },
}
}
pub fn unrestricted() -> Self {
Self {
dn: None,
scope: Scope::Subtree,
}
}
fn has_wildcard(dn: &str) -> bool {
crate::dn::has_wildcard(dn)
}
pub fn contains_dn(&self, dn: &str) -> bool {
match &self.dn {
None => true,
Some(base) => match self.scope {
Scope::Base => dn.eq_ignore_ascii_case(base),
Scope::OneLevel => is_direct_child(dn, base),
Scope::Subtree => is_descendant_or_self(dn, base),
},
}
}
fn contains_region(&self, other: &Self) -> bool {
match (&self.dn, &other.dn) {
(None, _) => true,
(Some(_), None) => false,
(Some(a), Some(b)) => {
if a.eq_ignore_ascii_case(b) {
matches!(
(self.scope, other.scope),
(Scope::Subtree, _)
| (Scope::OneLevel, Scope::OneLevel)
| (Scope::Base, Scope::Base)
)
} else {
if Self::has_wildcard(a) || Self::has_wildcard(b) {
return false;
}
if is_descendant_or_self(b, a) {
match self.scope {
Scope::Subtree => true,
Scope::OneLevel => other.scope == Scope::Base && is_direct_child(b, a),
Scope::Base => false,
}
} else {
false
}
}
}
}
}
}
impl PartialOrd for DnScope {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self == other {
return Some(Ordering::Equal);
}
let self_contains_other = self.contains_region(other);
let other_contains_self = other.contains_region(self);
match (self_contains_other, other_contains_self) {
(true, true) => Some(Ordering::Equal),
(true, false) => Some(Ordering::Greater),
(false, true) => Some(Ordering::Less),
(false, false) => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::operation::OperationType;
fn ds(dn: Option<&str>, scope: Scope) -> DnScope {
DnScope::new(dn.map(|s| s.to_string()), scope)
}
#[test]
fn unrestricted_is_greatest() {
let unr = DnScope::unrestricted();
assert!(unr > ds(Some("dc=example,dc=com"), Scope::Subtree));
assert!(unr > ds(Some("ou=people,dc=example,dc=com"), Scope::Base));
}
#[test]
fn unrestricted_normalized() {
let a = ds(None, Scope::Base);
let b = ds(None, Scope::Subtree);
assert_eq!(a, b);
assert_eq!(a.partial_cmp(&b), Some(Ordering::Equal));
}
#[test]
fn same_dn_subtree_covers_all() {
let sub = ds(Some("dc=example,dc=com"), Scope::Subtree);
let one = ds(Some("dc=example,dc=com"), Scope::OneLevel);
let base = ds(Some("dc=example,dc=com"), Scope::Base);
assert!(sub > one);
assert!(sub > base);
}
#[test]
fn same_dn_base_and_onelevel_incomparable() {
let base = ds(Some("dc=example,dc=com"), Scope::Base);
let one = ds(Some("dc=example,dc=com"), Scope::OneLevel);
assert_eq!(base.partial_cmp(&one), None);
}
#[test]
fn ancestor_subtree_covers_descendant() {
let parent = ds(Some("dc=example,dc=com"), Scope::Subtree);
let child_sub = ds(Some("ou=people,dc=example,dc=com"), Scope::Subtree);
let child_one = ds(Some("ou=people,dc=example,dc=com"), Scope::OneLevel);
let child_base = ds(Some("ou=people,dc=example,dc=com"), Scope::Base);
assert!(parent > child_sub);
assert!(parent > child_one);
assert!(parent > child_base);
}
#[test]
fn ancestor_subtree_covers_deep_descendant() {
let root = ds(Some("dc=example,dc=com"), Scope::Subtree);
let deep = ds(Some("uid=alice,ou=people,dc=example,dc=com"), Scope::Base);
assert!(root > deep);
}
#[test]
fn ancestor_onelevel_covers_direct_child_base() {
let parent = ds(Some("dc=example,dc=com"), Scope::OneLevel);
let child = ds(Some("ou=people,dc=example,dc=com"), Scope::Base);
assert!(parent > child);
}
#[test]
fn ancestor_onelevel_does_not_cover_child_onelevel() {
let parent = ds(Some("dc=example,dc=com"), Scope::OneLevel);
let child = ds(Some("ou=people,dc=example,dc=com"), Scope::OneLevel);
assert_eq!(parent.partial_cmp(&child), None);
}
#[test]
fn ancestor_onelevel_does_not_cover_grandchild() {
let grandparent = ds(Some("dc=example,dc=com"), Scope::OneLevel);
let grandchild = ds(Some("uid=alice,ou=people,dc=example,dc=com"), Scope::Base);
assert_eq!(grandparent.partial_cmp(&grandchild), None);
}
#[test]
fn ancestor_base_does_not_cover_child() {
let parent = ds(Some("dc=example,dc=com"), Scope::Base);
let child = ds(Some("ou=people,dc=example,dc=com"), Scope::Base);
assert_eq!(parent.partial_cmp(&child), None);
}
#[test]
fn sibling_dns_incomparable() {
let a = ds(Some("ou=people,dc=example,dc=com"), Scope::Subtree);
let b = ds(Some("ou=groups,dc=example,dc=com"), Scope::Subtree);
assert_eq!(a.partial_cmp(&b), None);
}
#[test]
fn wildcard_dns_incomparable() {
let wild = ds(Some("uid=*,ou=people,dc=example,dc=com"), Scope::Subtree);
let concrete = ds(Some("ou=people,dc=example,dc=com"), Scope::Subtree);
assert_eq!(wild.partial_cmp(&concrete), None);
}
#[test]
fn same_wildcard_dn_scope_comparison() {
let a = ds(Some("uid=*,dc=example,dc=com"), Scope::Subtree);
let b = ds(Some("uid=*,dc=example,dc=com"), Scope::Base);
assert!(a > b);
}
#[test]
fn case_insensitive_dn() {
let a = ds(Some("DC=EXAMPLE,DC=COM"), Scope::Subtree);
let b = ds(Some("dc=example,dc=com"), Scope::Subtree);
assert_ne!(a, b);
assert_eq!(a.partial_cmp(&b), Some(Ordering::Equal));
assert!(a.contains_dn("dc=example,dc=com"));
assert!(b.contains_dn("DC=EXAMPLE,DC=COM"));
}
#[test]
fn reflexive() {
let scopes = [
DnScope::unrestricted(),
ds(Some("dc=example,dc=com"), Scope::Base),
ds(Some("dc=example,dc=com"), Scope::OneLevel),
ds(Some("dc=example,dc=com"), Scope::Subtree),
ds(Some("uid=*,dc=example,dc=com"), Scope::Subtree),
];
for s in &scopes {
assert_eq!(s.partial_cmp(s), Some(Ordering::Equal));
}
}
#[test]
fn aci_dn_scope_accessor() {
use super::super::{AciBuilder, Scope};
let aci = AciBuilder::new("test")
.target_dn("ou=People,dc=Example,dc=COM")
.target_scope(Scope::OneLevel)
.permission(OperationType::Read)
.build();
let scope = aci.dn_scope();
assert_eq!(scope.dn, Some("ou=People,dc=Example,dc=COM".to_string()));
assert_eq!(scope.scope, Scope::OneLevel);
assert!(scope.contains_dn("uid=alice,ou=people,dc=example,dc=com"));
}
}