fix(iam): separate OIDC role and claim policies (#5493)

This commit is contained in:
Zhengchao An
2026-07-31 07:53:06 +08:00
committed by GitHub
parent 2e29c330a9
commit 1d383e239d
+58 -27
View File
@@ -1139,8 +1139,11 @@ impl OidcSys {
let mut policies = Vec::new();
let mut groups = Vec::new();
// Add default role policy if configured
if !config.role_policy.is_empty() {
// Role-policy and claim-based authorization are separate OIDC modes. When a
// role policy is configured, group claims still provide group context but
// must not also become policy names.
let has_role_policy = !config.role_policy.trim().is_empty();
if has_role_policy {
for policy in config.role_policy.split(',') {
let policy = policy.trim();
if !policy.is_empty() {
@@ -1149,21 +1152,20 @@ impl OidcSys {
}
}
// Map groups claim to policies
for group in &claims.groups {
groups.push(group.clone());
let policy_name = if config.claim_prefix.is_empty() {
group.clone()
} else {
format!("{}{}", config.claim_prefix, group)
};
policies.push(policy_name);
if !has_role_policy {
let policy_name = if config.claim_prefix.is_empty() {
group.clone()
} else {
format!("{}{}", config.claim_prefix, group)
};
policies.push(policy_name);
}
}
// Map primary claim (if different from groups)
if config.claim_name != config.groups_claim {
let claim_values = extract_groups_claim(&claims.raw, &config.claim_name);
for val in claim_values {
if !has_role_policy && config.claim_name != config.groups_claim {
for val in extract_groups_claim(&claims.raw, &config.claim_name) {
let policy_name = if config.claim_prefix.is_empty() {
val
} else {
@@ -3201,26 +3203,22 @@ mod tests {
}
#[test]
fn test_map_claims_to_policies_with_provider() {
let mut config = test_config("okta");
config.role_policy = "readwrite".to_string();
config.display_name = "Okta".to_string();
fn role_policy_does_not_map_groups_as_policies() {
let mut config = test_config("authentik");
config.role_policy = "consoleAdmin".to_string();
config.claim_name = "policy".to_string();
let sys = make_test_sys(vec![config]);
let claims = OidcClaims {
sub: "user123".to_string(),
email: "user@example.com".to_string(),
username: "user".to_string(),
groups: vec!["admin".to_string(), "devs".to_string()],
raw: HashMap::new(),
groups: vec!["authentik Admins".to_string(), "users".to_string()],
raw: HashMap::from([("policy".to_string(), serde_json::json!(["readonly"]))]),
..Default::default()
};
let (policies, groups) = sys.map_claims_to_policies("okta", &claims);
assert_eq!(groups, vec!["admin", "devs"]);
assert!(policies.contains(&"readwrite".to_string()));
assert!(policies.contains(&"admin".to_string()));
assert!(policies.contains(&"devs".to_string()));
let (policies, groups) = sys.map_claims_to_policies("authentik", &claims);
assert_eq!(groups, vec!["authentik Admins", "users"]);
assert_eq!(policies, vec!["consoleAdmin"]);
}
#[test]
@@ -3245,6 +3243,39 @@ mod tests {
assert_eq!(policies.len(), 1);
}
#[test]
fn blank_role_policy_uses_claim_mapping() {
let mut config = test_config("keycloak");
config.role_policy = " ".to_string();
let sys = make_test_sys(vec![config]);
let claims = OidcClaims {
groups: vec!["readonly".to_string()],
..Default::default()
};
let (policies, groups) = sys.map_claims_to_policies("keycloak", &claims);
assert_eq!(groups, vec!["readonly"]);
assert_eq!(policies, vec!["readonly"]);
}
#[test]
fn claim_mapping_keeps_groups_with_distinct_primary_claim() {
let mut config = test_config("keycloak");
config.claim_name = "policy".to_string();
let sys = make_test_sys(vec![config]);
let claims = OidcClaims {
groups: vec!["developers".to_string()],
raw: HashMap::from([("policy".to_string(), serde_json::json!(["readonly"]))]),
..Default::default()
};
let (policies, groups) = sys.map_claims_to_policies("keycloak", &claims);
assert_eq!(groups, vec!["developers"]);
assert_eq!(policies, vec!["developers", "readonly"]);
}
#[test]
fn test_list_providers() {
let mut config = test_config("keycloak");