1use 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#[derive(Debug, Clone)]
14#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
15pub struct TemporalPermission {
16 permission: Permission,
18 valid_from: Option<DateTime<Utc>>,
20 valid_until: Option<DateTime<Utc>>,
22 schedule: Option<Schedule>,
24 max_usage: Option<u32>,
26 usage_count: u32,
28}
29
30#[derive(Debug, Clone)]
32#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
33pub struct Schedule {
34 weekdays: Vec<Weekday>,
36 start_time: NaiveTime,
38 end_time: NaiveTime,
40 timezone: String,
42}
43
44#[derive(Debug, Clone)]
46#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
47pub struct TemporalPolicy {
48 name: String,
50 description: Option<String>,
52 permissions: Vec<TemporalPermission>,
54 effective_from: DateTime<Utc>,
56 expires_at: Option<DateTime<Utc>>,
58}
59
60impl TemporalPermission {
61 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 pub fn valid_from(mut self, from: DateTime<Utc>) -> Self {
75 self.valid_from = Some(from);
76 self
77 }
78
79 pub fn valid_until(mut self, until: DateTime<Utc>) -> Self {
81 self.valid_until = Some(until);
82 self
83 }
84
85 pub fn with_schedule(mut self, schedule: Schedule) -> Self {
87 self.schedule = Some(schedule);
88 self
89 }
90
91 pub fn with_max_usage(mut self, max_usage: u32) -> Self {
93 self.max_usage = Some(max_usage);
94 self
95 }
96
97 pub fn is_valid_at(&self, time: DateTime<Utc>) -> bool {
99 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 if let Some(max_usage) = self.max_usage
114 && self.usage_count >= max_usage
115 {
116 return false;
117 }
118
119 if let Some(ref schedule) = self.schedule {
121 return schedule.is_valid_at(time);
122 }
123
124 true
125 }
126
127 pub fn is_currently_valid(&self) -> bool {
129 self.is_valid_at(Utc::now())
130 }
131
132 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 pub fn permission(&self) -> &Permission {
150 &self.permission
151 }
152
153 pub fn usage_stats(&self) -> (u32, Option<u32>) {
155 (self.usage_count, self.max_usage)
156 }
157
158 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 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 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 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 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 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 pub fn is_valid_at(&self, time: DateTime<Utc>) -> bool {
232 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 time_of_day >= self.start_time && time_of_day <= self.end_time
245 } else {
246 time_of_day >= self.start_time || time_of_day <= self.end_time
248 }
249 }
250}
251
252impl TemporalPolicy {
253 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 pub fn name(&self) -> &str {
266 &self.name
267 }
268
269 pub fn description(&self) -> Option<&str> {
271 self.description.as_deref()
272 }
273
274 pub fn with_description(mut self, description: String) -> Self {
276 self.description = Some(description);
277 self
278 }
279
280 pub fn expires_at(mut self, expires_at: DateTime<Utc>) -> Self {
282 self.expires_at = Some(expires_at);
283 self
284 }
285
286 pub fn add_permission(mut self, permission: TemporalPermission) -> Self {
288 self.permissions.push(permission);
289 self
290 }
291
292 pub fn is_active(&self) -> bool {
294 self.is_active_at(Utc::now())
295 }
296
297 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 pub fn valid_permissions(&self) -> Vec<&Permission> {
314 self.valid_permissions_at(Utc::now())
315 }
316
317 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 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#[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
359pub struct TemporalPermissionBuilder {
361 permission: Permission,
362}
363
364impl TemporalPermissionBuilder {
365 pub fn new(action: &str, resource_type: &str) -> Self {
367 Self {
368 permission: Permission::new(action, resource_type),
369 }
370 }
371
372 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 pub fn business_hours(self, timezone: String) -> TemporalPermission {
381 TemporalPermission::new(self.permission).with_schedule(Schedule::business_hours(timezone))
382 }
383
384 pub fn weekends_only(self, timezone: String) -> TemporalPermission {
386 TemporalPermission::new(self.permission).with_schedule(Schedule::weekends(timezone))
387 }
388
389 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 let monday_10am = Utc.with_ymd_and_hms(2024, 1, 1, 10, 0, 0).unwrap(); assert!(schedule.is_valid_at(monday_10am));
439
440 let saturday_10am = Utc.with_ymd_and_hms(2024, 1, 6, 10, 0, 0).unwrap(); assert!(!schedule.is_valid_at(saturday_10am));
443
444 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}