Skip to main content

mockforge_http/handlers/
privileged_access.rs

1//! HTTP handlers for privileged access management
2//!
3//! **Internal / API-only.** No admin UI consumes these endpoints. They are
4//! intended for programmatic PAM integrations. Do not build speculative UI
5//! for these routes without a stakeholder-defined use case.
6//!
7//! This module provides REST API endpoints for managing privileged access requests,
8//! monitoring privileged actions, and managing privileged sessions.
9
10use axum::{
11    extract::{Path, State},
12    http::StatusCode,
13    response::Json,
14};
15use mockforge_core::security::{
16    emit_security_event, EventActor, EventOutcome, EventTarget, PrivilegedAccessManager,
17    PrivilegedActionType, PrivilegedRole, RequestStatus, SecurityEvent, SecurityEventType,
18};
19use serde::{Deserialize, Serialize};
20use std::sync::Arc;
21use tokio::sync::RwLock;
22use tracing::{error, info};
23use uuid::Uuid;
24
25use crate::handlers::auth_helpers::{extract_user_id_with_fallback, OptionalAuthClaims};
26
27/// State for privileged access handlers
28#[derive(Clone)]
29pub struct PrivilegedAccessState {
30    /// Privileged access manager
31    pub manager: Arc<RwLock<PrivilegedAccessManager>>,
32}
33
34/// Request to create a privileged access request
35#[derive(Debug, Deserialize)]
36pub struct CreatePrivilegedAccessRequest {
37    /// Requested role
38    pub requested_role: PrivilegedRole,
39    /// Justification
40    pub justification: String,
41    /// Business need
42    pub business_need: Option<String>,
43    /// Manager approval (optional)
44    pub manager_approval: Option<Uuid>,
45}
46
47/// Request to approve a privileged access request
48#[derive(Debug, Deserialize)]
49pub struct ApproveRequest {
50    /// Whether to approve
51    pub approved: bool,
52    /// Justification for approval/denial
53    pub justification: Option<String>,
54    /// Expiration days (if approved)
55    pub expiration_days: Option<u64>,
56}
57
58/// Response for privileged access request
59#[derive(Debug, Serialize)]
60pub struct PrivilegedAccessRequestResponse {
61    /// Request ID
62    pub request_id: Uuid,
63    /// Status
64    pub status: RequestStatus,
65    /// Created at
66    pub created_at: chrono::DateTime<chrono::Utc>,
67    /// Expires at
68    pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
69}
70
71/// Response for privileged actions list
72#[derive(Debug, Serialize)]
73pub struct PrivilegedActionsResponse {
74    /// Actions
75    pub actions: Vec<PrivilegedActionSummary>,
76}
77
78/// Summary of a privileged action
79#[derive(Debug, Serialize)]
80pub struct PrivilegedActionSummary {
81    /// Action ID
82    pub action_id: Uuid,
83    /// Action type
84    pub action_type: PrivilegedActionType,
85    /// Resource
86    pub resource: Option<String>,
87    /// Timestamp
88    pub timestamp: chrono::DateTime<chrono::Utc>,
89}
90
91/// Request privileged access
92///
93/// POST /api/v1/security/privileged-access/request
94pub async fn request_privileged_access(
95    State(state): State<PrivilegedAccessState>,
96    claims: OptionalAuthClaims,
97    Json(request): Json<CreatePrivilegedAccessRequest>,
98) -> Result<Json<PrivilegedAccessRequestResponse>, StatusCode> {
99    // Extract user ID from authentication claims, or use default for mock server
100    let user_id = extract_user_id_with_fallback(&claims);
101
102    let manager = state.manager.read().await;
103    let access_request = manager
104        .request_privileged_access(
105            user_id,
106            request.requested_role,
107            request.justification,
108            request.business_need,
109            request.manager_approval,
110        )
111        .await
112        .map_err(|e| {
113            error!("Failed to create privileged access request: {}", e);
114            StatusCode::INTERNAL_SERVER_ERROR
115        })?;
116
117    info!("Privileged access request created: {}", access_request.request_id);
118
119    // Emit security event
120    let event = SecurityEvent::new(SecurityEventType::AuthzPrivilegeEscalation, None, None)
121        .with_actor(EventActor {
122            user_id: Some(user_id.to_string()),
123            username: None,
124            ip_address: None,
125            user_agent: None,
126        })
127        .with_target(EventTarget {
128            resource_type: Some("privileged_access_request".to_string()),
129            resource_id: Some(access_request.request_id.to_string()),
130            method: None,
131        })
132        .with_outcome(EventOutcome {
133            success: true,
134            reason: Some("Privileged access request created".to_string()),
135        });
136    emit_security_event(event).await;
137
138    Ok(Json(PrivilegedAccessRequestResponse {
139        request_id: access_request.request_id,
140        status: access_request.status,
141        created_at: access_request.created_at,
142        expires_at: access_request.expires_at,
143    }))
144}
145
146/// Approve privileged access request (manager)
147///
148/// POST /api/v1/security/privileged-access/{request_id}/approve-manager
149pub async fn approve_manager(
150    State(state): State<PrivilegedAccessState>,
151    Path(request_id): Path<Uuid>,
152    claims: OptionalAuthClaims,
153) -> Result<Json<serde_json::Value>, StatusCode> {
154    // Extract approver ID from authentication claims, or use default for mock server
155    let approver_id = extract_user_id_with_fallback(&claims);
156
157    let manager = state.manager.write().await;
158    manager.approve_manager(request_id, approver_id).await.map_err(|e| {
159        error!("Failed to approve privileged access request: {}", e);
160        StatusCode::BAD_REQUEST
161    })?;
162
163    info!("Privileged access request approved by manager: {}", request_id);
164
165    // Emit security event
166    let event = SecurityEvent::new(SecurityEventType::AuthzPrivilegeEscalation, None, None)
167        .with_actor(EventActor {
168            user_id: Some(approver_id.to_string()),
169            username: None,
170            ip_address: None,
171            user_agent: None,
172        })
173        .with_target(EventTarget {
174            resource_type: Some("privileged_access_request".to_string()),
175            resource_id: Some(request_id.to_string()),
176            method: None,
177        })
178        .with_outcome(EventOutcome {
179            success: true,
180            reason: Some("Manager approval granted".to_string()),
181        });
182    emit_security_event(event).await;
183
184    Ok(Json(serde_json::json!({
185        "status": "approved",
186        "request_id": request_id
187    })))
188}
189
190/// Approve privileged access request (security)
191///
192/// POST /api/v1/security/privileged-access/{request_id}/approve-security
193pub async fn approve_security(
194    State(state): State<PrivilegedAccessState>,
195    Path(request_id): Path<Uuid>,
196    claims: OptionalAuthClaims,
197    Json(request): Json<ApproveRequest>,
198) -> Result<Json<serde_json::Value>, StatusCode> {
199    if !request.approved {
200        return Err(StatusCode::BAD_REQUEST);
201    }
202
203    // Extract approver ID from authentication claims, or use default for mock server
204    let approver_id = extract_user_id_with_fallback(&claims);
205
206    let expiration_days = request.expiration_days.unwrap_or(365);
207
208    let manager = state.manager.write().await;
209    manager
210        .approve_security(request_id, approver_id, expiration_days)
211        .await
212        .map_err(|e| {
213            error!("Failed to approve privileged access request: {}", e);
214            StatusCode::BAD_REQUEST
215        })?;
216
217    info!("Privileged access request approved by security: {}", request_id);
218
219    // Emit security event
220    let event = SecurityEvent::new(SecurityEventType::AuthzPrivilegeEscalation, None, None)
221        .with_actor(EventActor {
222            user_id: Some(approver_id.to_string()),
223            username: None,
224            ip_address: None,
225            user_agent: None,
226        })
227        .with_target(EventTarget {
228            resource_type: Some("privileged_access_request".to_string()),
229            resource_id: Some(request_id.to_string()),
230            method: None,
231        })
232        .with_outcome(EventOutcome {
233            success: true,
234            reason: Some("Security approval granted".to_string()),
235        });
236    emit_security_event(event).await;
237
238    Ok(Json(serde_json::json!({
239        "status": "approved",
240        "request_id": request_id
241    })))
242}
243
244/// Get privileged actions for a user
245///
246/// GET /api/v1/security/privileged-access/actions/{user_id}
247pub async fn get_user_actions(
248    State(state): State<PrivilegedAccessState>,
249    Path(user_id): Path<Uuid>,
250) -> Result<Json<PrivilegedActionsResponse>, StatusCode> {
251    let manager = state.manager.read().await;
252    let actions = manager.get_user_actions(user_id).await.map_err(|e| {
253        error!("Failed to get user actions: {}", e);
254        StatusCode::INTERNAL_SERVER_ERROR
255    })?;
256
257    let summaries: Vec<PrivilegedActionSummary> = actions
258        .into_iter()
259        .map(|a| PrivilegedActionSummary {
260            action_id: a.action_id,
261            action_type: a.action_type,
262            resource: a.resource,
263            timestamp: a.timestamp,
264        })
265        .collect();
266
267    Ok(Json(PrivilegedActionsResponse { actions: summaries }))
268}
269
270/// Get active privileged sessions
271///
272/// GET /api/v1/security/privileged-access/sessions
273pub async fn get_active_sessions(
274    State(state): State<PrivilegedAccessState>,
275) -> Result<Json<serde_json::Value>, StatusCode> {
276    let manager = state.manager.read().await;
277    let sessions = manager.get_active_sessions().await.map_err(|e| {
278        error!("Failed to get active sessions: {}", e);
279        StatusCode::INTERNAL_SERVER_ERROR
280    })?;
281
282    Ok(Json(serde_json::json!({
283        "sessions": sessions
284    })))
285}
286
287/// Create privileged access router
288pub fn privileged_access_router(state: PrivilegedAccessState) -> axum::Router {
289    use axum::routing::{get, post};
290
291    axum::Router::new()
292        .route("/request", post(request_privileged_access))
293        .route("/{request_id}/approve-manager", post(approve_manager))
294        .route("/{request_id}/approve-security", post(approve_security))
295        .route("/actions/{user_id}", get(get_user_actions))
296        .route("/sessions", get(get_active_sessions))
297        .with_state(state)
298}