Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Stream;

import jakarta.ws.rs.GET;
Expand Down Expand Up @@ -562,6 +561,7 @@ protected BrokeredIdentityContext extractIdentity(AccessTokenResponse tokenRespo
String familyName = (String)idToken.getOtherClaims().get(IDToken.FAMILY_NAME);
String preferredUsername = (String) idToken.getOtherClaims().get(getusernameClaimNameForIdToken());
String email = (String) idToken.getOtherClaims().get(IDToken.EMAIL);
Boolean emailVerified = getEmailVerifiedClaim(idToken);

if (!getConfig().isDisableUserInfoService()) {
String userInfoUrl = getUserInfoUrl();
Expand Down Expand Up @@ -594,11 +594,23 @@ protected BrokeredIdentityContext extractIdentity(AccessTokenResponse tokenRespo
givenName = getJsonProperty(userInfo, IDToken.GIVEN_NAME);
familyName = getJsonProperty(userInfo, IDToken.FAMILY_NAME);
preferredUsername = getUsernameFromUserInfo(userInfo);
email = getJsonProperty(userInfo, "email");
String userInfoEmail = getJsonProperty(userInfo, "email");
Boolean userInfoEmailVerified = Boolean.parseBoolean(getJsonProperty(userInfo, IDToken.EMAIL_VERIFIED));

if (userInfoEmail != null) {
email = userInfoEmail;
emailVerified = userInfoEmailVerified;
}

AbstractJsonUserAttributeMapper.storeUserProfileForMapper(identity, userInfo, getConfig().getAlias());
}
}
}

if (emailVerified != null) {
identity.getContextData().put(IDToken.EMAIL_VERIFIED, emailVerified);
}

identity.getContextData().put(VALIDATED_ID_TOKEN, idToken);

identity.setId(id);
Expand Down Expand Up @@ -1100,9 +1112,8 @@ public Stream<JWK> resolveKeys(TrustMaterialRequest request) {
protected void setEmailVerified(UserModel user, BrokeredIdentityContext context) {
OIDCIdentityProviderConfig config = getConfig();
Map<String, Object> contextData = context.getContextData();
JsonWebToken token = (JsonWebToken) Optional.ofNullable(contextData.get(VALIDATED_ID_TOKEN))
.orElseGet(() -> contextData.get(VALIDATED_ACCESS_TOKEN));
Boolean emailVerified = getEmailVerifiedClaim(token);

Boolean emailVerified = (Boolean) contextData.get(IDToken.EMAIL_VERIFIED);

if (Booleans.isFalse(config.isTrustEmail()) || emailVerified == null) {
// fallback to the default behavior if trust is disabled or there is no email_verified claim
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.keycloak.protocol.oidc.mappers.HardcodedClaim;
import org.keycloak.protocol.oidc.mappers.OIDCAttributeMapperHelper;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.representations.IDToken;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
import org.keycloak.representations.idm.IdentityProviderMapperRepresentation;
Expand Down Expand Up @@ -76,6 +77,7 @@
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.notNullValue;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;

/**
* Final class as it's not intended to be overriden. Feel free to remove "final" if you really know what you are doing.
Expand Down Expand Up @@ -494,6 +496,84 @@ public void testVerifyEmailWhenUpdateProfileAndEmailVerifiedAtIdP() {
assertThat(users.get(0).getEmail(), is("updated@keycloak.org"));
}

@Test
public void testEmailVerifiedFromUserInfo() {
RealmResource providerRealm = adminClient.realm(bc.providerRealmName());
RealmResource consumerRealm = adminClient.realm(bc.consumerRealmName());

IdentityProviderRepresentation idpRep = identityProviderResource.toRepresentation();
idpRep.setTrustEmail(true);
idpRep.getConfig().put("disableUserInfo", "false");
identityProviderResource.update(idpRep);

ClientScopeResource emailClientScope = AdminApiUtil.findClientScopeByName(providerRealm, "email");
List<ProtocolMapperRepresentation> originalMappers = emailClientScope.getProtocolMappers().getMappers();
originalMappers.forEach(protocolMapper -> emailClientScope.getProtocolMappers().delete(protocolMapper.getId()));

Comment thread
graziang marked this conversation as resolved.
Resolved
// Save and remove the "email" protocol mapper from the brokerapp client so that the email claim
// is not included in the ID token, allowing us to test that the email comes from userinfo only
ClientRepresentation brokerAppClient = providerRealm.clients().findByClientId("brokerapp").get(0);
ClientResource brokerAppResource = providerRealm.clients().get(brokerAppClient.getId());
ProtocolMapperRepresentation brokerAppEmailMapper = brokerAppResource.getProtocolMappers().getMappers().stream()
.filter(mapper -> "email".equals(mapper.getName()))
.findFirst()
.orElse(null);
brokerAppResource.getProtocolMappers().delete(brokerAppEmailMapper.getId());

Comment thread
graziang marked this conversation as resolved.
Resolved
ProtocolMapperRepresentation userinfoEmailVerifiedMapper = new ProtocolMapperRepresentation();
userinfoEmailVerifiedMapper.setName("userinfo-email-verified");
userinfoEmailVerifiedMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
userinfoEmailVerifiedMapper.setProtocolMapper(HardcodedClaim.PROVIDER_ID);
userinfoEmailVerifiedMapper.getConfig().put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, IDToken.EMAIL_VERIFIED);
userinfoEmailVerifiedMapper.getConfig().put(OIDCAttributeMapperHelper.JSON_TYPE, "boolean");
userinfoEmailVerifiedMapper.getConfig().put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "false");
userinfoEmailVerifiedMapper.getConfig().put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "false");
userinfoEmailVerifiedMapper.getConfig().put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true");
userinfoEmailVerifiedMapper.getConfig().put(HardcodedClaim.CLAIM_VALUE, "false");
emailClientScope.getProtocolMappers().createMapper(userinfoEmailVerifiedMapper).close();
Comment thread
graziang marked this conversation as resolved.
Resolved

ProtocolMapperRepresentation userinfoEmailMapper = new ProtocolMapperRepresentation();
userinfoEmailMapper.setName("userinfo-email");
userinfoEmailMapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
userinfoEmailMapper.setProtocolMapper(HardcodedClaim.PROVIDER_ID);
userinfoEmailMapper.getConfig().put(OIDCAttributeMapperHelper.TOKEN_CLAIM_NAME, IDToken.EMAIL);
userinfoEmailMapper.getConfig().put(OIDCAttributeMapperHelper.JSON_TYPE, "String");
userinfoEmailMapper.getConfig().put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "false");
userinfoEmailMapper.getConfig().put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "false");
userinfoEmailMapper.getConfig().put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true");
userinfoEmailMapper.getConfig().put(HardcodedClaim.CLAIM_VALUE, "different-email@test.com");
emailClientScope.getProtocolMappers().createMapper(userinfoEmailMapper).close();


oauth.client("broker-app");
loginPage.open(bc.consumerRealmName());
logInWithBroker(bc);

waitForPage(driver, "update account information", false);
updateAccountInformationPage.assertCurrent();
updateAccountInformationPage.updateAccountInformation("FirstName", "LastName");

// Verify user was created with email from ID token, not userinfo
Comment thread
graziang marked this conversation as resolved.
Resolved
List<UserRepresentation> users = consumerRealm.users().search(bc.getUserLogin());
assertEquals(1, users.size());
UserRepresentation consumerUser = users.get(0);

assertEquals(consumerUser.getEmail(), "different-email@test.com");
Comment thread
graziang marked this conversation as resolved.
Resolved

// Email should be not verified because it came from ID token which has email_verified=false
assertFalse(consumerUser.isEmailVerified());
Comment thread
graziang marked this conversation as resolved.
Resolved

emailClientScope.getProtocolMappers().getMappers().forEach(protocolMapper -> emailClientScope.getProtocolMappers().delete(protocolMapper.getId()));
emailClientScope.getProtocolMappers().createMapper(originalMappers);

// Restore the "email" protocol mapper on the brokerapp client
if (brokerAppEmailMapper != null) {
brokerAppEmailMapper.setId(null);
brokerAppResource.getProtocolMappers().createMapper(brokerAppEmailMapper).close();
}

}

@Test
public void testVerifyEmailWhenUpdateProfileSameEmailAndEmailVerifiedAtIdP() {
RealmResource providerRealm = adminClient.realm(bc.providerRealmName());
Expand Down
Loading