Do not lower-case the username from the IdP when creating the federated identity
Closes #28495 Signed-off-by: Pedro Igor <pigor.craveiro@gmail.com>
This commit is contained in:
parent
46f0da43da
commit
bbb83236f5
27 changed files with 149 additions and 48 deletions
|
@ -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.
|
||||
|===
|
||||
|
|
|
@ -3136,3 +3136,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.
|
|
@ -282,6 +282,10 @@ export const AdvancedSettings = ({ isOIDC, isSAML }: AdvancedSettingsProps) => {
|
|||
}}
|
||||
/>
|
||||
)}
|
||||
<SwitchField
|
||||
field="config.caseSensitiveOriginalUsername"
|
||||
label="caseSensitiveOriginalUsername"
|
||||
/>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
|
|
@ -168,7 +168,7 @@ public class JpaUserProvider implements UserProvider, UserCredentialStore {
|
|||
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);
|
||||
|
|
|
@ -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;
|
||||
|
@ -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() {
|
||||
|
@ -86,7 +89,11 @@ public class BrokeredIdentityContext {
|
|||
* @return
|
||||
*/
|
||||
public String getUsername() {
|
||||
return username;
|
||||
if (getIdpConfig().isCaseSensitiveOriginalUsername()) {
|
||||
return username;
|
||||
}
|
||||
|
||||
return username == null ? null : username.toLowerCase();
|
||||
}
|
||||
|
||||
public void setUsername(String username) {
|
||||
|
@ -142,10 +149,6 @@ public class BrokeredIdentityContext {
|
|||
return idpConfig;
|
||||
}
|
||||
|
||||
public void setIdpConfig(IdentityProviderModel idpConfig) {
|
||||
this.idpConfig = idpConfig;
|
||||
}
|
||||
|
||||
public IdentityProvider getIdp() {
|
||||
return idp;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
@ -321,6 +322,14 @@ public class IdentityProviderModel implements Serializable {
|
|||
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;
|
||||
|
|
|
@ -264,7 +264,14 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext {
|
|||
}
|
||||
|
||||
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());
|
||||
|
@ -275,13 +282,7 @@ public class SerializedBrokeredIdentityContext implements UpdateProfileContext {
|
|||
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();
|
||||
|
|
|
@ -562,7 +562,6 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
|||
if (federatedIdentity.getToken() == null)federatedIdentity.setToken(response);
|
||||
}
|
||||
|
||||
federatedIdentity.setIdpConfig(providerConfig);
|
||||
federatedIdentity.setIdp(provider);
|
||||
federatedIdentity.setAuthenticationSession(authSession);
|
||||
|
||||
|
@ -712,7 +711,6 @@ public abstract class AbstractOAuth2IdentityProvider<C extends OAuth2IdentityPro
|
|||
BrokeredIdentityContext context = exchangeExternalImpl(event, params);
|
||||
if (context != null) {
|
||||
context.setIdp(this);
|
||||
context.setIdpConfig(getConfig());
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
|
|
@ -469,7 +469,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
|||
|
||||
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);
|
||||
|
@ -791,7 +791,7 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
|||
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);
|
||||
|
@ -881,7 +881,6 @@ public class OIDCIdentityProvider extends AbstractOAuth2IdentityProvider<OIDCIde
|
|||
}
|
||||
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);
|
||||
|
|
|
@ -550,7 +550,7 @@ public class SAMLEndpoint {
|
|||
}
|
||||
|
||||
//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);
|
||||
|
@ -601,7 +601,6 @@ public class SAMLEndpoint {
|
|||
|
||||
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());
|
||||
|
|
|
@ -132,13 +132,10 @@ public class BitbucketIdentityProvider extends AbstractOAuth2IdentityProvider im
|
|||
}
|
||||
|
||||
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());
|
||||
|
|
|
@ -74,7 +74,7 @@ public class FacebookIdentityProvider extends AbstractOAuth2IdentityProvider<Fac
|
|||
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");
|
||||
|
||||
|
@ -101,7 +101,6 @@ public class FacebookIdentityProvider extends AbstractOAuth2IdentityProvider<Fac
|
|||
|
||||
user.setFirstName(firstName);
|
||||
user.setLastName(lastName);
|
||||
user.setIdpConfig(getConfig());
|
||||
user.setIdp(this);
|
||||
|
||||
AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
|
||||
|
|
|
@ -121,13 +121,12 @@ public class GitHubIdentityProvider extends AbstractOAuth2IdentityProvider imple
|
|||
|
||||
@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());
|
||||
|
|
|
@ -107,7 +107,7 @@ public class GitLabIdentityProvider extends OIDCIdentityProvider implements Soc
|
|||
|
||||
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");
|
||||
|
|
|
@ -66,9 +66,8 @@ public class InstagramIdentityProvider extends AbstractOAuth2IdentityProvider im
|
|||
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);
|
||||
|
|
|
@ -86,7 +86,7 @@ public class MicrosoftIdentityProvider extends AbstractOAuth2IdentityProvider im
|
|||
@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")) {
|
||||
|
@ -100,7 +100,6 @@ public class MicrosoftIdentityProvider extends AbstractOAuth2IdentityProvider im
|
|||
user.setLastName(getJsonProperty(profile, "surname"));
|
||||
if (email != null)
|
||||
user.setEmail(email);
|
||||
user.setIdpConfig(getConfig());
|
||||
user.setIdp(this);
|
||||
|
||||
AbstractJsonUserAttributeMapper.storeUserProfileForMapper(user, profile, getConfig().getAlias());
|
||||
|
|
|
@ -58,10 +58,9 @@ public class OpenshiftV3IdentityProvider extends AbstractOAuth2IdentityProvider<
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -94,10 +94,9 @@ public class OpenshiftV4IdentityProvider extends AbstractOAuth2IdentityProvider<
|
|||
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;
|
||||
}
|
||||
|
|
|
@ -58,12 +58,11 @@ public class PayPalIdentityProvider extends AbstractOAuth2IdentityProvider<PayPa
|
|||
|
||||
@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());
|
||||
|
|
|
@ -76,14 +76,13 @@ public class StackoverflowIdentityProvider extends AbstractOAuth2IdentityProvide
|
|||
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());
|
||||
|
|
|
@ -222,7 +222,7 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
|
|||
.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());
|
||||
|
@ -244,7 +244,6 @@ public class TwitterIdentityProvider extends AbstractIdentityProvider<OAuth2Iden
|
|||
}
|
||||
identity.getContextData().put(IdentityProvider.FEDERATED_ACCESS_TOKEN, token);
|
||||
|
||||
identity.setIdpConfig(providerConfig);
|
||||
identity.setAuthenticationSession(authSession);
|
||||
|
||||
return callback.authenticated(identity);
|
||||
|
|
|
@ -147,7 +147,7 @@ public class AbstractOAuth2IdentityProviderTest {
|
|||
}
|
||||
|
||||
protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
|
||||
return new BrokeredIdentityContext(accessToken);
|
||||
return new BrokeredIdentityContext(accessToken, getConfig());
|
||||
};
|
||||
|
||||
};
|
||||
|
|
|
@ -20,6 +20,7 @@ import org.keycloak.dom.saml.v2.assertion.AttributeStatementType;
|
|||
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;
|
||||
|
||||
|
@ -110,7 +111,7 @@ public class XPathAttributeMapperTest {
|
|||
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);
|
||||
|
|
|
@ -40,6 +40,7 @@ public class TestKeycloakOidcIdentityProviderFactory extends KeycloakOIDCIdentit
|
|||
public static final String ID = "test-keycloak-oidc";
|
||||
public static final String IGNORE_MAX_AGE_PARAM = "ignore-max-age-param";
|
||||
public static final String USE_SINGLE_REFRESH_TOKEN = "use-single-refresh-token";
|
||||
public static final String PREFERRED_USERNAME = "preferred-username";
|
||||
|
||||
public static void setIgnoreMaxAgeParam(IdentityProviderRepresentation rep) {
|
||||
rep.getConfig().put(IGNORE_MAX_AGE_PARAM, Boolean.TRUE.toString());
|
||||
|
@ -59,6 +60,11 @@ public class TestKeycloakOidcIdentityProviderFactory extends KeycloakOIDCIdentit
|
|||
@Override
|
||||
public BrokeredIdentityContext getFederatedIdentity(String response) {
|
||||
BrokeredIdentityContext context = super.getFederatedIdentity(response);
|
||||
String preferredUsername = getPreferredUsername();
|
||||
|
||||
if (preferredUsername != null) {
|
||||
context.setUsername(preferredUsername);
|
||||
}
|
||||
if (Boolean.valueOf(model.getConfig().get(USE_SINGLE_REFRESH_TOKEN))) {
|
||||
// refresh token will be available only in the first login.
|
||||
if (!usernames.add(context.getUsername())) {
|
||||
|
@ -92,6 +98,10 @@ public class TestKeycloakOidcIdentityProviderFactory extends KeycloakOIDCIdentit
|
|||
private boolean isIgnoreMaxAgeParam() {
|
||||
return Boolean.parseBoolean(model.getConfig().getOrDefault(IGNORE_MAX_AGE_PARAM, Boolean.FALSE.toString()));
|
||||
}
|
||||
|
||||
private String getPreferredUsername() {
|
||||
return model.getConfig().get(PREFERRED_USERNAME);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -6,11 +6,13 @@ import org.junit.Test;
|
|||
import org.keycloak.admin.client.resource.IdentityProviderResource;
|
||||
import org.keycloak.admin.client.resource.RealmResource;
|
||||
import org.keycloak.models.FederatedIdentityModel;
|
||||
import org.keycloak.models.IdentityProviderModel;
|
||||
import org.keycloak.models.IdentityProviderSyncMode;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.models.UserModel;
|
||||
import org.keycloak.representations.AccessTokenResponse;
|
||||
import org.keycloak.representations.idm.ClientRepresentation;
|
||||
import org.keycloak.representations.idm.FederatedIdentityRepresentation;
|
||||
import org.keycloak.representations.idm.IdentityProviderRepresentation;
|
||||
import org.keycloak.representations.idm.UserRepresentation;
|
||||
import org.keycloak.testsuite.Assert;
|
||||
|
@ -42,6 +44,8 @@ import static org.keycloak.testsuite.forms.VerifyProfileTest.ATTRIBUTE_DEPARTMEN
|
|||
import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ADMIN_EDITABLE;
|
||||
import static org.keycloak.testsuite.forms.VerifyProfileTest.PERMISSIONS_ALL;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
|
@ -805,6 +809,59 @@ public class KcOidcFirstBrokerLoginTest extends AbstractFirstBrokerLoginTest {
|
|||
assertEquals(null, user.firstAttribute(ATTRIBUTE_DEPARTMENT));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFederatedIdentityCaseSensitiveOriginalUsername() {
|
||||
String expectedBrokeredUserName = "camelCase";
|
||||
IdentityProviderResource idp = realmsResouce().realm(bc.consumerRealmName()).identityProviders().get(bc.getIDPAlias());
|
||||
IdentityProviderRepresentation representation = idp.toRepresentation();
|
||||
representation.getConfig().put(TestKeycloakOidcIdentityProviderFactory.PREFERRED_USERNAME, expectedBrokeredUserName);
|
||||
representation.getConfig().put(IdentityProviderModel.CASE_SENSITIVE_ORIGINAL_USERNAME, Boolean.TRUE.toString());
|
||||
idp.update(representation);
|
||||
createUser(bc.providerRealmName(), expectedBrokeredUserName, BrokerTestConstants.USER_PASSWORD, "f", "l", "fl@example.org");
|
||||
|
||||
oauth.clientId("broker-app");
|
||||
loginPage.open(bc.consumerRealmName());
|
||||
// the username is stored as lower-case in the provider realm local database
|
||||
logInWithIdp(bc.getIDPAlias(), expectedBrokeredUserName.toLowerCase(), BrokerTestConstants.USER_PASSWORD);
|
||||
|
||||
RealmResource realm = adminClient.realm(bc.consumerRealmName());
|
||||
UserRepresentation userRepresentation = AccountHelper.getUserRepresentation(realm, expectedBrokeredUserName.toLowerCase());
|
||||
// the username is in lower case in the local database
|
||||
assertEquals(userRepresentation.getUsername(), expectedBrokeredUserName.toLowerCase());
|
||||
|
||||
// the original username is preserved
|
||||
List<FederatedIdentityRepresentation> federatedIdentities = realm.users().get(userRepresentation.getId()).getFederatedIdentity();
|
||||
assertFalse(federatedIdentities.isEmpty());
|
||||
FederatedIdentityRepresentation federatedIdentity = federatedIdentities.get(0);
|
||||
assertEquals(expectedBrokeredUserName, federatedIdentity.getUserName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testFederatedIdentityCaseInsensitiveOriginalUsername() {
|
||||
String expectedBrokeredUserName = "camelCase";
|
||||
IdentityProviderResource idp = realmsResouce().realm(bc.consumerRealmName()).identityProviders().get(bc.getIDPAlias());
|
||||
IdentityProviderRepresentation representation = idp.toRepresentation();
|
||||
representation.getConfig().put(TestKeycloakOidcIdentityProviderFactory.PREFERRED_USERNAME, expectedBrokeredUserName);
|
||||
idp.update(representation);
|
||||
createUser(bc.providerRealmName(), expectedBrokeredUserName, BrokerTestConstants.USER_PASSWORD, "f", "l", "fl@example.org");
|
||||
|
||||
oauth.clientId("broker-app");
|
||||
loginPage.open(bc.consumerRealmName());
|
||||
// the username is stored as lower-case in the provider realm local database
|
||||
logInWithIdp(bc.getIDPAlias(), expectedBrokeredUserName.toLowerCase(), BrokerTestConstants.USER_PASSWORD);
|
||||
|
||||
RealmResource realm = adminClient.realm(bc.consumerRealmName());
|
||||
UserRepresentation userRepresentation = AccountHelper.getUserRepresentation(realm, expectedBrokeredUserName.toLowerCase());
|
||||
// the username is in lower case in the local database
|
||||
assertEquals(userRepresentation.getUsername(), expectedBrokeredUserName.toLowerCase());
|
||||
|
||||
// the original username is preserved
|
||||
List<FederatedIdentityRepresentation> federatedIdentities = realm.users().get(userRepresentation.getId()).getFederatedIdentity();
|
||||
assertFalse(federatedIdentities.isEmpty());
|
||||
FederatedIdentityRepresentation federatedIdentity = federatedIdentities.get(0);
|
||||
assertEquals(expectedBrokeredUserName.toLowerCase(), federatedIdentity.getUserName());
|
||||
}
|
||||
|
||||
public void addDepartmentScopeIntoRealm() {
|
||||
testRealm().clientScopes().create(ClientScopeBuilder.create().name("department").protocol("openid-connect").build());
|
||||
}
|
||||
|
|
|
@ -251,7 +251,7 @@ public class ExportImportUtil {
|
|||
} else if ("google1".equals(federatedIdentityRep.getIdentityProvider())) {
|
||||
googleFound = true;
|
||||
Assert.assertEquals("google1", federatedIdentityRep.getUserId());
|
||||
Assert.assertEquals("mysocialuser@gmail.com", federatedIdentityRep.getUserName());
|
||||
Assert.assertEquals("mySocialUser@gmail.com", federatedIdentityRep.getUserName());
|
||||
} else if ("twitter1".equals(federatedIdentityRep.getIdentityProvider())) {
|
||||
twitterFound = true;
|
||||
Assert.assertEquals("twitter1", federatedIdentityRep.getUserId());
|
||||
|
@ -260,6 +260,12 @@ public class ExportImportUtil {
|
|||
}
|
||||
Assert.assertTrue(facebookFound && twitterFound && googleFound);
|
||||
|
||||
// make sure the username format is the same when importing
|
||||
UserResource socialUserLowercase = realmRsc.users().get(findByUsername(realmRsc, "lowercasesocialuser").getId());
|
||||
List<FederatedIdentityRepresentation> socialLowercaseLinks = socialUserLowercase.getFederatedIdentity();
|
||||
Assert.assertEquals(1, socialLowercaseLinks.size());
|
||||
Assert.assertEquals("lowercasesocialuser@gmail.com", socialLowercaseLinks.get(0).getUserName());
|
||||
|
||||
UserRepresentation foundSocialUser = testingClient.testing().getUserByFederatedIdentity(realm.getRealm(), "facebook1", "facebook1", "fbuser1");
|
||||
Assert.assertEquals(foundSocialUser.getUsername(), socialUser.toRepresentation().getUsername());
|
||||
Assert.assertNull(testingClient.testing().getUserByFederatedIdentity(realm.getRealm(), "facebook", "not-existing", "not-existing"));
|
||||
|
@ -283,7 +289,7 @@ public class ExportImportUtil {
|
|||
|
||||
// Test identity providers
|
||||
List<IdentityProviderRepresentation> identityProviders = realm.getIdentityProviders();
|
||||
Assert.assertEquals(3, identityProviders.size());
|
||||
Assert.assertEquals(4, identityProviders.size());
|
||||
IdentityProviderRepresentation google = null;
|
||||
for (IdentityProviderRepresentation idpRep : identityProviders) {
|
||||
if (idpRep.getAlias().equals("google1")) google = idpRep;
|
||||
|
|
|
@ -35,6 +35,16 @@
|
|||
"clientSecret": "googleSecret"
|
||||
}
|
||||
},
|
||||
{
|
||||
"providerId" : "github",
|
||||
"alias" : "github1",
|
||||
"enabled": true,
|
||||
"config": {
|
||||
"syncMode": "IMPORT",
|
||||
"clientId": "googleId",
|
||||
"clientSecret": "googleSecret"
|
||||
}
|
||||
},
|
||||
{
|
||||
"providerId" : "facebook",
|
||||
"alias" : "facebook1",
|
||||
|
@ -239,6 +249,17 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"username": "lowercasesocialuser",
|
||||
"enabled": true,
|
||||
"federatedIdentities": [
|
||||
{
|
||||
"identityProvider": "github1",
|
||||
"userId": "github1",
|
||||
"userName": "lowercasesocialuser@gmail.com"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"username": "service-account-otherapp",
|
||||
"enabled": true,
|
||||
|
|
Loading…
Reference in a new issue