Skip to content

Commit

Permalink
Do not lower-case the username from the IdP when creating the federat…
Browse files Browse the repository at this point in the history
…ed identity

Closes keycloak#28495

Signed-off-by: Pedro Igor <[email protected]>
  • Loading branch information
pedroigor committed May 22, 2024
1 parent 37f8593 commit 6fb3cf5
Show file tree
Hide file tree
Showing 27 changed files with 149 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -76,4 +76,7 @@ Although each type of identity provider has its configuration options, all share

|Sync Mode
|Strategy to update user information from the identity provider through mappers. When choosing *legacy*, {project_name} used the current behavior. *Import* does not update user data and *force* updates user data when possible. See <<_mappers, Identity Provider Mappers>> for more information.

|Case-sensitive username
|If enabled, the original username from the identity provider is kept as is when federating users. Otherwise, the username from the identity provider is lower-cased and might not match the original value if it is case-sensitive. This setting only affects the username associated with the federated identity as usernames in the server are always in lower-case.
|===
Original file line number Diff line number Diff line change
Expand Up @@ -3134,3 +3134,5 @@ logo=Logo
avatarImage=Avatar image
organizationsEnabled=Organizations
organizationsEnabledHelp=If enabled, allows managing organizations. Otherwise, existing organizations are still kept but you will not be able to manage them anymore or authenticate their members.
caseSensitiveOriginalUsername=Case-sensitive username
caseSensitiveOriginalUsernameHelp=If enabled, the original username from the identity provider is kept as is when federating users. Otherwise, the username from the identity provider is lower-cased and might not match the original value if it is case-sensitive. This setting only affects the username associated with the federated identity as usernames in the server are always in lower-case.
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,10 @@ export const AdvancedSettings = ({ isOIDC, isSAML }: AdvancedSettingsProps) => {
}}
/>
)}
<SwitchField
field="config.caseSensitiveOriginalUsername"
label="caseSensitiveOriginalUsername"
/>
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ public void addFederatedIdentity(RealmModel realm, UserModel user, FederatedIden
entity.setRealmId(realm.getId());
entity.setIdentityProvider(identity.getIdentityProvider());
entity.setUserId(identity.getUserId());
entity.setUserName(identity.getUserName().toLowerCase());
entity.setUserName(identity.getUserName());
entity.setToken(identity.getToken());
UserEntity userEntity = em.getReference(UserEntity.class, user.getId());
entity.setUser(userEntity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
*/
package org.keycloak.broker.provider;

import static java.util.Optional.ofNullable;

import org.keycloak.models.Constants;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.models.UserSessionModel;
Expand Down Expand Up @@ -51,12 +53,13 @@ public class BrokeredIdentityContext {
private Map<String, Object> contextData = new HashMap<>();
private AuthenticationSessionModel authenticationSession;

public BrokeredIdentityContext(String id) {
public BrokeredIdentityContext(String id, IdentityProviderModel idpConfig) {
if (id == null) {
throw new RuntimeException("No identifier provider for identity.");
}

this.id = id;
this.idpConfig = idpConfig;
}

public String getId() {
Expand Down Expand Up @@ -86,7 +89,11 @@ public void setLegacyId(String legacyId) {
* @return
*/
public String getUsername() {
return username;
if (getIdpConfig().isCaseSensitiveOriginalUsername()) {
return username;
}

return username == null ? null : username.toLowerCase();
}

public void setUsername(String username) {
Expand Down Expand Up @@ -142,10 +149,6 @@ public IdentityProviderModel getIdpConfig() {
return idpConfig;
}

public void setIdpConfig(IdentityProviderModel idpConfig) {
this.idpConfig = idpConfig;
}

public IdentityProvider getIdp() {
return idp;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public class IdentityProviderModel implements Serializable {
public static final String CLAIM_FILTER_VALUE = "claimFilterValue";
public static final String DO_NOT_STORE_USERS = "doNotStoreUsers";
public static final String METADATA_DESCRIPTOR_URL = "metadataDescriptorUrl";
public static final String CASE_SENSITIVE_ORIGINAL_USERNAME = "caseSensitiveOriginalUsername";

private String internalId;

Expand Down Expand Up @@ -321,6 +322,14 @@ public void setMetadataDescriptorUrl(String metadataDescriptorUrl) {
getConfig().put(METADATA_DESCRIPTOR_URL, metadataDescriptorUrl);
}

public boolean isCaseSensitiveOriginalUsername() {
return Boolean.parseBoolean(getConfig().getOrDefault(CASE_SENSITIVE_ORIGINAL_USERNAME, Boolean.FALSE.toString()));
}

public void setCaseSensitiveOriginalUsername(boolean caseSensitive) {
getConfig().put(CASE_SENSITIVE_ORIGINAL_USERNAME, Boolean.valueOf(caseSensitive).toString());
}

@Override
public int hashCode() {
int hash = 5;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -264,7 +264,14 @@ public String getFirstAttribute(String name) {
}

public BrokeredIdentityContext deserialize(KeycloakSession session, AuthenticationSessionModel authSession) {
BrokeredIdentityContext ctx = new BrokeredIdentityContext(getId());
RealmModel realm = authSession.getRealm();
IdentityProviderModel idpConfig = realm.getIdentityProviderByAlias(getIdentityProviderId());

if (idpConfig == null) {
throw new ModelException("Can't find identity provider with ID " + getIdentityProviderId() + " in realm " + realm.getName());
}

BrokeredIdentityContext ctx = new BrokeredIdentityContext(getId(), idpConfig);

ctx.setUsername(getBrokerUsername());
ctx.setModelUsername(getModelUsername());
Expand All @@ -275,13 +282,7 @@ public BrokeredIdentityContext deserialize(KeycloakSession session, Authenticati
ctx.setBrokerUserId(getBrokerUserId());
ctx.setToken(getToken());

RealmModel realm = authSession.getRealm();
IdentityProviderModel idpConfig = realm.getIdentityProviderByAlias(getIdentityProviderId());
if (idpConfig == null) {
throw new ModelException("Can't find identity provider with ID " + getIdentityProviderId() + " in realm " + realm.getName());
}
IdentityProvider idp = IdentityBrokerService.getIdentityProvider(session, realm, idpConfig.getAlias());
ctx.setIdpConfig(idpConfig);
ctx.setIdp(idp);

IdentityProviderDataMarshaller serializer = idp.getMarshaller();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -562,7 +562,6 @@ public Response authResponse(@QueryParam(AbstractOAuth2IdentityProvider.OAUTH2_P
if (federatedIdentity.getToken() == null)federatedIdentity.setToken(response);
}

federatedIdentity.setIdpConfig(providerConfig);
federatedIdentity.setIdp(provider);
federatedIdentity.setAuthenticationSession(authSession);

Expand Down Expand Up @@ -712,7 +711,6 @@ final public BrokeredIdentityContext exchangeExternal(EventBuilder event, Multiv
BrokeredIdentityContext context = exchangeExternalImpl(event, params);
if (context != null) {
context.setIdp(this);
context.setIdpConfig(getConfig());
}
return context;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -469,7 +469,7 @@ protected boolean isAuthTimeExpired(JsonWebToken idToken, AuthenticationSessionM

protected BrokeredIdentityContext extractIdentity(AccessTokenResponse tokenResponse, String accessToken, JsonWebToken idToken) throws IOException {
String id = idToken.getSubject();
BrokeredIdentityContext identity = new BrokeredIdentityContext(id);
BrokeredIdentityContext identity = new BrokeredIdentityContext(id, getConfig());
String name = (String) idToken.getOtherClaims().get(IDToken.NAME);
String givenName = (String)idToken.getOtherClaims().get(IDToken.GIVEN_NAME);
String familyName = (String)idToken.getOtherClaims().get(IDToken.FAMILY_NAME);
Expand Down Expand Up @@ -791,7 +791,7 @@ protected BrokeredIdentityContext extractIdentityFromProfile(EventBuilder event,
event.error(Errors.INVALID_TOKEN);
throw new ErrorResponseException(OAuthErrorException.INVALID_TOKEN, "invalid token", Response.Status.BAD_REQUEST);
}
BrokeredIdentityContext identity = new BrokeredIdentityContext(id);
BrokeredIdentityContext identity = new BrokeredIdentityContext(id, getConfig());

String name = getJsonProperty(userInfo, "name");
String preferredUsername = getUsernameFromUserInfo(userInfo);
Expand Down Expand Up @@ -881,7 +881,6 @@ final protected BrokeredIdentityContext validateJwt(EventBuilder event, String s
}
context.getContextData().put(EXCHANGE_PROVIDER, getConfig().getAlias());
context.setIdp(this);
context.setIdpConfig(getConfig());
return context;
} catch (IOException e) {
logger.debug("Unable to extract identity from identity token", e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -550,7 +550,7 @@ protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder h
}

//Map<String, String> notes = new HashMap<>();
BrokeredIdentityContext identity = new BrokeredIdentityContext(principal);
BrokeredIdentityContext identity = new BrokeredIdentityContext(principal, config);
identity.getContextData().put(SAML_LOGIN_RESPONSE, responseType);
identity.getContextData().put(SAML_ASSERTION, assertion);
identity.setAuthenticationSession(authSession);
Expand Down Expand Up @@ -601,7 +601,6 @@ protected Response handleLoginResponse(String samlResponse, SAMLDocumentHolder h

String brokerUserId = config.getAlias() + "." + principal;
identity.setBrokerUserId(brokerUserId);
identity.setIdpConfig(config);
identity.setIdp(provider);
if (authn != null && authn.getSessionIndex() != null) {
identity.setBrokerSessionId(config.getAlias() + "." + authn.getSessionIndex());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -132,13 +132,10 @@ protected BrokeredIdentityContext validateExternalTokenThroughUserInfo(EventBuil
}

private BrokeredIdentityContext extractUserInfo(String subjectToken, JsonNode profile) {
BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "account_id"));


BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "account_id"), getConfig());
String username = getJsonProperty(profile, "username");
user.setUsername(username);
user.setName(getJsonProperty(profile, "display_name"));
user.setIdpConfig(getConfig());
user.setIdp(this);

AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ protected String getProfileEndpointForValidation(EventBuilder event) {
protected BrokeredIdentityContext extractIdentityFromProfile(EventBuilder event, JsonNode profile) {
String id = getJsonProperty(profile, "id");

BrokeredIdentityContext user = new BrokeredIdentityContext(id);
BrokeredIdentityContext user = new BrokeredIdentityContext(id, getConfig());

String email = getJsonProperty(profile, "email");

Expand All @@ -101,7 +101,6 @@ protected BrokeredIdentityContext extractIdentityFromProfile(EventBuilder event,

user.setFirstName(firstName);
user.setLastName(lastName);
user.setIdpConfig(getConfig());
user.setIdp(this);

AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,12 @@ protected String getProfileEndpointForValidation(EventBuilder event) {

@Override
protected BrokeredIdentityContext extractIdentityFromProfile(EventBuilder event, JsonNode profile) {
BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "id"));
BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "id"), getConfig());

String username = getJsonProperty(profile, "login");
user.setUsername(username);
user.setName(getJsonProperty(profile, "name"));
user.setEmail(getJsonProperty(profile, "email"));
user.setIdpConfig(getConfig());
user.setIdp(this);

AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ protected BrokeredIdentityContext extractIdentityFromProfile(EventBuilder event,

private BrokeredIdentityContext gitlabExtractFromProfile(JsonNode profile) {
String id = getJsonProperty(profile, "id");
BrokeredIdentityContext identity = new BrokeredIdentityContext(id);
BrokeredIdentityContext identity = new BrokeredIdentityContext(id, getConfig());

String name = getJsonProperty(profile, "name");
String preferredUsername = getJsonProperty(profile, "username");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,9 +66,8 @@ protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
String username = getJsonProperty(profile, "username");
String legacyId = getJsonProperty(profile, LEGACY_ID_FIELD);

BrokeredIdentityContext user = new BrokeredIdentityContext(id);
BrokeredIdentityContext user = new BrokeredIdentityContext(id, getConfig());
user.setUsername(username);
user.setIdpConfig(getConfig());
user.setIdp(this);
if (legacyId != null && !legacyId.isEmpty()) {
user.setLegacyId(legacyId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
@Override
protected BrokeredIdentityContext extractIdentityFromProfile(EventBuilder event, JsonNode profile) {
String id = getJsonProperty(profile, "id");
BrokeredIdentityContext user = new BrokeredIdentityContext(id);
BrokeredIdentityContext user = new BrokeredIdentityContext(id, getConfig());

String email = getJsonProperty(profile, "mail");
if (email == null && profile.has("userPrincipalName")) {
Expand All @@ -100,7 +100,6 @@ protected BrokeredIdentityContext extractIdentityFromProfile(EventBuilder event,
user.setLastName(getJsonProperty(profile, "surname"));
if (email != null)
user.setEmail(email);
user.setIdpConfig(getConfig());
user.setIdp(this);

AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,10 +58,9 @@ protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
private BrokeredIdentityContext extractUserContext(JsonNode profile) {
JsonNode metadata = profile.get("metadata");

final BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(metadata, "uid"));
final BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(metadata, "uid"), getConfig());
user.setUsername(getJsonProperty(metadata, "name"));
user.setName(getJsonProperty(profile, "fullName"));
user.setIdpConfig(getConfig());
user.setIdp(this);
return user;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,10 +94,9 @@ private BrokeredIdentityContext extractUserContext(JsonNode profile) {
getJsonProperty(metadata, "uid") != null
? getJsonProperty(metadata, "uid")
: tryGetKubeAdmin(metadata)
);
, getConfig());
user.setUsername(getJsonProperty(metadata, "name"));
user.setName(getJsonProperty(profile, "fullName"));
user.setIdpConfig(getConfig());
user.setIdp(this);
return user;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,12 +58,11 @@ protected String getProfileEndpointForValidation(EventBuilder event) {

@Override
protected BrokeredIdentityContext extractIdentityFromProfile(EventBuilder event, JsonNode profile) {
BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "user_id"));
BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "user_id"), getConfig());

user.setUsername(getJsonProperty(profile, "email"));
user.setName(getJsonProperty(profile, "name"));
user.setEmail(getJsonProperty(profile, "email"));
user.setIdpConfig(getConfig());
user.setIdp(this);

AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,13 @@ protected SimpleHttp buildUserInfoRequest(String subjectToken, String userInfoUr
protected BrokeredIdentityContext extractIdentityFromProfile(EventBuilder event, JsonNode node) {
JsonNode profile = node.get("items").get(0);

BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "user_id"));
BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "user_id"), getConfig());

String username = extractUsernameFromProfileURL(getJsonProperty(profile, "link"));
user.setUsername(username);
user.setName(unescapeHtml3(getJsonProperty(profile, "display_name")));
// email is not provided
// user.setEmail(getJsonProperty(profile, "email"));
user.setIdpConfig(getConfig());
user.setIdp(this);

AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ public Response authResponse(@QueryParam("state") String state,
.build();
User twitterUser = twitter.v1().users().verifyCredentials();

BrokeredIdentityContext identity = new BrokeredIdentityContext(Long.toString(twitterUser.getId()));
BrokeredIdentityContext identity = new BrokeredIdentityContext(Long.toString(twitterUser.getId()), providerConfig);
identity.setIdp(provider);

identity.setUsername(twitterUser.getScreenName());
Expand All @@ -244,7 +244,6 @@ public Response authResponse(@QueryParam("state") String state,
}
identity.getContextData().put(IdentityProvider.FEDERATED_ACCESS_TOKEN, token);

identity.setIdpConfig(providerConfig);
identity.setAuthenticationSession(authSession);

return callback.authenticated(identity);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ protected String getDefaultScopes() {
}

protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
return new BrokeredIdentityContext(accessToken);
return new BrokeredIdentityContext(accessToken, getConfig());
};

};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.keycloak.dom.saml.v2.assertion.AttributeType;
import org.keycloak.dom.saml.v2.assertion.NameIDType;
import org.keycloak.models.IdentityProviderMapperModel;
import org.keycloak.models.IdentityProviderModel;
import org.keycloak.saml.common.exceptions.ParsingException;
import org.keycloak.saml.processing.core.saml.v2.util.AssertionUtil;

Expand Down Expand Up @@ -110,7 +111,7 @@ private String testMapping(String attributeValue, String xpath, String attribute
config.put(XPathAttributeMapper.ATTRIBUTE_NAME, attributeNameToSearch);
config.put(XPathAttributeMapper.USER_ATTRIBUTE, attribute);
config.put(XPathAttributeMapper.ATTRIBUTE_XPATH, xpath);
BrokeredIdentityContext context = new BrokeredIdentityContext("brokeredIdentityContext");
BrokeredIdentityContext context = new BrokeredIdentityContext("brokeredIdentityContext", new IdentityProviderModel());
AssertionType assertion = AssertionUtil.createAssertion("assertionId", NameIDType.deserializeFromString("nameIDType"));
AttributeStatementType statement = new AttributeStatementType();
assertion.addStatement(statement);
Expand Down
Loading

0 comments on commit 6fb3cf5

Please sign in to comment.