Initial Dever 0.0.1
This commit is contained in:
commit
81233d098c
1479 files changed
+163782
No files matched your search
@@ -0,0 +1,131 @@
|
||||
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()
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user