use acls_rs::algebra::{BoundedJoinSemilattice, BoundedMeetSemilattice};
use acls_rs::prelude::*;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
use std::cmp::Ordering;
use std::collections::BTreeSet;
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum TargetAttrFilterOp {
Add,
Del,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct TargetAttrFilter {
pub op: TargetAttrFilterOp,
pub attr: String,
pub filter: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum AttributeSet {
All,
Include(BTreeSet<String>),
Exclude(BTreeSet<String>),
}
impl AttributeSet {
pub fn from_vec(attrs: &[String], excluded: bool) -> Self {
if attrs.is_empty() {
return Self::All;
}
let set: BTreeSet<String> = attrs.iter().map(|s| s.to_lowercase()).collect();
if excluded {
Self::Exclude(set)
} else {
Self::Include(set)
}
}
pub fn is_all(&self) -> bool {
matches!(self, Self::All)
}
pub fn contains_attr(&self, attr: &str) -> bool {
let lower = attr.to_lowercase();
match self {
Self::All => true,
Self::Include(set) => set.contains(&lower),
Self::Exclude(set) => !set.contains(&lower),
}
}
pub fn attrs(&self) -> &BTreeSet<String> {
static EMPTY: std::sync::LazyLock<BTreeSet<String>> =
std::sync::LazyLock::new(BTreeSet::new);
match self {
Self::All => &EMPTY,
Self::Include(set) | Self::Exclude(set) => set,
}
}
pub fn is_excluded(&self) -> bool {
matches!(self, Self::Exclude(_))
}
pub fn to_vec(&self) -> Vec<String> {
match self {
Self::All => Vec::new(),
Self::Include(set) | Self::Exclude(set) => set.iter().cloned().collect(),
}
}
}
impl PartialOrd for AttributeSet {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self, other) {
(Self::All, Self::All) => Some(Ordering::Equal),
(Self::All, _) => Some(Ordering::Greater),
(_, Self::All) => Some(Ordering::Less),
(Self::Include(a), Self::Include(b)) => {
let a_sub_b = a.is_subset(b);
let b_sub_a = b.is_subset(a);
match (a_sub_b, b_sub_a) {
(true, true) => Some(Ordering::Equal),
(true, false) => Some(Ordering::Less),
(false, true) => Some(Ordering::Greater),
(false, false) => None,
}
}
(Self::Exclude(a), Self::Exclude(b)) => {
let a_sub_b = a.is_subset(b);
let b_sub_a = b.is_subset(a);
match (a_sub_b, b_sub_a) {
(true, true) => Some(Ordering::Equal),
(true, false) => Some(Ordering::Greater),
(false, true) => Some(Ordering::Less),
(false, false) => None,
}
}
(Self::Include(_), Self::Exclude(_)) | (Self::Exclude(_), Self::Include(_)) => None,
}
}
}
impl Semigroup for AttributeSet {
fn combine(self, other: Self) -> Self {
self.join(other)
}
}
impl Monoid for AttributeSet {
fn identity() -> Self {
Self::Include(BTreeSet::new())
}
}
impl MeetSemilattice for AttributeSet {
fn meet(self, other: Self) -> Self {
match (self, other) {
(Self::All, x) | (x, Self::All) => x,
(Self::Include(a), Self::Include(b)) => {
Self::Include(a.intersection(&b).cloned().collect())
}
(Self::Exclude(a), Self::Exclude(b)) => Self::Exclude(a.union(&b).cloned().collect()),
(Self::Include(inc), Self::Exclude(exc)) | (Self::Exclude(exc), Self::Include(inc)) => {
Self::Include(inc.difference(&exc).cloned().collect())
}
}
}
}
impl JoinSemilattice for AttributeSet {
fn join(self, other: Self) -> Self {
match (self, other) {
(Self::All, _) | (_, Self::All) => Self::All,
(Self::Include(a), Self::Include(b)) => Self::Include(a.union(&b).cloned().collect()),
(Self::Exclude(a), Self::Exclude(b)) => {
let result: BTreeSet<String> = a.intersection(&b).cloned().collect();
if result.is_empty() {
Self::All
} else {
Self::Exclude(result)
}
}
(Self::Include(inc), Self::Exclude(exc)) | (Self::Exclude(exc), Self::Include(inc)) => {
let remaining: BTreeSet<String> = exc.difference(&inc).cloned().collect();
if remaining.is_empty() {
Self::All
} else {
Self::Exclude(remaining)
}
}
}
}
}
impl BoundedMeetSemilattice for AttributeSet {
fn top() -> Self {
Self::All
}
}
impl BoundedJoinSemilattice for AttributeSet {
fn bottom() -> Self {
Self::Include(BTreeSet::new())
}
}
impl std::fmt::Display for AttributeSet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::All => write!(f, "*"),
Self::Include(set) => {
write!(f, "{}", set.iter().cloned().collect::<Vec<_>>().join(", "))
}
Self::Exclude(set) => {
write!(
f,
"all except: {}",
set.iter().cloned().collect::<Vec<_>>().join(", ")
)
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::operation::OperationType;
fn inc(attrs: &[&str]) -> AttributeSet {
AttributeSet::Include(attrs.iter().map(|s| s.to_lowercase()).collect())
}
fn exc(attrs: &[&str]) -> AttributeSet {
AttributeSet::Exclude(attrs.iter().map(|s| s.to_lowercase()).collect())
}
#[test]
fn all_is_greatest() {
assert!(AttributeSet::All > inc(&["cn"]));
assert!(AttributeSet::All > exc(&["cn"]));
assert!(AttributeSet::All > inc(&[]));
}
#[test]
fn include_subset() {
let small = inc(&["cn"]);
let big = inc(&["cn", "sn"]);
assert!(small < big);
}
#[test]
fn include_equal() {
let a = inc(&["cn", "sn"]);
let b = inc(&["sn", "cn"]);
assert_eq!(a.partial_cmp(&b), Some(Ordering::Equal));
}
#[test]
fn include_incomparable() {
let a = inc(&["cn"]);
let b = inc(&["sn"]);
assert_eq!(a.partial_cmp(&b), None);
}
#[test]
fn exclude_fewer_is_wider() {
let fewer = exc(&["cn"]);
let more = exc(&["cn", "sn"]);
assert!(fewer > more);
}
#[test]
fn include_vs_exclude_incomparable() {
assert_eq!(inc(&["cn"]).partial_cmp(&exc(&["sn"])), None);
}
#[test]
fn empty_include_is_bottom() {
let bottom = AttributeSet::bottom();
assert_eq!(bottom, inc(&[]));
for set in [inc(&["cn"]), inc(&["cn", "sn"]), AttributeSet::All] {
assert_eq!(set.clone().join(bottom.clone()), set);
}
}
#[test]
fn meet_include_include() {
let a = inc(&["cn", "sn"]);
let b = inc(&["cn", "uid"]);
assert_eq!(a.meet(b), inc(&["cn"]));
}
#[test]
fn meet_exclude_exclude() {
let a = exc(&["cn"]);
let b = exc(&["sn"]);
assert_eq!(a.meet(b), exc(&["cn", "sn"]));
}
#[test]
fn meet_include_exclude() {
let a = inc(&["cn", "sn", "uid"]);
let b = exc(&["sn"]);
assert_eq!(a.meet(b), inc(&["cn", "uid"]));
}
#[test]
fn meet_all_identity() {
let set = inc(&["cn", "sn"]);
assert_eq!(set.clone().meet(AttributeSet::All), set);
}
#[test]
fn join_include_include() {
let a = inc(&["cn"]);
let b = inc(&["sn"]);
assert_eq!(a.join(b), inc(&["cn", "sn"]));
}
#[test]
fn join_exclude_exclude() {
let a = exc(&["cn", "sn"]);
let b = exc(&["sn", "uid"]);
assert_eq!(a.join(b), exc(&["sn"]));
}
#[test]
fn join_include_exclude() {
let a = inc(&["cn"]);
let b = exc(&["sn"]);
assert_eq!(a.join(b), exc(&["sn"]));
}
#[test]
fn join_include_exclude_overlap() {
let a = inc(&["cn"]);
let b = exc(&["cn"]);
assert_eq!(a.join(b), AttributeSet::All);
}
#[test]
fn contains_attr() {
assert!(AttributeSet::All.contains_attr("anything"));
assert!(inc(&["cn", "sn"]).contains_attr("CN"));
assert!(!inc(&["cn", "sn"]).contains_attr("uid"));
assert!(!exc(&["cn"]).contains_attr("CN"));
assert!(exc(&["cn"]).contains_attr("sn"));
}
#[test]
fn from_vec_compat() {
let set = AttributeSet::from_vec(&["CN".to_string(), "SN".to_string()], false);
assert_eq!(set, inc(&["cn", "sn"]));
let set = AttributeSet::from_vec(&["CN".to_string()], true);
assert_eq!(set, exc(&["cn"]));
let set = AttributeSet::from_vec(&[], false);
assert_eq!(set, AttributeSet::All);
}
#[test]
fn aci_attribute_set_accessor() {
use super::super::AciBuilder;
let aci = AciBuilder::new("test")
.target_attribute("cn")
.target_attribute("sn")
.permission(OperationType::Read)
.build();
assert_eq!(aci.attribute_set(), inc(&["cn", "sn"]));
}
#[test]
fn idempotence() {
for set in [AttributeSet::All, inc(&["cn", "sn"]), exc(&["uid"])] {
assert_eq!(set.clone().meet(set.clone()), set);
assert_eq!(set.clone().join(set.clone()), set);
}
}
}