1use axum::{
7 extract::Request,
8 http::{HeaderMap, StatusCode},
9 middleware::Next,
10 response::Response,
11};
12use mockforge_collab::models::UserRole;
13use mockforge_collab::permissions::{Permission, RolePermissions};
14use serde::{Deserialize, Serialize};
15
16fn is_truthy_env(name: &str) -> bool {
17 matches!(
18 std::env::var(name).ok().as_deref().map(str::to_ascii_lowercase).as_deref(),
19 Some("1") | Some("true") | Some("yes") | Some("on")
20 )
21}
22
23fn is_development_environment() -> bool {
24 if cfg!(test) {
25 return true;
26 }
27
28 matches!(
29 std::env::var("ENVIRONMENT")
30 .unwrap_or_else(|_| "production".to_string())
31 .to_ascii_lowercase()
32 .as_str(),
33 "development" | "dev" | "local"
34 )
35}
36
37fn is_dev_auth_enabled() -> bool {
38 cfg!(test) || is_truthy_env("MOCKFORGE_ENABLE_DEV_AUTH")
39}
40
41fn allow_unauthenticated_dev_access() -> bool {
42 is_development_environment()
43 && is_dev_auth_enabled()
44 && (is_truthy_env("MOCKFORGE_ALLOW_UNAUTHENTICATED")
45 || is_truthy_env("MOCKFORGE_ALLOW_UNAUTHENTICATED_DEV"))
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct UserContext {
51 pub user_id: String,
53 pub username: String,
55 pub role: UserRole,
57 pub email: Option<String>,
59}
60
61pub struct AdminActionPermissions;
63
64impl AdminActionPermissions {
65 pub fn get_required_permissions(action: &str) -> Vec<Permission> {
68 match action {
69 "update_latency"
71 | "update_faults"
72 | "update_proxy"
73 | "update_traffic_shaping"
74 | "update_validation" => {
75 vec![Permission::ManageSettings]
76 }
77
78 "restart_servers" | "shutdown_servers" => {
80 vec![Permission::ManageSettings]
81 }
82
83 "clear_logs" | "export_logs" => {
85 vec![Permission::ManageSettings]
86 }
87
88 "create_fixture" => {
90 vec![Permission::MockCreate]
91 }
92 "update_fixture" | "rename_fixture" | "move_fixture" => {
93 vec![Permission::MockUpdate]
94 }
95 "delete_fixture" | "delete_fixtures_bulk" => {
96 vec![Permission::MockDelete]
97 }
98
99 "enable_route" | "disable_route" | "create_route" | "update_route" | "delete_route" => {
101 vec![Permission::MockUpdate]
102 }
103
104 "enable_service" | "disable_service" | "update_service_config" => {
106 vec![Permission::ManageSettings]
107 }
108
109 "create_user" | "update_user" | "delete_user" | "change_role" => {
111 vec![Permission::ChangeRoles]
112 }
113
114 "grant_permission" | "revoke_permission" => {
116 vec![Permission::ChangeRoles]
117 }
118
119 "create_api_key" | "delete_api_key" | "rotate_api_key" => {
121 vec![Permission::ManageSettings]
122 }
123
124 "update_security_policy" => {
126 vec![Permission::ManageSettings]
127 }
128
129 "get_dashboard" | "get_logs" | "get_metrics" | "get_routes" | "get_fixtures"
131 | "get_config" => {
132 vec![Permission::WorkspaceRead, Permission::MockRead]
133 }
134
135 "get_audit_logs" | "get_audit_stats" => {
137 vec![Permission::ManageSettings]
138 }
139
140 "modify_scenario_chaos_rules" | "update_scenario_chaos" => {
143 vec![Permission::ScenarioModifyChaosRules]
144 }
145 "modify_scenario_reality_defaults" | "update_scenario_reality" => {
147 vec![Permission::ScenarioModifyRealityDefaults]
148 }
149 "promote_scenario" | "create_scenario_promotion" => {
151 vec![Permission::ScenarioPromote]
152 }
153 "approve_scenario_promotion" | "reject_scenario_promotion" => {
155 vec![Permission::ScenarioApprove]
156 }
157 "modify_scenario_drift_budget" | "update_scenario_drift_budget" => {
159 vec![Permission::ScenarioModifyDriftBudgets]
160 }
161
162 _ => {
164 vec![Permission::ManageSettings]
165 }
166 }
167 }
168}
169
170pub fn extract_user_context(headers: &HeaderMap) -> Option<UserContext> {
175 if let Some(auth_header) = headers.get("authorization") {
177 if let Ok(auth_str) = auth_header.to_str() {
178 if let Some(token) = auth_str.strip_prefix("Bearer ") {
179 if let Some(user) = parse_jwt_token(token) {
180 return Some(user);
181 }
182 }
183 }
184 }
185
186 if is_development_environment() && is_dev_auth_enabled() {
188 let user_id = headers.get("x-user-id")?.to_str().ok()?.to_string();
189 let username = headers.get("x-username")?.to_str().ok()?.to_string();
190 let role_str = headers.get("x-user-role")?.to_str().ok()?;
191 let role = parse_role(role_str)?;
192
193 return Some(UserContext {
194 user_id,
195 username,
196 role,
197 email: headers.get("x-user-email").and_then(|h| h.to_str().ok()).map(|s| s.to_string()),
198 });
199 }
200
201 None
202}
203
204fn parse_jwt_token(token: &str) -> Option<UserContext> {
207 use crate::auth::{claims_to_user_context, validate_token};
208
209 if let Ok(claims) = validate_token(token) {
211 return Some(claims_to_user_context(&claims));
212 }
213
214 if is_development_environment() && is_dev_auth_enabled() && token.starts_with("mock.") {
216 let parts: Vec<&str> = token.split('.').collect();
217 if parts.len() >= 3 {
218 let payload_part = parts[2];
220 let base64_str = payload_part.replace('-', "+").replace('_', "/");
222 let padding = (4 - (base64_str.len() % 4)) % 4;
224 let padded = format!("{}{}", base64_str, "=".repeat(padding));
225
226 use base64::{engine::general_purpose, Engine as _};
228 if let Ok(decoded) = general_purpose::STANDARD.decode(&padded) {
229 if let Ok(payload_str) = String::from_utf8(decoded) {
230 return parse_jwt_payload(&payload_str);
231 }
232 }
233 }
234 }
235
236 None
237}
238
239fn parse_jwt_payload(payload_str: &str) -> Option<UserContext> {
241 if let Ok(payload) = serde_json::from_str::<serde_json::Value>(payload_str) {
242 let user_id = payload.get("sub")?.as_str()?.to_string();
243 let username = payload.get("username")?.as_str()?.to_string();
244 let role_str = payload.get("role")?.as_str()?;
245 let role = parse_role(role_str)?;
246
247 return Some(UserContext {
248 user_id,
249 username,
250 role,
251 email: payload.get("email").and_then(|v| v.as_str()).map(|s| s.to_string()),
252 });
253 }
254 None
255}
256
257fn parse_role(role_str: &str) -> Option<UserRole> {
259 match role_str.to_lowercase().as_str() {
260 "admin" => Some(UserRole::Admin),
261 "editor" => Some(UserRole::Editor),
262 "viewer" => Some(UserRole::Viewer),
263 _ => None,
264 }
265}
266
267pub fn get_default_user_context() -> Option<UserContext> {
270 if allow_unauthenticated_dev_access() {
273 Some(UserContext {
274 user_id: "system".to_string(),
275 username: "system".to_string(),
276 role: UserRole::Admin,
277 email: None,
278 })
279 } else {
280 None
281 }
282}
283
284pub async fn rbac_middleware(mut request: Request, next: Next) -> Result<Response, StatusCode> {
286 let path = request.uri().path();
288 let method = request.method().as_str();
289 let headers = request.headers();
290
291 let is_public_route = path == "/"
296 || path.starts_with("/assets/")
297 || path == "/__mockforge/auth/login"
298 || path == "/__mockforge/auth/refresh"
299 || path == "/__mockforge/auth/logout"
300 || path == "/__mockforge/health"
301 || path.starts_with("/mockforge-")
302 || path == "/manifest.json"
303 || path == "/sw.js"
304 || path == "/api-docs"
305 || (method == "GET" && path.starts_with("/__mockforge/"));
306
307 if is_public_route {
308 return Ok(next.run(request).await);
309 }
310
311 let action_name = match (method, path) {
313 (_, p) if p.contains("/config/latency") => "update_latency",
314 (_, p) if p.contains("/config/faults") => "update_faults",
315 (_, p) if p.contains("/config/proxy") => "update_proxy",
316 (_, p) if p.contains("/config/traffic-shaping") => "update_traffic_shaping",
317 ("DELETE", p) if p.contains("/logs") => "clear_logs",
318 ("POST", p) if p.contains("/restart") => "restart_servers",
319 ("DELETE", p) if p.contains("/fixtures") => "delete_fixture",
320 ("POST", p) if p.contains("/fixtures") && p.contains("/rename") => "rename_fixture",
321 ("POST", p) if p.contains("/fixtures") && p.contains("/move") => "move_fixture",
322 ("GET", p) if p.contains("/audit/logs") => "get_audit_logs",
323 ("GET", p) if p.contains("/audit/stats") => "get_audit_stats",
324 ("GET", _) => "read", _ => "unknown",
326 };
327
328 let user_context = extract_user_context(headers).or_else(get_default_user_context);
330
331 let user_context = match user_context {
333 Some(ctx) => ctx,
334 None => {
335 if allow_unauthenticated_dev_access() {
338 get_default_user_context().unwrap_or_else(|| UserContext {
340 user_id: "system".to_string(),
341 username: "system".to_string(),
342 role: UserRole::Admin,
343 email: None,
344 })
345 } else {
346 return Err(StatusCode::UNAUTHORIZED);
347 }
348 }
349 };
350
351 let required_permissions = AdminActionPermissions::get_required_permissions(action_name);
353
354 let has_permission = required_permissions
356 .iter()
357 .any(|&perm| RolePermissions::has_permission(user_context.role, perm));
358
359 if !has_permission {
360 tracing::warn!(
362 user_id = %user_context.user_id,
363 username = %user_context.username,
364 role = ?user_context.role,
365 action = %action_name,
366 "Authorization denied: User does not have required permissions"
367 );
368
369 return Err(StatusCode::FORBIDDEN);
370 }
371
372 request.extensions_mut().insert(user_context);
375
376 Ok(next.run(request).await)
377}
378
379pub fn get_user_context_from_request(request: &Request) -> Option<UserContext> {
381 request.extensions().get::<UserContext>().cloned()
382}
383
384pub fn get_user_context_from_state<T>(_state: &T) -> Option<UserContext>
386where
387 T: std::any::Any,
388{
389 None
391}
392
393#[cfg(test)]
394mod tests {
395 use super::*;
396 use axum::http::{HeaderValue, Method};
397
398 #[test]
399 fn test_parse_role_valid() {
400 assert_eq!(parse_role("admin"), Some(UserRole::Admin));
401 assert_eq!(parse_role("Admin"), Some(UserRole::Admin));
402 assert_eq!(parse_role("ADMIN"), Some(UserRole::Admin));
403 assert_eq!(parse_role("editor"), Some(UserRole::Editor));
404 assert_eq!(parse_role("viewer"), Some(UserRole::Viewer));
405 }
406
407 #[test]
408 fn test_parse_role_invalid() {
409 assert_eq!(parse_role("invalid"), None);
410 assert_eq!(parse_role(""), None);
411 assert_eq!(parse_role("super_admin"), None);
412 }
413
414 #[test]
415 fn test_user_context_serialization() {
416 let context = UserContext {
417 user_id: "user123".to_string(),
418 username: "testuser".to_string(),
419 role: UserRole::Editor,
420 email: Some("test@example.com".to_string()),
421 };
422
423 let serialized = serde_json::to_string(&context).unwrap();
424 let deserialized: UserContext = serde_json::from_str(&serialized).unwrap();
425
426 assert_eq!(deserialized.user_id, context.user_id);
427 assert_eq!(deserialized.username, context.username);
428 assert_eq!(deserialized.role, context.role);
429 assert_eq!(deserialized.email, context.email);
430 }
431
432 #[test]
433 fn test_extract_user_context_from_headers() {
434 let mut headers = HeaderMap::new();
435 headers.insert("x-user-id", HeaderValue::from_static("user123"));
436 headers.insert("x-username", HeaderValue::from_static("testuser"));
437 headers.insert("x-user-role", HeaderValue::from_static("admin"));
438 headers.insert("x-user-email", HeaderValue::from_static("test@example.com"));
439
440 let context = extract_user_context(&headers).unwrap();
441 assert_eq!(context.user_id, "user123");
442 assert_eq!(context.username, "testuser");
443 assert_eq!(context.role, UserRole::Admin);
444 assert_eq!(context.email, Some("test@example.com".to_string()));
445 }
446
447 #[test]
448 fn test_extract_user_context_missing_headers() {
449 let headers = HeaderMap::new();
450 let context = extract_user_context(&headers);
451 assert!(context.is_none());
452 }
453
454 #[test]
455 fn test_extract_user_context_partial_headers() {
456 let mut headers = HeaderMap::new();
457 headers.insert("x-user-id", HeaderValue::from_static("user123"));
458 let context = extract_user_context(&headers);
461 assert!(context.is_none());
462 }
463
464 #[test]
465 fn test_extract_user_context_without_email() {
466 let mut headers = HeaderMap::new();
467 headers.insert("x-user-id", HeaderValue::from_static("user123"));
468 headers.insert("x-username", HeaderValue::from_static("testuser"));
469 headers.insert("x-user-role", HeaderValue::from_static("viewer"));
470
471 let context = extract_user_context(&headers).unwrap();
472 assert_eq!(context.user_id, "user123");
473 assert_eq!(context.username, "testuser");
474 assert_eq!(context.role, UserRole::Viewer);
475 assert_eq!(context.email, None);
476 }
477
478 #[test]
479 fn test_parse_jwt_payload() {
480 let payload_json = r#"{
481 "sub": "user456",
482 "username": "jwtuser",
483 "role": "editor",
484 "email": "jwt@example.com"
485 }"#;
486
487 let context = parse_jwt_payload(payload_json).unwrap();
488 assert_eq!(context.user_id, "user456");
489 assert_eq!(context.username, "jwtuser");
490 assert_eq!(context.role, UserRole::Editor);
491 assert_eq!(context.email, Some("jwt@example.com".to_string()));
492 }
493
494 #[test]
495 fn test_parse_jwt_payload_without_email() {
496 let payload_json = r#"{
497 "sub": "user456",
498 "username": "jwtuser",
499 "role": "viewer"
500 }"#;
501
502 let context = parse_jwt_payload(payload_json).unwrap();
503 assert_eq!(context.email, None);
504 }
505
506 #[test]
507 fn test_parse_jwt_payload_invalid_json() {
508 let payload_json = "invalid json";
509 let context = parse_jwt_payload(payload_json);
510 assert!(context.is_none());
511 }
512
513 #[test]
514 fn test_parse_jwt_payload_missing_fields() {
515 let payload_json = r#"{"sub": "user456"}"#;
516 let context = parse_jwt_payload(payload_json);
517 assert!(context.is_none());
518 }
519
520 #[test]
521 fn test_parse_jwt_payload_invalid_role() {
522 let payload_json = r#"{
523 "sub": "user456",
524 "username": "jwtuser",
525 "role": "invalid_role"
526 }"#;
527
528 let context = parse_jwt_payload(payload_json);
529 assert!(context.is_none());
530 }
531
532 #[test]
533 fn test_admin_action_permissions_config_changes() {
534 let perms = AdminActionPermissions::get_required_permissions("update_latency");
535 assert_eq!(perms, vec![Permission::ManageSettings]);
536
537 let perms = AdminActionPermissions::get_required_permissions("update_faults");
538 assert_eq!(perms, vec![Permission::ManageSettings]);
539
540 let perms = AdminActionPermissions::get_required_permissions("update_proxy");
541 assert_eq!(perms, vec![Permission::ManageSettings]);
542 }
543
544 #[test]
545 fn test_admin_action_permissions_fixture_management() {
546 let perms = AdminActionPermissions::get_required_permissions("create_fixture");
547 assert_eq!(perms, vec![Permission::MockCreate]);
548
549 let perms = AdminActionPermissions::get_required_permissions("update_fixture");
550 assert_eq!(perms, vec![Permission::MockUpdate]);
551
552 let perms = AdminActionPermissions::get_required_permissions("delete_fixture");
553 assert_eq!(perms, vec![Permission::MockDelete]);
554 }
555
556 #[test]
557 fn test_admin_action_permissions_user_management() {
558 let perms = AdminActionPermissions::get_required_permissions("create_user");
559 assert_eq!(perms, vec![Permission::ChangeRoles]);
560
561 let perms = AdminActionPermissions::get_required_permissions("change_role");
562 assert_eq!(perms, vec![Permission::ChangeRoles]);
563 }
564
565 #[test]
566 fn test_admin_action_permissions_read_operations() {
567 let perms = AdminActionPermissions::get_required_permissions("get_dashboard");
568 assert_eq!(perms, vec![Permission::WorkspaceRead, Permission::MockRead]);
569
570 let perms = AdminActionPermissions::get_required_permissions("get_logs");
571 assert_eq!(perms, vec![Permission::WorkspaceRead, Permission::MockRead]);
572 }
573
574 #[test]
575 fn test_admin_action_permissions_scenario_operations() {
576 let perms = AdminActionPermissions::get_required_permissions("modify_scenario_chaos_rules");
577 assert_eq!(perms, vec![Permission::ScenarioModifyChaosRules]);
578
579 let perms =
580 AdminActionPermissions::get_required_permissions("modify_scenario_reality_defaults");
581 assert_eq!(perms, vec![Permission::ScenarioModifyRealityDefaults]);
582
583 let perms = AdminActionPermissions::get_required_permissions("promote_scenario");
584 assert_eq!(perms, vec![Permission::ScenarioPromote]);
585
586 let perms = AdminActionPermissions::get_required_permissions("approve_scenario_promotion");
587 assert_eq!(perms, vec![Permission::ScenarioApprove]);
588 }
589
590 #[test]
591 fn test_admin_action_permissions_unknown_action() {
592 let perms = AdminActionPermissions::get_required_permissions("unknown_action");
593 assert_eq!(perms, vec![Permission::ManageSettings]);
594 }
595
596 #[test]
597 fn test_get_default_user_context_without_env_var() {
598 std::env::remove_var("MOCKFORGE_ALLOW_UNAUTHENTICATED");
599 let context = get_default_user_context();
600 assert!(context.is_none());
601 }
602
603 #[test]
604 fn test_get_default_user_context_with_env_var() {
605 std::env::set_var("MOCKFORGE_ALLOW_UNAUTHENTICATED", "1");
606 let context = get_default_user_context();
607 assert!(context.is_some());
608
609 let context = context.unwrap();
610 assert_eq!(context.user_id, "system");
611 assert_eq!(context.username, "system");
612 assert_eq!(context.role, UserRole::Admin);
613
614 std::env::remove_var("MOCKFORGE_ALLOW_UNAUTHENTICATED");
615 }
616
617 #[test]
618 fn test_all_permission_actions_covered() {
619 let actions = vec![
621 "update_latency",
622 "update_faults",
623 "restart_servers",
624 "create_fixture",
625 "update_fixture",
626 "delete_fixture",
627 "enable_route",
628 "create_user",
629 "grant_permission",
630 "create_api_key",
631 "get_dashboard",
632 "get_audit_logs",
633 "modify_scenario_chaos_rules",
634 "promote_scenario",
635 "approve_scenario_promotion",
636 ];
637
638 for action in actions {
639 let perms = AdminActionPermissions::get_required_permissions(action);
640 assert!(!perms.is_empty(), "Action {} should have permissions", action);
641 }
642 }
643
644 #[test]
645 fn test_role_permissions_admin_has_all() {
646 assert!(RolePermissions::has_permission(UserRole::Admin, Permission::ManageSettings));
648 assert!(RolePermissions::has_permission(UserRole::Admin, Permission::MockCreate));
649 assert!(RolePermissions::has_permission(UserRole::Admin, Permission::MockUpdate));
650 assert!(RolePermissions::has_permission(UserRole::Admin, Permission::MockDelete));
651 assert!(RolePermissions::has_permission(UserRole::Admin, Permission::WorkspaceRead));
652 assert!(RolePermissions::has_permission(UserRole::Admin, Permission::ChangeRoles));
653 }
654
655 #[test]
656 fn test_role_permissions_editor_limited() {
657 assert!(!RolePermissions::has_permission(UserRole::Editor, Permission::ManageSettings));
659 assert!(RolePermissions::has_permission(UserRole::Editor, Permission::MockUpdate));
660 assert!(!RolePermissions::has_permission(UserRole::Editor, Permission::ChangeRoles));
661 }
662
663 #[test]
664 fn test_role_permissions_viewer_readonly() {
665 assert!(!RolePermissions::has_permission(UserRole::Viewer, Permission::ManageSettings));
667 assert!(!RolePermissions::has_permission(UserRole::Viewer, Permission::MockCreate));
668 assert!(!RolePermissions::has_permission(UserRole::Viewer, Permission::MockUpdate));
669 assert!(!RolePermissions::has_permission(UserRole::Viewer, Permission::MockDelete));
670 assert!(RolePermissions::has_permission(UserRole::Viewer, Permission::WorkspaceRead));
671 assert!(RolePermissions::has_permission(UserRole::Viewer, Permission::MockRead));
672 }
673
674 #[test]
675 fn test_scenario_permissions() {
676 assert!(RolePermissions::has_permission(
678 UserRole::Admin,
679 Permission::ScenarioModifyChaosRules
680 ));
681 assert!(RolePermissions::has_permission(
682 UserRole::Admin,
683 Permission::ScenarioModifyRealityDefaults
684 ));
685 assert!(RolePermissions::has_permission(UserRole::Admin, Permission::ScenarioPromote));
686 assert!(RolePermissions::has_permission(UserRole::Admin, Permission::ScenarioApprove));
687 }
688
689 #[tokio::test]
690 async fn test_rbac_middleware_public_routes() {
691 use axum::routing::get;
692 use axum::{body::Body, middleware::from_fn, Router};
693 use tower::ServiceExt;
694
695 async fn handler() -> &'static str {
696 "OK"
697 }
698
699 let app = Router::new().route("/", get(handler)).layer(from_fn(rbac_middleware));
700
701 let request = axum::http::Request::builder()
702 .uri("/")
703 .method(Method::GET)
704 .body(Body::empty())
705 .unwrap();
706
707 let response = app.oneshot(request).await.unwrap();
708 assert_eq!(response.status(), StatusCode::OK);
709 }
710
711 #[tokio::test]
712 async fn test_rbac_middleware_health_route() {
713 use axum::routing::get;
714 use axum::{body::Body, middleware::from_fn, Router};
715 use tower::ServiceExt;
716
717 async fn handler() -> &'static str {
718 "OK"
719 }
720
721 let app = Router::new()
722 .route("/__mockforge/health", get(handler))
723 .layer(from_fn(rbac_middleware));
724
725 let request = axum::http::Request::builder()
726 .uri("/__mockforge/health")
727 .method(Method::GET)
728 .body(Body::empty())
729 .unwrap();
730
731 let response = app.oneshot(request).await.unwrap();
732 assert_eq!(response.status(), StatusCode::OK);
733 }
734
735 #[tokio::test]
736 async fn test_rbac_middleware_assets_route() {
737 use axum::routing::get;
738 use axum::{body::Body, middleware::from_fn, Router};
739 use tower::ServiceExt;
740
741 async fn handler() -> &'static str {
742 "OK"
743 }
744
745 let app = Router::new()
746 .route("/assets/style.css", get(handler))
747 .layer(from_fn(rbac_middleware));
748
749 let request = axum::http::Request::builder()
750 .uri("/assets/style.css")
751 .method(Method::GET)
752 .body(Body::empty())
753 .unwrap();
754
755 let response = app.oneshot(request).await.unwrap();
756 assert_eq!(response.status(), StatusCode::OK);
757 }
758
759 #[tokio::test]
760 async fn test_rbac_middleware_allows_get_mockforge_api() {
761 use axum::routing::get;
762 use axum::{body::Body, middleware::from_fn, Router};
763 use tower::ServiceExt;
764
765 async fn handler() -> &'static str {
766 "OK"
767 }
768
769 let app = Router::new()
770 .route("/__mockforge/dashboard", get(handler))
771 .layer(from_fn(rbac_middleware));
772
773 let request = axum::http::Request::builder()
775 .uri("/__mockforge/dashboard")
776 .method(Method::GET)
777 .body(Body::empty())
778 .unwrap();
779
780 let response = app.oneshot(request).await.unwrap();
781 assert_eq!(response.status(), StatusCode::OK);
782 }
783
784 #[tokio::test]
785 async fn test_rbac_middleware_with_valid_headers() {
786 use axum::routing::get;
787 use axum::{body::Body, middleware::from_fn, Router};
788 use tower::ServiceExt;
789
790 async fn handler() -> &'static str {
791 "OK"
792 }
793
794 let app = Router::new().route("/api/test", get(handler)).layer(from_fn(rbac_middleware));
795
796 let request = axum::http::Request::builder()
797 .uri("/api/test")
798 .method(Method::GET)
799 .header("x-user-id", "user123")
800 .header("x-username", "testuser")
801 .header("x-user-role", "admin")
802 .body(Body::empty())
803 .unwrap();
804
805 let response = app.oneshot(request).await.unwrap();
806 assert_eq!(response.status(), StatusCode::OK);
807 }
808
809 #[test]
810 fn test_action_name_mapping() {
811 let test_cases = vec![
813 ("/config/latency", "update_latency"),
814 ("/config/faults", "update_faults"),
815 ("/config/proxy", "update_proxy"),
816 ("/logs", "clear_logs"), ("/fixtures/test", "delete_fixture"), ("/audit/logs", "get_audit_logs"), ];
820
821 for (_path, expected_action) in test_cases {
824 assert!(!expected_action.is_empty());
825 }
826 }
827
828 #[test]
829 fn test_user_context_clone() {
830 let context = UserContext {
831 user_id: "user123".to_string(),
832 username: "testuser".to_string(),
833 role: UserRole::Editor,
834 email: Some("test@example.com".to_string()),
835 };
836
837 let cloned = context.clone();
838 assert_eq!(cloned.user_id, context.user_id);
839 assert_eq!(cloned.username, context.username);
840 assert_eq!(cloned.role, context.role);
841 assert_eq!(cloned.email, context.email);
842 }
843
844 #[test]
845 fn test_user_context_debug() {
846 let context = UserContext {
847 user_id: "user123".to_string(),
848 username: "testuser".to_string(),
849 role: UserRole::Viewer,
850 email: None,
851 };
852
853 let debug_str = format!("{:?}", context);
854 assert!(debug_str.contains("user123"));
855 assert!(debug_str.contains("testuser"));
856 }
857}