1use crate::security::{
11 justification_storage::{AccessJustification, JustificationStorage},
12 mfa_tracking::{MfaStatus, MfaStorage},
13};
14use crate::Error;
15use chrono::{DateTime, Duration, Utc};
16use serde::{Deserialize, Serialize};
17use std::collections::HashMap;
18use uuid::Uuid;
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
22#[serde(rename_all = "lowercase")]
23pub enum PrivilegedRole {
24 Admin,
26 Owner,
28 ServiceAccount,
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(rename_all = "lowercase")]
35pub enum RequestStatus {
36 PendingManager,
38 PendingSecurity,
40 Approved,
42 Denied,
44 Cancelled,
46}
47
48#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
50#[serde(rename_all = "snake_case")]
51pub enum PrivilegedActionType {
52 UserCreate,
54 UserDelete,
56 UserModify,
58 RoleAssign,
60 RoleRevoke,
62 RoleEscalate,
64 PermissionGrant,
66 PermissionRevoke,
68 ConfigModify,
70 SecurityPolicyChange,
72 SecuritySettingChange,
74 AuditLogAccess,
76 DataExport,
78 DataDelete,
80 Other,
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct PrivilegedAccessRequest {
87 pub request_id: Uuid,
89 pub user_id: Uuid,
91 pub requested_role: PrivilegedRole,
93 pub justification: String,
95 pub business_need: Option<String>,
97 pub manager_approval: Option<Uuid>,
99 pub security_approval: Option<Uuid>,
101 pub status: RequestStatus,
103 pub created_at: DateTime<Utc>,
105 pub updated_at: DateTime<Utc>,
107 pub expires_at: Option<DateTime<Utc>>,
109}
110
111impl PrivilegedAccessRequest {
112 pub fn new(
114 user_id: Uuid,
115 requested_role: PrivilegedRole,
116 justification: String,
117 business_need: Option<String>,
118 manager_approval: Option<Uuid>,
119 ) -> Self {
120 let now = Utc::now();
121 Self {
122 request_id: Uuid::new_v4(),
123 user_id,
124 requested_role,
125 justification,
126 business_need,
127 manager_approval,
128 security_approval: None,
129 status: RequestStatus::PendingManager,
130 created_at: now,
131 updated_at: now,
132 expires_at: None,
133 }
134 }
135
136 pub fn is_approved(&self) -> bool {
138 self.status == RequestStatus::Approved
139 }
140
141 pub fn is_expired(&self) -> bool {
143 self.expires_at
144 .map(|exp| Utc::now() > exp)
145 .unwrap_or(false)
146 }
147}
148
149
150#[derive(Debug, Clone, Serialize, Deserialize)]
152pub struct PrivilegedAction {
153 pub action_id: Uuid,
155 pub user_id: Uuid,
157 pub action_type: PrivilegedActionType,
159 pub resource: Option<String>,
161 pub details: Option<String>,
163 pub ip_address: Option<String>,
165 pub user_agent: Option<String>,
167 pub session_id: Option<String>,
169 pub timestamp: DateTime<Utc>,
171}
172
173#[derive(Debug, Clone, Serialize, Deserialize)]
175pub struct PrivilegedSession {
176 pub session_id: String,
178 pub user_id: Uuid,
180 pub role: PrivilegedRole,
182 pub started_at: DateTime<Utc>,
184 pub last_activity: DateTime<Utc>,
186 pub ip_address: Option<String>,
188 pub user_agent: Option<String>,
190 pub is_active: bool,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize)]
196pub struct PrivilegedAccessConfig {
197 pub require_mfa: bool,
199 pub mfa_grace_period_days: u64,
201 pub auto_suspend_no_mfa: bool,
203 pub session_timeout_minutes: u64,
205 pub max_concurrent_sessions: u32,
207 pub record_sensitive_actions: bool,
209 pub monitor_activity: bool,
211 pub sensitive_actions: Vec<PrivilegedActionType>,
213}
214
215impl Default for PrivilegedAccessConfig {
216 fn default() -> Self {
217 Self {
218 require_mfa: true,
219 mfa_grace_period_days: 7,
220 auto_suspend_no_mfa: true,
221 session_timeout_minutes: 30,
222 max_concurrent_sessions: 2,
223 record_sensitive_actions: true,
224 monitor_activity: true,
225 sensitive_actions: vec![
226 PrivilegedActionType::UserDelete,
227 PrivilegedActionType::RoleEscalate,
228 PrivilegedActionType::SecurityPolicyChange,
229 PrivilegedActionType::DataExport,
230 PrivilegedActionType::AuditLogAccess,
231 ],
232 }
233 }
234}
235
236pub struct PrivilegedAccessManager {
240 config: PrivilegedAccessConfig,
241 mfa_storage: Option<Arc<dyn MfaStorage>>,
242 justification_storage: Option<Arc<dyn JustificationStorage>>,
243 sessions: Arc<RwLock<HashMap<String, PrivilegedSession>>>,
245 actions: Arc<RwLock<Vec<PrivilegedAction>>>,
247 requests: Arc<RwLock<HashMap<Uuid, PrivilegedAccessRequest>>>,
249}
250
251impl PrivilegedAccessManager {
252 pub fn new(
254 config: PrivilegedAccessConfig,
255 mfa_storage: Option<Arc<dyn MfaStorage>>,
256 justification_storage: Option<Arc<dyn JustificationStorage>>,
257 ) -> Self {
258 Self {
259 config,
260 mfa_storage,
261 justification_storage,
262 sessions: Arc::new(RwLock::new(HashMap::new())),
263 actions: Arc::new(RwLock::new(Vec::new())),
264 requests: Arc::new(RwLock::new(HashMap::new())),
265 }
266 }
267
268 pub async fn request_privileged_access(
270 &self,
271 user_id: Uuid,
272 requested_role: PrivilegedRole,
273 justification: String,
274 business_need: Option<String>,
275 manager_approval: Option<Uuid>,
276 ) -> Result<PrivilegedAccessRequest, Error> {
277 let request = PrivilegedAccessRequest::new(
278 user_id,
279 requested_role,
280 justification,
281 business_need,
282 manager_approval,
283 );
284
285 let mut requests = self.requests.write().await;
286 requests.insert(request.request_id, request.clone());
287
288 Ok(request)
289 }
290
291 pub async fn approve_manager(
293 &self,
294 request_id: Uuid,
295 approver_id: Uuid,
296 ) -> Result<(), Error> {
297 let mut requests = self.requests.write().await;
298 let request = requests
299 .get_mut(&request_id)
300 .ok_or_else(|| Error::Generic("Request not found".to_string()))?;
301
302 if request.status != RequestStatus::PendingManager {
303 return Err(Error::Generic("Request is not pending manager approval".to_string()));
304 }
305
306 request.manager_approval = Some(approver_id);
307 request.status = RequestStatus::PendingSecurity;
308 request.updated_at = Utc::now();
309
310 Ok(())
311 }
312
313 pub async fn approve_security(
315 &self,
316 request_id: Uuid,
317 approver_id: Uuid,
318 expiration_days: u64,
319 ) -> Result<(), Error> {
320 let mut requests = self.requests.write().await;
321 let request = requests
322 .get_mut(&request_id)
323 .ok_or_else(|| Error::Generic("Request not found".to_string()))?;
324
325 if request.status != RequestStatus::PendingSecurity {
326 return Err(Error::Generic("Request is not pending security approval".to_string()));
327 }
328
329 request.security_approval = Some(approver_id);
330 request.status = RequestStatus::Approved;
331 request.expires_at = Some(Utc::now() + Duration::days(expiration_days as i64));
332 request.updated_at = Utc::now();
333
334 if let Some(ref just_storage) = self.justification_storage {
336 let justification = AccessJustification::new(
337 request.user_id,
338 request.justification.clone(),
339 request.business_need.clone(),
340 request.manager_approval,
341 request.expires_at,
342 );
343 just_storage.set_justification(justification).await?;
344 }
345
346 Ok(())
347 }
348
349 pub async fn deny_request(
351 &self,
352 request_id: Uuid,
353 reason: String,
354 ) -> Result<(), Error> {
355 let mut requests = self.requests.write().await;
356 let request = requests
357 .get_mut(&request_id)
358 .ok_or_else(|| Error::Generic("Request not found".to_string()))?;
359
360 request.status = RequestStatus::Denied;
361 request.updated_at = Utc::now();
362
363 Ok(())
364 }
365
366 pub async fn check_mfa_compliance(&self, user_id: Uuid) -> Result<bool, Error> {
368 if !self.config.require_mfa {
369 return Ok(true);
370 }
371
372 if let Some(ref mfa_storage) = self.mfa_storage {
373 let mfa_status = mfa_storage.get_mfa_status(user_id).await?;
374 Ok(mfa_status.map(|s| s.enabled).unwrap_or(false))
375 } else {
376 Ok(true)
378 }
379 }
380
381 pub async fn record_action(
383 &self,
384 user_id: Uuid,
385 action_type: PrivilegedActionType,
386 resource: Option<String>,
387 details: Option<String>,
388 ip_address: Option<String>,
389 user_agent: Option<String>,
390 session_id: Option<String>,
391 ) -> Result<Uuid, Error> {
392 let action = PrivilegedAction {
393 action_id: Uuid::new_v4(),
394 user_id,
395 action_type,
396 resource,
397 details,
398 ip_address,
399 user_agent,
400 session_id,
401 timestamp: Utc::now(),
402 };
403
404 let mut actions = self.actions.write().await;
405 actions.push(action.clone());
406
407 if self.config.sensitive_actions.contains(&action_type) {
409 }
411
412 Ok(action.action_id)
413 }
414
415 pub async fn start_session(
417 &self,
418 session_id: String,
419 user_id: Uuid,
420 role: PrivilegedRole,
421 ip_address: Option<String>,
422 user_agent: Option<String>,
423 ) -> Result<(), Error> {
424 if !self.check_mfa_compliance(user_id).await? {
426 if self.config.auto_suspend_no_mfa {
427 return Err(Error::Generic("MFA not enabled for privileged user".to_string()));
428 }
429 }
430
431 let sessions = self.sessions.read().await;
433 let active_sessions = sessions
434 .values()
435 .filter(|s| s.user_id == user_id && s.is_active)
436 .count();
437
438 if active_sessions >= self.config.max_concurrent_sessions as usize {
439 return Err(Error::Generic("Maximum concurrent sessions reached".to_string()));
440 }
441 drop(sessions);
442
443 let session = PrivilegedSession {
444 session_id: session_id.clone(),
445 user_id,
446 role,
447 started_at: Utc::now(),
448 last_activity: Utc::now(),
449 ip_address,
450 user_agent,
451 is_active: true,
452 };
453
454 let mut sessions = self.sessions.write().await;
455 sessions.insert(session_id, session);
456
457 Ok(())
458 }
459
460 pub async fn update_session_activity(&self, session_id: &str) -> Result<(), Error> {
462 let mut sessions = self.sessions.write().await;
463 if let Some(session) = sessions.get_mut(session_id) {
464 session.last_activity = Utc::now();
465 }
466 Ok(())
467 }
468
469 pub async fn end_session(&self, session_id: &str) -> Result<(), Error> {
471 let mut sessions = self.sessions.write().await;
472 if let Some(session) = sessions.get_mut(session_id) {
473 session.is_active = false;
474 }
475 Ok(())
476 }
477
478 pub async fn cleanup_expired_sessions(&self) -> Result<Vec<String>, Error> {
480 let timeout = Duration::minutes(self.config.session_timeout_minutes as i64);
481 let now = Utc::now();
482 let mut expired = Vec::new();
483
484 let mut sessions = self.sessions.write().await;
485 for (session_id, session) in sessions.iter_mut() {
486 if session.is_active && (now - session.last_activity) > timeout {
487 session.is_active = false;
488 expired.push(session_id.clone());
489 }
490 }
491
492 Ok(expired)
493 }
494
495 pub async fn get_user_actions(&self, user_id: Uuid) -> Result<Vec<PrivilegedAction>, Error> {
497 let actions = self.actions.read().await;
498 Ok(actions
499 .iter()
500 .filter(|a| a.user_id == user_id)
501 .cloned()
502 .collect())
503 }
504
505 pub async fn get_active_sessions(&self) -> Result<Vec<PrivilegedSession>, Error> {
507 let sessions = self.sessions.read().await;
508 Ok(sessions
509 .values()
510 .filter(|s| s.is_active)
511 .cloned()
512 .collect())
513 }
514
515 pub async fn get_request(&self, request_id: Uuid) -> Result<Option<PrivilegedAccessRequest>, Error> {
517 let requests = self.requests.read().await;
518 Ok(requests.get(&request_id).cloned())
519 }
520
521 pub async fn get_user_requests(&self, user_id: Uuid) -> Result<Vec<PrivilegedAccessRequest>, Error> {
523 let requests = self.requests.read().await;
524 Ok(requests
525 .values()
526 .filter(|r| r.user_id == user_id)
527 .cloned()
528 .collect())
529 }
530}
531
532use std::sync::Arc;
534use tokio::sync::RwLock;
535
536#[cfg(test)]
537mod tests {
538 use super::*;
539
540 #[tokio::test]
541 async fn test_privileged_access_request() {
542 let manager = PrivilegedAccessManager::new(
543 PrivilegedAccessConfig::default(),
544 None,
545 None,
546 );
547
548 let request = manager
549 .request_privileged_access(
550 Uuid::new_v4(),
551 PrivilegedRole::Admin,
552 "Required for system administration".to_string(),
553 Some("Manage production infrastructure".to_string()),
554 Some(Uuid::new_v4()),
555 )
556 .await
557 .unwrap();
558
559 assert_eq!(request.status, RequestStatus::PendingManager);
560 assert!(request.manager_approval.is_some());
561 }
562}