From d9dee7ab3cf8470e652365e8b8dfb7c5ed9ad811 Mon Sep 17 00:00:00 2001 From: Martin Kanis Date: Tue, 6 Oct 2026 10:32:13 +0200 Subject: [PATCH] Regression with Trust Email when the userinfo endpoint is used Closes #53471 Signed-off-by: Martin Kanis --- .../topics/identity-broker/configuration.adoc | 3 +- .../broker/oidc/OIDCIdentityProvider.java | 3 +- .../tests/broker/oidc/KcOidcBrokerTest.java | 47 +++++++++++++++++++ 3 files changed, 51 insertions(+), 2 deletions(-) diff --git a/docs/documentation/server_admin/topics/identity-broker/configuration.adoc b/docs/documentation/server_admin/topics/identity-broker/configuration.adoc index 2958228f9f7a..d49855eeb67a 100644 --- a/docs/documentation/server_admin/topics/identity-broker/configuration.adoc +++ b/docs/documentation/server_admin/topics/identity-broker/configuration.adoc @@ -55,7 +55,8 @@ Although each type of identity provider has its configuration options, all share |Trust Email |When *ON*, {project_name} trusts email addresses from the identity provider. If the realm requires email validation, users that log in from this identity provider do not need to perform the email verification process. If the target identity provider supports email verification and advertises this information when returning the user profile information, the email of the federated user will be (un)marked as verified. -For instance, an OpenID Connect Provider returning a `email_verified` claim in their ID Tokens. +For an OpenID Connect provider, this applies to the `email_verified` claim accompanying the email address in its ID token or UserInfo response. +If the claim is absent, the *Trust Email* setting provides the default verification status. Note that this setting will set the email as verified when the user is federated for the first time and on subsequent logins through the broker if the sync mode is set to `FORCE`. diff --git a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java index 2a1622b429a6..dfaf9d02c813 100755 --- a/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java +++ b/services/src/main/java/org/keycloak/broker/oidc/OIDCIdentityProvider.java @@ -597,7 +597,8 @@ protected BrokeredIdentityContext extractIdentity(AccessTokenResponse tokenRespo familyName = getJsonProperty(userInfo, IDToken.FAMILY_NAME); preferredUsername = getUsernameFromUserInfo(userInfo); String userInfoEmail = getJsonProperty(userInfo, "email"); - Boolean userInfoEmailVerified = Boolean.parseBoolean(getJsonProperty(userInfo, IDToken.EMAIL_VERIFIED)); + String userInfoEmailVerifiedClaim = getJsonProperty(userInfo, IDToken.EMAIL_VERIFIED); + Boolean userInfoEmailVerified = userInfoEmailVerifiedClaim == null ? null : Boolean.valueOf(userInfoEmailVerifiedClaim); if (userInfoEmail != null) { email = userInfoEmail; diff --git a/tests/base/src/test/java/org/keycloak/tests/broker/oidc/KcOidcBrokerTest.java b/tests/base/src/test/java/org/keycloak/tests/broker/oidc/KcOidcBrokerTest.java index 2d4f6a5198b2..eba420776a9f 100644 --- a/tests/base/src/test/java/org/keycloak/tests/broker/oidc/KcOidcBrokerTest.java +++ b/tests/base/src/test/java/org/keycloak/tests/broker/oidc/KcOidcBrokerTest.java @@ -1,8 +1,55 @@ package org.keycloak.tests.broker.oidc; +import org.keycloak.admin.client.resource.ClientResource; +import org.keycloak.admin.client.resource.ClientScopeResource; +import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper; +import org.keycloak.representations.IDToken; +import org.keycloak.representations.idm.IdentityProviderRepresentation; +import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.testframework.annotations.KeycloakIntegrationTest; import org.keycloak.tests.broker.AbstractKcOidcBrokerTest; +import org.keycloak.tests.utils.admin.AdminApiUtil; +import org.keycloak.testsuite.util.AccountHelper; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; @KeycloakIntegrationTest public class KcOidcBrokerTest extends AbstractKcOidcBrokerTest { + + @Test + public void testTrustEmailWhenEmailVerifiedClaimIsMissingFromUserInfo() { + IdentityProviderRepresentation idp = consumerRealm.admin().identityProviders().get(getIdpAlias()).toRepresentation(); + idp.setTrustEmail(true); + idp.getConfig().put("disableUserInfo", "false"); + consumerRealm.admin().identityProviders().get(getIdpAlias()).update(idp); + + // Send the email only through UserInfo and omit email_verified. + ClientScopeResource emailScope = AdminApiUtil.findClientScopeByName(providerRealm.admin(), "email"); + emailScope.getProtocolMappers().getMappers().forEach(mapper -> { + if (IDToken.EMAIL_VERIFIED.equals(mapper.getConfig().get(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME))) { + emailScope.getProtocolMappers().delete(mapper.getId()); + } else if (IDToken.EMAIL.equals(mapper.getConfig().get(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME))) { + mapper.getConfig().put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "false"); + emailScope.getProtocolMappers().update(mapper.getId(), mapper); + } + }); + ClientResource providerClient = providerRealm.admin().clients().get( + providerRealm.admin().clients().findByClientId(CLIENT_ID).get(0).getId()); + providerClient.getProtocolMappers().getMappers().forEach(mapper -> { + if (IDToken.EMAIL.equals(mapper.getConfig().get(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME))) { + mapper.getConfig().put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "false"); + providerClient.getProtocolMappers().update(mapper.getId(), mapper); + } + }); + + disableUpdateProfileOnFirstLogin(); + logInAsUserInIDP(); + assertTrue(oauth.parseLoginResponse().isSuccess()); + UserRepresentation consumerUser = AccountHelper.getUserRepresentation(consumerRealm.admin(), getUserLogin()); + assertEquals(getUserEmail(), consumerUser.getEmail()); + assertTrue(consumerUser.isEmailVerified()); + } }