OIDC: Fallback to userinfo endpoint when JWT token does not contain an email claim.
This commit is contained in:
@@ -68,6 +68,71 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"clientId": "stalwart-fallback",
|
||||
"enabled": true,
|
||||
"clientAuthenticatorType": "client-secret",
|
||||
"secret": "stalwart-fallback-secret",
|
||||
"redirectUris": [
|
||||
"*"
|
||||
],
|
||||
"webOrigins": [
|
||||
"*"
|
||||
],
|
||||
"publicClient": false,
|
||||
"protocol": "openid-connect",
|
||||
"directAccessGrantsEnabled": true,
|
||||
"standardFlowEnabled": true,
|
||||
"serviceAccountsEnabled": true,
|
||||
"defaultClientScopes": [
|
||||
"openid"
|
||||
],
|
||||
"optionalClientScopes": [
|
||||
"email",
|
||||
"profile",
|
||||
"roles"
|
||||
],
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "email-claim-userinfo-only",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-attribute-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"user.attribute": "email",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "false",
|
||||
"claim.name": "email",
|
||||
"userinfo.token.claim": "true",
|
||||
"jsonType.label": "String"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "groups-userinfo-only",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-group-membership-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"full.path": "false",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "false",
|
||||
"claim.name": "groups",
|
||||
"userinfo.token.claim": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "audience",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-audience-mapper",
|
||||
"consentRequired": false,
|
||||
"config": {
|
||||
"included.client.audience": "stalwart",
|
||||
"id.token.claim": "false",
|
||||
"access.token.claim": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"users": [
|
||||
|
||||
@@ -70,6 +70,55 @@ pub async fn test() {
|
||||
}
|
||||
);
|
||||
|
||||
// Test ODIC userinfo fallback
|
||||
let mut config_userinfo_fallback = config.clone();
|
||||
config_userinfo_fallback.claim_username = "email".to_string();
|
||||
config_userinfo_fallback.require_scopes = Map::new(vec!["openid".to_string()]);
|
||||
|
||||
let token_openid_only = get_token_for_client(
|
||||
"stalwart-fallback",
|
||||
"stalwart-fallback-secret",
|
||||
"john.doe@example.org",
|
||||
"this is an OIDC password",
|
||||
"openid",
|
||||
)
|
||||
.await;
|
||||
|
||||
let mut oidc_broken_userinfo = OpenIdDirectory::open(config_userinfo_fallback.clone())
|
||||
.await
|
||||
.unwrap();
|
||||
if let Directory::OpenId(directory) = &mut oidc_broken_userinfo {
|
||||
directory.discovery.document.userinfo_endpoint = "http://invalid".to_string();
|
||||
}
|
||||
assert!(
|
||||
oidc_broken_userinfo
|
||||
.authenticate(&Credentials::Bearer {
|
||||
username: None,
|
||||
token: token_openid_only.clone(),
|
||||
})
|
||||
.await
|
||||
.is_err()
|
||||
);
|
||||
let oidc_userinfo_fallback = OpenIdDirectory::open(config_userinfo_fallback)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
oidc_userinfo_fallback
|
||||
.authenticate(&Credentials::Bearer {
|
||||
username: None,
|
||||
token: token_openid_only,
|
||||
})
|
||||
.await
|
||||
.unwrap(),
|
||||
Account {
|
||||
email: "john.doe@example.org".to_string(),
|
||||
email_aliases: vec![],
|
||||
secret: None,
|
||||
groups: vec!["sales@example.org".to_string()],
|
||||
description: None,
|
||||
}
|
||||
);
|
||||
|
||||
// Not matching the required audience should fail
|
||||
let mut config_wrong_audience = config.clone();
|
||||
config_wrong_audience.require_audience = Some("wrong_audience".to_string());
|
||||
@@ -115,17 +164,34 @@ pub async fn test() {
|
||||
}
|
||||
|
||||
async fn get_token(username: &str, password: &str) -> String {
|
||||
get_token_for_client(
|
||||
"stalwart",
|
||||
"stalwart-secret",
|
||||
username,
|
||||
password,
|
||||
"openid email profile",
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn get_token_for_client(
|
||||
client_id: &str,
|
||||
client_secret: &str,
|
||||
username: &str,
|
||||
password: &str,
|
||||
scope: &str,
|
||||
) -> String {
|
||||
let client = reqwest::Client::new();
|
||||
|
||||
let response = client
|
||||
.post("http://localhost:9080/realms/stalwart/protocol/openid-connect/token")
|
||||
.form(&[
|
||||
("grant_type", "password"),
|
||||
("client_id", "stalwart"),
|
||||
("client_secret", "stalwart-secret"),
|
||||
("client_id", client_id),
|
||||
("client_secret", client_secret),
|
||||
("username", username),
|
||||
("password", password),
|
||||
("scope", "openid email profile"),
|
||||
("scope", scope),
|
||||
])
|
||||
.send()
|
||||
.await
|
||||
|
||||
Reference in New Issue
Block a user