use acls_rs::algebra::{BoundedJoinSemilattice, BoundedMeetSemilattice};
use acls_rs::permission::AtomicPermission;
use acls_rs::prelude::*;
use std::cmp::Ordering;
use std::fmt;
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[non_exhaustive]
pub enum OperationType {
Bind,
Search,
Compare,
Read,
Add,
Delete,
Modify,
ModifyDn,
All,
SelfWrite,
}
impl OperationType {
pub fn as_str(&self) -> &'static str {
match self {
Self::Bind => "bind",
Self::Search => "search",
Self::Compare => "compare",
Self::Read => "read",
Self::Add => "add",
Self::Delete => "delete",
Self::Modify => "modify",
Self::ModifyDn => "modifydn",
Self::All => "all",
Self::SelfWrite => "selfwrite",
}
}
pub fn grants(&self, other: &OperationType) -> bool {
*self == OperationType::All
|| *self == *other
|| (*self == OperationType::SelfWrite && *other == OperationType::Modify)
}
pub fn is_write(&self) -> bool {
matches!(
self,
Self::Modify | Self::Add | Self::Delete | Self::ModifyDn | Self::All | Self::SelfWrite
)
}
pub fn is_read(&self) -> bool {
matches!(self, Self::Read | Self::Search | Self::Compare | Self::All)
}
fn bit(self) -> u16 {
match self {
Self::Bind => 1 << 0,
Self::Search => 1 << 1,
Self::Compare => 1 << 2,
Self::Read => 1 << 3,
Self::Add => 1 << 4,
Self::Delete => 1 << 5,
Self::Modify => 1 << 6,
Self::ModifyDn => 1 << 7,
Self::SelfWrite => 1 << 8,
Self::All => OperationSet::ALL_CONCRETE,
}
}
pub fn bit_index(self) -> Option<usize> {
match self {
Self::Bind => Some(0),
Self::Search => Some(1),
Self::Compare => Some(2),
Self::Read => Some(3),
Self::Add => Some(4),
Self::Delete => Some(5),
Self::Modify => Some(6),
Self::ModifyDn => Some(7),
Self::SelfWrite => Some(8),
Self::All => None,
}
}
}
impl PartialOrd for OperationType {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
if self == other {
return Some(Ordering::Equal);
}
if self.grants(other) {
return Some(Ordering::Greater);
}
if other.grants(self) {
return Some(Ordering::Less);
}
None
}
}
pub trait PermissionSlice {
fn grants(&self, op: &OperationType) -> bool;
fn grants_write(&self) -> bool;
fn grants_read(&self) -> bool;
}
impl PermissionSlice for [OperationType] {
fn grants(&self, op: &OperationType) -> bool {
self.iter().any(|p| p.grants(op))
}
fn grants_write(&self) -> bool {
self.iter().any(|p| p.is_write())
}
fn grants_read(&self) -> bool {
self.iter().any(|p| p.is_read())
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct LdapPermission {
operation: OperationType,
target: String,
}
impl LdapPermission {
pub fn new(operation: OperationType, target: impl Into<String>) -> Self {
Self {
operation,
target: target.into(),
}
}
pub fn read(dn: impl Into<String>) -> Self {
Self::new(OperationType::Read, dn)
}
pub fn write(dn: impl Into<String>) -> Self {
Self::new(OperationType::Modify, dn)
}
pub fn search(base_dn: impl Into<String>) -> Self {
Self::new(OperationType::Search, base_dn)
}
pub fn add(parent_dn: impl Into<String>) -> Self {
Self::new(OperationType::Add, parent_dn)
}
pub fn delete(dn: impl Into<String>) -> Self {
Self::new(OperationType::Delete, dn)
}
pub fn to_atomic(&self) -> AtomicPermission {
AtomicPermission::new(&self.target, self.operation.as_str())
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LdapOperation {
pub operation_type: OperationType,
pub target_dn: String,
pub attributes: Vec<String>,
}
impl LdapOperation {
pub fn new(op: OperationType, dn: impl Into<String>) -> Self {
Self {
operation_type: op,
target_dn: dn.into(),
attributes: Vec::new(),
}
}
pub fn with_attributes(mut self, attrs: Vec<String>) -> Self {
self.attributes = attrs.into_iter().map(|a| a.to_lowercase()).collect();
self
}
pub fn with_target(&self, target_dn: impl Into<String>) -> Self {
Self {
operation_type: self.operation_type,
target_dn: target_dn.into(),
attributes: self.attributes.clone(),
}
}
pub fn to_permission(&self) -> LdapPermission {
LdapPermission::new(self.operation_type, &self.target_dn)
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct OperationSet(u16);
pub const NUM_CONCRETE_OPS: usize = 9;
impl OperationSet {
const ALL_CONCRETE: u16 = (1 << NUM_CONCRETE_OPS) - 1;
pub(crate) fn raw_bits(self) -> u16 {
self.0
}
pub fn empty() -> Self {
Self(0)
}
pub fn all() -> Self {
Self(Self::ALL_CONCRETE)
}
pub fn contains(self, op: OperationType) -> bool {
if self.0 & op.bit() == op.bit() {
return true;
}
op == OperationType::Modify && self.0 & OperationType::SelfWrite.bit() != 0
}
pub fn is_empty(self) -> bool {
self.0 == 0
}
pub fn len(self) -> usize {
self.0.count_ones() as usize
}
pub fn insert(&mut self, op: OperationType) {
self.0 |= op.bit();
}
pub fn union(self, other: Self) -> Self {
Self(self.0 | other.0)
}
pub fn intersection(self, other: Self) -> Self {
Self(self.0 & other.0)
}
pub fn difference(self, other: Self) -> Self {
Self(self.0 & !other.0)
}
pub fn is_subset_of(self, other: Self) -> bool {
self.0 & other.0 == self.0
}
pub fn is_superset_of(self, other: Self) -> bool {
other.is_subset_of(self)
}
pub fn grants_write(self) -> bool {
let write_bits = OperationType::Modify.bit()
| OperationType::Add.bit()
| OperationType::Delete.bit()
| OperationType::ModifyDn.bit()
| OperationType::SelfWrite.bit();
self.0 & write_bits != 0
}
pub fn grants_read(self) -> bool {
let read_bits =
OperationType::Read.bit() | OperationType::Search.bit() | OperationType::Compare.bit();
self.0 & read_bits != 0
}
pub fn iter(self) -> OperationSetIter {
OperationSetIter {
bits: self.0,
pos: 0,
}
}
pub fn to_vec(self) -> Vec<OperationType> {
self.iter().collect()
}
}
impl From<OperationType> for OperationSet {
fn from(op: OperationType) -> Self {
Self(op.bit())
}
}
impl From<&[OperationType]> for OperationSet {
fn from(ops: &[OperationType]) -> Self {
let mut bits = 0u16;
for op in ops {
bits |= op.bit();
}
Self(bits)
}
}
impl FromIterator<OperationType> for OperationSet {
fn from_iter<I: IntoIterator<Item = OperationType>>(iter: I) -> Self {
let mut bits = 0u16;
for op in iter {
bits |= op.bit();
}
Self(bits)
}
}
impl fmt::Debug for OperationSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_set().entries(self.iter()).finish()
}
}
impl fmt::Display for OperationSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ops: Vec<&str> = self.iter().map(|op| op.as_str()).collect();
write!(f, "{}", ops.join(","))
}
}
impl PartialOrd for OperationSet {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
let a_sub_b = self.is_subset_of(*other);
let b_sub_a = other.is_subset_of(*self);
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,
}
}
}
impl Semigroup for OperationSet {
fn combine(self, other: Self) -> Self {
self.union(other)
}
}
impl Monoid for OperationSet {
fn identity() -> Self {
Self::empty()
}
}
impl MeetSemilattice for OperationSet {
fn meet(self, other: Self) -> Self {
self.intersection(other)
}
}
impl JoinSemilattice for OperationSet {
fn join(self, other: Self) -> Self {
self.union(other)
}
}
impl BoundedMeetSemilattice for OperationSet {
fn top() -> Self {
Self::all()
}
}
impl BoundedJoinSemilattice for OperationSet {
fn bottom() -> Self {
Self::empty()
}
}
pub struct OperationSetIter {
bits: u16,
pos: u8,
}
const OPERATION_TABLE: [OperationType; 9] = [
OperationType::Bind,
OperationType::Search,
OperationType::Compare,
OperationType::Read,
OperationType::Add,
OperationType::Delete,
OperationType::Modify,
OperationType::ModifyDn,
OperationType::SelfWrite,
];
impl Iterator for OperationSetIter {
type Item = OperationType;
fn next(&mut self) -> Option<Self::Item> {
while (self.pos as usize) < OPERATION_TABLE.len() {
let bit = 1u16 << self.pos;
self.pos += 1;
if self.bits & bit != 0 {
return Some(OPERATION_TABLE[(self.pos - 1) as usize]);
}
}
None
}
}
#[cfg(test)]
mod tests {
use super::*;
mod operation_type_ordering {
use super::*;
#[test]
fn all_subsumes_everything() {
for op in OPERATION_TABLE {
assert!(OperationType::All >= op, "All should subsume {:?}", op);
}
}
#[test]
fn selfwrite_subsumes_modify() {
assert!(OperationType::SelfWrite > OperationType::Modify);
}
#[test]
fn incomparable_leaves() {
assert_eq!(
OperationType::Read.partial_cmp(&OperationType::Search),
None
);
assert_eq!(OperationType::Add.partial_cmp(&OperationType::Delete), None);
assert_eq!(
OperationType::Bind.partial_cmp(&OperationType::Modify),
None
);
}
#[test]
fn reflexive() {
for op in OPERATION_TABLE {
assert_eq!(op.partial_cmp(&op), Some(Ordering::Equal));
}
assert_eq!(
OperationType::All.partial_cmp(&OperationType::All),
Some(Ordering::Equal)
);
}
}
mod operation_set {
use super::*;
#[test]
fn from_slice() {
let ops = [OperationType::Read, OperationType::Search];
let set = OperationSet::from(ops.as_slice());
assert!(set.contains(OperationType::Read));
assert!(set.contains(OperationType::Search));
assert!(!set.contains(OperationType::Modify));
assert_eq!(set.len(), 2);
}
#[test]
fn all_expands() {
let set = OperationSet::from(OperationType::All);
assert_eq!(set.len(), 9);
for op in OPERATION_TABLE {
assert!(set.contains(op), "All should contain {:?}", op);
}
}
#[test]
fn selfwrite_subsumes_modify_in_set() {
let set = OperationSet::from(OperationType::SelfWrite);
assert!(set.contains(OperationType::SelfWrite));
assert!(set.contains(OperationType::Modify));
assert_eq!(set.len(), 1);
}
#[test]
fn subset_ordering() {
let small = OperationSet::from([OperationType::Read].as_slice());
let big = OperationSet::from([OperationType::Read, OperationType::Search].as_slice());
assert!(small < big);
assert!(small <= big);
assert!(big > small);
}
#[test]
fn incomparable_sets() {
let a = OperationSet::from([OperationType::Read].as_slice());
let b = OperationSet::from([OperationType::Modify].as_slice());
assert_eq!(a.partial_cmp(&b), None);
}
#[test]
fn lattice_meet() {
let a = OperationSet::from([OperationType::Read, OperationType::Search].as_slice());
let b = OperationSet::from([OperationType::Read, OperationType::Modify].as_slice());
let meet = a.meet(b);
assert_eq!(meet.len(), 1);
assert!(meet.contains(OperationType::Read));
}
#[test]
fn lattice_join() {
let a = OperationSet::from([OperationType::Read, OperationType::Search].as_slice());
let b = OperationSet::from([OperationType::Read, OperationType::Modify].as_slice());
let join = a.join(b);
assert_eq!(join.len(), 3);
assert!(join.contains(OperationType::Read));
assert!(join.contains(OperationType::Search));
assert!(join.contains(OperationType::Modify));
}
#[test]
fn monoid_identity() {
let set = OperationSet::from([OperationType::Read, OperationType::Search].as_slice());
assert_eq!(set.combine(OperationSet::identity()), set);
assert_eq!(OperationSet::identity().combine(set), set);
}
#[test]
fn bounded_top_bottom() {
let set = OperationSet::from([OperationType::Read, OperationType::Search].as_slice());
assert_eq!(set.meet(OperationSet::top()), set);
assert_eq!(set.join(OperationSet::bottom()), set);
}
#[test]
fn idempotence() {
let set = OperationSet::from([OperationType::Read, OperationType::Modify].as_slice());
assert_eq!(set.meet(set), set);
assert_eq!(set.join(set), set);
}
#[test]
fn iter_round_trip() {
let ops = [
OperationType::Read,
OperationType::Search,
OperationType::Add,
];
let set = OperationSet::from(ops.as_slice());
let vec = set.to_vec();
assert_eq!(vec.len(), 3);
assert!(vec.contains(&OperationType::Read));
assert!(vec.contains(&OperationType::Search));
assert!(vec.contains(&OperationType::Add));
}
}
}