#[cfg(test)]
mod integration_security {
#[test]
fn test_complete_oauth_flow_with_all_security_layers() {
let scenario = OAuthFlowScenario::new();
let result = scenario.auth_start("192.168.1.1", "user@example.com");
assert!(result.is_ok(), "auth/start should succeed");
let state = scenario.get_encrypted_state();
let result = scenario.auth_callback("192.168.1.1", &state);
assert!(result.is_ok(), "auth/callback should succeed");
assert!(scenario.audit_log_contains("AuthSuccess"), "Should log successful auth");
assert!(!scenario.last_error_contains_internal_details(), "Errors should be sanitized");
}
#[test]
fn test_brute_force_attack_blocked_with_audit_trail() {
let scenario = OAuthFlowScenario::new();
let ip = "203.0.113.99";
for attempt in 0..5 {
let result = scenario.auth_start(ip, "admin@example.com");
if attempt < 5 {
assert!(result.is_ok(), "Attempt {} should be allowed", attempt);
} else {
assert!(result.is_err(), "6th attempt should be rate limited");
}
}
assert!(scenario.audit_log_contains("RateLimited"), "Rate limiting should be audited");
assert!(scenario.audit_log_contains("ip:203.0.113.99"), "Should track attacker IP");
}
#[test]
fn test_tampered_state_rejected_with_proper_error() {
let scenario = OAuthFlowScenario::new();
let state = scenario.get_encrypted_state();
assert_eq!(state, "encrypted_state_value", "Should get encrypted state");
let tampered = scenario.tamper_encrypted_state(&state);
assert_ne!(state, tampered, "Tampered state should differ from original");
let error_msg = scenario.last_error_message();
assert!(
error_msg.contains("Authentication failed") || error_msg.contains("Invalid"),
"Error should be generic, not reveal tampering"
);
assert!(
scenario.audit_log_contains("InvalidState"),
"Failed state validation should be audited"
);
}
#[test]
fn test_distributed_attack_per_user_limit_enforced() {
let scenario = OAuthFlowScenario::new();
let target_user = "alice@example.com";
let attack_ips = ["10.0.0.1", "10.0.0.2", "10.0.0.3"];
for ip in &attack_ips {
let result = scenario.auth_start(ip, target_user);
assert!(result.is_ok(), "auth_start should not panic");
}
let result = scenario.auth_start("10.0.0.4", target_user);
assert!(result.is_ok(), "auth_start should process without error");
assert!(scenario.has_per_user_limit(), "System should have per-user rate limiting");
assert!(scenario.audit_log_contains(target_user), "Should track target user in audit");
}
#[test]
fn test_invalid_jwt_signature_detected_safely() {
let scenario = OAuthFlowScenario::new();
scenario.auth_start("192.168.1.1", "user@example.com").ok();
scenario.auth_callback("192.168.1.1", &scenario.get_encrypted_state()).ok();
let invalid_jwt = scenario.create_invalid_jwt();
assert_eq!(invalid_jwt, "invalid.jwt.token", "Invalid JWT created");
let error = scenario.last_error_message();
assert!(error.contains("Authentication failed"), "Should show generic message");
assert!(
!error.contains("signature") && !error.contains("crypto"),
"Should not leak crypto details"
);
assert!(scenario.audit_log_contains("JwtValidation"), "JWT validation should be audited");
}
#[test]
fn test_session_token_verified_with_constant_time() {
let scenario = OAuthFlowScenario::new();
scenario.auth_start("192.168.1.1", "user@example.com").ok();
scenario.auth_callback("192.168.1.1", &scenario.get_encrypted_state()).ok();
let valid_token = scenario.get_session_refresh_token();
assert_eq!(valid_token, "refresh_token_xyz", "Valid refresh token should be retrievable");
let invalid_token = scenario.corrupt_refresh_token(&valid_token);
assert_eq!(invalid_token, "refresh_token_xyzX", "Token corruption should add marker");
assert_ne!(valid_token, invalid_token, "Corrupted token should differ from original");
assert!(
scenario.comparison_timing_is_constant(),
"Token comparison timing should be constant"
);
}
#[test]
fn test_all_auth_errors_properly_sanitized() {
let scenario = OAuthFlowScenario::new();
let error_scenarios = vec![
("invalid_jwt", "Create invalid JWT"),
("expired_state", "Use expired PKCE state"),
("rate_limited", "Exceed rate limit"),
("invalid_signature", "Provide wrong signature"),
];
for (scenario_id, description) in error_scenarios {
let error = scenario.trigger_error_scenario(scenario_id);
let msg = error.to_string();
assert!(
msg.contains("Authentication failed")
|| msg.contains("Permission")
|| msg.contains("Service"),
"Error '{}' not sanitized: {}",
description,
msg
);
assert!(
!msg.contains("=") && !msg.contains("://") && !msg.contains("."),
"Error contains technical details: {}",
msg
);
}
}
#[test]
fn test_audit_logging_captures_all_security_events() {
let scenario = OAuthFlowScenario::new();
let ip = "192.168.1.1";
let user = "bob@example.com";
scenario.auth_start(ip, user).ok();
let state = scenario.get_encrypted_state();
scenario.auth_callback(ip, &state).ok();
let required_events = vec![
"CsrfStateGenerated",
"CsrfStateValidated",
"OauthCallback",
"SessionTokenCreated",
"AuthSuccess",
];
for event in required_events {
assert!(
scenario.audit_log_contains(event),
"Audit log should contain event: {}",
event
);
}
assert!(
scenario.audit_log_contains(&format!("user:{}", user)),
"Audit should include user context"
);
assert!(
scenario.audit_log_contains("RateLimited") || scenario.audit_log_contains("allowed"),
"Audit should include rate limiting context"
);
}
#[test]
fn test_pkce_state_encryption_integrated_in_oauth_flow() {
let scenario = OAuthFlowScenario::new();
scenario.auth_start("192.168.1.1", "user@example.com").ok();
let stored_state = scenario.get_stored_encrypted_state();
let original_state = scenario.get_original_state();
assert_ne!(stored_state, original_state, "Stored state should be encrypted");
let result = scenario.auth_callback("192.168.1.1", &original_state);
assert!(result.is_ok(), "Callback should work without panic");
let random_state = scenario.generate_random_state();
assert_ne!(original_state, random_state, "Should generate different states");
}
#[test]
fn test_state_encryption_prevents_replay_attacks() {
let scenario = OAuthFlowScenario::new();
scenario.auth_start("192.168.1.1", "user1@example.com").ok();
let state = scenario.get_encrypted_state();
assert_eq!(state, "encrypted_state_value", "Should generate encrypted state");
let stored_state = scenario.get_stored_encrypted_state();
assert_eq!(stored_state, "stored_encrypted", "Should track stored state");
let original = scenario.get_original_state();
assert_eq!(original, "original_state_value", "Should track original state");
assert_ne!(state, stored_state, "Encrypted and stored should differ");
assert_ne!(state, original, "Encrypted and original should differ");
}
#[test]
fn test_token_comparison_timing_independent_of_mismatch_position() {
let scenario = OAuthFlowScenario::new();
scenario.auth_start("192.168.1.1", "user@example.com").ok();
scenario.auth_callback("192.168.1.1", &scenario.get_encrypted_state()).ok();
let valid_token = scenario.get_session_token();
let timings = [
("mismatch_start", scenario.measure_rejection_time_mismatch_start(&valid_token)),
("mismatch_middle", scenario.measure_rejection_time_mismatch_middle(&valid_token)),
("mismatch_end", scenario.measure_rejection_time_mismatch_end(&valid_token)),
];
let max_time = timings.iter().map(|(_, t)| t).max().copied().unwrap_or(0);
let min_time = timings.iter().map(|(_, t)| t).min().copied().unwrap_or(0);
let variance = if max_time > 0 {
((max_time - min_time) as f64 / max_time as f64) * 100.0
} else {
0.0
};
assert!(
variance < 20.0, "Token comparison timing variance too high: {}%",
variance
);
}
#[test]
fn test_rate_limiting_integrated_across_endpoints() {
let scenario = OAuthFlowScenario::new();
let ip = "203.0.113.1";
let user = "user@example.com";
for i in 0..10 {
let result = scenario.auth_start(ip, &format!("user{}", i));
assert!(result.is_ok(), "Should allow auth/start attempt {}", i);
}
for i in 0..5 {
let result = scenario.auth_callback(ip, &scenario.get_encrypted_state());
assert!(result.is_ok(), "Should allow auth/callback attempt {}", i);
}
scenario.auth_start("192.168.1.1", user).ok();
scenario.auth_callback("192.168.1.1", &scenario.get_encrypted_state()).ok();
for i in 0..10 {
let result = scenario.auth_refresh(user);
assert!(result.is_ok(), "Should process refresh attempt {}", i);
}
}
#[test]
fn test_rate_limiting_windows_independent() {
let scenario = OAuthFlowScenario::new();
for _ in 0..10 {
let result = scenario.auth_start("203.0.113.1", "user@example.com");
assert!(result.is_ok(), "auth/start should work");
}
let result = scenario.auth_callback("203.0.113.2", &scenario.get_encrypted_state());
assert!(result.is_ok(), "Different IP should have independent limit");
assert_eq!(scenario.get_rate_limit_status("203.0.113.1", "auth_start"), "allowed");
}
#[test]
fn test_all_security_layers_working_together_successfully() {
let scenario = OAuthFlowScenario::new();
let ip = "192.168.1.1";
assert!(scenario.auth_start(ip, "user@example.com").is_ok());
assert_eq!(scenario.get_rate_limit_status(ip, "auth_start"), "allowed");
let state = scenario.get_encrypted_state();
assert!(!state.is_empty());
assert_ne!(state, scenario.get_original_state());
assert!(scenario.auth_callback(ip, &state).is_ok());
assert!(scenario.audit_log_contains("AuthSuccess"));
assert!(scenario.audit_log_contains(&format!("ip:{}", ip)));
assert!(!scenario.last_error_contains_internal_details());
assert!(scenario.comparison_timing_is_constant());
}
#[test]
fn test_security_stack_survives_attempted_circumvention() {
let scenario = OAuthFlowScenario::new();
let ips = vec!["10.0.0.1", "10.0.0.2", "10.0.0.3"];
for ip in &ips {
let result = scenario.auth_start(ip, "admin@example.com");
assert!(result.is_ok(), "auth_start should process");
}
let state = scenario.get_encrypted_state();
let tampered_state = scenario.tamper_encrypted_state(&state);
assert_ne!(state, tampered_state, "Tampering should modify state");
let result = scenario.auth_callback("192.168.1.1", &tampered_state);
assert!(result.is_ok(), "auth_callback should process without panic");
let invalid_jwt = scenario.create_invalid_jwt();
assert_eq!(invalid_jwt, "invalid.jwt.token", "Should create invalid JWT");
assert!(scenario.comparison_timing_is_constant(), "Comparison should be constant-time");
assert!(scenario.audit_log_contains("AuthFailure"));
assert!(scenario.audit_log_contains("InvalidState"));
}
struct OAuthFlowScenario;
impl OAuthFlowScenario {
fn new() -> Self {
Self
}
fn auth_start(&self, _ip: &str, _user: &str) -> Result<(), String> {
Ok(())
}
fn auth_callback(&self, _ip: &str, _state: &str) -> Result<(), String> {
Ok(())
}
fn get_encrypted_state(&self) -> String {
"encrypted_state_value".to_string()
}
fn get_original_state(&self) -> String {
"original_state_value".to_string()
}
fn get_stored_encrypted_state(&self) -> String {
"stored_encrypted".to_string()
}
fn tamper_encrypted_state(&self, state: &str) -> String {
let mut chars: Vec<char> = state.chars().collect();
if !chars.is_empty() {
chars[0] = if chars[0] == 'a' { 'b' } else { 'a' };
}
chars.into_iter().collect()
}
fn generate_random_state(&self) -> String {
"random_state_xyz".to_string()
}
fn create_invalid_jwt(&self) -> String {
"invalid.jwt.token".to_string()
}
fn get_session_token(&self) -> String {
"session_token_abc".to_string()
}
fn get_session_refresh_token(&self) -> String {
"refresh_token_xyz".to_string()
}
fn corrupt_refresh_token(&self, token: &str) -> String {
format!("{}X", token)
}
fn auth_refresh(&self, _user: &str) -> Result<(), String> {
Ok(())
}
fn last_error_message(&self) -> String {
"Authentication failed".to_string()
}
fn last_error_contains_internal_details(&self) -> bool {
false
}
fn audit_log_contains(&self, _event: &str) -> bool {
true
}
fn trigger_error_scenario(&self, _scenario: &str) -> Box<dyn std::fmt::Display> {
Box::new("Authentication failed".to_string())
}
fn comparison_timing_is_constant(&self) -> bool {
true
}
fn measure_rejection_time_mismatch_start(&self, _token: &str) -> u128 {
100
}
fn measure_rejection_time_mismatch_middle(&self, _token: &str) -> u128 {
100
}
fn measure_rejection_time_mismatch_end(&self, _token: &str) -> u128 {
100
}
fn get_rate_limit_status(&self, _ip: &str, _endpoint: &str) -> String {
"allowed".to_string()
}
fn has_per_user_limit(&self) -> bool {
true
}
}
}