1use crate::{
4 error::{Error, Result},
5 permission::Permission,
6};
7use chrono::{DateTime, Datelike, Duration, NaiveTime, Utc, Weekday};
8use chrono_tz::Tz;
9
10#[derive(Debug, Clone)]
12#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
13pub struct TemporalPermission {
14 permission: Permission,
16 valid_from: Option<DateTime<Utc>>,
18 valid_until: Option<DateTime<Utc>>,
20 schedule: Option<Schedule>,
22 max_usage: Option<u32>,
24 usage_count: u32,
26}
27
28#[derive(Debug, Clone)]
30#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
31pub struct Schedule {
32 weekdays: Vec<Weekday>,
34 start_time: NaiveTime,
36 end_time: NaiveTime,
38 timezone: String,
40}
41
42#[derive(Debug, Clone)]
44#[cfg_attr(feature = "persistence", derive(serde::Serialize, serde::Deserialize))]
45pub struct TemporalPolicy {
46 name: String,
48 description: Option<String>,
50 permissions: Vec<TemporalPermission>,
52 effective_from: DateTime<Utc>,
54 expires_at: Option<DateTime<Utc>>,
56}
57
58impl TemporalPermission {
59 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 pub fn valid_from(mut self, from: DateTime<Utc>) -> Self {
73 self.valid_from = Some(from);
74 self
75 }
76
77 pub fn valid_until(mut self, until: DateTime<Utc>) -> Self {
79 self.valid_until = Some(until);
80 self
81 }
82
83 pub fn with_schedule(mut self, schedule: Schedule) -> Self {
85 self.schedule = Some(schedule);
86 self
87 }
88
89 pub fn with_max_usage(mut self, max_usage: u32) -> Self {
91 self.max_usage = Some(max_usage);
92 self
93 }
94
95 pub fn is_valid_at(&self, time: DateTime<Utc>) -> bool {
97 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 if let Some(max_usage) = self.max_usage
112 && self.usage_count >= max_usage
113 {
114 return false;
115 }
116
117 if let Some(ref schedule) = self.schedule {
119 return schedule.is_valid_at(time);
120 }
121
122 true
123 }
124
125 pub fn is_currently_valid(&self) -> bool {
127 self.is_valid_at(Utc::now())
128 }
129
130 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 pub fn permission(&self) -> &Permission {
148 &self.permission
149 }
150
151 pub fn usage_stats(&self) -> (u32, Option<u32>) {
153 (self.usage_count, self.max_usage)
154 }
155
156 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 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 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 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 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 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 pub fn is_valid_at(&self, time: DateTime<Utc>) -> bool {
230 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 time_of_day >= self.start_time && time_of_day <= self.end_time
243 } else {
244 time_of_day >= self.start_time || time_of_day <= self.end_time
246 }
247 }
248}
249
250impl TemporalPolicy {
251 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 pub fn name(&self) -> &str {
264 &self.name
265 }
266
267 pub fn description(&self) -> Option<&str> {
269 self.description.as_deref()
270 }
271
272 pub fn with_description(mut self, description: String) -> Self {
274 self.description = Some(description);
275 self
276 }
277
278 pub fn expires_at(mut self, expires_at: DateTime<Utc>) -> Self {
280 self.expires_at = Some(expires_at);
281 self
282 }
283
284 pub fn add_permission(mut self, permission: TemporalPermission) -> Self {
286 self.permissions.push(permission);
287 self
288 }
289
290 pub fn is_active(&self) -> bool {
292 self.is_active_at(Utc::now())
293 }
294
295 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 pub fn valid_permissions(&self) -> Vec<&Permission> {
312 self.valid_permissions_at(Utc::now())
313 }
314
315 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 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#[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
357pub struct TemporalPermissionBuilder {
359 permission: Permission,
360}
361
362impl TemporalPermissionBuilder {
363 pub fn new(action: &str, resource_type: &str) -> Self {
365 Self {
366 permission: Permission::new(action, resource_type),
367 }
368 }
369
370 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 pub fn business_hours(self, timezone: String) -> TemporalPermission {
379 TemporalPermission::new(self.permission).with_schedule(Schedule::business_hours(timezone))
380 }
381
382 pub fn weekends_only(self, timezone: String) -> TemporalPermission {
384 TemporalPermission::new(self.permission).with_schedule(Schedule::weekends(timezone))
385 }
386
387 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 let monday_10am = Utc.with_ymd_and_hms(2024, 1, 1, 10, 0, 0).unwrap(); assert!(schedule.is_valid_at(monday_10am));
437
438 let saturday_10am = Utc.with_ymd_and_hms(2024, 1, 6, 10, 0, 0).unwrap(); assert!(!schedule.is_valid_at(saturday_10am));
441
442 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}