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:
Pedro Igor 2024-04-29 10:29:31 -03:00
parent 46f0da43da
commit bbb83236f5
27 changed files with 149 additions and 48 deletions

View file

@ -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.
|===

View file

@ -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.

View file

@ -282,6 +282,10 @@ export const AdvancedSettings = ({ isOIDC, isSAML }: AdvancedSettingsProps) => {
}}
/>
)}
<SwitchField
field="config.caseSensitiveOriginalUsername"
label="caseSensitiveOriginalUsername"
/>
</>
);
};

View file

@ -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);

View file

@ -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,9 +89,13 @@ public class BrokeredIdentityContext {
* @return
*/
public String getUsername() {
if (getIdpConfig().isCaseSensitiveOriginalUsername()) {
return username;
}
return username == null ? null : username.toLowerCase();
}
public void setUsername(String username) {
this.username = username;
}
@ -142,10 +149,6 @@ public class BrokeredIdentityContext {
return idpConfig;
}
public void setIdpConfig(IdentityProviderModel idpConfig) {
this.idpConfig = idpConfig;
}
public IdentityProvider getIdp() {
return idp;
}

View file

@ -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;

View file

@ -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();

View file

@ -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;
}

View file

@ -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);

View file

@ -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());

View file

@ -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());

View file

@ -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());

View file

@ -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());

View file

@ -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");

View file

@ -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);

View file

@ -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());

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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());

View file

@ -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());

View file

@ -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);

View file

@ -147,7 +147,7 @@ public class AbstractOAuth2IdentityProviderTest {
}
protected BrokeredIdentityContext doGetFederatedIdentity(String accessToken) {
return new BrokeredIdentityContext(accessToken);
return new BrokeredIdentityContext(accessToken, getConfig());
};
};

View file

@ -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);

View file

@ -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);
}
};
}

View file

@ -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());
}

View file

@ -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;

View file

@ -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,