132 lines
4.6 KiB
Rust
132 lines
4.6 KiB
Rust
use base64::{Engine, engine::general_purpose::URL_SAFE_NO_PAD};
|
|
use dever_runtime::{
|
|
adapter::Document,
|
|
auth::{Claims, Settings},
|
|
value::secret::Secret,
|
|
};
|
|
use hmac::{Hmac, Mac};
|
|
use sha2::Sha256;
|
|
#[cfg(feature = "authorization")]
|
|
mod entry;
|
|
mod metadata;
|
|
mod request;
|
|
mod tenant;
|
|
|
|
const KEY: &str = "unit-test-only-key-not-for-deployment";
|
|
|
|
fn settings() -> Settings {
|
|
let document = Document::parse(serde_json::json!({"auth":{"providers":{
|
|
"session":{"verify":"user.account.verify","jwtSecret":KEY,"ttlSeconds":60,"cookie":"session"},
|
|
"other":{"verify":"user.account.verify","jwtSecret":KEY,"ttlSeconds":60}
|
|
}}}).to_string()).unwrap();
|
|
Settings::load(&document).unwrap()
|
|
}
|
|
|
|
fn signed(header: &str, payload: &str, key: &str) -> Secret {
|
|
let signed = format!(
|
|
"{}.{}",
|
|
URL_SAFE_NO_PAD.encode(header),
|
|
URL_SAFE_NO_PAD.encode(payload)
|
|
);
|
|
let mut mac = Hmac::<Sha256>::new_from_slice(key.as_bytes()).unwrap();
|
|
mac.update(signed.as_bytes());
|
|
Secret::from_input(
|
|
format!(
|
|
"{signed}.{}",
|
|
URL_SAFE_NO_PAD.encode(mac.finalize().into_bytes())
|
|
)
|
|
.into_bytes(),
|
|
)
|
|
}
|
|
|
|
#[test]
|
|
fn signed_claims_remain_bound_to_provider_site_session_and_time() {
|
|
let settings = settings();
|
|
let provider = &settings.providers["session"];
|
|
let claims = Claims {
|
|
subject: "账号".into(),
|
|
session: "opaque-session".into(),
|
|
tenant: None,
|
|
site: "admin".into(),
|
|
};
|
|
let token = provider.issue(&claims, 100).unwrap();
|
|
assert_eq!(provider.verify_token(&token, "admin", 100).unwrap(), claims);
|
|
assert_eq!(provider.verify_token(&token, "admin", 159).unwrap(), claims);
|
|
assert!(provider.verify_token(&token, "admin", 160).is_err());
|
|
assert!(provider.verify_token(&token, "admin", 99).is_err());
|
|
assert!(provider.verify_token(&token, "front", 100).is_err());
|
|
assert!(
|
|
settings.providers["other"]
|
|
.verify_token(&token, "admin", 100)
|
|
.is_err()
|
|
);
|
|
assert!(!format!("{token:?}").contains("opaque-session"));
|
|
}
|
|
|
|
#[test]
|
|
fn token_validation_rejects_algorithm_confusion_tampering_and_ambiguous_claims() {
|
|
let settings = settings();
|
|
let provider = &settings.providers["session"];
|
|
let payload =
|
|
r#"{"iss":"session","aud":"admin","sub":"u","sid":"s","tenant":null,"iat":100,"exp":160}"#;
|
|
let header = r#"{"alg":"HS256","typ":"JWT"}"#;
|
|
assert!(
|
|
provider
|
|
.verify_token(&signed(header, payload, KEY), "admin", 100)
|
|
.is_ok()
|
|
);
|
|
for token in [
|
|
signed(r#"{"alg":"none","typ":"JWT"}"#, payload, KEY),
|
|
signed(r#"{"alg":"RS256","typ":"JWT"}"#, payload, KEY),
|
|
signed(header, payload, "different-unit-test-signing-key"),
|
|
signed(
|
|
header,
|
|
&payload.replace("\"sub\":\"u\"", "\"sub\":\"u\",\"sub\":\"v\""),
|
|
KEY,
|
|
),
|
|
signed(
|
|
header,
|
|
&payload.replace("\"sub\":\"u\"", "\"sub\":\"\""),
|
|
KEY,
|
|
),
|
|
signed(header, &payload.replace("\"exp\":160", "\"exp\":161"), KEY),
|
|
signed(header, &payload.replace("\"iat\":100", "\"iat\":101"), KEY),
|
|
signed(header, &payload.replace("\"iat\":100", "\"iat\":-1"), KEY),
|
|
signed(header, &payload.replace("\"exp\":160", "\"exp\":1e9"), KEY),
|
|
signed(
|
|
header,
|
|
&payload.replace("\"tenant\":null,", "\"permissions\":[\"*\"],"),
|
|
KEY,
|
|
),
|
|
Secret::from_input(vec![b'a'; 8193]),
|
|
] {
|
|
assert_eq!(
|
|
provider.verify_token(&token, "admin", 100).unwrap_err(),
|
|
"invalid authentication"
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn authentication_settings_require_deployment_key_and_unambiguous_fields() {
|
|
for value in [
|
|
serde_json::json!({"verify":"user.account.verify","jwtSecret":"short","ttlSeconds":60}),
|
|
serde_json::json!({"verify":"user.account.verify","jwtSecret":KEY,"ttlSeconds":0}),
|
|
serde_json::json!({"verify":"user.account.verify","jwtSecret":KEY,"ttlSeconds":60,"cookie":"session; bad"}),
|
|
serde_json::json!({"verify":"user.account","jwtSecret":KEY,"ttlSeconds":60}),
|
|
serde_json::json!({"verify":"user.account.verify","jwtSecret":KEY,"ttlSeconds":60,"algorithm":"none"}),
|
|
] {
|
|
let document = Document::parse(
|
|
serde_json::json!({"auth":{"providers":{"session":value}}}).to_string(),
|
|
)
|
|
.unwrap();
|
|
assert!(Settings::load(&document).is_err());
|
|
}
|
|
assert!(
|
|
Settings::load(&Document::parse("{}".into()).unwrap())
|
|
.unwrap()
|
|
.providers
|
|
.is_empty()
|
|
);
|
|
}
|