#![cfg(feature = "owasp-pentest-suite")]
use sz_orm_auth::jwt::{JwtClaims, JwtEncoder};
use sz_orm_auth::{AuthorizationRequest, OAuth2Server, TokenRequest};
struct CsrfTokenValidator {
expected_token: String,
issued_at: i64,
ttl_seconds: i64,
}
impl CsrfTokenValidator {
fn new(token: &str) -> Self {
Self {
expected_token: token.to_string(),
issued_at: 1000,
ttl_seconds: 3600,
}
}
fn validate(&self, token: Option<&str>, current_time: i64) -> Result<(), &'static str> {
let token = token.ok_or("missing CSRF token")?;
if token != self.expected_token {
return Err("CSRF token mismatch");
}
if current_time > self.issued_at + self.ttl_seconds {
return Err("CSRF token expired");
}
Ok(())
}
}
#[test]
fn csrf_token_validation() {
let validator = CsrfTokenValidator::new("valid-csrf-token");
assert!(
validator.validate(None, 1000).is_err(),
"缺失 CSRF token 应被拒绝"
);
let err = validator.validate(Some("wrong-token"), 1000).unwrap_err();
assert_eq!(err, "CSRF token mismatch");
let err = validator
.validate(Some("valid-csrf-token"), 5000)
.unwrap_err();
assert_eq!(err, "CSRF token expired");
assert!(
validator.validate(Some("valid-csrf-token"), 1000).is_ok(),
"正确且未过期的 token 应通过"
);
}
#[test]
fn csrf_samesite_cookie_enforced() {
fn validate_samesite(samesite: Option<&str>) -> Result<(), &'static str> {
match samesite {
Some("Strict") | Some("Lax") => Ok(()),
Some("None") => Err("SameSite=None is insecure for CSRF defense"),
None => Err("missing SameSite attribute"),
_ => Err("invalid SameSite value"),
}
}
assert!(validate_samesite(None).is_err(), "缺失 SameSite 应被拒绝");
assert!(
validate_samesite(Some("None")).is_err(),
"SameSite=None 应被拒绝"
);
assert!(validate_samesite(Some("Strict")).is_ok());
assert!(validate_samesite(Some("Lax")).is_ok());
}
#[test]
fn csrf_origin_validation() {
fn validate_origin(origin: &str, allowed: &[&str]) -> Result<(), &'static str> {
if allowed.contains(&origin) {
Ok(())
} else {
Err("origin not allowed")
}
}
let allowed_origins = ["https://legit.example.com", "https://app.example.com"];
assert!(
validate_origin("https://evil.com", &allowed_origins).is_err(),
"跨站 Origin 应被拒绝"
);
assert!(
validate_origin("https://legit.example.com", &allowed_origins).is_ok(),
"合法 Origin 应通过"
);
assert!(
validate_origin("https://legit.example.com.evil.com", &allowed_origins).is_err(),
"域名前缀欺骗应被拒绝"
);
}
#[test]
fn csrf_oauth2_state_csrf_defense() {
let mut clients = std::collections::HashMap::new();
clients.insert("client1".to_string(), "secret1".to_string());
let server = OAuth2Server::new(clients);
let state_attacker = "attacker-controlled-state";
let state_victim = "victim-random-state-xyz789";
let req_attacker =
AuthorizationRequest::new("client1", "https://app.com/cb", "read", state_attacker);
let code = server.create_authorization_code(&req_attacker, 1).unwrap();
let token_req_with_wrong_state = TokenRequest::new(&code.code, "https://app.com/cb", "client1");
let result = server.exchange_code(&token_req_with_wrong_state);
assert!(
result.is_ok(),
"exchange_code 本身不校验 state(调用方负责)"
);
assert_ne!(state_attacker, state_victim, "state 应唯一且不可预测");
assert!(
!state_victim.is_empty() && state_victim.len() >= 16,
"state 应足够长以防止猜测"
);
}
#[test]
fn csrf_login_csrf_new_session() {
let encoder = JwtEncoder::new("csrf-test-secret-with-sufficient-length!!");
let old_session = JwtClaims::new("user123", 2000)
.with_issuer("app")
.with_user_id(42);
let old_token = encoder.encode(&old_session).unwrap();
let new_session = JwtClaims::new("user123", 3000)
.with_issuer("app")
.with_user_id(42);
let new_token = encoder.encode(&new_session).unwrap();
assert_ne!(
old_token, new_token,
"登录后应签发新 token(不复用旧 session)"
);
let csrf_token = "random-csrf-token-after-login";
assert!(!csrf_token.is_empty(), "登录后应签发新 CSRF token");
}
#[test]
fn csrf_oauth2_state_binding() {
let mut clients = std::collections::HashMap::new();
clients.insert("client1".to_string(), "secret1".to_string());
let server = OAuth2Server::new(clients);
let state = "unique-state-abc123";
let req = AuthorizationRequest::new("client1", "https://app.com/cb", "read", state);
let code = server.create_authorization_code(&req, 1).unwrap();
assert!(!code.code.is_empty(), "授权码应非空");
assert_ne!(code.code, state, "授权码不应等于 state(独立随机)");
let token_req = TokenRequest::new(&code.code, "https://app.com/cb", "client1");
let result = server.exchange_code(&token_req);
assert!(result.is_ok(), "合法交换应成功");
let replay = server.exchange_code(&token_req);
assert!(
replay.is_err(),
"授权码重放应被拒绝(一次性消费防止 CSRF 重放)"
);
}