From e4204a56f50fdca6ddfd0a062a40ea13db9980ce Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Fri, 8 May 2015 20:41:31 -0400 Subject: [PATCH] username mapper --- .../provider/AbstractIdentityProvider.java | 5 + .../AbstractIdentityProviderFactory.java | 2 + .../AbstractIdentityProviderMapper.java | 7 + .../provider/BrokeredIdentityContext.java | 19 +++ .../broker/provider/IdentityProvider.java | 3 + .../provider/IdentityProviderFactory.java | 2 + .../provider/IdentityProviderMapper.java | 30 ++++ .../KeycloakOIDCIdentityProviderFactory.java | 8 +- .../oidc/OIDCIdentityProviderFactory.java | 8 +- .../oidc/mappers/AbstractClaimMapper.java | 6 +- .../oidc/mappers/UsernameTemplateMapper.java | 109 ++++++++++++++ ...oak.broker.provider.IdentityProviderMapper | 3 +- .../saml/SAMLIdentityProviderFactory.java | 5 + .../saml/mappers/UsernameTemplateMapper.java | 133 ++++++++++++++++++ ...oak.broker.provider.IdentityProviderMapper | 3 +- .../java/org/keycloak/JsonParserTest.java | 18 +++ .../keycloak/models/jpa/JpaUserProvider.java | 11 +- .../models/jpa/entities/UserEntity.java | 17 +++ .../services/managers/UserManager.java | 8 +- .../resources/IdentityBrokerService.java | 37 +++-- .../admin/IdentityProvidersResource.java | 3 +- .../social/github/GitHubIdentityProvider.java | 3 +- .../linkedin/LinkedInIdentityProvider.java | 3 +- .../StackoverflowIdentityProvider.java | 3 +- 24 files changed, 422 insertions(+), 24 deletions(-) mode change 100644 => 100755 broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderFactory.java create mode 100755 broker/oidc/src/main/java/org/keycloak/broker/oidc/mappers/UsernameTemplateMapper.java create mode 100755 broker/saml/src/main/java/org/keycloak/broker/saml/mappers/UsernameTemplateMapper.java diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java index f4ddeaedecf..e402e047061 100755 --- a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java +++ b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProvider.java @@ -73,6 +73,11 @@ public abstract class AbstractIdentityProvider } + @Override + public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, BrokeredIdentityContext context) { + + } + @Override public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context) { diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderFactory.java b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderFactory.java index 28286eeb66c..898cee440f7 100755 --- a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderFactory.java +++ b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderFactory.java @@ -18,8 +18,10 @@ package org.keycloak.broker.provider; import org.keycloak.Config; +import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RealmModel; import java.io.InputStream; import java.util.HashMap; diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderMapper.java b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderMapper.java index 15b83e8643b..092f23bef01 100755 --- a/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderMapper.java +++ b/broker/core/src/main/java/org/keycloak/broker/provider/AbstractIdentityProviderMapper.java @@ -1,8 +1,10 @@ package org.keycloak.broker.provider; import org.keycloak.broker.provider.IdentityProviderMapper; +import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; +import org.keycloak.models.RealmModel; /** * @author Bill Burke @@ -28,4 +30,9 @@ public abstract class AbstractIdentityProviderMapper implements IdentityProvider public void postInit(KeycloakSessionFactory factory) { } + + @Override + public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { + + } } diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/BrokeredIdentityContext.java b/broker/core/src/main/java/org/keycloak/broker/provider/BrokeredIdentityContext.java index 7bee93623d2..f90f803efe4 100755 --- a/broker/core/src/main/java/org/keycloak/broker/provider/BrokeredIdentityContext.java +++ b/broker/core/src/main/java/org/keycloak/broker/provider/BrokeredIdentityContext.java @@ -32,6 +32,7 @@ public class BrokeredIdentityContext { private String id; private String username; + private String modelUsername; private String email; private String firstName; private String lastName; @@ -59,6 +60,11 @@ public class BrokeredIdentityContext { this.id = id; } + /** + * Username in remote idp + * + * @return + */ public String getUsername() { return username; } @@ -67,6 +73,19 @@ public class BrokeredIdentityContext { this.username = username; } + /** + * username to store in UserModel + * + * @return + */ + public String getModelUsername() { + return modelUsername; + } + + public void setModelUsername(String modelUsername) { + this.modelUsername = modelUsername; + } + public String getEmail() { return email; } diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java index 2c503bbf059..47037fae22f 100755 --- a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java +++ b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProvider.java @@ -20,6 +20,7 @@ package org.keycloak.broker.provider; import org.keycloak.events.EventBuilder; import org.keycloak.models.ClientSessionModel; import org.keycloak.models.FederatedIdentityModel; +import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; import org.keycloak.models.RealmModel; @@ -46,6 +47,8 @@ public interface IdentityProvider extends Provi public Response authenticated(BrokeredIdentityContext context); } + + void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, BrokeredIdentityContext context); void attachUserSession(UserSessionModel userSession, ClientSessionModel clientSession, BrokeredIdentityContext context); void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context); void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, BrokeredIdentityContext context); diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderFactory.java b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderFactory.java old mode 100644 new mode 100755 index 1f1bdccf055..db2570d3972 --- a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderFactory.java +++ b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderFactory.java @@ -18,6 +18,8 @@ package org.keycloak.broker.provider; import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; import org.keycloak.provider.ProviderFactory; import java.io.InputStream; diff --git a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderMapper.java b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderMapper.java index 40174999da2..d20aa00f9fd 100755 --- a/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderMapper.java +++ b/broker/core/src/main/java/org/keycloak/broker/provider/IdentityProviderMapper.java @@ -19,7 +19,37 @@ public interface IdentityProviderMapper extends Provider, ProviderFactoryBill Burke + * @version $Revision: 1 $ + */ +public class UsernameTemplateMapper extends AbstractClaimMapper { + + public static final String[] COMPATIBLE_PROVIDERS = {KeycloakOIDCIdentityProviderFactory.PROVIDER_ID, OIDCIdentityProviderFactory.PROVIDER_ID}; + + private static final List configProperties = new ArrayList(); + + public static final String TEMPLATE = "template"; + + static { + ProviderConfigProperty property; + property = new ProviderConfigProperty(); + property.setName(TEMPLATE); + property.setLabel("Template"); + property.setHelpText("Template to use to format the username to import. Substitutions are enclosed in ${}. For example: '${ALIAS}.${CLAIM.sub}'. ALIAS is the provider alias. CLAIM. references an ID or Access token claim."); + property.setType(ProviderConfigProperty.STRING_TYPE); + property.setDefaultValue("${ALIAS}.${CLAIM.preferred_username}"); + configProperties.add(property); + } + + public static final String PROVIDER_ID = "oidc-username-idp-mapper"; + + @Override + public List getConfigProperties() { + return configProperties; + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String[] getCompatibleProviders() { + return COMPATIBLE_PROVIDERS; + } + + @Override + public String getDisplayCategory() { + return "Preprocessor"; + } + + @Override + public String getDisplayType() { + return "Username Template Importer"; + } + + @Override + public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { + } + + @Override + public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { + } + + static Pattern substitution = Pattern.compile("\\$\\{([^}]+)\\}"); + + @Override + public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { + String template = mapperModel.getConfig().get(TEMPLATE); + Matcher m = substitution.matcher(template); + StringBuffer sb = new StringBuffer(); + while (m.find()) { + String variable = m.group(1); + if (variable.equals("ALIAS")) { + m.appendReplacement(sb, context.getIdpConfig().getAlias()); + } else if (variable.equals("UUID")) { + m.appendReplacement(sb, KeycloakModelUtils.generateId()); + } else if (variable.startsWith("CLAIM.")) { + String name = variable.substring("CLAIM.".length()); + Object value = AbstractClaimMapper.getClaimValue(context, name); + if (value == null) value = ""; + m.appendReplacement(sb, value.toString()); + } else { + m.appendReplacement(sb, m.group(1)); + } + + } + m.appendTail(sb); + String username = sb.toString(); + context.setModelUsername(username); + + } + + @Override + public String getHelpText() { + return "Format the username to import."; + } +} diff --git a/broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper b/broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper index de86af2eec3..62e67412c4c 100755 --- a/broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper +++ b/broker/oidc/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper @@ -1,3 +1,4 @@ org.keycloak.broker.oidc.mappers.ClaimToRoleMapper org.keycloak.broker.oidc.mappers.ExternalKeycloakRoleToRoleMapper -org.keycloak.broker.oidc.mappers.UserAttributeMapper \ No newline at end of file +org.keycloak.broker.oidc.mappers.UserAttributeMapper +org.keycloak.broker.oidc.mappers.UsernameTemplateMapper \ No newline at end of file diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java index 98ad9080504..1cd28fd6afd 100755 --- a/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java +++ b/broker/saml/src/main/java/org/keycloak/broker/saml/SAMLIdentityProviderFactory.java @@ -18,13 +18,17 @@ package org.keycloak.broker.saml; import org.keycloak.broker.provider.AbstractIdentityProviderFactory; +import org.keycloak.broker.saml.mappers.UsernameTemplateMapper; import org.keycloak.dom.saml.v2.metadata.EndpointType; import org.keycloak.dom.saml.v2.metadata.EntitiesDescriptorType; import org.keycloak.dom.saml.v2.metadata.EntityDescriptorType; import org.keycloak.dom.saml.v2.metadata.IDPSSODescriptorType; import org.keycloak.dom.saml.v2.metadata.KeyDescriptorType; import org.keycloak.dom.saml.v2.metadata.KeyTypes; +import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; import org.keycloak.saml.common.constants.JBossSAMLURIConstants; import org.keycloak.saml.common.exceptions.ParsingException; import org.keycloak.saml.common.util.DocumentUtil; @@ -150,4 +154,5 @@ public class SAMLIdentityProviderFactory extends AbstractIdentityProviderFactory public String getId() { return PROVIDER_ID; } + } diff --git a/broker/saml/src/main/java/org/keycloak/broker/saml/mappers/UsernameTemplateMapper.java b/broker/saml/src/main/java/org/keycloak/broker/saml/mappers/UsernameTemplateMapper.java new file mode 100755 index 00000000000..0ed0c1b04e0 --- /dev/null +++ b/broker/saml/src/main/java/org/keycloak/broker/saml/mappers/UsernameTemplateMapper.java @@ -0,0 +1,133 @@ +package org.keycloak.broker.saml.mappers; + +import org.keycloak.broker.provider.AbstractIdentityProviderMapper; +import org.keycloak.broker.provider.BrokeredIdentityContext; +import org.keycloak.broker.saml.SAMLEndpoint; +import org.keycloak.broker.saml.SAMLIdentityProviderFactory; +import org.keycloak.dom.saml.v2.assertion.AssertionType; +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.dom.saml.v2.assertion.SubjectType; +import org.keycloak.models.IdentityProviderMapperModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.provider.ProviderConfigProperty; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public class UsernameTemplateMapper extends AbstractIdentityProviderMapper { + + public static final String[] COMPATIBLE_PROVIDERS = {SAMLIdentityProviderFactory.PROVIDER_ID}; + + private static final List configProperties = new ArrayList(); + + public static final String TEMPLATE = "template"; + + static { + ProviderConfigProperty property; + property = new ProviderConfigProperty(); + property.setName(TEMPLATE); + property.setLabel("Template"); + property.setHelpText("Template to use to format the username to import. Substitutions are enclosed in ${}. For example: '${ALIAS}.${NAMEID}'. ALIAS is the provider alias. NAMEID is that SAML name id assertion. ATTRIBUTE. references a SAML attribute where name is the attribute name or friendly name."); + property.setType(ProviderConfigProperty.STRING_TYPE); + property.setDefaultValue("${ALIAS}.${NAMEID}"); + configProperties.add(property); + } + + public static final String PROVIDER_ID = "saml-username-idp-mapper"; + + @Override + public List getConfigProperties() { + return configProperties; + } + + @Override + public String getId() { + return PROVIDER_ID; + } + + @Override + public String[] getCompatibleProviders() { + return COMPATIBLE_PROVIDERS; + } + + @Override + public String getDisplayCategory() { + return "Preprocessor"; + } + + @Override + public String getDisplayType() { + return "Username Template Importer"; + } + + @Override + public void importNewUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { + + } + + @Override + public void updateBrokeredUser(KeycloakSession session, RealmModel realm, UserModel user, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { + + } + static Pattern substitution = Pattern.compile("\\$\\{([^}]+)\\}"); + + @Override + public void preprocessFederatedIdentity(KeycloakSession session, RealmModel realm, IdentityProviderMapperModel mapperModel, BrokeredIdentityContext context) { + AssertionType assertion = (AssertionType)context.getContextData().get(SAMLEndpoint.SAML_ASSERTION); + String template = mapperModel.getConfig().get(TEMPLATE); + Matcher m = substitution.matcher(template); + StringBuffer sb = new StringBuffer(); + while (m.find()) { + String variable = m.group(1); + if (variable.equals("ALIAS")) { + m.appendReplacement(sb, context.getIdpConfig().getAlias()); + } else if (variable.equals("UUID")) { + m.appendReplacement(sb, KeycloakModelUtils.generateId()); + } else if (variable.equals("NAMEID")) { + SubjectType subject = assertion.getSubject(); + SubjectType.STSubType subType = subject.getSubType(); + NameIDType subjectNameID = (NameIDType) subType.getBaseID(); + m.appendReplacement(sb, subjectNameID.getValue()); + } else if (variable.startsWith("ATTRIBUTE.")) { + String name = variable.substring("ATTRIBUTE.".length()); + String value = ""; + for (AttributeStatementType statement : assertion.getAttributeStatements()) { + for (AttributeStatementType.ASTChoiceType choice : statement.getAttributes()) { + AttributeType attr = choice.getAttribute(); + if (name.equals(attr.getName()) || name.equals(attr.getFriendlyName())) { + List attributeValue = attr.getAttributeValue(); + if (attributeValue != null && !attributeValue.isEmpty()) { + value = attributeValue.get(0).toString(); + } + break; + } + } + } + m.appendReplacement(sb, value); + } else { + m.appendReplacement(sb, m.group(1)); + } + + } + m.appendTail(sb); + context.setModelUsername(sb.toString()); + + } + + @Override + public String getHelpText() { + return "Format the username to import."; + } + +} diff --git a/broker/saml/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper b/broker/saml/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper index 9de7273b798..89cdf9290ea 100755 --- a/broker/saml/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper +++ b/broker/saml/src/main/resources/META-INF/services/org.keycloak.broker.provider.IdentityProviderMapper @@ -1,2 +1,3 @@ org.keycloak.broker.saml.mappers.AttributeToRoleMapper -org.keycloak.broker.saml.mappers.UserAttributeMapper \ No newline at end of file +org.keycloak.broker.saml.mappers.UserAttributeMapper +org.keycloak.broker.saml.mappers.UsernameTemplateMapper \ No newline at end of file diff --git a/core/src/test/java/org/keycloak/JsonParserTest.java b/core/src/test/java/org/keycloak/JsonParserTest.java index 010eae4fad3..ed0831d8ff2 100755 --- a/core/src/test/java/org/keycloak/JsonParserTest.java +++ b/core/src/test/java/org/keycloak/JsonParserTest.java @@ -4,6 +4,7 @@ import java.io.IOException; import java.io.InputStream; import java.util.HashMap; import java.util.Map; +import java.util.regex.Matcher; import java.util.regex.Pattern; import org.codehaus.jackson.annotate.JsonAnyGetter; @@ -90,4 +91,21 @@ public class JsonParserTest { Assert.assertEquals(100, config.getCorsMaxAge()); Assert.assertEquals(200, config.getConnectionPoolSize()); } + + static Pattern substitution = Pattern.compile("\\$\\{([^}]+)\\}"); + + @Test + public void testSub() { + String pattern = "${ALIAS}.${CRAP}"; + Matcher m = substitution.matcher(pattern); + StringBuffer sb = new StringBuffer(); + while (m.find()) { + System.out.println("GROUP: " + m.group(1)); + m.appendReplacement(sb, m.group(1)); + + } + m.appendTail(sb); + System.out.println(sb.toString()); + } + } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java index d0af512b05d..dee433e9662 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/JpaUserProvider.java @@ -86,12 +86,21 @@ public class JpaUserProvider implements UserProvider { } private void removeUser(UserEntity user) { + String id = user.getId(); em.createNamedQuery("deleteUserRoleMappingsByUser").setParameter("user", user).executeUpdate(); em.createNamedQuery("deleteFederatedIdentityByUser").setParameter("user", user).executeUpdate(); em.createNamedQuery("deleteUserConsentRolesByUser").setParameter("user", user).executeUpdate(); em.createNamedQuery("deleteUserConsentProtMappersByUser").setParameter("user", user).executeUpdate(); em.createNamedQuery("deleteUserConsentsByUser").setParameter("user", user).executeUpdate(); - em.remove(user); + em.flush(); + // not sure why i have to do a clear() here. I was getting some messed up errors that Hibernate couldn't + // un-delete the UserEntity. + em.clear(); + user = em.find(UserEntity.class, id); + if (user != null) { + em.remove(user); + } + em.flush(); } @Override diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java index 5264f17de42..d812a3441be 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserEntity.java @@ -186,4 +186,21 @@ public class UserEntity { public void setFederationLink(String federationLink) { this.federationLink = federationLink; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + UserEntity that = (UserEntity) o; + + if (!id.equals(that.id)) return false; + + return true; + } + + @Override + public int hashCode() { + return id.hashCode(); + } } diff --git a/services/src/main/java/org/keycloak/services/managers/UserManager.java b/services/src/main/java/org/keycloak/services/managers/UserManager.java index c6732b74255..3c0252c2f03 100755 --- a/services/src/main/java/org/keycloak/services/managers/UserManager.java +++ b/services/src/main/java/org/keycloak/services/managers/UserManager.java @@ -17,11 +17,11 @@ public class UserManager { } public boolean removeUser(RealmModel realm, UserModel user) { + UserSessionProvider sessions = session.sessions(); + if (sessions != null) { + sessions.onUserRemoved(realm, user); + } if (session.users().removeUser(realm, user)) { - UserSessionProvider sessions = session.sessions(); - if (sessions != null) { - sessions.onUserRemoved(realm, user); - } return true; } return false; diff --git a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java index a8119ce4651..e6660ee0592 100755 --- a/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java +++ b/services/src/main/java/org/keycloak/services/resources/IdentityBrokerService.java @@ -44,6 +44,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; +import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.protocol.oidc.TokenManager; import org.keycloak.provider.ProviderFactory; import org.keycloak.representations.AccessToken; @@ -249,6 +250,16 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal } ClientSessionModel clientSession = clientCode.getClientSession(); + context.getIdp().preprocessFederatedIdentity(session, realmModel, context); + Set mappers = realmModel.getIdentityProviderMappersByAlias(context.getIdpConfig().getAlias()); + if (mappers != null) { + KeycloakSessionFactory sessionFactory = session.getKeycloakSessionFactory(); + for (IdentityProviderMapperModel mapper : mappers) { + IdentityProviderMapper target = (IdentityProviderMapper)sessionFactory.getProviderFactory(IdentityProviderMapper.class, mapper.getIdentityProviderMapper()); + target.preprocessFederatedIdentity(session, realmModel, mapper, context); + } + } + FederatedIdentityModel federatedIdentityModel = new FederatedIdentityModel(providerId, context.getId(), context.getUsername(), context.getToken()); @@ -492,18 +503,24 @@ public class IdentityBrokerService implements IdentityProvider.AuthenticationCal fireErrorEvent(Errors.FEDERATED_IDENTITY_EMAIL_EXISTS); throw new IdentityBrokerException(Messages.FEDERATED_IDENTITY_EMAIL_EXISTS); } + String username = context.getModelUsername(); + if (username == null) { + username = context.getUsername(); + if (this.realmModel.isRegistrationEmailAsUsername() && !Validation.isEmpty(context.getEmail())) { + username = context.getEmail(); + } else if (username == null) { + username = context.getIdpConfig().getAlias() + "." + context.getId(); + } else { + username = context.getIdpConfig().getAlias() + "." + context.getUsername(); + } + } + if (username == null) { + LOGGER.warn("Unknown username"); + fireErrorEvent(Errors.FEDERATED_IDENTITY_USERNAME_EXISTS); + throw new IdentityBrokerException(Messages.FEDERATED_IDENTITY_USERNAME_EXISTS); - String username = context.getUsername(); - if (this.realmModel.isRegistrationEmailAsUsername() && !Validation.isEmpty(context.getEmail())) { - username = context.getEmail(); - } else if (username == null) { - username = context.getIdpConfig().getAlias() + "." + context.getId(); - } else { - username = context.getIdpConfig().getAlias() + "." + context.getUsername(); - } - if (username != null) { - username = username.trim(); } + username = username.trim(); existingUser = this.session.users().getUserByUsername(username, this.realmModel); diff --git a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java index ec33725c22f..ecedae7692c 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/IdentityProvidersResource.java @@ -129,7 +129,8 @@ public class IdentityProvidersResource { this.auth.requireManage(); try { - this.realm.addIdentityProvider(RepresentationToModel.toModel(representation)); + IdentityProviderModel identityProvider = RepresentationToModel.toModel(representation); + this.realm.addIdentityProvider(identityProvider); return Response.created(uriInfo.getAbsolutePathBuilder().path(representation.getProviderId()).build()).build(); } catch (ModelDuplicateException e) { diff --git a/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java b/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java index d9db0e9df13..40a883b24a5 100755 --- a/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java +++ b/social/github/src/main/java/org/keycloak/social/github/GitHubIdentityProvider.java @@ -33,7 +33,8 @@ public class GitHubIdentityProvider extends AbstractOAuth2IdentityProvider imple BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "id")); - user.setUsername(getJsonProperty(profile, "login")); + String username = getJsonProperty(profile, "login"); + user.setUsername(username); user.setName(getJsonProperty(profile, "name")); user.setEmail(getJsonProperty(profile, "email")); user.setIdpConfig(getConfig()); diff --git a/social/linkedin/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java b/social/linkedin/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java index c031443ff56..5c5265e8211 100755 --- a/social/linkedin/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java +++ b/social/linkedin/src/main/java/org/keycloak/social/linkedin/LinkedInIdentityProvider.java @@ -60,7 +60,8 @@ public class LinkedInIdentityProvider extends AbstractOAuth2IdentityProvider imp BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "id")); - user.setUsername(extractUsernameFromProfileURL(getJsonProperty(profile, "publicProfileUrl"))); + String username = extractUsernameFromProfileURL(getJsonProperty(profile, "publicProfileUrl")); + user.setUsername(username); user.setName(getJsonProperty(profile, "formattedName")); user.setEmail(getJsonProperty(profile, "emailAddress")); user.setIdpConfig(getConfig()); diff --git a/social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java b/social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java index 3e9cc8c2c3b..280753bf21a 100755 --- a/social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java +++ b/social/stackoverflow/src/main/java/org/keycloak/social/stackoverflow/StackoverflowIdentityProvider.java @@ -69,7 +69,8 @@ public class StackoverflowIdentityProvider extends AbstractOAuth2IdentityProvide BrokeredIdentityContext user = new BrokeredIdentityContext(getJsonProperty(profile, "user_id")); - user.setUsername(extractUsernameFromProfileURL(getJsonProperty(profile, "link"))); + 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"));