role_system/
role.rs

1//! Role definitions and management.
2
3use crate::{
4    error::{Error, Result},
5    permission::{Permission, PermissionSet},
6};
7use std::{
8    collections::HashMap,
9    time::{Duration, Instant},
10};
11use uuid::Uuid;
12
13/// A role represents a collection of permissions that can be assigned to subjects.
14#[derive(Debug, Clone)]
15#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
16pub struct Role {
17    /// Unique identifier for the role.
18    id: String,
19    /// Human-readable name of the role.
20    name: String,
21    /// Optional description of the role.
22    description: Option<String>,
23    /// Permissions granted by this role.
24    permissions: PermissionSet,
25    /// Metadata associated with the role.
26    metadata: HashMap<String, String>,
27    /// Whether this role is active.
28    active: bool,
29}
30
31impl Role {
32    /// Create a new role with the given name.
33    pub fn new(name: impl Into<String>) -> Self {
34        let name = name.into();
35        Self {
36            id: Uuid::new_v4().to_string(),
37            name,
38            description: None,
39            permissions: PermissionSet::new(),
40            metadata: HashMap::new(),
41            active: true,
42        }
43    }
44
45    /// Create a new role with a specific ID.
46    pub fn with_id(id: impl Into<String>, name: impl Into<String>) -> Self {
47        let mut role = Self::new(name);
48        role.id = id.into();
49        role
50    }
51
52    /// Get the role's unique identifier.
53    pub fn id(&self) -> &str {
54        &self.id
55    }
56
57    /// Get the role's name.
58    pub fn name(&self) -> &str {
59        &self.name
60    }
61
62    /// Set the role's description.
63    pub fn with_description(mut self, description: impl Into<String>) -> Self {
64        self.description = Some(description.into());
65        self
66    }
67
68    /// Get the role's description.
69    pub fn description(&self) -> Option<&str> {
70        self.description.as_deref()
71    }
72
73    /// Add a permission to this role.
74    pub fn add_permission(mut self, permission: Permission) -> Self {
75        self.permissions.add(permission);
76        self
77    }
78
79    /// Add multiple permissions to this role.
80    pub fn add_permissions(mut self, permissions: impl IntoIterator<Item = Permission>) -> Self {
81        for permission in permissions {
82            self.permissions.add(permission);
83        }
84        self
85    }
86
87    /// Remove a permission from this role.
88    pub fn remove_permission(&mut self, permission: &Permission) {
89        self.permissions.remove(permission);
90    }
91
92    /// Check if this role has a specific permission.
93    pub fn has_permission_exact(&self, permission: &Permission) -> bool {
94        self.permissions.contains(permission)
95    }
96
97    /// Check if this role grants permission for an action on a resource type.
98    pub fn has_permission(
99        &self,
100        action: &str,
101        resource_type: &str,
102        context: &HashMap<String, String>,
103    ) -> bool {
104        if !self.active {
105            return false;
106        }
107        self.permissions.grants(action, resource_type, context)
108    }
109
110    /// Get all permissions granted by this role.
111    pub fn permissions(&self) -> &PermissionSet {
112        &self.permissions
113    }
114
115    /// Set metadata for this role.
116    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
117        self.metadata.insert(key.into(), value.into());
118        self
119    }
120
121    /// Get metadata value for a key.
122    pub fn metadata(&self, key: &str) -> Option<&str> {
123        self.metadata.get(key).map(|s| s.as_str())
124    }
125
126    /// Get all metadata.
127    pub fn all_metadata(&self) -> &HashMap<String, String> {
128        &self.metadata
129    }
130
131    /// Set whether this role is active.
132    pub fn set_active(&mut self, active: bool) {
133        self.active = active;
134    }
135
136    /// Check if this role is active.
137    pub fn is_active(&self) -> bool {
138        self.active
139    }
140
141    /// Deactivate this role.
142    pub fn deactivate(mut self) -> Self {
143        self.active = false;
144        self
145    }
146
147    /// Merge permissions from another role into this one.
148    pub fn merge_permissions(&mut self, other: &Role) {
149        for permission in other.permissions() {
150            self.permissions.add(permission.clone());
151        }
152    }
153}
154
155/// A temporary role elevation for a subject.
156#[derive(Debug, Clone)]
157#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
158pub struct RoleElevation {
159    /// The role being elevated to.
160    role_name: String,
161    /// When the elevation was created.
162    #[cfg_attr(feature = "persistence", serde(with = "instant_serde"))]
163    created_at: Instant,
164    /// How long the elevation lasts (None for permanent).
165    duration: Option<Duration>,
166    /// Reason for the elevation.
167    reason: Option<String>,
168}
169
170impl RoleElevation {
171    /// Create a new role elevation.
172    pub fn new(role_name: String, duration: Option<Duration>) -> Self {
173        Self {
174            role_name,
175            created_at: Instant::now(),
176            duration,
177            reason: None,
178        }
179    }
180
181    /// Create a role elevation with a reason.
182    pub fn with_reason(role_name: String, duration: Option<Duration>, reason: String) -> Self {
183        Self {
184            role_name,
185            created_at: Instant::now(),
186            duration,
187            reason: Some(reason),
188        }
189    }
190
191    /// Get the role name being elevated to.
192    pub fn role_name(&self) -> &str {
193        &self.role_name
194    }
195
196    /// Get when the elevation was created.
197    pub fn created_at(&self) -> Instant {
198        self.created_at
199    }
200
201    /// Get the duration of the elevation.
202    pub fn duration(&self) -> Option<Duration> {
203        self.duration
204    }
205
206    /// Get the reason for the elevation.
207    pub fn reason(&self) -> Option<&str> {
208        self.reason.as_deref()
209    }
210
211    /// Check if the elevation has expired.
212    pub fn is_expired(&self, now: Instant) -> bool {
213        if let Some(duration) = self.duration {
214            now.duration_since(self.created_at) > duration
215        } else {
216            false // Permanent elevation
217        }
218    }
219
220    /// Get the time remaining for this elevation.
221    pub fn time_remaining(&self, now: Instant) -> Option<Duration> {
222        if let Some(duration) = self.duration {
223            let elapsed = now.duration_since(self.created_at);
224            if elapsed < duration {
225                Some(duration - elapsed)
226            } else {
227                Some(Duration::ZERO)
228            }
229        } else {
230            None // Permanent elevation
231        }
232    }
233}
234
235/// Builder for creating roles with a fluent API.
236#[derive(Debug, Default)]
237pub struct RoleBuilder {
238    name: Option<String>,
239    description: Option<String>,
240    permissions: Vec<Permission>,
241    metadata: HashMap<String, String>,
242    active: bool,
243}
244
245impl RoleBuilder {
246    /// Create a new role builder.
247    pub fn new() -> Self {
248        Self {
249            active: true,
250            ..Default::default()
251        }
252    }
253
254    /// Set the role name.
255    pub fn name(mut self, name: impl Into<String>) -> Self {
256        self.name = Some(name.into());
257        self
258    }
259
260    /// Set the role description.
261    pub fn description(mut self, description: impl Into<String>) -> Self {
262        self.description = Some(description.into());
263        self
264    }
265
266    /// Add a permission to the role.
267    pub fn permission(mut self, permission: Permission) -> Self {
268        self.permissions.push(permission);
269        self
270    }
271
272    /// Add multiple permissions to the role.
273    pub fn permissions(mut self, permissions: impl IntoIterator<Item = Permission>) -> Self {
274        self.permissions.extend(permissions);
275        self
276    }
277
278    /// Add metadata to the role.
279    pub fn metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
280        self.metadata.insert(key.into(), value.into());
281        self
282    }
283
284    /// Set whether the role is active.
285    pub fn active(mut self, active: bool) -> Self {
286        self.active = active;
287        self
288    }
289
290    /// Build the role.
291    pub fn build(self) -> Result<Role> {
292        let name = self.name.ok_or_else(|| {
293            Error::InvalidConfiguration("Role name is required".to_string())
294        })?;
295
296        let mut role = Role::new(name);
297        
298        if let Some(description) = self.description {
299            role = role.with_description(description);
300        }
301
302        for permission in self.permissions {
303            role = role.add_permission(permission);
304        }
305
306        for (key, value) in self.metadata {
307            role = role.with_metadata(key, value);
308        }
309
310        role.set_active(self.active);
311
312        Ok(role)
313    }
314}
315
316// Helper module for serializing Instant with serde
317#[cfg(feature = "persistence")]
318mod instant_serde {
319    use serde::{Deserialize, Deserializer, Serialize, Serializer};
320    use std::time::{Instant, SystemTime, UNIX_EPOCH};
321
322    pub fn serialize<S>(_instant: &Instant, serializer: S) -> Result<S::Ok, S::Error>
323    where
324        S: Serializer,
325    {
326        // Convert Instant to a duration since a reference point
327        // Note: This is a simplified approach and may not work across process restarts
328        let duration_since_epoch = SystemTime::now()
329            .duration_since(UNIX_EPOCH)
330            .unwrap()
331            .as_nanos();
332        duration_since_epoch.serialize(serializer)
333    }
334
335    pub fn deserialize<'de, D>(deserializer: D) -> Result<Instant, D::Error>
336    where
337        D: Deserializer<'de>,
338    {
339        let _nanos = u128::deserialize(deserializer)?;
340        // This is a simplified approach - in a real implementation you'd want
341        // to store a reference point and calculate relative to that
342        Ok(Instant::now())
343    }
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349    use crate::permission::Permission;
350
351    #[test]
352    fn test_role_creation() {
353        let role = Role::new("admin")
354            .with_description("Administrator role")
355            .add_permission(Permission::new("read", "documents"))
356            .add_permission(Permission::new("write", "documents"));
357
358        assert_eq!(role.name(), "admin");
359        assert_eq!(role.description(), Some("Administrator role"));
360        assert_eq!(role.permissions().len(), 2);
361        assert!(role.is_active());
362    }
363
364    #[test]
365    fn test_role_permissions() {
366        let role = Role::new("reader")
367            .add_permission(Permission::new("read", "documents"));
368
369        let context = HashMap::new();
370        assert!(role.has_permission("read", "documents", &context));
371        assert!(!role.has_permission("write", "documents", &context));
372    }
373
374    #[test]
375    fn test_role_builder() {
376        let role = RoleBuilder::new()
377            .name("test-role")
378            .description("A test role")
379            .permission(Permission::new("read", "documents"))
380            .metadata("department", "IT")
381            .active(true)
382            .build()
383            .unwrap();
384
385        assert_eq!(role.name(), "test-role");
386        assert_eq!(role.description(), Some("A test role"));
387        assert_eq!(role.metadata("department"), Some("IT"));
388        assert!(role.is_active());
389    }
390
391    #[test]
392    fn test_role_elevation() {
393        let elevation = RoleElevation::new("admin".to_string(), Some(Duration::from_secs(3600)));
394        
395        assert_eq!(elevation.role_name(), "admin");
396        assert_eq!(elevation.duration(), Some(Duration::from_secs(3600)));
397        assert!(!elevation.is_expired(Instant::now()));
398    }
399
400    #[test]
401    fn test_inactive_role_permissions() {
402        let mut role = Role::new("inactive")
403            .add_permission(Permission::new("read", "documents"));
404        
405        role.set_active(false);
406        
407        let context = HashMap::new();
408        assert!(!role.has_permission("read", "documents", &context));
409    }
410}