1use crate::api::{ApiResponse, ApiState};
7use crate::tokens::AuthToken;
8use axum::{
9 Extension,
10 extract::{Path, Query, State},
11 http::StatusCode,
12 response::Json,
13};
14use role_system::Permission;
15use serde::{Deserialize, Serialize};
16use std::collections::HashMap;
17use tracing::debug;
18use tracing::{info, warn};
19
20#[derive(Debug, Deserialize)]
22pub struct CreateRoleRequest {
23 pub name: String,
24 pub description: Option<String>,
25 pub parent_id: Option<String>,
26 pub permissions: Option<Vec<String>>,
27}
28
29#[derive(Debug, Deserialize)]
31pub struct UpdateRoleRequest {
32 pub name: Option<String>,
33 pub description: Option<String>,
34 pub parent_id: Option<String>,
35}
36
37#[derive(Debug, Deserialize)]
39pub struct CreatePermissionRequest {
40 pub action: String,
41 pub resource: String,
42 pub conditions: Option<HashMap<String, String>>,
43}
44
45#[derive(Debug, Deserialize)]
47pub struct AssignRoleRequest {
48 pub role_id: String,
49 pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
50 pub reason: Option<String>,
51}
52
53#[derive(Debug, Deserialize)]
55pub struct BulkAssignRequest {
56 pub assignments: Vec<BulkAssignment>,
57}
58
59#[derive(Debug, Deserialize)]
60pub struct BulkAssignment {
61 pub user_id: String,
62 pub role_id: String,
63 pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
64}
65
66#[derive(Debug, Deserialize)]
68pub struct ElevateRoleRequest {
69 pub target_role: String,
70 pub duration_minutes: Option<u32>,
71 pub justification: String,
72}
73
74#[derive(Debug, Serialize)]
76pub struct RoleResponse {
77 pub id: String,
78 pub name: String,
79 pub description: Option<String>,
80 pub parent_id: Option<String>,
81 pub permissions: Vec<String>,
82 pub created_at: chrono::DateTime<chrono::Utc>,
83 pub updated_at: chrono::DateTime<chrono::Utc>,
84}
85
86#[derive(Debug, Serialize)]
88pub struct PermissionResponse {
89 pub id: String,
90 pub action: String,
91 pub resource: String,
92 pub conditions: Option<HashMap<String, String>>,
93 pub created_at: chrono::DateTime<chrono::Utc>,
94}
95
96#[derive(Debug, Serialize)]
98pub struct UserRolesResponse {
99 pub user_id: String,
100 pub roles: Vec<UserRole>,
101 pub effective_permissions: Vec<String>,
102}
103
104#[derive(Debug, Serialize)]
105pub struct UserRole {
106 pub role_id: String,
107 pub role_name: String,
108 pub assigned_at: chrono::DateTime<chrono::Utc>,
109 pub assigned_by: Option<String>,
110 pub expires_at: Option<chrono::DateTime<chrono::Utc>>,
111}
112
113#[derive(Debug, Serialize)]
115pub struct AuditLogResponse {
116 pub entries: Vec<AuditEntryResponse>,
117 pub total_count: u64,
118 pub page: u32,
119 pub per_page: u32,
120}
121
122#[derive(Debug, Serialize)]
123pub struct AuditEntryResponse {
124 pub id: String,
125 pub user_id: Option<String>,
126 pub action: String,
127 pub resource: Option<String>,
128 pub result: String,
129 pub context: HashMap<String, String>,
130 pub timestamp: chrono::DateTime<chrono::Utc>,
131}
132
133#[derive(Debug, Deserialize)]
135pub struct RoleListQuery {
136 pub page: Option<u32>,
137 pub per_page: Option<u32>,
138 pub parent_id: Option<String>,
139 pub include_permissions: Option<bool>,
140}
141
142#[derive(Debug, Deserialize)]
144pub struct AuditQuery {
145 pub user_id: Option<String>,
146 pub action: Option<String>,
147 pub resource: Option<String>,
148 pub start_time: Option<chrono::DateTime<chrono::Utc>>,
149 pub end_time: Option<chrono::DateTime<chrono::Utc>>,
150 pub page: Option<u32>,
151 pub per_page: Option<u32>,
152}
153
154#[derive(Debug, Deserialize)]
156pub struct PermissionCheckRequest {
157 pub action: String,
158 pub resource: String,
159 pub context: Option<HashMap<String, String>>,
160}
161
162#[derive(Debug, Serialize)]
164pub struct PermissionCheckResponse {
165 pub granted: bool,
166 pub reason: String,
167 pub required_roles: Vec<String>,
168 pub missing_permissions: Vec<String>,
169}
170
171pub async fn create_role(
178 State(state): State<ApiState>,
179 Extension(auth_token): Extension<AuthToken>,
180 Json(request): Json<CreateRoleRequest>,
181) -> Result<Json<ApiResponse<RoleResponse>>, StatusCode> {
182 if !auth_token.has_permission("manage:roles") {
184 return Ok(Json(
185 ApiResponse::<RoleResponse>::forbidden_with_message_typed(
186 "Insufficient permissions to manage roles",
187 ),
188 ));
189 }
190
191 let now = chrono::Utc::now();
192
193 let permissions: Vec<Permission> = request
195 .permissions
196 .unwrap_or_default()
197 .into_iter()
198 .filter_map(|perm_str| {
199 if let Some((action, resource)) = perm_str.split_once(':') {
201 Some(Permission::new(action, resource))
202 } else {
203 warn!("Invalid permission format: {}", perm_str);
204 None
205 }
206 })
207 .collect();
208
209 match state
210 .authorization_service
211 .create_role(
212 &request.name,
213 &request.description.unwrap_or_default(),
214 permissions,
215 request.parent_id.map(|p| vec![p]),
216 )
217 .await
218 {
219 Ok(_) => {
220 info!("Role created: {} by {}", request.name, auth_token.user_id);
221
222 match state.authorization_service.get_role(&request.name).await {
224 Ok(Some(role)) => {
225 let permissions_strings: Vec<String> =
228 vec!["read:resource".to_string(), "write:resource".to_string()];
229
230 let hierarchy_depth = role.hierarchy_depth();
232 let is_root = role.is_root_role();
233 let is_leaf = role.is_leaf_role();
234 let child_ids = role.child_role_ids();
235
236 debug!(
237 "Role '{}' - Depth: {}, Root: {}, Leaf: {}, Children: {:?}",
238 role.name(),
239 hierarchy_depth,
240 is_root,
241 is_leaf,
242 child_ids
243 );
244
245 let response = RoleResponse {
246 id: role.id().to_string(),
247 name: role.name().to_string(),
248 description: role.description().map(|s| s.to_string()),
249 parent_id: role.parent_role_id().map(|s| s.to_string()), permissions: permissions_strings,
251 created_at: now,
252 updated_at: now,
253 };
254
255 Ok(Json(ApiResponse::success(response)))
256 }
257 _ => Ok(Json(ApiResponse::<RoleResponse>::error_with_message_typed(
258 "ROLE_FETCH_FAILED",
259 "Role created but failed to fetch details",
260 ))),
261 }
262 }
263 Err(e) => {
264 warn!("Failed to create role: {}", e);
265 Ok(Json(ApiResponse::<RoleResponse>::error_with_message_typed(
266 "ROLE_CREATION_FAILED",
267 "Failed to create role",
268 )))
269 }
270 }
271}
272
273pub async fn get_role(
276 State(state): State<ApiState>,
277 Extension(auth_token): Extension<AuthToken>,
278 Path(role_id): Path<String>,
279) -> Result<Json<ApiResponse<RoleResponse>>, StatusCode> {
280 if !auth_token.has_permission("read:roles") {
282 return Ok(Json(
283 ApiResponse::<RoleResponse>::forbidden_with_message_typed(
284 "Insufficient permissions to read roles",
285 ),
286 ));
287 }
288
289 match state.authorization_service.get_role(&role_id).await {
290 Ok(Some(role)) => {
291 let permissions_strings: Vec<String> =
292 vec!["read:resource".to_string(), "write:resource".to_string()];
293
294 let response = RoleResponse {
295 id: role.id().to_string(),
296 name: role.name().to_string(),
297 description: role.description().map(|s| s.to_string()),
298 parent_id: role.parent_role_id().map(|s| s.to_string()), permissions: permissions_strings,
300 created_at: chrono::Utc::now(), updated_at: chrono::Utc::now(),
302 };
303
304 Ok(Json(ApiResponse::success(response)))
305 }
306 Ok(None) => Ok(Json(
307 ApiResponse::<RoleResponse>::not_found_with_message_typed("Role not found"),
308 )),
309 Err(e) => {
310 warn!("Failed to get role: {}", e);
311 Ok(Json(ApiResponse::<RoleResponse>::error_with_message_typed(
312 "ROLE_FETCH_FAILED",
313 "Failed to fetch role",
314 )))
315 }
316 }
317}
318
319pub async fn list_roles(
322 State(_state): State<ApiState>,
323 Extension(auth_token): Extension<AuthToken>,
324 Query(_query): Query<RoleListQuery>,
325) -> Result<Json<ApiResponse<Vec<RoleResponse>>>, StatusCode> {
326 if !auth_token.has_permission("read:roles") {
328 return Ok(Json(
329 ApiResponse::<Vec<RoleResponse>>::forbidden_with_message_typed(
330 "Insufficient permissions to read roles",
331 ),
332 ));
333 }
334
335 let response: Vec<RoleResponse> = Vec::new();
338
339 Ok(Json(ApiResponse::success(response)))
340}
341
342pub async fn update_role(
345 State(_state): State<ApiState>,
346 Extension(auth_token): Extension<AuthToken>,
347 Path(_role_id): Path<String>,
348 Json(_request): Json<UpdateRoleRequest>,
349) -> Result<Json<ApiResponse<RoleResponse>>, StatusCode> {
350 if !auth_token.has_permission("manage:roles") {
352 return Ok(Json(
353 ApiResponse::<RoleResponse>::forbidden_with_message_typed(
354 "Insufficient permissions to manage roles",
355 ),
356 ));
357 }
358
359 Ok(Json(ApiResponse::<RoleResponse>::error_with_message_typed(
362 "OPERATION_NOT_SUPPORTED",
363 "Role updates are not currently supported",
364 )))
365}
366
367pub async fn delete_role(
370 State(state): State<ApiState>,
371 Extension(auth_token): Extension<AuthToken>,
372 Path(role_id): Path<String>,
373) -> Result<Json<ApiResponse<()>>, StatusCode> {
374 if !auth_token.has_permission("manage:roles") {
376 return Ok(Json(ApiResponse::forbidden_typed()));
377 }
378
379 match state.authorization_service.delete_role(&role_id).await {
380 Ok(_) => {
381 info!("Role deleted: {} by {}", role_id, auth_token.user_id);
382 Ok(Json(ApiResponse::success(())))
383 }
384 Err(e) => {
385 warn!("Failed to delete role {}: {}", role_id, e);
386 Ok(Json(ApiResponse::<()>::error_typed(
387 "ROLE_DELETE_FAILED",
388 "Failed to delete role",
389 )))
390 }
391 }
392}
393
394pub async fn assign_user_role(
401 State(state): State<ApiState>,
402 Extension(auth_token): Extension<AuthToken>,
403 Path(user_id): Path<String>,
404 Json(request): Json<AssignRoleRequest>,
405) -> Result<Json<ApiResponse<()>>, StatusCode> {
406 if !auth_token.has_permission("manage:user_roles") {
408 return Ok(Json(ApiResponse::<()>::forbidden_with_message_typed(
409 "Insufficient permissions to manage user roles",
410 )));
411 }
412
413 match state
414 .authorization_service
415 .assign_role(&user_id, &request.role_id)
416 .await
417 {
418 Ok(_) => {
419 info!(
420 "Role {} assigned to user {} by {}",
421 request.role_id, user_id, auth_token.user_id
422 );
423 Ok(Json(ApiResponse::success(())))
424 }
425 Err(e) => {
426 warn!("Failed to assign role: {}", e);
427 Ok(Json(ApiResponse::<()>::error_with_message_typed(
428 "ROLE_ASSIGNMENT_FAILED",
429 "Failed to assign role",
430 )))
431 }
432 }
433}
434
435pub async fn revoke_user_role(
438 State(state): State<ApiState>,
439 Extension(auth_token): Extension<AuthToken>,
440 Path((user_id, role_id)): Path<(String, String)>,
441) -> Result<Json<ApiResponse<()>>, StatusCode> {
442 if !auth_token.has_permission("manage:user_roles") {
444 return Ok(Json(ApiResponse::<()>::forbidden_with_message_typed(
445 "Insufficient permissions to manage user roles",
446 )));
447 }
448
449 match state
450 .authorization_service
451 .remove_role(&user_id, &role_id)
452 .await
453 {
454 Ok(_) => {
455 info!(
456 "Role {} revoked from user {} by {}",
457 role_id, user_id, auth_token.user_id
458 );
459 Ok(Json(ApiResponse::success(())))
460 }
461 Err(e) => {
462 warn!("Failed to revoke role: {}", e);
463 Ok(Json(ApiResponse::<()>::error_with_message_typed(
464 "ROLE_REVOCATION_FAILED",
465 "Failed to revoke role",
466 )))
467 }
468 }
469}
470
471pub async fn get_user_roles(
474 State(_state): State<ApiState>,
475 Extension(auth_token): Extension<AuthToken>,
476 Path(user_id): Path<String>,
477) -> Result<Json<ApiResponse<UserRolesResponse>>, StatusCode> {
478 if user_id != auth_token.user_id && !auth_token.has_permission("read:user_roles") {
480 return Ok(Json(
481 ApiResponse::<UserRolesResponse>::forbidden_with_message_typed(
482 "Insufficient permissions to read user roles",
483 ),
484 ));
485 }
486
487 let response = UserRolesResponse {
490 user_id,
491 roles: Vec::new(),
492 effective_permissions: Vec::new(),
493 };
494
495 Ok(Json(ApiResponse::success(response)))
496}
497
498pub async fn bulk_assign_roles(
501 State(state): State<ApiState>,
502 Extension(auth_token): Extension<AuthToken>,
503 Json(request): Json<BulkAssignRequest>,
504) -> Result<Json<ApiResponse<()>>, StatusCode> {
505 if !auth_token.has_permission("manage:user_roles") {
507 return Ok(Json(ApiResponse::<()>::forbidden_with_message_typed(
508 "Insufficient permissions to manage user roles",
509 )));
510 }
511
512 let mut success_count = 0;
514 let mut error_count = 0;
515
516 for assignment in request.assignments {
517 match state
518 .authorization_service
519 .assign_role(&assignment.user_id, &assignment.role_id)
520 .await
521 {
522 Ok(_) => success_count += 1,
523 Err(e) => {
524 warn!(
525 "Failed to assign role {} to user {}: {}",
526 assignment.role_id, assignment.user_id, e
527 );
528 error_count += 1;
529 }
530 }
531 }
532
533 info!(
534 "Bulk role assignment completed by {} - {} successes, {} errors",
535 auth_token.user_id, success_count, error_count
536 );
537
538 if error_count == 0 {
539 Ok(Json(ApiResponse::success(())))
540 } else {
541 Ok(Json(ApiResponse::<()>::error_with_message_typed(
542 "PARTIAL_BULK_ASSIGNMENT_FAILED",
543 format!(
544 "Bulk assignment partially failed: {} successes, {} errors",
545 success_count, error_count
546 ),
547 )))
548 }
549}
550
551pub async fn check_permission(
558 State(state): State<ApiState>,
559 Extension(auth_token): Extension<AuthToken>,
560 Json(request): Json<PermissionCheckRequest>,
561) -> Result<Json<ApiResponse<PermissionCheckResponse>>, StatusCode> {
562 let context = request.context.unwrap_or_default();
563
564 match state
565 .authorization_service
566 .check_permission(
567 &auth_token.user_id,
568 &request.action,
569 &request.resource,
570 Some(&context),
571 )
572 .await
573 {
574 Ok(granted) => {
575 let response = PermissionCheckResponse {
576 granted,
577 reason: if granted {
578 "Permission granted".to_string()
579 } else {
580 "Permission denied".to_string()
581 },
582 required_roles: Vec::new(), missing_permissions: Vec::new(), };
585
586 Ok(Json(ApiResponse::success(response)))
587 }
588 Err(e) => {
589 warn!("Permission check failed: {}", e);
590 Ok(Json(ApiResponse::<PermissionCheckResponse>::error_typed(
591 "PERMISSION_CHECK_FAILED",
592 "Failed to check permission",
593 )))
594 }
595 }
596}
597
598pub async fn elevate_role(
601 State(state): State<ApiState>,
602 Extension(auth_token): Extension<AuthToken>,
603 Json(request): Json<ElevateRoleRequest>,
604) -> Result<Json<ApiResponse<()>>, StatusCode> {
605 let duration_seconds = (request.duration_minutes.unwrap_or(30) * 60) as u64;
606
607 match state
608 .authorization_service
609 .elevate_role(
610 &auth_token.user_id,
611 &request.target_role,
612 Some(duration_seconds),
613 )
614 .await
615 {
616 Ok(_) => {
617 info!(
618 "Role elevation granted to {}: {} for {} minutes - {}",
619 auth_token.user_id,
620 request.target_role,
621 request.duration_minutes.unwrap_or(30),
622 request.justification
623 );
624 Ok(Json(ApiResponse::success(())))
625 }
626 Err(e) => {
627 warn!("Role elevation failed: {}", e);
628 Ok(Json(ApiResponse::<()>::error_with_message_typed(
629 "ELEVATION_FAILED",
630 "Failed to elevate role",
631 )))
632 }
633 }
634}
635
636pub async fn get_audit_logs(
643 State(_state): State<ApiState>,
644 Extension(auth_token): Extension<AuthToken>,
645 Query(query): Query<AuditQuery>,
646) -> Result<Json<ApiResponse<AuditLogResponse>>, StatusCode> {
647 if !auth_token.has_permission("read:audit_logs") {
649 return Ok(Json(
650 ApiResponse::<AuditLogResponse>::forbidden_with_message_typed(
651 "Insufficient permissions to read audit logs",
652 ),
653 ));
654 }
655
656 let response = AuditLogResponse {
659 entries: Vec::new(),
660 total_count: 0,
661 page: query.page.unwrap_or(1),
662 per_page: query.per_page.unwrap_or(20),
663 };
664
665 Ok(Json(ApiResponse::success(response)))
666}
667
668#[cfg(test)]
669mod tests {
670 use super::*;
671
672 #[allow(dead_code)] fn create_test_token(permissions: Vec<&str>) -> AuthToken {
675 use crate::tokens::TokenMetadata;
676 use chrono::Utc;
677
678 AuthToken {
679 token_id: "test_token_123".to_string(),
680 user_id: "test_user".to_string(),
681 access_token: "test_access_token".to_string(),
682 token_type: Some("bearer".to_string()),
683 subject: Some("test_user".to_string()),
684 issuer: Some("auth-framework".to_string()),
685 refresh_token: Some("test_refresh_token".to_string()),
686 issued_at: Utc::now(),
687 expires_at: Utc::now() + chrono::Duration::hours(1),
688 scopes: vec!["read".to_string(), "write".to_string()],
689 auth_method: "password".to_string(),
690 client_id: Some("test_client".to_string()),
691 user_profile: None,
692 permissions: permissions.into_iter().map(|s| s.to_string()).collect(),
693 roles: vec!["admin".to_string()],
694 metadata: TokenMetadata::default(),
695 }
696 }
697
698 #[tokio::test]
699 async fn test_create_role_unauthorized() {
700 }
703
704 #[tokio::test]
705 async fn test_create_role_success() {
706 }
709
710 #[tokio::test]
711 async fn test_permission_check() {
712 }
715}
716
717