acls-rs 0.2.1

Algebraically-correct permissions system with RBAC, ABAC, and temporal support
Documentation
//! Subject type representing users/roles with permissions.

use crate::algebra::{MonoidAction, Semigroup};
use crate::calculation::HasPermissions;
use crate::permission::{
    AtomicPermission, GrantDenialPair, PermissionDelta, PermissionSet, TemporalPermissionSet,
    Timestamp,
};
use std::fmt;

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// Errors from building a [`Subject`] via [`SubjectBuilder`].
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub enum BuilderError {
    /// A required field was not set.
    MissingField(&'static str),
}

impl fmt::Display for BuilderError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            BuilderError::MissingField(field) => write!(f, "missing required field: {}", field),
        }
    }
}

impl std::error::Error for BuilderError {}

/// A subject (user, service account, etc.) with permissions.
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Subject {
    /// Unique identifier for this subject.
    pub id: String,
    /// Direct grant/denial permissions assigned to this subject.
    pub permissions: GrantDenialPair,
    /// Role names assigned to this subject.
    pub roles: Vec<String>,
    /// Time-bounded permissions for this subject.
    pub temporal_permissions: TemporalPermissionSet,
}

impl Subject {
    /// Create a new subject with the given identifier and no permissions.
    pub fn new(id: impl Into<String>) -> Self {
        Self {
            id: id.into(),
            permissions: GrantDenialPair::empty(),
            roles: Vec::new(),
            temporal_permissions: TemporalPermissionSet::new(),
        }
    }

    /// Compute effective permissions at the current time.
    pub fn effective_permissions(&self) -> PermissionSet {
        let base = self.permissions.effective_permissions();
        let temporal = self.temporal_permissions.currently_effective();
        base.combine(temporal)
    }

    /// Compute effective permissions at the given timestamp.
    pub fn effective_permissions_at(&self, time: Timestamp) -> PermissionSet {
        let base = self.permissions.effective_permissions();
        let temporal = self.temporal_permissions.effective_at(time);
        base.combine(temporal)
    }

    /// Returns `true` if the subject effectively has the given permission.
    pub fn has_permission(&self, perm: &AtomicPermission) -> bool {
        self.effective_permissions().contains(perm)
    }

    /// Grant a permission to this subject.
    pub fn grant(&mut self, perm: AtomicPermission) {
        self.permissions.grants.extend([perm]);
    }

    /// Remove a previously granted permission.
    pub fn revoke(&mut self, perm: AtomicPermission) {
        self.permissions.grants = self
            .permissions
            .grants
            .difference(&PermissionSet::from([perm]));
    }

    /// Explicitly deny a permission for this subject.
    pub fn deny(&mut self, perm: AtomicPermission) {
        self.permissions.denials.extend([perm]);
    }

    /// Return a [`SubjectBuilder`] for constructing a subject.
    pub fn builder() -> SubjectBuilder {
        SubjectBuilder::default()
    }
}

impl Default for SubjectBuilder {
    fn default() -> Self {
        Self {
            id: None,
            permissions: GrantDenialPair::empty(),
            roles: Vec::new(),
        }
    }
}
/// Builder for constructing a [`Subject`] with validation.
pub struct SubjectBuilder {
    id: Option<String>,
    permissions: GrantDenialPair,
    roles: Vec<String>,
}

impl SubjectBuilder {
    /// Set the subject identifier.
    pub fn id(mut self, id: impl Into<String>) -> Self {
        self.id = Some(id.into());
        self
    }

    /// Add a role to the subject.
    pub fn role(mut self, role: impl Into<String>) -> Self {
        self.roles.push(role.into());
        self
    }

    /// Grant a permission to the subject being built.
    pub fn grant(mut self, perm: AtomicPermission) -> Self {
        self.permissions.grants.extend([perm]);
        self
    }

    /// Deny a permission for the subject being built.
    pub fn deny(mut self, perm: AtomicPermission) -> Self {
        self.permissions.denials.extend([perm]);
        self
    }

    /// Build the [`Subject`], returning an error if required fields are missing.
    pub fn build(self) -> Result<Subject, BuilderError> {
        Ok(Subject {
            id: self.id.ok_or(BuilderError::MissingField("id"))?,
            permissions: self.permissions,
            roles: self.roles,
            temporal_permissions: TemporalPermissionSet::new(),
        })
    }
}

// MonoidAction: PermissionDelta acts on Subject
impl MonoidAction<PermissionDelta, Subject> for PermissionDelta {
    fn act(delta: PermissionDelta, mut subject: Subject) -> Subject {
        subject.permissions.grants = delta.apply_to(subject.permissions.grants);
        subject
    }
}

// Implement HasPermissions for Subject
impl HasPermissions for Subject {
    fn permissions(&self) -> &GrantDenialPair {
        &self.permissions
    }

    fn permissions_mut(&mut self) -> &mut GrantDenialPair {
        &mut self.permissions
    }

    fn effective_permissions_at(&self, time: Timestamp) -> PermissionSet {
        self.effective_permissions_at(time)
    }
}