1use 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#[derive(Debug, Clone)]
15#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
16pub struct Role {
17 id: String,
19 name: String,
21 description: Option<String>,
23 permissions: PermissionSet,
25 metadata: HashMap<String, String>,
27 active: bool,
29}
30
31impl Role {
32 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 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 pub fn id(&self) -> &str {
54 &self.id
55 }
56
57 pub fn name(&self) -> &str {
59 &self.name
60 }
61
62 pub fn with_description(mut self, description: impl Into<String>) -> Self {
64 self.description = Some(description.into());
65 self
66 }
67
68 pub fn description(&self) -> Option<&str> {
70 self.description.as_deref()
71 }
72
73 pub fn add_permission(mut self, permission: Permission) -> Self {
75 self.permissions.add(permission);
76 self
77 }
78
79 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 pub fn remove_permission(&mut self, permission: &Permission) {
89 self.permissions.remove(permission);
90 }
91
92 pub fn has_permission_exact(&self, permission: &Permission) -> bool {
94 self.permissions.contains(permission)
95 }
96
97 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 pub fn permissions(&self) -> &PermissionSet {
112 &self.permissions
113 }
114
115 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 pub fn metadata(&self, key: &str) -> Option<&str> {
123 self.metadata.get(key).map(|s| s.as_str())
124 }
125
126 pub fn all_metadata(&self) -> &HashMap<String, String> {
128 &self.metadata
129 }
130
131 pub fn set_active(&mut self, active: bool) {
133 self.active = active;
134 }
135
136 pub fn is_active(&self) -> bool {
138 self.active
139 }
140
141 pub fn deactivate(mut self) -> Self {
143 self.active = false;
144 self
145 }
146
147 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#[derive(Debug, Clone)]
157#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
158pub struct RoleElevation {
159 role_name: String,
161 #[cfg_attr(feature = "persistence", serde(with = "instant_serde"))]
163 created_at: Instant,
164 duration: Option<Duration>,
166 reason: Option<String>,
168}
169
170impl RoleElevation {
171 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 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 pub fn role_name(&self) -> &str {
193 &self.role_name
194 }
195
196 pub fn created_at(&self) -> Instant {
198 self.created_at
199 }
200
201 pub fn duration(&self) -> Option<Duration> {
203 self.duration
204 }
205
206 pub fn reason(&self) -> Option<&str> {
208 self.reason.as_deref()
209 }
210
211 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 }
218 }
219
220 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 }
232 }
233}
234
235#[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 pub fn new() -> Self {
248 Self {
249 active: true,
250 ..Default::default()
251 }
252 }
253
254 pub fn name(mut self, name: impl Into<String>) -> Self {
256 self.name = Some(name.into());
257 self
258 }
259
260 pub fn description(mut self, description: impl Into<String>) -> Self {
262 self.description = Some(description.into());
263 self
264 }
265
266 pub fn permission(mut self, permission: Permission) -> Self {
268 self.permissions.push(permission);
269 self
270 }
271
272 pub fn permissions(mut self, permissions: impl IntoIterator<Item = Permission>) -> Self {
274 self.permissions.extend(permissions);
275 self
276 }
277
278 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 pub fn active(mut self, active: bool) -> Self {
286 self.active = active;
287 self
288 }
289
290 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#[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 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 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}