//! SOX Access Control Documentation Tests //! //! Validates that access control matrix, role hierarchies, separation of duties, //! and exception processes are properly documented and enforceable. use chrono::{Duration, Utc}; use trading_engine::compliance::sox_compliance::{ AccessControlMatrix, AssignedRole, AssignmentStatus, Permission, RoleDefinition, RolePermissions, RiskLevel, SegregationMatrix, IncompatibleRoles, RequiredSeparation, AccessReview, AccessReviewType, ReviewScope, ReviewPeriod, ReviewStatus, }; #[test] fn test_complete_permission_matrix_validation() { // Test that all roles have complete permission definitions let access_matrix = AccessControlMatrix::new(); // Define all critical roles for trading system let critical_roles = vec![ "trader", "risk_manager", "compliance_officer", "operations", "admin", "auditor", ]; // Validate each role has: // 1. Resource permissions defined // 2. Action constraints // 3. Risk level assigned for role in critical_roles { let role_def = RoleDefinition { role_id: format!("ROLE-{}", role.to_uppercase()), role_name: role.to_string(), description: format!("{} role with defined permissions", role), permissions: vec![ Permission { permission_id: format!("PERM-{}-001", role.to_uppercase()), resource: "orders".to_string(), actions: vec!["create".to_string(), "read".to_string()], constraints: vec!["max_order_size: 1000000".to_string()], }, Permission { permission_id: format!("PERM-{}-002", role.to_uppercase()), resource: "positions".to_string(), actions: vec!["read".to_string()], constraints: vec!["own_account_only".to_string()], }, ], risk_level: match role { "trader" => RiskLevel::High, "admin" => RiskLevel::Critical, "auditor" => RiskLevel::Low, _ => RiskLevel::Medium, }, requires_approval: match role { "admin" => true, "trader" => true, _ => false, }, }; // Validate role has permissions defined assert!(!role_def.permissions.is_empty(), "Role {} has no permissions defined", role); assert!(role_def.role_id != "", "Role ID missing for {}", role); assert!(role_def.description != "", "Role description missing for {}", role); // Validate each permission has constraints for perm in &role_def.permissions { assert!(!perm.actions.is_empty(), "No actions defined for permission {}", perm.permission_id); assert!(!perm.constraints.is_empty(), "No constraints for permission {}", perm.permission_id); } } } #[test] fn test_role_hierarchy_enforcement() { // Test role hierarchy: Admin > Manager > Supervisor > User let access_matrix = AccessControlMatrix::new(); let role_hierarchy = vec![ ("admin", 4, vec!["*"]), // Full access ("manager", 3, vec!["orders", "positions", "reports"]), ("supervisor", 2, vec!["orders", "positions"]), ("user", 1, vec!["orders"]), ]; for (role_name, level, resources) in role_hierarchy { let role = RoleDefinition { role_id: format!("ROLE-{}", role_name.to_uppercase()), role_name: role_name.to_string(), description: format!("Hierarchy level {}", level), permissions: resources.iter().map(|res| Permission { permission_id: format!("PERM-{}-{}", role_name.to_uppercase(), res.to_uppercase()), resource: res.to_string(), actions: vec!["read".to_string(), "write".to_string()], constraints: vec![], }).collect(), risk_level: if level >= 3 { RiskLevel::High } else { RiskLevel::Medium }, requires_approval: level >= 3, }; // Validate hierarchy level reflected in permissions if level == 4 { // Admin has wildcard access assert!(role.permissions.iter().any(|p| p.resource == "*"), "Admin missing wildcard access"); } else { // Other roles have specific resources assert!(role.permissions.len() == resources.len(), "Role {} permission count mismatch", role_name); } // Higher levels require approval assert_eq!(role.requires_approval, level >= 3, "Approval requirement mismatch for {}", role_name); } } #[test] fn test_separation_of_duties_matrix() { // Test SOD matrix prevents incompatible role combinations let sod_matrix = SegregationMatrix { roles: std::collections::HashMap::new(), incompatible_combinations: vec![ IncompatibleRoles { rule_id: "SOD-001".to_string(), role_a: "trader".to_string(), role_b: "risk_approver".to_string(), reason: "Trader cannot approve own trades".to_string(), exception_process: Some("CFO approval required".to_string()), }, IncompatibleRoles { rule_id: "SOD-002".to_string(), role_a: "order_creator".to_string(), role_b: "order_approver".to_string(), reason: "Cannot create and approve same order".to_string(), exception_process: None, }, IncompatibleRoles { rule_id: "SOD-003".to_string(), role_a: "payment_initiator".to_string(), role_b: "payment_approver".to_string(), reason: "Financial control separation".to_string(), exception_process: Some("Dual approval from CFO + CEO".to_string()), }, ], required_separations: vec![ RequiredSeparation { separation_id: "SEP-001".to_string(), process_name: "Trade Execution".to_string(), separated_functions: vec![ "trade_entry".to_string(), "trade_approval".to_string(), "trade_settlement".to_string(), ], justification: "SOX 404 control requirement".to_string(), }, ], }; // Validate incompatible combinations defined assert_eq!(sod_matrix.incompatible_combinations.len(), 3, "Insufficient SOD rules"); // Validate each rule has clear reason for rule in &sod_matrix.incompatible_combinations { assert!(rule.rule_id != "", "SOD rule missing ID"); assert!(rule.role_a != rule.role_b, "SOD rule {} has same roles", rule.rule_id); assert!(rule.reason != "", "SOD rule {} missing reason", rule.rule_id); } // Validate required separations assert_eq!(sod_matrix.required_separations.len(), 1, "Missing required separations"); assert!(sod_matrix.required_separations[0].separated_functions.len() >= 2, "Insufficient separation functions"); } #[test] fn test_access_review_completeness() { // Test quarterly access reviews are properly documented let access_matrix = AccessControlMatrix::new(); let access_review = AccessReview { review_id: "REVIEW-Q1-2025".to_string(), review_type: AccessReviewType::UserAccess, scope: ReviewScope { users: vec!["user_001".to_string(), "user_002".to_string()], roles: vec!["trader".to_string(), "risk_manager".to_string()], systems: vec!["trading_platform".to_string(), "risk_system".to_string()], period: ReviewPeriod { start_date: Utc::now() - Duration::days(90), end_date: Utc::now(), }, }, review_date: Utc::now(), reviewer: "compliance_officer_001".to_string(), findings: vec![], // Populated during review status: ReviewStatus::InProgress, }; // Validate review covers all required elements assert!(access_review.review_id != "", "Review ID missing"); assert!(!access_review.scope.users.is_empty(), "No users in review scope"); assert!(!access_review.scope.roles.is_empty(), "No roles in review scope"); assert!(access_review.reviewer != "", "Reviewer not assigned"); // Validate review period is quarterly (90 days) let review_duration = access_review.scope.period.end_date .signed_duration_since(access_review.scope.period.start_date); assert!(review_duration.num_days() >= 85 && review_duration.num_days() <= 95, "Review period not quarterly (expected ~90 days, got {})", review_duration.num_days()); } #[test] fn test_exception_approval_workflow() { // Test exception process for SOD violations let sod_matrix = SegregationMatrix { roles: std::collections::HashMap::new(), incompatible_combinations: vec![ IncompatibleRoles { rule_id: "SOD-EXCEPTION-001".to_string(), role_a: "developer".to_string(), role_b: "production_deployer".to_string(), reason: "Developer cannot deploy own code".to_string(), exception_process: Some("CTO approval + independent code review required".to_string()), }, ], required_separations: vec![], }; // Validate exception process defined for critical SOD rules let rule = &sod_matrix.incompatible_combinations[0]; assert!(rule.exception_process.is_some(), "Exception process not defined for critical SOD rule"); let exception_process = rule.exception_process.as_ref().unwrap(); assert!(exception_process.contains("approval"), "Exception process missing approval requirement"); assert!(exception_process.len() > 20, "Exception process not detailed enough"); // Exception workflow requires: // 1. Business justification // 2. Risk assessment // 3. Executive approval (CFO/CTO/CEO) // 4. Compensating controls // 5. Time-bound (temporary only) } #[test] fn test_role_permission_documentation() { // Test that role permissions are fully documented let role_permissions = RolePermissions { role_id: "ROLE-TRADER".to_string(), permissions: vec![ Permission { permission_id: "PERM-TRADER-001".to_string(), resource: "orders".to_string(), actions: vec!["create".to_string(), "read".to_string(), "cancel".to_string()], constraints: vec![ "max_order_size: 1000000".to_string(), "trading_hours_only".to_string(), "pre_trade_risk_check_required".to_string(), ], }, Permission { permission_id: "PERM-TRADER-002".to_string(), resource: "positions".to_string(), actions: vec!["read".to_string()], constraints: vec!["own_account_only".to_string()], }, Permission { permission_id: "PERM-TRADER-003".to_string(), resource: "market_data".to_string(), actions: vec!["read".to_string()], constraints: vec!["real_time_feed_access".to_string()], }, ], effective_date: Utc::now(), last_modified: Utc::now(), modified_by: "compliance_officer".to_string(), }; // Validate documentation completeness assert!(role_permissions.role_id != "", "Role ID missing"); assert!(!role_permissions.permissions.is_empty(), "No permissions defined"); assert!(role_permissions.modified_by != "", "Modifier not documented"); // Validate each permission has: // 1. Unique ID // 2. Resource target // 3. Allowed actions // 4. Constraints/limitations for perm in &role_permissions.permissions { assert!(perm.permission_id.starts_with("PERM-"), "Invalid permission ID format"); assert!(perm.resource != "", "Resource not specified"); assert!(!perm.actions.is_empty(), "No actions defined"); assert!(!perm.constraints.is_empty(), "No constraints defined (required for SOX)"); } } #[test] fn test_user_role_assignment_tracking() { // Test that role assignments are properly tracked with audit trail use trading_engine::compliance::sox_compliance::UserRoleAssignment; let user_assignment = UserRoleAssignment { user_id: "user_trader_001".to_string(), roles: vec![ AssignedRole { role_id: "ROLE-TRADER".to_string(), assigned_date: Utc::now() - Duration::days(30), assigned_by: "manager_001".to_string(), expiration_date: Some(Utc::now() + Duration::days(335)), // 1 year - 30 days justification: "Hired as junior trader, completed training".to_string(), }, ], last_review_date: Utc::now() - Duration::days(30), next_review_date: Utc::now() + Duration::days(60), // Quarterly review status: AssignmentStatus::Active, }; // Validate assignment tracking assert!(user_assignment.user_id != "", "User ID missing"); assert!(!user_assignment.roles.is_empty(), "No roles assigned"); // Validate each role assignment has: // 1. Assigned by (WHO) // 2. Assigned date (WHEN) // 3. Business justification (WHY) // 4. Expiration tracking // 5. Review schedule for role in &user_assignment.roles { assert!(role.assigned_by != "", "Assigner not documented"); assert!(role.justification != "", "Business justification missing (SOX requirement)"); assert!(role.expiration_date.is_some(), "No expiration date (SOX requires periodic review)"); } // Validate review schedule (quarterly) let review_interval = user_assignment.next_review_date .signed_duration_since(user_assignment.last_review_date); assert!(review_interval.num_days() >= 85 && review_interval.num_days() <= 95, "Review not quarterly"); } #[test] fn test_privileged_access_monitoring() { // Test that privileged access (admin, root, etc.) is monitored let privileged_roles = vec!["admin", "root", "dba", "security_admin"]; for role in privileged_roles { let role_def = RoleDefinition { role_id: format!("ROLE-{}", role.to_uppercase()), role_name: role.to_string(), description: format!("Privileged {} access", role), permissions: vec![ Permission { permission_id: format!("PERM-{}-PRIVILEGED", role.to_uppercase()), resource: "*".to_string(), // Wildcard access actions: vec!["*".to_string()], // All actions constraints: vec![ "mfa_required".to_string(), "session_recording_required".to_string(), "dual_approval_required".to_string(), "time_limited_access".to_string(), ], }, ], risk_level: RiskLevel::Critical, requires_approval: true, }; // Validate privileged roles have enhanced controls assert_eq!(role_def.risk_level, RiskLevel::Critical, "Privileged role {} not marked critical", role); assert!(role_def.requires_approval, "Privileged role {} missing approval requirement", role); // Validate enhanced constraints for privileged access let constraints = &role_def.permissions[0].constraints; assert!(constraints.contains(&"mfa_required".to_string()), "MFA not required for {}", role); assert!(constraints.contains(&"dual_approval_required".to_string()), "Dual approval missing for {}", role); assert!(constraints.len() >= 3, "Insufficient controls for privileged role {}", role); } } #[test] fn test_inactive_account_detection() { // Test detection of inactive accounts requiring review/termination let inactive_threshold_days = 90; let user_assignment = trading_engine::compliance::sox_compliance::UserRoleAssignment { user_id: "user_inactive_001".to_string(), roles: vec![ AssignedRole { role_id: "ROLE-TRADER".to_string(), assigned_date: Utc::now() - Duration::days(180), assigned_by: "manager_001".to_string(), expiration_date: None, justification: "Initial assignment".to_string(), }, ], last_review_date: Utc::now() - Duration::days(120), // Not reviewed in 120 days next_review_date: Utc::now() - Duration::days(30), // Overdue by 30 days status: AssignmentStatus::Active, }; // Check if account is inactive (last review > threshold) let days_since_review = Utc::now() .signed_duration_since(user_assignment.last_review_date) .num_days(); assert!(days_since_review > inactive_threshold_days, "Inactive account detection failed: {} days since review (threshold: {})", days_since_review, inactive_threshold_days); // Check if review is overdue let is_overdue = user_assignment.next_review_date < Utc::now(); assert!(is_overdue, "Overdue review not detected"); // Inactive accounts require: // 1. Immediate access review // 2. Suspension if no activity // 3. Termination after extended inactivity // 4. Audit logging of all actions }