role_system/
temporal.rs

1//! Temporal and time-based permission management.
2
3use crate::{
4    error::{Error, Result},
5    permission::Permission,
6};
7use chrono::{DateTime, Datelike, Duration, NaiveTime, Utc, Weekday};
8use chrono_tz::Tz;
9
10/// Time-based permission that includes temporal constraints.
11#[derive(Debug, Clone)]
12#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
13pub struct TemporalPermission {
14    /// The base permission
15    permission: Permission,
16    /// When this permission becomes valid
17    valid_from: Option<DateTime<Utc>>,
18    /// When this permission expires
19    valid_until: Option<DateTime<Utc>>,
20    /// Recurring schedule for this permission
21    schedule: Option<Schedule>,
22    /// Maximum usage count (for consumable permissions)
23    max_usage: Option<u32>,
24    /// Current usage count
25    usage_count: u32,
26}
27
28/// Recurring schedule for permissions.
29#[derive(Debug, Clone)]
30#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
31pub struct Schedule {
32    /// Days of the week this permission is active
33    weekdays: Vec<Weekday>,
34    /// Start time each day (in UTC)
35    start_time: NaiveTime,
36    /// End time each day (in UTC)
37    end_time: NaiveTime,
38    /// Time zone for schedule interpretation
39    timezone: String,
40}
41
42/// Time-based access control policy.
43#[derive(Debug, Clone)]
44#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
45pub struct TemporalPolicy {
46    /// Policy name
47    name: String,
48    /// Description
49    description: Option<String>,
50    /// List of temporal permissions
51    permissions: Vec<TemporalPermission>,
52    /// Policy effective date
53    effective_from: DateTime<Utc>,
54    /// Policy expiration date
55    expires_at: Option<DateTime<Utc>>,
56}
57
58impl TemporalPermission {
59    /// Create a new temporal permission.
60    pub fn new(permission: Permission) -> Self {
61        Self {
62            permission,
63            valid_from: None,
64            valid_until: None,
65            schedule: None,
66            max_usage: None,
67            usage_count: 0,
68        }
69    }
70
71    /// Set the valid from time.
72    pub fn valid_from(mut self, from: DateTime<Utc>) -> Self {
73        self.valid_from = Some(from);
74        self
75    }
76
77    /// Set the valid until time.
78    pub fn valid_until(mut self, until: DateTime<Utc>) -> Self {
79        self.valid_until = Some(until);
80        self
81    }
82
83    /// Set a recurring schedule.
84    pub fn with_schedule(mut self, schedule: Schedule) -> Self {
85        self.schedule = Some(schedule);
86        self
87    }
88
89    /// Set maximum usage count.
90    pub fn with_max_usage(mut self, max_usage: u32) -> Self {
91        self.max_usage = Some(max_usage);
92        self
93    }
94
95    /// Check if the permission is valid at the given time.
96    pub fn is_valid_at(&self, time: DateTime<Utc>) -> bool {
97        // Check absolute time bounds
98        if let Some(from) = self.valid_from
99            && time < from
100        {
101            return false;
102        }
103
104        if let Some(until) = self.valid_until
105            && time > until
106        {
107            return false;
108        }
109
110        // Check usage limits
111        if let Some(max_usage) = self.max_usage
112            && self.usage_count >= max_usage
113        {
114            return false;
115        }
116
117        // Check recurring schedule
118        if let Some(ref schedule) = self.schedule {
119            return schedule.is_valid_at(time);
120        }
121
122        true
123    }
124
125    /// Check if the permission is currently valid.
126    pub fn is_currently_valid(&self) -> bool {
127        self.is_valid_at(Utc::now())
128    }
129
130    /// Record usage of this permission.
131    pub fn record_usage(&mut self) -> Result<()> {
132        if let Some(max_usage) = self.max_usage
133            && self.usage_count >= max_usage
134        {
135            return Err(Error::ValidationError {
136                field: "usage_limit".to_string(),
137                reason: "Permission usage limit exceeded".to_string(),
138                invalid_value: Some(self.usage_count.to_string()),
139            });
140        }
141
142        self.usage_count += 1;
143        Ok(())
144    }
145
146    /// Get the underlying permission.
147    pub fn permission(&self) -> &Permission {
148        &self.permission
149    }
150
151    /// Get usage statistics.
152    pub fn usage_stats(&self) -> (u32, Option<u32>) {
153        (self.usage_count, self.max_usage)
154    }
155
156    /// Get time until this permission becomes valid (if not yet valid).
157    pub fn time_until_valid(&self) -> Option<Duration> {
158        if let Some(valid_from) = self.valid_from {
159            let now = Utc::now();
160            if now < valid_from {
161                return Some(valid_from - now);
162            }
163        }
164        None
165    }
166
167    /// Get time until this permission expires.
168    pub fn time_until_expiry(&self) -> Option<Duration> {
169        if let Some(valid_until) = self.valid_until {
170            let now = Utc::now();
171            if now < valid_until {
172                return Some(valid_until - now);
173            }
174        }
175        None
176    }
177}
178
179impl Schedule {
180    /// Create a new schedule.
181    pub fn new(
182        weekdays: Vec<Weekday>,
183        start_time: NaiveTime,
184        end_time: NaiveTime,
185        timezone: String,
186    ) -> Self {
187        Self {
188            weekdays,
189            start_time,
190            end_time,
191            timezone,
192        }
193    }
194
195    /// Create a business hours schedule (Monday-Friday, 9 AM - 5 PM).
196    pub fn business_hours(timezone: String) -> Self {
197        use Weekday::*;
198        Self::new(
199            vec![Mon, Tue, Wed, Thu, Fri],
200            NaiveTime::from_hms_opt(9, 0, 0).unwrap(),
201            NaiveTime::from_hms_opt(17, 0, 0).unwrap(),
202            timezone,
203        )
204    }
205
206    /// Create a 24/7 schedule.
207    pub fn always(timezone: String) -> Self {
208        use Weekday::*;
209        Self::new(
210            vec![Mon, Tue, Wed, Thu, Fri, Sat, Sun],
211            NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
212            NaiveTime::from_hms_opt(23, 59, 59).unwrap(),
213            timezone,
214        )
215    }
216
217    /// Create a weekend-only schedule.
218    pub fn weekends(timezone: String) -> Self {
219        use Weekday::*;
220        Self::new(
221            vec![Sat, Sun],
222            NaiveTime::from_hms_opt(0, 0, 0).unwrap(),
223            NaiveTime::from_hms_opt(23, 59, 59).unwrap(),
224            timezone,
225        )
226    }
227
228    /// Check if the schedule is valid at the given time.
229    pub fn is_valid_at(&self, time: DateTime<Utc>) -> bool {
230        // Convert UTC time to the target timezone using chrono-tz
231        let target_tz: Tz = self.timezone.parse().unwrap_or(chrono_tz::UTC);
232        let local_time = time.with_timezone(&target_tz);
233
234        let weekday = local_time.weekday();
235        if !self.weekdays.contains(&weekday) {
236            return false;
237        }
238
239        let time_of_day = local_time.time();
240        if self.start_time <= self.end_time {
241            // Same day schedule
242            time_of_day >= self.start_time && time_of_day <= self.end_time
243        } else {
244            // Overnight schedule (e.g., 22:00 - 06:00)
245            time_of_day >= self.start_time || time_of_day <= self.end_time
246        }
247    }
248}
249
250impl TemporalPolicy {
251    /// Create a new temporal policy.
252    pub fn new(name: String, effective_from: DateTime<Utc>) -> Self {
253        Self {
254            name,
255            description: None,
256            permissions: Vec::new(),
257            effective_from,
258            expires_at: None,
259        }
260    }
261
262    /// Get the policy name.
263    pub fn name(&self) -> &str {
264        &self.name
265    }
266
267    /// Get the policy description.
268    pub fn description(&self) -> Option<&str> {
269        self.description.as_deref()
270    }
271
272    /// Set the policy description.
273    pub fn with_description(mut self, description: String) -> Self {
274        self.description = Some(description);
275        self
276    }
277
278    /// Set the expiration time.
279    pub fn expires_at(mut self, expires_at: DateTime<Utc>) -> Self {
280        self.expires_at = Some(expires_at);
281        self
282    }
283
284    /// Add a temporal permission to this policy.
285    pub fn add_permission(mut self, permission: TemporalPermission) -> Self {
286        self.permissions.push(permission);
287        self
288    }
289
290    /// Check if the policy is currently active.
291    pub fn is_active(&self) -> bool {
292        self.is_active_at(Utc::now())
293    }
294
295    /// Check if the policy is active at the given time.
296    pub fn is_active_at(&self, time: DateTime<Utc>) -> bool {
297        if time < self.effective_from {
298            return false;
299        }
300
301        if let Some(expires_at) = self.expires_at
302            && time > expires_at
303        {
304            return false;
305        }
306
307        true
308    }
309
310    /// Get all valid permissions at the current time.
311    pub fn valid_permissions(&self) -> Vec<&Permission> {
312        self.valid_permissions_at(Utc::now())
313    }
314
315    /// Get all valid permissions at the given time.
316    pub fn valid_permissions_at(&self, time: DateTime<Utc>) -> Vec<&Permission> {
317        if !self.is_active_at(time) {
318            return Vec::new();
319        }
320
321        self.permissions
322            .iter()
323            .filter(|tp| tp.is_valid_at(time))
324            .map(|tp| tp.permission())
325            .collect()
326    }
327
328    /// Get policy statistics.
329    pub fn stats(&self) -> PolicyStats {
330        let total_permissions = self.permissions.len();
331        let currently_valid = self.valid_permissions().len();
332        let expired_permissions = self
333            .permissions
334            .iter()
335            .filter(|tp| !tp.is_currently_valid())
336            .count();
337
338        PolicyStats {
339            total_permissions,
340            currently_valid,
341            expired_permissions,
342            is_active: self.is_active(),
343        }
344    }
345}
346
347/// Statistics for a temporal policy.
348#[derive(Debug, Clone)]
349#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
350pub struct PolicyStats {
351    pub total_permissions: usize,
352    pub currently_valid: usize,
353    pub expired_permissions: usize,
354    pub is_active: bool,
355}
356
357/// Builder for creating temporal permissions easily.
358pub struct TemporalPermissionBuilder {
359    permission: Permission,
360}
361
362impl TemporalPermissionBuilder {
363    /// Create a new builder.
364    pub fn new(action: &str, resource_type: &str) -> Self {
365        Self {
366            permission: Permission::new(action, resource_type),
367        }
368    }
369
370    /// Set valid time range.
371    pub fn valid_between(self, from: DateTime<Utc>, until: DateTime<Utc>) -> TemporalPermission {
372        TemporalPermission::new(self.permission)
373            .valid_from(from)
374            .valid_until(until)
375    }
376
377    /// Set business hours schedule.
378    pub fn business_hours(self, timezone: String) -> TemporalPermission {
379        TemporalPermission::new(self.permission).with_schedule(Schedule::business_hours(timezone))
380    }
381
382    /// Set weekend schedule.
383    pub fn weekends_only(self, timezone: String) -> TemporalPermission {
384        TemporalPermission::new(self.permission).with_schedule(Schedule::weekends(timezone))
385    }
386
387    /// Set usage limit.
388    pub fn with_usage_limit(self, max_usage: u32) -> TemporalPermission {
389        TemporalPermission::new(self.permission).with_max_usage(max_usage)
390    }
391}
392
393#[cfg(test)]
394mod tests {
395    use super::*;
396    use chrono::TimeZone;
397
398    #[test]
399    fn test_temporal_permission_time_bounds() {
400        let permission = Permission::new("read", "documents");
401        let now = Utc::now();
402        let future = now + Duration::hours(1);
403        let past = now - Duration::hours(1);
404
405        let temp_perm = TemporalPermission::new(permission)
406            .valid_from(past)
407            .valid_until(future);
408
409        assert!(temp_perm.is_valid_at(now));
410        assert!(!temp_perm.is_valid_at(past - Duration::minutes(1)));
411        assert!(!temp_perm.is_valid_at(future + Duration::minutes(1)));
412    }
413
414    #[test]
415    fn test_usage_limits() {
416        let permission = Permission::new("delete", "files");
417        let mut temp_perm = TemporalPermission::new(permission).with_max_usage(2);
418
419        assert!(temp_perm.is_currently_valid());
420
421        temp_perm.record_usage().unwrap();
422        assert!(temp_perm.is_currently_valid());
423
424        temp_perm.record_usage().unwrap();
425        assert!(!temp_perm.is_currently_valid());
426
427        assert!(temp_perm.record_usage().is_err());
428    }
429
430    #[test]
431    fn test_business_hours_schedule() {
432        let schedule = Schedule::business_hours("UTC".to_string());
433
434        // Monday 10 AM should be valid
435        let monday_10am = Utc.with_ymd_and_hms(2024, 1, 1, 10, 0, 0).unwrap(); // Jan 1, 2024 was a Monday
436        assert!(schedule.is_valid_at(monday_10am));
437
438        // Saturday 10 AM should not be valid
439        let saturday_10am = Utc.with_ymd_and_hms(2024, 1, 6, 10, 0, 0).unwrap(); // Jan 6, 2024 was a Saturday
440        assert!(!schedule.is_valid_at(saturday_10am));
441
442        // Monday 6 PM should not be valid (after business hours)
443        let monday_6pm = Utc.with_ymd_and_hms(2024, 1, 1, 18, 0, 0).unwrap();
444        assert!(!schedule.is_valid_at(monday_6pm));
445    }
446
447    #[test]
448    fn test_temporal_policy() {
449        let now = Utc::now();
450        let permission = Permission::new("read", "documents");
451        let temp_perm = TemporalPermission::new(permission)
452            .valid_from(now - Duration::hours(1))
453            .valid_until(now + Duration::hours(1));
454
455        let policy = TemporalPolicy::new("test_policy".to_string(), now - Duration::hours(2))
456            .add_permission(temp_perm);
457
458        assert!(policy.is_active());
459        assert_eq!(policy.valid_permissions().len(), 1);
460
461        let stats = policy.stats();
462        assert_eq!(stats.total_permissions, 1);
463        assert_eq!(stats.currently_valid, 1);
464        assert!(stats.is_active);
465    }
466
467    #[test]
468    fn test_temporal_permission_builder() {
469        let now = Utc::now();
470        let temp_perm = TemporalPermissionBuilder::new("read", "documents")
471            .valid_between(now, now + Duration::hours(2));
472
473        assert!(temp_perm.is_currently_valid());
474        assert_eq!(temp_perm.permission().action(), "read");
475    }
476}