Skip to main content

acls_rs/
subject.rs

1//! Subject type representing users/roles with permissions.
2
3use crate::algebra::{MonoidAction, Semigroup};
4use crate::calculation::HasPermissions;
5use crate::permission::{
6    AtomicPermission, GrantDenialPair, PermissionDelta, PermissionSet, TemporalPermissionSet,
7    Timestamp,
8};
9use std::fmt;
10
11#[cfg(feature = "serde")]
12use serde::{Deserialize, Serialize};
13
14/// Errors from building a [`Subject`] via [`SubjectBuilder`].
15#[derive(Debug, Clone, PartialEq, Eq)]
16#[non_exhaustive]
17pub enum BuilderError {
18    /// A required field was not set.
19    MissingField(&'static str),
20}
21
22impl fmt::Display for BuilderError {
23    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
24        match self {
25            BuilderError::MissingField(field) => write!(f, "missing required field: {}", field),
26        }
27    }
28}
29
30impl std::error::Error for BuilderError {}
31
32/// A subject (user, service account, etc.) with permissions.
33#[derive(Debug, Clone)]
34#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
35pub struct Subject {
36    /// Unique identifier for this subject.
37    pub id: String,
38    /// Direct grant/denial permissions assigned to this subject.
39    pub permissions: GrantDenialPair,
40    /// Role names assigned to this subject.
41    pub roles: Vec<String>,
42    /// Time-bounded permissions for this subject.
43    pub temporal_permissions: TemporalPermissionSet,
44}
45
46impl Subject {
47    /// Create a new subject with the given identifier and no permissions.
48    pub fn new(id: impl Into<String>) -> Self {
49        Self {
50            id: id.into(),
51            permissions: GrantDenialPair::empty(),
52            roles: Vec::new(),
53            temporal_permissions: TemporalPermissionSet::new(),
54        }
55    }
56
57    /// Compute effective permissions at the current time.
58    pub fn effective_permissions(&self) -> PermissionSet {
59        let base = self.permissions.effective_permissions();
60        let temporal = self.temporal_permissions.currently_effective();
61        base.combine(temporal)
62    }
63
64    /// Compute effective permissions at the given timestamp.
65    pub fn effective_permissions_at(&self, time: Timestamp) -> PermissionSet {
66        let base = self.permissions.effective_permissions();
67        let temporal = self.temporal_permissions.effective_at(time);
68        base.combine(temporal)
69    }
70
71    /// Returns `true` if the subject effectively has the given permission.
72    pub fn has_permission(&self, perm: &AtomicPermission) -> bool {
73        self.effective_permissions().contains(perm)
74    }
75
76    /// Grant a permission to this subject.
77    pub fn grant(&mut self, perm: AtomicPermission) {
78        self.permissions.grants.extend([perm]);
79    }
80
81    /// Remove a previously granted permission.
82    pub fn revoke(&mut self, perm: AtomicPermission) {
83        self.permissions.grants = self
84            .permissions
85            .grants
86            .difference(&PermissionSet::from([perm]));
87    }
88
89    /// Explicitly deny a permission for this subject.
90    pub fn deny(&mut self, perm: AtomicPermission) {
91        self.permissions.denials.extend([perm]);
92    }
93
94    /// Return a [`SubjectBuilder`] for constructing a subject.
95    pub fn builder() -> SubjectBuilder {
96        SubjectBuilder::default()
97    }
98}
99
100impl Default for SubjectBuilder {
101    fn default() -> Self {
102        Self {
103            id: None,
104            permissions: GrantDenialPair::empty(),
105            roles: Vec::new(),
106        }
107    }
108}
109/// Builder for constructing a [`Subject`] with validation.
110pub struct SubjectBuilder {
111    id: Option<String>,
112    permissions: GrantDenialPair,
113    roles: Vec<String>,
114}
115
116impl SubjectBuilder {
117    /// Set the subject identifier.
118    pub fn id(mut self, id: impl Into<String>) -> Self {
119        self.id = Some(id.into());
120        self
121    }
122
123    /// Add a role to the subject.
124    pub fn role(mut self, role: impl Into<String>) -> Self {
125        self.roles.push(role.into());
126        self
127    }
128
129    /// Grant a permission to the subject being built.
130    pub fn grant(mut self, perm: AtomicPermission) -> Self {
131        self.permissions.grants.extend([perm]);
132        self
133    }
134
135    /// Deny a permission for the subject being built.
136    pub fn deny(mut self, perm: AtomicPermission) -> Self {
137        self.permissions.denials.extend([perm]);
138        self
139    }
140
141    /// Build the [`Subject`], returning an error if required fields are missing.
142    pub fn build(self) -> Result<Subject, BuilderError> {
143        Ok(Subject {
144            id: self.id.ok_or(BuilderError::MissingField("id"))?,
145            permissions: self.permissions,
146            roles: self.roles,
147            temporal_permissions: TemporalPermissionSet::new(),
148        })
149    }
150}
151
152// MonoidAction: PermissionDelta acts on Subject
153impl MonoidAction<PermissionDelta, Subject> for PermissionDelta {
154    fn act(delta: PermissionDelta, mut subject: Subject) -> Subject {
155        subject.permissions.grants = delta.apply_to(subject.permissions.grants);
156        subject
157    }
158}
159
160// Implement HasPermissions for Subject
161impl HasPermissions for Subject {
162    fn permissions(&self) -> &GrantDenialPair {
163        &self.permissions
164    }
165
166    fn permissions_mut(&mut self) -> &mut GrantDenialPair {
167        &mut self.permissions
168    }
169
170    fn effective_permissions_at(&self, time: Timestamp) -> PermissionSet {
171        self.effective_permissions_at(time)
172    }
173}