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