use crate::permission::{
AtomicPermission, DenialSet, GrantDenialPair, PermissionDelta, PermissionSet,
};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PermissionEffect {
pub grants_added: PermissionSet,
pub grants_removed: PermissionSet,
pub denials_added: DenialSet,
pub denials_removed: DenialSet,
pub net_effective_change: PermissionSet,
}
impl PermissionEffect {
pub fn from_transition(before: &GrantDenialPair, after: &GrantDenialPair) -> Self {
let grants_added = after.grants.difference(&before.grants);
let grants_removed = before.grants.difference(&after.grants);
let denials_added = after.denials.difference(&before.denials);
let denials_removed = before.denials.difference(&after.denials);
let before_effective = before.effective_permissions();
let after_effective = after.effective_permissions();
let net_effective_change = after_effective.difference(&before_effective);
Self {
grants_added,
grants_removed,
denials_added,
denials_removed,
net_effective_change,
}
}
pub fn is_empty(&self) -> bool {
self.grants_added.is_empty()
&& self.grants_removed.is_empty()
&& self.denials_added.is_empty()
&& self.denials_removed.is_empty()
}
pub fn total_changes_count(&self) -> usize {
self.grants_added.len()
+ self.grants_removed.len()
+ self.denials_added.len()
+ self.denials_removed.len()
}
pub fn to_delta(&self) -> PermissionDelta {
PermissionDelta::new(self.grants_added.clone(), self.grants_removed.clone())
}
pub fn grants_delta(&self) -> PermissionDelta {
PermissionDelta::new(self.grants_added.clone(), self.grants_removed.clone())
}
pub fn denials_delta(&self) -> PermissionDelta {
PermissionDelta::new(self.denials_added.clone(), self.denials_removed.clone())
}
}
#[derive(Debug, Clone)]
pub struct PermissionEffectBuilder {
original: GrantDenialPair,
current: GrantDenialPair,
}
impl PermissionEffectBuilder {
pub fn new(initial: GrantDenialPair) -> Self {
Self {
original: initial.clone(),
current: initial,
}
}
pub fn grant(mut self, perm: AtomicPermission) -> Self {
self.current.grants.extend([perm]);
self
}
pub fn revoke(mut self, perm: AtomicPermission) -> Self {
self.current.grants = self.current.grants.difference(&PermissionSet::from([perm]));
self
}
pub fn deny(mut self, perm: AtomicPermission) -> Self {
self.current.denials.extend([perm]);
self
}
pub fn undeny(mut self, perm: AtomicPermission) -> Self {
self.current.denials = self
.current
.denials
.difference(&PermissionSet::from([perm]));
self
}
pub fn grant_str(self, namespace: &str, action: &str) -> Self {
self.grant(AtomicPermission::new(namespace, action))
}
pub fn deny_str(self, namespace: &str, action: &str) -> Self {
self.deny(AtomicPermission::new(namespace, action))
}
pub fn apply_delta(mut self, delta: &PermissionDelta) -> Self {
self.current.grants = delta.apply_to(self.current.grants);
self
}
pub fn build(self) -> (GrantDenialPair, PermissionEffect) {
let effect = PermissionEffect::from_transition(&self.original, &self.current);
(self.current, effect)
}
pub fn has_changes(&self) -> bool {
self.current != self.original
}
pub fn preview_effect(&self) -> PermissionEffect {
PermissionEffect::from_transition(&self.original, &self.current)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_effect_from_transition() {
let before = GrantDenialPair::new(
PermissionSet::from([AtomicPermission::new("file", "read")]),
PermissionSet::new(),
);
let after = GrantDenialPair::new(
PermissionSet::from([
AtomicPermission::new("file", "read"),
AtomicPermission::new("file", "write"),
]),
PermissionSet::from([AtomicPermission::new("file", "delete")]),
);
let effect = PermissionEffect::from_transition(&before, &after);
assert_eq!(effect.grants_added.len(), 1);
assert!(effect
.grants_added
.contains(&AtomicPermission::new("file", "write")));
assert_eq!(effect.denials_added.len(), 1);
assert!(effect
.denials_added
.contains(&AtomicPermission::new("file", "delete")));
}
#[test]
fn test_effect_builder() {
let initial = GrantDenialPair::new(
PermissionSet::from([AtomicPermission::new("file", "read")]),
PermissionSet::new(),
);
let (updated, effect) = PermissionEffectBuilder::new(initial)
.grant(AtomicPermission::new("file", "write"))
.deny(AtomicPermission::new("file", "delete"))
.build();
assert_eq!(effect.grants_added.len(), 1);
assert_eq!(effect.denials_added.len(), 1);
assert_eq!(updated.grants.len(), 2);
assert_eq!(updated.denials.len(), 1);
}
#[test]
fn test_builder_revoke() {
let initial = GrantDenialPair::new(
PermissionSet::from([
AtomicPermission::new("file", "read"),
AtomicPermission::new("file", "write"),
]),
PermissionSet::new(),
);
let (updated, effect) = PermissionEffectBuilder::new(initial)
.revoke(AtomicPermission::new("file", "write"))
.build();
assert_eq!(effect.grants_removed.len(), 1);
assert_eq!(updated.grants.len(), 1);
}
#[test]
fn test_builder_preview() {
let initial = GrantDenialPair::new(
PermissionSet::from([AtomicPermission::new("file", "read")]),
PermissionSet::new(),
);
let builder =
PermissionEffectBuilder::new(initial).grant(AtomicPermission::new("file", "write"));
let preview = builder.preview_effect();
assert_eq!(preview.grants_added.len(), 1);
assert!(builder.has_changes());
}
}