diff --git a/src/config.rs b/src/config.rs index a56748f4..da9e7651 100644 --- a/src/config.rs +++ b/src/config.rs @@ -834,6 +834,8 @@ make_config! { sso_scopes: String, true, def, "email profile".to_owned(); /// Authorization request extra parameters sso_authorize_extra_params: String, true, def, String::new(); + /// SSO name claim |> The OIDC claim to use for the user's display name. Falls back to `preferred_username`. + sso_name_claim: String, true, def, String::new(); /// Use PKCE during Authorization flow sso_pkce: bool, true, def, true; /// Regex for additional trusted Id token audience |> By default only the client_id is trusted. diff --git a/src/sso.rs b/src/sso.rs index 6b575a3e..29807bd0 100644 --- a/src/sso.rs +++ b/src/sso.rs @@ -290,7 +290,46 @@ pub async fn exchange_code( let email_verified = id_claims.email_verified().or(user_info.email_verified()); - let user_name = id_claims.preferred_username().or(user_info.preferred_username()).map(|un| un.to_string()); + let configured_claim = CONFIG.sso_name_claim(); + let mut user_name = None; + + if configured_claim == "name" { + user_name = id_claims + .name() + .and_then(|n| n.get(id_claims.locale().as_ref()).or_else(|| n.get(None))) + .or_else(|| { + user_info + .name() + .and_then(|n| n.get(user_info.locale().as_ref()).or_else(|| n.get(None))) + }) + .map(|n| n.to_string()) + .filter(|s| !s.trim().is_empty()); + } else if !configured_claim.is_empty() { + let extract_claim = |claims: &serde_json::Value, claim_key: &str| -> Option { + claims.get(claim_key) + .and_then(|v| v.as_str()) + .map(|s| s.to_string()) + .filter(|s| !s.trim().is_empty()) + }; + + let id_claims_json = serde_json::to_value(&id_claims).unwrap_or_default(); + let user_info_json = serde_json::to_value(&user_info).unwrap_or_default(); + + user_name = extract_claim(&id_claims_json, &configured_claim) + .or_else(|| extract_claim(&user_info_json, &configured_claim)); + } + + let user_name = user_name + .or_else(|| { + id_claims.preferred_username() + .map(|n| n.to_string()) + .filter(|s| !s.trim().is_empty()) + }) + .or_else(|| { + user_info.preferred_username() + .map(|n| n.to_string()) + .filter(|s| !s.trim().is_empty()) + }); let refresh_token = token_response.refresh_token().map(openidconnect::RefreshToken::secret); if refresh_token.is_none() && CONFIG.sso_scopes_vec().contains(&"offline_access".to_owned()) {