mirror of
https://github.com/rustfs/rustfs.git
synced 2026-07-26 16:28:15 +00:00
fix(oidc): support case-insensitive claim name matching (#2362)
Co-authored-by: loverustfs <hello@rustfs.com> Co-authored-by: 安正超 <anzhengchao@gmail.com>
This commit is contained in:
+53
-5
@@ -1007,14 +1007,27 @@ pub(crate) fn decode_jwt_payload(token: &str) -> HashMap<String, serde_json::Val
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract a string claim from raw claims.
|
||||
fn extract_string_claim(claims: &HashMap<String, serde_json::Value>, key: &str) -> String {
|
||||
claims.get(key).and_then(|v| v.as_str()).unwrap_or_default().to_string()
|
||||
/// Get a claim value from raw claims with case-insensitive fallback.
|
||||
/// First tries exact match, then falls back to case-insensitive match if not found.
|
||||
fn get_claim_case_insensitive<'a>(claims: &'a HashMap<String, serde_json::Value>, key: &str) -> Option<&'a serde_json::Value> {
|
||||
if let Some(v) = claims.get(key) {
|
||||
return Some(v);
|
||||
}
|
||||
let key_lower = key.to_lowercase();
|
||||
claims.iter().find(|(k, _)| k.to_lowercase() == key_lower).map(|(_, v)| v)
|
||||
}
|
||||
|
||||
/// Extract a groups/array claim from raw claims. Handles both string arrays and single strings.
|
||||
/// Extract a string claim from raw claims with case-insensitive fallback.
|
||||
fn extract_string_claim(claims: &HashMap<String, serde_json::Value>, key: &str) -> String {
|
||||
get_claim_case_insensitive(claims, key)
|
||||
.and_then(serde_json::Value::as_str)
|
||||
.unwrap_or_default()
|
||||
.to_string()
|
||||
}
|
||||
|
||||
/// Extract a groups/array claim from raw claims with case-insensitive fallback. Handles both string arrays and single strings.
|
||||
fn extract_groups_claim(claims: &HashMap<String, serde_json::Value>, key: &str) -> Vec<String> {
|
||||
match claims.get(key) {
|
||||
match get_claim_case_insensitive(claims, key) {
|
||||
Some(serde_json::Value::Array(arr)) => arr.iter().filter_map(|v| v.as_str().map(String::from)).collect(),
|
||||
Some(serde_json::Value::String(s)) => s.split(',').map(|s| s.trim().to_string()).collect(),
|
||||
_ => vec![],
|
||||
@@ -1069,6 +1082,41 @@ mod tests {
|
||||
assert!(groups.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_string_claim_case_insensitive() {
|
||||
let mut claims = HashMap::new();
|
||||
claims.insert("policyminio".to_string(), serde_json::json!("consoleAdmin"));
|
||||
|
||||
assert_eq!(extract_string_claim(&claims, "policyMinio"), "consoleAdmin");
|
||||
assert_eq!(extract_string_claim(&claims, "POLICYMINIO"), "consoleAdmin");
|
||||
assert_eq!(extract_string_claim(&claims, "policyminio"), "consoleAdmin");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_groups_claim_case_insensitive() {
|
||||
let mut claims = HashMap::new();
|
||||
claims.insert("policyminio".to_string(), serde_json::json!(["consoleAdmin", "readwrite"]));
|
||||
|
||||
let groups = extract_groups_claim(&claims, "policyMinio");
|
||||
assert_eq!(groups, vec!["consoleAdmin", "readwrite"]);
|
||||
|
||||
let groups = extract_groups_claim(&claims, "POLICYMINIO");
|
||||
assert_eq!(groups, vec!["consoleAdmin", "readwrite"]);
|
||||
|
||||
let groups = extract_groups_claim(&claims, "policyminio");
|
||||
assert_eq!(groups, vec!["consoleAdmin", "readwrite"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_extract_groups_claim_exact_match_preferred() {
|
||||
let mut claims = HashMap::new();
|
||||
claims.insert("Policy".to_string(), serde_json::json!(["exact_match"]));
|
||||
claims.insert("policy".to_string(), serde_json::json!(["lowercase"]));
|
||||
|
||||
let groups = extract_groups_claim(&claims, "Policy");
|
||||
assert_eq!(groups, vec!["exact_match"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_decode_jwt_payload() {
|
||||
let payload = r#"{"sub":"user123","email":"user@example.com"}"#;
|
||||
|
||||
@@ -239,9 +239,20 @@ impl Validator for BucketPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_claim_case_insensitive<'a>(claims: &'a HashMap<String, Value>, claim_name: &str) -> Option<&'a Value> {
|
||||
if let Some(v) = claims.get(claim_name) {
|
||||
return Some(v);
|
||||
}
|
||||
let claim_name_lower = claim_name.to_lowercase();
|
||||
claims
|
||||
.iter()
|
||||
.find(|(k, _)| k.to_lowercase() == claim_name_lower)
|
||||
.map(|(_, v)| v)
|
||||
}
|
||||
|
||||
fn get_values_from_claims(claims: &HashMap<String, Value>, claim_name: &str) -> (HashSet<String>, bool) {
|
||||
let mut s = HashSet::new();
|
||||
if let Some(pname) = claims.get(claim_name) {
|
||||
if let Some(pname) = get_claim_case_insensitive(claims, claim_name) {
|
||||
if let Some(pnames) = pname.as_array() {
|
||||
for pname in pnames {
|
||||
if let Some(pname_str) = pname.as_str() {
|
||||
@@ -1693,4 +1704,44 @@ mod test {
|
||||
"principal and resource match should keep ExistingObjectTag fetch hint"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_values_from_claims_case_insensitive() {
|
||||
let mut claims = HashMap::new();
|
||||
claims.insert("policyminio".to_string(), Value::Array(vec![Value::String("consoleAdmin".to_string())]));
|
||||
|
||||
let (policies, found) = get_values_from_claims(&claims, "policyMinio");
|
||||
assert!(found);
|
||||
assert!(policies.contains("consoleAdmin"));
|
||||
|
||||
let (policies, found) = get_values_from_claims(&claims, "POLICYMINIO");
|
||||
assert!(found);
|
||||
assert!(policies.contains("consoleAdmin"));
|
||||
|
||||
let (policies, found) = get_values_from_claims(&claims, "policyminio");
|
||||
assert!(found);
|
||||
assert!(policies.contains("consoleAdmin"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_values_from_claims_exact_match_preferred() {
|
||||
let mut claims = HashMap::new();
|
||||
claims.insert("Policy".to_string(), Value::Array(vec![Value::String("exact_match".to_string())]));
|
||||
claims.insert("policy".to_string(), Value::Array(vec![Value::String("lowercase".to_string())]));
|
||||
|
||||
let (policies, _) = get_values_from_claims(&claims, "Policy");
|
||||
assert!(policies.contains("exact_match"));
|
||||
assert!(!policies.contains("lowercase"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_get_policies_from_claims_case_insensitive_string() {
|
||||
let mut claims = HashMap::new();
|
||||
claims.insert("policyminio".to_string(), Value::String("consoleAdmin,readwrite".to_string()));
|
||||
|
||||
let (policies, found) = get_policies_from_claims(&claims, "policyMinio");
|
||||
assert!(found);
|
||||
assert!(policies.contains("consoleAdmin"));
|
||||
assert!(policies.contains("readwrite"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user