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#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
51#[serde(rename_all = "snake_case")]
52pub enum PrivilegedActionType {
53 UserCreate,
55 UserDelete,
57 UserModify,
59 RoleAssign,
61 RoleRevoke,
63 RoleEscalate,
65 PermissionGrant,
67 PermissionRevoke,
69 ConfigModify,
71 SecurityPolicyChange,
73 SecuritySettingChange,
75 AuditLogAccess,
77 DataExport,
79 DataDelete,
81 Other,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct PrivilegedAccessRequest {
88 pub request_id: Uuid,
90 pub user_id: Uuid,
92 pub requested_role: PrivilegedRole,
94 pub justification: String,
96 pub business_need: Option<String>,
98 pub manager_approval: Option<Uuid>,
100 pub security_approval: Option<Uuid>,
102 pub status: RequestStatus,
104 pub created_at: DateTime<Utc>,
106 pub updated_at: DateTime<Utc>,
108 pub expires_at: Option<DateTime<Utc>>,
110}
111
112impl PrivilegedAccessRequest {
113 pub fn new(
115 user_id: Uuid,
116 requested_role: PrivilegedRole,
117 justification: String,
118 business_need: Option<String>,
119 manager_approval: Option<Uuid>,
120 ) -> Self {
121 let now = Utc::now();
122 Self {
123 request_id: Uuid::new_v4(),
124 user_id,
125 requested_role,
126 justification,
127 business_need,
128 manager_approval,
129 security_approval: None,
130 status: RequestStatus::PendingManager,
131 created_at: now,
132 updated_at: now,
133 expires_at: None,
134 }
135 }
136
137 pub fn is_approved(&self) -> bool {
139 self.status == RequestStatus::Approved
140 }
141
142 pub fn is_expired(&self) -> bool {
144 self.expires_at.map(|exp| Utc::now() > exp).unwrap_or(false)
145 }
146}
147
148#[derive(Debug, Clone, Serialize, Deserialize)]
150pub struct PrivilegedAction {
151 pub action_id: Uuid,
153 pub user_id: Uuid,
155 pub action_type: PrivilegedActionType,
157 pub resource: Option<String>,
159 pub details: Option<String>,
161 pub ip_address: Option<String>,
163 pub user_agent: Option<String>,
165 pub session_id: Option<String>,
167 pub timestamp: DateTime<Utc>,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize)]
173pub struct PrivilegedSession {
174 pub session_id: String,
176 pub user_id: Uuid,
178 pub role: PrivilegedRole,
180 pub started_at: DateTime<Utc>,
182 pub last_activity: DateTime<Utc>,
184 pub ip_address: Option<String>,
186 pub user_agent: Option<String>,
188 pub is_active: bool,
190}
191
192#[derive(Debug, Clone, Serialize, Deserialize)]
194#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
195pub struct PrivilegedAccessConfig {
196 pub require_mfa: bool,
198 pub mfa_grace_period_days: u64,
200 pub auto_suspend_no_mfa: bool,
202 pub session_timeout_minutes: u64,
204 pub max_concurrent_sessions: u32,
206 pub record_sensitive_actions: bool,
208 pub monitor_activity: bool,
210 pub sensitive_actions: Vec<PrivilegedActionType>,
212}
213
214impl Default for PrivilegedAccessConfig {
215 fn default() -> Self {
216 Self {
217 require_mfa: true,
218 mfa_grace_period_days: 7,
219 auto_suspend_no_mfa: true,
220 session_timeout_minutes: 30,
221 max_concurrent_sessions: 2,
222 record_sensitive_actions: true,
223 monitor_activity: true,
224 sensitive_actions: vec![
225 PrivilegedActionType::UserDelete,
226 PrivilegedActionType::RoleEscalate,
227 PrivilegedActionType::SecurityPolicyChange,
228 PrivilegedActionType::DataExport,
229 PrivilegedActionType::AuditLogAccess,
230 ],
231 }
232 }
233}
234
235pub struct PrivilegedAccessManager {
239 config: PrivilegedAccessConfig,
240 mfa_storage: Option<Arc<dyn MfaStorage>>,
241 justification_storage: Option<Arc<dyn JustificationStorage>>,
242 sessions: Arc<RwLock<HashMap<String, PrivilegedSession>>>,
244 actions: Arc<RwLock<Vec<PrivilegedAction>>>,
246 requests: Arc<RwLock<HashMap<Uuid, PrivilegedAccessRequest>>>,
248}
249
250impl PrivilegedAccessManager {
251 pub fn new(
253 config: PrivilegedAccessConfig,
254 mfa_storage: Option<Arc<dyn MfaStorage>>,
255 justification_storage: Option<Arc<dyn JustificationStorage>>,
256 ) -> Self {
257 Self {
258 config,
259 mfa_storage,
260 justification_storage,
261 sessions: Arc::new(RwLock::new(HashMap::new())),
262 actions: Arc::new(RwLock::new(Vec::new())),
263 requests: Arc::new(RwLock::new(HashMap::new())),
264 }
265 }
266
267 pub async fn request_privileged_access(
269 &self,
270 user_id: Uuid,
271 requested_role: PrivilegedRole,
272 justification: String,
273 business_need: Option<String>,
274 manager_approval: Option<Uuid>,
275 ) -> Result<PrivilegedAccessRequest, Error> {
276 let request = PrivilegedAccessRequest::new(
277 user_id,
278 requested_role,
279 justification,
280 business_need,
281 manager_approval,
282 );
283
284 let mut requests = self.requests.write().await;
285 requests.insert(request.request_id, request.clone());
286
287 Ok(request)
288 }
289
290 pub async fn approve_manager(&self, request_id: Uuid, approver_id: Uuid) -> Result<(), Error> {
292 let mut requests = self.requests.write().await;
293 let request = requests
294 .get_mut(&request_id)
295 .ok_or_else(|| Error::Generic("Request not found".to_string()))?;
296
297 if request.status != RequestStatus::PendingManager {
298 return Err(Error::Generic("Request is not pending manager approval".to_string()));
299 }
300
301 request.manager_approval = Some(approver_id);
302 request.status = RequestStatus::PendingSecurity;
303 request.updated_at = Utc::now();
304
305 Ok(())
306 }
307
308 pub async fn approve_security(
310 &self,
311 request_id: Uuid,
312 approver_id: Uuid,
313 expiration_days: u64,
314 ) -> Result<(), Error> {
315 let mut requests = self.requests.write().await;
316 let request = requests
317 .get_mut(&request_id)
318 .ok_or_else(|| Error::Generic("Request not found".to_string()))?;
319
320 if request.status != RequestStatus::PendingSecurity {
321 return Err(Error::Generic("Request is not pending security approval".to_string()));
322 }
323
324 request.security_approval = Some(approver_id);
325 request.status = RequestStatus::Approved;
326 request.expires_at = Some(Utc::now() + Duration::days(expiration_days as i64));
327 request.updated_at = Utc::now();
328
329 if let Some(ref just_storage) = self.justification_storage {
331 let justification = AccessJustification::new(
332 request.user_id,
333 request.justification.clone(),
334 request.business_need.clone(),
335 request.manager_approval,
336 request.expires_at,
337 );
338 just_storage.set_justification(justification).await?;
339 }
340
341 Ok(())
342 }
343
344 pub async fn deny_request(&self, request_id: Uuid, reason: String) -> Result<(), Error> {
346 let mut requests = self.requests.write().await;
347 let request = requests
348 .get_mut(&request_id)
349 .ok_or_else(|| Error::Generic("Request not found".to_string()))?;
350
351 request.status = RequestStatus::Denied;
352 request.updated_at = Utc::now();
353
354 Ok(())
355 }
356
357 pub async fn check_mfa_compliance(&self, user_id: Uuid) -> Result<bool, Error> {
359 if !self.config.require_mfa {
360 return Ok(true);
361 }
362
363 if let Some(ref mfa_storage) = self.mfa_storage {
364 let mfa_status = mfa_storage.get_mfa_status(user_id).await?;
365 Ok(mfa_status.map(|s| s.enabled).unwrap_or(false))
366 } else {
367 Ok(true)
369 }
370 }
371
372 pub async fn record_action(
374 &self,
375 user_id: Uuid,
376 action_type: PrivilegedActionType,
377 resource: Option<String>,
378 details: Option<String>,
379 ip_address: Option<String>,
380 user_agent: Option<String>,
381 session_id: Option<String>,
382 ) -> Result<Uuid, Error> {
383 let action = PrivilegedAction {
384 action_id: Uuid::new_v4(),
385 user_id,
386 action_type,
387 resource,
388 details,
389 ip_address,
390 user_agent,
391 session_id,
392 timestamp: Utc::now(),
393 };
394
395 let mut actions = self.actions.write().await;
396 actions.push(action.clone());
397
398 if self.config.sensitive_actions.contains(&action_type) {
400 }
402
403 Ok(action.action_id)
404 }
405
406 pub async fn start_session(
408 &self,
409 session_id: String,
410 user_id: Uuid,
411 role: PrivilegedRole,
412 ip_address: Option<String>,
413 user_agent: Option<String>,
414 ) -> Result<(), Error> {
415 if !self.check_mfa_compliance(user_id).await? {
417 if self.config.auto_suspend_no_mfa {
418 return Err(Error::Generic("MFA not enabled for privileged user".to_string()));
419 }
420 }
421
422 let sessions = self.sessions.read().await;
424 let active_sessions =
425 sessions.values().filter(|s| s.user_id == user_id && s.is_active).count();
426
427 if active_sessions >= self.config.max_concurrent_sessions as usize {
428 return Err(Error::Generic("Maximum concurrent sessions reached".to_string()));
429 }
430 drop(sessions);
431
432 let session = PrivilegedSession {
433 session_id: session_id.clone(),
434 user_id,
435 role,
436 started_at: Utc::now(),
437 last_activity: Utc::now(),
438 ip_address,
439 user_agent,
440 is_active: true,
441 };
442
443 let mut sessions = self.sessions.write().await;
444 sessions.insert(session_id, session);
445
446 Ok(())
447 }
448
449 pub async fn update_session_activity(&self, session_id: &str) -> Result<(), Error> {
451 let mut sessions = self.sessions.write().await;
452 if let Some(session) = sessions.get_mut(session_id) {
453 session.last_activity = Utc::now();
454 }
455 Ok(())
456 }
457
458 pub async fn end_session(&self, session_id: &str) -> Result<(), Error> {
460 let mut sessions = self.sessions.write().await;
461 if let Some(session) = sessions.get_mut(session_id) {
462 session.is_active = false;
463 }
464 Ok(())
465 }
466
467 pub async fn cleanup_expired_sessions(&self) -> Result<Vec<String>, Error> {
469 let timeout = Duration::minutes(self.config.session_timeout_minutes as i64);
470 let now = Utc::now();
471 let mut expired = Vec::new();
472
473 let mut sessions = self.sessions.write().await;
474 for (session_id, session) in sessions.iter_mut() {
475 if session.is_active && (now - session.last_activity) > timeout {
476 session.is_active = false;
477 expired.push(session_id.clone());
478 }
479 }
480
481 Ok(expired)
482 }
483
484 pub async fn get_user_actions(&self, user_id: Uuid) -> Result<Vec<PrivilegedAction>, Error> {
486 let actions = self.actions.read().await;
487 Ok(actions.iter().filter(|a| a.user_id == user_id).cloned().collect())
488 }
489
490 pub async fn get_active_sessions(&self) -> Result<Vec<PrivilegedSession>, Error> {
492 let sessions = self.sessions.read().await;
493 Ok(sessions.values().filter(|s| s.is_active).cloned().collect())
494 }
495
496 pub async fn get_request(
498 &self,
499 request_id: Uuid,
500 ) -> Result<Option<PrivilegedAccessRequest>, Error> {
501 let requests = self.requests.read().await;
502 Ok(requests.get(&request_id).cloned())
503 }
504
505 pub async fn get_user_requests(
507 &self,
508 user_id: Uuid,
509 ) -> Result<Vec<PrivilegedAccessRequest>, Error> {
510 let requests = self.requests.read().await;
511 Ok(requests.values().filter(|r| r.user_id == user_id).cloned().collect())
512 }
513}
514
515use std::sync::Arc;
517use tokio::sync::RwLock;
518
519#[cfg(test)]
520mod tests {
521 use super::*;
522
523 #[tokio::test]
524 async fn test_privileged_access_request() {
525 let manager = PrivilegedAccessManager::new(PrivilegedAccessConfig::default(), None, None);
526
527 let request = manager
528 .request_privileged_access(
529 Uuid::new_v4(),
530 PrivilegedRole::Admin,
531 "Required for system administration".to_string(),
532 Some("Manage production infrastructure".to_string()),
533 Some(Uuid::new_v4()),
534 )
535 .await
536 .unwrap();
537
538 assert_eq!(request.status, RequestStatus::PendingManager);
539 assert!(request.manager_approval.is_some());
540 }
541}