diff --git a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml index 27869eb1a5..73b98121b6 100755 --- a/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml +++ b/connections/jpa-liquibase/src/main/resources/META-INF/jpa-changelog-1.3.0.Beta1.xml @@ -2,6 +2,7 @@ + @@ -21,6 +22,33 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -67,6 +95,11 @@ + + + + + diff --git a/connections/jpa/src/main/resources/META-INF/persistence.xml b/connections/jpa/src/main/resources/META-INF/persistence.xml index 5bcb77ee99..a5af4652f9 100755 --- a/connections/jpa/src/main/resources/META-INF/persistence.xml +++ b/connections/jpa/src/main/resources/META-INF/persistence.xml @@ -9,6 +9,7 @@ org.keycloak.models.jpa.entities.RealmAttributeEntity org.keycloak.models.jpa.entities.RequiredCredentialEntity org.keycloak.models.jpa.entities.UserFederationProviderEntity + org.keycloak.models.jpa.entities.UserFederationMapperEntity org.keycloak.models.jpa.entities.RoleEntity org.keycloak.models.jpa.entities.FederatedIdentityEntity org.keycloak.models.jpa.entities.MigrationModelEntity diff --git a/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java b/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java index 73d6b84f97..ed6844c3ed 100755 --- a/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java +++ b/connections/mongo/src/main/java/org/keycloak/connections/mongo/DefaultMongoConnectionFactoryProvider.java @@ -39,6 +39,7 @@ public class DefaultMongoConnectionFactoryProvider implements MongoConnectionPro "org.keycloak.models.sessions.mongo.entities.MongoUserSessionEntity", "org.keycloak.models.sessions.mongo.entities.MongoClientSessionEntity", "org.keycloak.models.entities.UserFederationProviderEntity", + "org.keycloak.models.entities.UserFederationMapperEntity", "org.keycloak.models.entities.ProtocolMapperEntity", "org.keycloak.models.entities.IdentityProviderMapperEntity", "org.keycloak.models.mongo.keycloak.entities.MongoUserConsentEntity", diff --git a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java index 0240a8dbe4..6ff027ca7d 100755 --- a/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java +++ b/core/src/main/java/org/keycloak/representations/idm/RealmRepresentation.java @@ -53,6 +53,7 @@ public class RealmRepresentation { protected Map browserSecurityHeaders; protected Map smtpServer; protected List userFederationProviders; + protected List userFederationMappers; protected String loginTheme; protected String accountTheme; protected String adminTheme; @@ -536,6 +537,19 @@ public class RealmRepresentation { this.userFederationProviders = userFederationProviders; } + public List getUserFederationMappers() { + return userFederationMappers; + } + + public void setUserFederationMappers(List userFederationMappers) { + this.userFederationMappers = userFederationMappers; + } + + public void addUserFederationMapper(UserFederationMapperRepresentation userFederationMapper) { + if (userFederationMappers == null) userFederationMappers = new LinkedList<>(); + userFederationMappers.add(userFederationMapper); + } + public List getIdentityProviders() { return identityProviders; } diff --git a/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperRepresentation.java new file mode 100644 index 0000000000..2e87e4e8c5 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperRepresentation.java @@ -0,0 +1,56 @@ +package org.keycloak.representations.idm; + +import java.util.Map; + +/** + * @author Marek Posolda + */ +public class UserFederationMapperRepresentation { + + protected String id; + protected String name; + protected String federationProviderDisplayName; + protected String federationMapperType; + protected Map config; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getFederationProviderDisplayName() { + return federationProviderDisplayName; + } + + public void setFederationProviderDisplayName(String federationProviderDisplayName) { + this.federationProviderDisplayName = federationProviderDisplayName; + } + + public String getFederationMapperType() { + return federationMapperType; + } + + public void setFederationMapperType(String federationMapperType) { + this.federationMapperType = federationMapperType; + } + + public Map getConfig() { + return config; + } + + public void setConfig(Map config) { + this.config = config; + } +} + diff --git a/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperTypeRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperTypeRepresentation.java new file mode 100644 index 0000000000..f03b3765c0 --- /dev/null +++ b/core/src/main/java/org/keycloak/representations/idm/UserFederationMapperTypeRepresentation.java @@ -0,0 +1,55 @@ +package org.keycloak.representations.idm; + +import java.util.List; + +/** + * @author Marek Posolda + */ +public class UserFederationMapperTypeRepresentation { + protected String id; + protected String name; + protected String category; + protected String helpText; + + protected List properties; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getCategory() { + return category; + } + + public void setCategory(String category) { + this.category = category; + } + + public String getHelpText() { + return helpText; + } + + public void setHelpText(String helpText) { + this.helpText = helpText; + } + + public List getProperties() { + return properties; + } + + public void setProperties(List properties) { + this.properties = properties; + } +} diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java index ad55bc3534..fcdc11c17d 100755 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPFederationProvider.java @@ -111,7 +111,7 @@ public class LDAPFederationProvider implements UserFederationProvider { proxied = new UnsyncedLDAPUserModelDelegate(local, this); } - List federationMappers = realm.getUserFederationMappers(); + Set federationMappers = realm.getUserFederationMappers(); for (UserFederationMapperModel mapperModel : federationMappers) { LDAPFederationMapper ldapMapper = getMapper(mapperModel); proxied = ldapMapper.proxy(mapperModel, this, ldapObject, proxied, realm); @@ -268,7 +268,7 @@ public class LDAPFederationProvider implements UserFederationProvider { UserModel imported = session.userStorage().addUser(realm, ldapUsername); imported.setEnabled(true); - List federationMappers = realm.getUserFederationMappers(); + Set federationMappers = realm.getUserFederationMappers(); for (UserFederationMapperModel mapperModel : federationMappers) { LDAPFederationMapper ldapMapper = getMapper(mapperModel); ldapMapper.onImportUserFromLDAP(mapperModel, this, ldapUser, imported, realm, true); @@ -404,7 +404,7 @@ public class LDAPFederationProvider implements UserFederationProvider { if ((fedModel.getId().equals(currentUser.getFederationLink())) && (ldapUser.getUuid().equals(currentUser.getAttribute(LDAPConstants.LDAP_ID)))) { // Update keycloak user - List federationMappers = realm.getUserFederationMappers(); + Set federationMappers = realm.getUserFederationMappers(); for (UserFederationMapperModel mapperModel : federationMappers) { LDAPFederationMapper ldapMapper = getMapper(mapperModel); ldapMapper.onImportUserFromLDAP(mapperModel, this, ldapUser, currentUser, realm, false); @@ -477,9 +477,9 @@ public class LDAPFederationProvider implements UserFederationProvider { } public LDAPFederationMapper getMapper(UserFederationMapperModel mapperModel) { - LDAPFederationMapper ldapMapper = (LDAPFederationMapper) getSession().getProvider(UserFederationMapper.class, mapperModel.getFederationMapperId()); + LDAPFederationMapper ldapMapper = (LDAPFederationMapper) getSession().getProvider(UserFederationMapper.class, mapperModel.getFederationMapperType()); if (ldapMapper == null) { - throw new ModelException("Can't find mapper type with ID: " + mapperModel.getFederationMapperId()); + throw new ModelException("Can't find mapper type with ID: " + mapperModel.getFederationMapperType()); } return ldapMapper; diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java index 6c5ac93237..1e9a9e3e08 100755 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/LDAPUtils.java @@ -1,6 +1,7 @@ package org.keycloak.federation.ldap; import java.util.List; +import java.util.Set; import org.keycloak.federation.ldap.idm.model.LDAPDn; import org.keycloak.federation.ldap.idm.model.LDAPObject; @@ -34,7 +35,7 @@ public class LDAPUtils { ldapObject.setRdnAttributeName(ldapConfig.getRdnLdapAttribute()); ldapObject.setObjectClasses(ldapConfig.getObjectClasses()); - List federationMappers = realm.getUserFederationMappers(); + Set federationMappers = realm.getUserFederationMappers(); for (UserFederationMapperModel mapperModel : federationMappers) { LDAPFederationMapper ldapMapper = ldapProvider.getMapper(mapperModel); ldapMapper.onRegisterUserToLDAP(mapperModel, ldapProvider, ldapObject, user, realm); @@ -130,7 +131,7 @@ public class LDAPUtils { ldapQuery.addSearchDns(config.getUserDns()); ldapQuery.addObjectClasses(config.getObjectClasses()); - List mapperModels = realm.getUserFederationMappers(); + Set mapperModels = realm.getUserFederationMappers(); ldapQuery.addMappers(mapperModels); return ldapQuery; diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java index 63b5443aee..0a2a767e3d 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/idm/store/ldap/LDAPOperationManager.java @@ -475,12 +475,12 @@ public class LDAPOperationManager { String url = this.config.getConnectionUrl(); - if (url == null) { - throw new RuntimeException("url"); + if (url != null) { + env.put(Context.PROVIDER_URL, url); + } else { + logger.warn("LDAP URL is null. LDAPOperationManager won't work correctly"); } - env.put(Context.PROVIDER_URL, url); - String connectionPooling = this.config.getConnectionPooling(); if (connectionPooling != null) { env.put("com.sun.jndi.ldap.connect.pool", connectionPooling); diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java index 682034551f..6ef69791dc 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/FullNameLDAPFederationMapperFactory.java @@ -11,6 +11,8 @@ import org.keycloak.provider.ProviderConfigProperty; */ public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory { + public static final String ID = "full-name-ldap-mapper"; + @Override public String getHelpText() { return "Some help text - full name mapper - TODO"; @@ -23,7 +25,7 @@ public class FullNameLDAPFederationMapperFactory extends AbstractLDAPFederationM @Override public String getId() { - return "full-name-ldap-mapper"; + return ID; } @Override diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java index 321a0d5fa8..cbae85069f 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/RoleLDAPFederationMapperFactory.java @@ -11,6 +11,8 @@ import org.keycloak.provider.ProviderConfigProperty; */ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory { + public static final String ID = "role-ldap-mapper"; + @Override public String getHelpText() { return "Some help text - role mapper - TODO"; @@ -23,7 +25,7 @@ public class RoleLDAPFederationMapperFactory extends AbstractLDAPFederationMappe @Override public String getId() { - return "role-ldap-mapper"; + return ID ; } @Override diff --git a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java index 16d4879449..564b012491 100644 --- a/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java +++ b/federation/ldap/src/main/java/org/keycloak/federation/ldap/mappers/UserAttributeLDAPFederationMapperFactory.java @@ -11,6 +11,8 @@ import org.keycloak.provider.ProviderConfigProperty; */ public class UserAttributeLDAPFederationMapperFactory extends AbstractLDAPFederationMapperFactory { + public static final String ID = "user-attribute-ldap-mapper"; + @Override public String getHelpText() { return "Some help text TODO"; @@ -23,7 +25,7 @@ public class UserAttributeLDAPFederationMapperFactory extends AbstractLDAPFedera @Override public String getId() { - return "user-attribute-ldap-mapper"; + return ID; } @Override diff --git a/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperSpi.java b/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperSpi.java index b67a23a850..f668058d3e 100644 --- a/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperSpi.java +++ b/model/api/src/main/java/org/keycloak/mappers/UserFederationMapperSpi.java @@ -23,4 +23,9 @@ public class UserFederationMapperSpi implements Spi { public Class getProviderFactoryClass() { return UserFederationMapperFactory.class; } + + @Override + public boolean isPrivate() { + return false; + } } diff --git a/model/api/src/main/java/org/keycloak/models/RealmModel.java b/model/api/src/main/java/org/keycloak/models/RealmModel.java index 1247bd96ba..bfcaefb172 100755 --- a/model/api/src/main/java/org/keycloak/models/RealmModel.java +++ b/model/api/src/main/java/org/keycloak/models/RealmModel.java @@ -189,7 +189,13 @@ public interface RealmModel extends RoleContainerModel { void removeUserFederationProvider(UserFederationProviderModel provider); void setUserFederationProviders(List providers); - List getUserFederationMappers(); + Set getUserFederationMappers(); + Set getUserFederationMappersByFederationProvider(String federationProviderId); + UserFederationMapperModel addUserFederationMapper(UserFederationMapperModel mapper); + void removeUserFederationMapper(UserFederationMapperModel mapper); + void updateUserFederationMapper(UserFederationMapperModel mapper); + UserFederationMapperModel getUserFederationMapperById(String id); + UserFederationMapperModel getUserFederationMapperByName(String federationProviderId, String name); String getLoginTheme(); diff --git a/model/api/src/main/java/org/keycloak/models/UserFederationMapperModel.java b/model/api/src/main/java/org/keycloak/models/UserFederationMapperModel.java index 50b47c3d9d..54a347bdfb 100644 --- a/model/api/src/main/java/org/keycloak/models/UserFederationMapperModel.java +++ b/model/api/src/main/java/org/keycloak/models/UserFederationMapperModel.java @@ -9,7 +9,13 @@ public class UserFederationMapperModel { protected String id; protected String name; - protected String federationMapperId; + + // Refers to DB ID of federation provider + protected String federationProviderId; + + // Refers to ID of UserFederationMapper implementation ( UserFederationMapperFactory.getId ) + protected String federationMapperType; + protected Map config; public String getId() { @@ -28,12 +34,20 @@ public class UserFederationMapperModel { this.name = name; } - public String getFederationMapperId() { - return federationMapperId; + public String getFederationProviderId() { + return federationProviderId; } - public void setFederationMapperId(String federationMapperId) { - this.federationMapperId = federationMapperId; + public void setFederationProviderId(String federationProviderId) { + this.federationProviderId = federationProviderId; + } + + public String getFederationMapperType() { + return federationMapperType; + } + + public void setFederationMapperType(String federationMapperType) { + this.federationMapperType = federationMapperType; } public Map getConfig() { diff --git a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java index 718a1c5ed1..1b8ccf21df 100755 --- a/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java +++ b/model/api/src/main/java/org/keycloak/models/entities/RealmEntity.java @@ -53,6 +53,7 @@ public class RealmEntity extends AbstractIdentifiableEntity { private List requiredCredentials = new ArrayList(); private List userFederationProviders = new ArrayList(); + private List userFederationMappers = new ArrayList(); private List identityProviders = new ArrayList(); private Map browserSecurityHeaders = new HashMap(); @@ -426,6 +427,14 @@ public class RealmEntity extends AbstractIdentifiableEntity { this.userFederationProviders = userFederationProviders; } + public List getUserFederationMappers() { + return userFederationMappers; + } + + public void setUserFederationMappers(List userFederationMappers) { + this.userFederationMappers = userFederationMappers; + } + public List getIdentityProviders() { return identityProviders; } diff --git a/model/api/src/main/java/org/keycloak/models/entities/UserFederationMapperEntity.java b/model/api/src/main/java/org/keycloak/models/entities/UserFederationMapperEntity.java new file mode 100644 index 0000000000..a920047ecc --- /dev/null +++ b/model/api/src/main/java/org/keycloak/models/entities/UserFederationMapperEntity.java @@ -0,0 +1,55 @@ +package org.keycloak.models.entities; + +import java.util.Map; + +/** + * @author Marek Posolda + */ +public class UserFederationMapperEntity { + + protected String id; + protected String name; + protected String federationProviderId; + protected String federationMapperType; + protected Map config; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getFederationProviderId() { + return federationProviderId; + } + + public void setFederationProviderId(String federationProviderId) { + this.federationProviderId = federationProviderId; + } + + public String getFederationMapperType() { + return federationMapperType; + } + + public void setFederationMapperType(String federationMapperType) { + this.federationMapperType = federationMapperType; + } + + public Map getConfig() { + return config; + } + + public void setConfig(Map config) { + this.config = config; + } +} diff --git a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java index 64625c5c5e..382d9c0c8c 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java +++ b/model/api/src/main/java/org/keycloak/models/utils/KeycloakModelUtils.java @@ -6,9 +6,12 @@ import org.keycloak.models.KeycloakSession; import org.keycloak.models.KeycloakSessionFactory; import org.keycloak.models.KeycloakSessionTask; import org.keycloak.models.KeycloakTransaction; +import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserFederationProvider; +import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; import org.keycloak.util.CertificateUtils; import org.keycloak.util.PemUtils; @@ -23,6 +26,8 @@ import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.PublicKey; import java.security.cert.X509Certificate; +import java.util.LinkedList; +import java.util.List; import java.util.Set; import java.util.UUID; @@ -267,4 +272,49 @@ public final class KeycloakModelUtils { } return false; } + + /** + * Ensure that displayName of myProvider (if not null) is unique and there is no other provider with same displayName in the list. + * + * @param displayName to check for duplications + * @param myProvider provider, which is excluded from the list (if present) + * @param federationProviders + * @throws ModelDuplicateException if there is other provider with same displayName + */ + public static void ensureUniqueDisplayName(String displayName, UserFederationProviderModel myProvider, List federationProviders) throws ModelDuplicateException { + if (displayName != null) { + + for (UserFederationProviderModel federationProvider : federationProviders) { + if (myProvider != null && (myProvider.equals(federationProvider) || (myProvider.getId() != null && myProvider.getId().equals(federationProvider.getId())))) { + continue; + } + + if (displayName.equals(federationProvider.getDisplayName())) { + throw new ModelDuplicateException("There is already existing federation provider with display name: " + displayName); + } + } + } + } + + public static UserFederationProviderModel findUserFederationProviderByDisplayName(String displayName, RealmModel realm) { + if (displayName == null) { + return null; + } + + for (UserFederationProviderModel fedProvider : realm.getUserFederationProviders()) { + if (displayName.equals(fedProvider.getDisplayName())) { + return fedProvider; + } + } + return null; + } + + public static UserFederationProviderModel findUserFederationProviderById(String fedProviderId, RealmModel realm) { + for (UserFederationProviderModel fedProvider : realm.getUserFederationProviders()) { + if (fedProviderId.equals(fedProvider.getId())) { + return fedProvider; + } + } + return null; + } } diff --git a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index 84248fdada..2d202667b2 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/model/api/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -5,12 +5,14 @@ import org.keycloak.models.ClientSessionModel; import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderModel; +import org.keycloak.models.ModelException; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserConsentModel; import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserFederationMapperModel; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserSessionModel; @@ -25,6 +27,7 @@ import org.keycloak.representations.idm.RealmEventsConfigRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.UserConsentRepresentation; +import org.keycloak.representations.idm.UserFederationMapperRepresentation; import org.keycloak.representations.idm.UserFederationProviderRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.UserSessionRepresentation; @@ -162,6 +165,10 @@ public class ModelToRepresentation { rep.setUserFederationProviders(fedProviderReps); } + for (UserFederationMapperModel mapper : realm.getUserFederationMappers()) { + rep.addUserFederationMapper(toRepresentation(realm, mapper)); + } + for (IdentityProviderModel provider : realm.getIdentityProviders()) { rep.addIdentityProvider(toRepresentation(provider)); } @@ -291,6 +298,24 @@ public class ModelToRepresentation { return rep; } + public static UserFederationMapperRepresentation toRepresentation(RealmModel realm, UserFederationMapperModel model) { + UserFederationMapperRepresentation rep = new UserFederationMapperRepresentation(); + rep.setId(model.getId()); + rep.setName(model.getName()); + rep.setFederationMapperType(model.getFederationMapperType()); + Map config = new HashMap(); + config.putAll(model.getConfig()); + rep.setConfig(config); + + UserFederationProviderModel fedProvider = KeycloakModelUtils.findUserFederationProviderById(model.getId(), realm); + if (fedProvider == null) { + throw new ModelException("Couldn't find federation provider with ID " + model.getId()); + } + rep.setFederationProviderDisplayName(fedProvider.getDisplayName()); + + return rep; + } + public static IdentityProviderRepresentation toRepresentation(IdentityProviderModel identityProviderModel) { IdentityProviderRepresentation providerRep = new IdentityProviderRepresentation(); diff --git a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index 9f84087138..f59f0b5629 100755 --- a/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/model/api/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -11,6 +11,7 @@ import org.keycloak.models.FederatedIdentityModel; import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelException; import org.keycloak.models.PasswordPolicy; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.RealmModel; @@ -18,6 +19,7 @@ import org.keycloak.models.RoleModel; import org.keycloak.models.UserConsentModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialValueModel; +import org.keycloak.models.UserFederationMapperModel; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; import org.keycloak.representations.idm.ApplicationRepresentation; @@ -34,6 +36,7 @@ import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.ScopeMappingRepresentation; import org.keycloak.representations.idm.SocialLinkRepresentation; import org.keycloak.representations.idm.UserConsentRepresentation; +import org.keycloak.representations.idm.UserFederationMapperRepresentation; import org.keycloak.representations.idm.UserFederationProviderRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.util.UriUtils; @@ -240,6 +243,11 @@ public class RepresentationToModel { List providerModels = convertFederationProviders(rep.getUserFederationProviders()); newRealm.setUserFederationProviders(providerModels); } + if (rep.getUserFederationMappers() != null) { + for (UserFederationMapperRepresentation representation : rep.getUserFederationMappers()) { + newRealm.addUserFederationMapper(toModel(newRealm, representation)); + } + } // create users and their role mappings and social mappings @@ -475,6 +483,23 @@ public class RepresentationToModel { return result; } + public static UserFederationMapperModel toModel(RealmModel realm, UserFederationMapperRepresentation rep) { + UserFederationMapperModel model = new UserFederationMapperModel(); + model.setId(rep.getId()); + model.setName(rep.getName()); + model.setFederationMapperType(rep.getFederationMapperType()); + model.setConfig(rep.getConfig()); + + UserFederationProviderModel fedProvider = KeycloakModelUtils.findUserFederationProviderByDisplayName(rep.getFederationProviderDisplayName(), realm); + if (fedProvider == null) { + throw new ModelException("Couldn't find federation provider with display name [" + rep.getFederationProviderDisplayName() + "] referenced from mapper [" + + rep.getName()); + } + model.setFederationProviderId(fedProvider.getId()); + + return model; + } + // Roles public static void createRole(RealmModel newRealm, RoleRepresentation roleRep) { diff --git a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java index 12ace4ce30..4092ffab4c 100755 --- a/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java +++ b/model/file/src/main/java/org/keycloak/models/file/adapter/RealmAdapter.java @@ -35,6 +35,7 @@ import org.keycloak.models.entities.IdentityProviderMapperEntity; import org.keycloak.models.entities.RealmEntity; import org.keycloak.models.entities.RequiredCredentialEntity; import org.keycloak.models.entities.RoleEntity; +import org.keycloak.models.entities.UserFederationMapperEntity; import org.keycloak.models.entities.UserFederationProviderEntity; import org.keycloak.models.utils.KeycloakModelUtils; @@ -812,6 +813,8 @@ public class RealmAdapter implements RealmModel { @Override public UserFederationProviderModel addUserFederationProvider(String providerName, Map config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync) { + KeycloakModelUtils.ensureUniqueDisplayName(displayName, null, getUserFederationProviders()); + UserFederationProviderEntity entity = new UserFederationProviderEntity(); entity.setId(KeycloakModelUtils.generateId()); entity.setPriority(priority); @@ -837,6 +840,12 @@ public class RealmAdapter implements RealmModel { if (entity.getId().equals(provider.getId())) { session.users().preRemove(this, new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName(), entity.getFullSyncPeriod(), entity.getChangedSyncPeriod(), entity.getLastSync())); + + Set mappers = getUserFederationMapperEntitiesByFederationProvider(provider.getId()); + for (UserFederationMapperEntity mapper : mappers) { + realm.getUserFederationMappers().remove(mapper); + } + it.remove(); } } @@ -844,6 +853,8 @@ public class RealmAdapter implements RealmModel { @Override public void updateUserFederationProvider(UserFederationProviderModel model) { + KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), model, getUserFederationProviders()); + Iterator it = realm.getUserFederationProviders().iterator(); while (it.hasNext()) { UserFederationProviderEntity entity = it.next(); @@ -889,6 +900,10 @@ public class RealmAdapter implements RealmModel { @Override public void setUserFederationProviders(List providers) { + for (UserFederationProviderModel currentProvider : providers) { + KeycloakModelUtils.ensureUniqueDisplayName(currentProvider.getDisplayName(), currentProvider, providers); + } + List entities = new LinkedList(); for (UserFederationProviderModel model : providers) { UserFederationProviderEntity entity = new UserFederationProviderEntity(); @@ -1063,16 +1078,7 @@ public class RealmAdapter implements RealmModel { public Set getIdentityProviderMappers() { Set mappings = new HashSet<>(); for (IdentityProviderMapperEntity entity : this.realm.getIdentityProviderMappers()) { - IdentityProviderMapperModel mapping = new IdentityProviderMapperModel(); - mapping.setId(entity.getId()); - mapping.setName(entity.getName()); - mapping.setIdentityProviderAlias(entity.getIdentityProviderAlias()); - mapping.setIdentityProviderMapper(entity.getIdentityProviderMapper()); - Map config = new HashMap(); - if (entity.getConfig() != null) { - config.putAll(entity.getConfig()); - } - mapping.setConfig(config); + IdentityProviderMapperModel mapping = entityToModel(entity); mappings.add(mapping); } return mappings; @@ -1084,16 +1090,7 @@ public class RealmAdapter implements RealmModel { if (!entity.getIdentityProviderAlias().equals(brokerAlias)) { continue; } - IdentityProviderMapperModel mapping = new IdentityProviderMapperModel(); - mapping.setId(entity.getId()); - mapping.setName(entity.getName()); - mapping.setIdentityProviderAlias(entity.getIdentityProviderAlias()); - mapping.setIdentityProviderMapper(entity.getIdentityProviderMapper()); - Map config = new HashMap(); - if (entity.getConfig() != null) { - config.putAll(entity.getConfig()); - } - mapping.setConfig(config); + IdentityProviderMapperModel mapping = entityToModel(entity); mappings.add(mapping); } return mappings; @@ -1102,7 +1099,7 @@ public class RealmAdapter implements RealmModel { @Override public IdentityProviderMapperModel addIdentityProviderMapper(IdentityProviderMapperModel model) { if (getIdentityProviderMapperByName(model.getIdentityProviderAlias(), model.getIdentityProviderMapper()) != null) { - throw new RuntimeException("protocol mapper name must be unique per protocol"); + throw new RuntimeException("identity provider mapper name must be unique per identity provider"); } String id = KeycloakModelUtils.generateId(); IdentityProviderMapperEntity entity = new IdentityProviderMapperEntity(); @@ -1186,8 +1183,116 @@ public class RealmAdapter implements RealmModel { } @Override - public List getUserFederationMappers() { - throw new IllegalStateException("Not yet implemented"); + public Set getUserFederationMappers() { + Set mappers = new HashSet(); + for (UserFederationMapperEntity entity : this.realm.getUserFederationMappers()) { + UserFederationMapperModel mapper = entityToModel(entity); + mappers.add(mapper); + } + return mappers; } + @Override + public Set getUserFederationMappersByFederationProvider(String federationProviderId) { + Set mappers = new HashSet(); + Set mapperEntities = getUserFederationMapperEntitiesByFederationProvider(federationProviderId); + for (UserFederationMapperEntity entity : mapperEntities) { + mappers.add(entityToModel(entity)); + } + return mappers; + } + + @Override + public UserFederationMapperModel addUserFederationMapper(UserFederationMapperModel model) { + if (getUserFederationMapperByName(model.getFederationProviderId(), model.getName()) != null) { + throw new ModelDuplicateException("User federation mapper must be unique per federation provider"); + } + String id = KeycloakModelUtils.generateId(); + UserFederationMapperEntity entity = new UserFederationMapperEntity(); + entity.setId(id); + entity.setName(model.getName()); + entity.setFederationProviderId(model.getFederationProviderId()); + entity.setFederationMapperType(model.getFederationMapperType()); + entity.setConfig(model.getConfig()); + + this.realm.getUserFederationMappers().add(entity); + return entityToModel(entity); + } + + protected UserFederationMapperEntity getUserFederationMapperEntity(String id) { + for (UserFederationMapperEntity entity : this.realm.getUserFederationMappers()) { + if (entity.getId().equals(id)) { + return entity; + } + } + return null; + + } + + protected UserFederationMapperEntity getUserFederationMapperEntityByName(String federationProviderId, String name) { + for (UserFederationMapperEntity entity : this.realm.getUserFederationMappers()) { + if (entity.getFederationProviderId().equals(federationProviderId) && entity.getName().equals(name)) { + return entity; + } + } + return null; + + } + + protected Set getUserFederationMapperEntitiesByFederationProvider(String federationProviderId) { + Set mappers = new HashSet(); + for (UserFederationMapperEntity entity : this.realm.getUserFederationMappers()) { + if (federationProviderId.equals(entity.getFederationProviderId())) { + mappers.add(entity); + } + } + return mappers; + } + + @Override + public void removeUserFederationMapper(UserFederationMapperModel mapper) { + UserFederationMapperEntity toDelete = getUserFederationMapperEntity(mapper.getId()); + if (toDelete != null) { + this.realm.getUserFederationMappers().remove(toDelete); + } + } + + @Override + public void updateUserFederationMapper(UserFederationMapperModel mapper) { + UserFederationMapperEntity entity = getUserFederationMapperEntity(mapper.getId()); + entity.setFederationProviderId(mapper.getFederationProviderId()); + entity.setFederationMapperType(mapper.getFederationMapperType()); + if (entity.getConfig() == null) { + entity.setConfig(mapper.getConfig()); + } else { + entity.getConfig().clear(); + entity.getConfig().putAll(mapper.getConfig()); + } + } + + @Override + public UserFederationMapperModel getUserFederationMapperById(String id) { + UserFederationMapperEntity entity = getUserFederationMapperEntity(id); + if (entity == null) return null; + return entityToModel(entity); + } + + @Override + public UserFederationMapperModel getUserFederationMapperByName(String federationProviderId, String name) { + UserFederationMapperEntity entity = getUserFederationMapperEntityByName(federationProviderId, name); + if (entity == null) return null; + return entityToModel(entity); + } + + protected UserFederationMapperModel entityToModel(UserFederationMapperEntity entity) { + UserFederationMapperModel mapper = new UserFederationMapperModel(); + mapper.setId(entity.getId()); + mapper.setName(entity.getName()); + mapper.setFederationProviderId(entity.getFederationProviderId()); + mapper.setFederationMapperType(entity.getFederationMapperType()); + Map config = new HashMap(); + if (entity.getConfig() != null) config.putAll(entity.getConfig()); + mapper.setConfig(config); + return mapper; + } } diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java index d62d8d8001..778472470e 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/RealmAdapter.java @@ -952,79 +952,67 @@ public class RealmAdapter implements RealmModel { return null; } - public static String LDAP_MODE = "LDAP_ONLY"; - @Override - public List getUserFederationMappers() { - // TODO: Some hardcoded stuff... - List mappers = new ArrayList(); - - mappers.add(createMapperModel("usn", "usernameMapper", "user-attribute-ldap-mapper", - "user.model.attribute", UserModel.USERNAME, - "ldap.attribute", LDAPConstants.UID)); - - // Uncomment this for CN + SN config - /*mappers.add(createMapperModel("fn", "firstNameMapper", "user-attribute-ldap-mapper", - "user.model.attribute", UserModel.FIRST_NAME, - "ldap.attribute", LDAPConstants.CN));*/ - - // Uncomment this for CN + SN + givenname config - mappers.add(createMapperModel("fn", "firstNameMapper", "user-attribute-ldap-mapper", - "user.model.attribute", UserModel.FIRST_NAME, - "ldap.attribute", LDAPConstants.GIVENNAME)); - mappers.add(createMapperModel("fulln", "fullNameMapper", "full-name-ldap-mapper", - "ldap.full.name.attribute", LDAPConstants.CN)); - - mappers.add(createMapperModel("ln", "lastNameMapper", "user-attribute-ldap-mapper", - "user.model.attribute", UserModel.LAST_NAME, - "ldap.attribute", LDAPConstants.SN)); - - mappers.add(createMapperModel("emailMpr", "emailMapper", "user-attribute-ldap-mapper", - "user.model.attribute", UserModel.EMAIL, - "ldap.attribute", LDAPConstants.EMAIL)); - mappers.add(createMapperModel("postalCodeMpr", "postalCodeMapper", "user-attribute-ldap-mapper", - "user.model.attribute", "postal_code", - "ldap.attribute", LDAPConstants.POSTAL_CODE)); - mappers.add(createMapperModel("createdDateMpr", "createTimeStampMapper", "user-attribute-ldap-mapper", - "user.model.attribute", LDAPConstants.CREATE_TIMESTAMP, - "ldap.attribute", LDAPConstants.CREATE_TIMESTAMP, - "read.only", "true")); - mappers.add(createMapperModel("modifyDateMpr", "modifyTimeStampMapper", "user-attribute-ldap-mapper", - "user.model.attribute", LDAPConstants.MODIFY_TIMESTAMP, - "ldap.attribute", LDAPConstants.MODIFY_TIMESTAMP, - "read.only", "true")); - - mappers.add(createMapperModel("realmRoleMpr", "realmRoleMapper", "role-ldap-mapper", - "roles.dn", "ou=RealmRoles,dc=keycloak,dc=org", - "use.realm.roles.mapping", "true", - "mode", LDAP_MODE)); - mappers.add(createMapperModel("financeRoleMpr", "financeRoleMapper", "role-ldap-mapper", - "roles.dn", "ou=FinanceRoles,dc=keycloak,dc=org", - "use.realm.roles.mapping", "false", - "client.id", "finance", - "mode", LDAP_MODE)); - + public Set getUserFederationMappers() { + if (updated != null) return updated.getUserFederationMappers(); + Set mappers = new HashSet(); + for (List models : cached.getUserFederationMappers().values()) { + for (UserFederationMapperModel model : models) { + mappers.add(model); + } + } return mappers; } - private static UserFederationMapperModel createMapperModel(String id, String name, String mapperId, String... config) { - UserFederationMapperModel mapperModel = new UserFederationMapperModel(); - mapperModel.setId(id); - mapperModel.setName(name); - mapperModel.setFederationMapperId(mapperId); + @Override + public Set getUserFederationMappersByFederationProvider(String federationProviderId) { + if (updated != null) return updated.getUserFederationMappersByFederationProvider(federationProviderId); + Set mappers = new HashSet<>(); + List list = cached.getUserFederationMappers().getList(federationProviderId); + for (UserFederationMapperModel entity : list) { + mappers.add(entity); + } + return mappers; + } - Map configMap = new HashMap(); - String key = null; - for (String configEntry : config) { - if (key == null) { - key = configEntry; - } else { - configMap.put(key, configEntry); - key = null; + @Override + public UserFederationMapperModel addUserFederationMapper(UserFederationMapperModel mapper) { + getDelegateForUpdate(); + return updated.addUserFederationMapper(mapper); + } + + @Override + public void removeUserFederationMapper(UserFederationMapperModel mapper) { + getDelegateForUpdate(); + updated.removeUserFederationMapper(mapper); + } + + @Override + public void updateUserFederationMapper(UserFederationMapperModel mapper) { + getDelegateForUpdate(); + updated.updateUserFederationMapper(mapper); + } + + @Override + public UserFederationMapperModel getUserFederationMapperById(String id) { + if (updated != null) return updated.getUserFederationMapperById(id); + for (List models : cached.getUserFederationMappers().values()) { + for (UserFederationMapperModel model : models) { + if (model.getId().equals(id)) return model; } } - mapperModel.setConfig(configMap); - return mapperModel; + return null; + } + + @Override + public UserFederationMapperModel getUserFederationMapperByName(String federationProviderId, String name) { + if (updated != null) return updated.getUserFederationMapperByName(federationProviderId, name); + List models = cached.getUserFederationMappers().getList(federationProviderId); + if (models == null) return null; + for (UserFederationMapperModel model : models) { + if (model.getName().equals(name)) return model; + } + return null; } } diff --git a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java index 22f57a95df..faf7ba27d5 100755 --- a/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java +++ b/model/invalidation-cache/model-adapters/src/main/java/org/keycloak/models/cache/entities/CachedRealm.java @@ -9,6 +9,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.RealmProvider; import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RoleModel; +import org.keycloak.models.UserFederationMapperModel; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.cache.RealmCache; import org.keycloak.util.MultivaluedHashMap; @@ -70,6 +71,7 @@ public class CachedRealm { private List requiredCredentials = new ArrayList(); private List userFederationProviders = new ArrayList(); + private MultivaluedHashMap userFederationMappers = new MultivaluedHashMap(); private List identityProviders = new ArrayList(); private Map browserSecurityHeaders = new HashMap(); @@ -136,6 +138,9 @@ public class CachedRealm { requiredCredentials = model.getRequiredCredentials(); userFederationProviders = model.getUserFederationProviders(); + for (UserFederationMapperModel mapper : model.getUserFederationMappers()) { + userFederationMappers.add(mapper.getFederationProviderId(), mapper); + } this.identityProviders = new ArrayList<>(); @@ -373,6 +378,10 @@ public class CachedRealm { return userFederationProviders; } + public MultivaluedHashMap getUserFederationMappers() { + return userFederationMappers; + } + public String getCertificatePem() { return certificatePem; } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java index 3dde3d36ba..990b087b4b 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/RealmAdapter.java @@ -5,11 +5,14 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelDuplicateException; +import org.keycloak.models.ModelException; import org.keycloak.models.PasswordPolicy; import org.keycloak.models.RealmModel; import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserFederationMapperModel; +import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.jpa.entities.ClientEntity; import org.keycloak.models.jpa.entities.IdentityProviderEntity; @@ -18,6 +21,7 @@ import org.keycloak.models.jpa.entities.RealmAttributeEntity; import org.keycloak.models.jpa.entities.RealmEntity; import org.keycloak.models.jpa.entities.RequiredCredentialEntity; import org.keycloak.models.jpa.entities.RoleEntity; +import org.keycloak.models.jpa.entities.UserFederationMapperEntity; import org.keycloak.models.jpa.entities.UserFederationProviderEntity; import org.keycloak.models.utils.KeycloakModelUtils; @@ -760,6 +764,8 @@ public class RealmAdapter implements RealmModel { @Override public UserFederationProviderModel addUserFederationProvider(String providerName, Map config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync) { + KeycloakModelUtils.ensureUniqueDisplayName(displayName, null, getUserFederationProviders()); + String id = KeycloakModelUtils.generateId(); UserFederationProviderEntity entity = new UserFederationProviderEntity(); entity.setId(id); @@ -786,7 +792,14 @@ public class RealmAdapter implements RealmModel { while (it.hasNext()) { UserFederationProviderEntity entity = it.next(); if (entity.getId().equals(provider.getId())) { + session.users().preRemove(this, provider); + + Set mappers = getUserFederationMapperEntitiesByFederationProvider(provider.getId()); + for (UserFederationMapperEntity mapper : mappers) { + realm.getUserFederationMappers().remove(mapper); + em.remove(mapper); + } it.remove(); em.remove(entity); return; @@ -795,6 +808,8 @@ public class RealmAdapter implements RealmModel { } @Override public void updateUserFederationProvider(UserFederationProviderModel model) { + KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), model, getUserFederationProviders()); + Iterator it = realm.getUserFederationProviders().iterator(); while (it.hasNext()) { UserFederationProviderEntity entity = it.next(); @@ -817,6 +832,9 @@ public class RealmAdapter implements RealmModel { @Override public void setUserFederationProviders(List providers) { + for (UserFederationProviderModel currentProvider : providers) { + KeycloakModelUtils.ensureUniqueDisplayName(currentProvider.getDisplayName(), currentProvider, providers); + } Iterator it = realm.getUserFederationProviders().iterator(); while (it.hasNext()) { @@ -881,6 +899,15 @@ public class RealmAdapter implements RealmModel { } } + protected UserFederationProviderEntity getUserFederationProviderEntityById(String federationProviderId) { + for (UserFederationProviderEntity entity : realm.getUserFederationProviders()) { + if (entity.getId().equals(federationProviderId)) { + return entity; + } + } + return null; + } + @Override public RoleModel getRole(String name) { TypedQuery query = em.createNamedQuery("getRealmRoleByName", RoleEntity.class); @@ -1225,16 +1252,7 @@ public class RealmAdapter implements RealmModel { public Set getIdentityProviderMappers() { Set mappings = new HashSet(); for (IdentityProviderMapperEntity entity : this.realm.getIdentityProviderMappers()) { - IdentityProviderMapperModel mapping = new IdentityProviderMapperModel(); - mapping.setId(entity.getId()); - mapping.setName(entity.getName()); - mapping.setIdentityProviderAlias(entity.getIdentityProviderAlias()); - mapping.setIdentityProviderMapper(entity.getIdentityProviderMapper()); - Map config = new HashMap(); - if (entity.getConfig() != null) { - config.putAll(entity.getConfig()); - } - mapping.setConfig(config); + IdentityProviderMapperModel mapping = entityToModel(entity); mappings.add(mapping); } return mappings; @@ -1247,16 +1265,7 @@ public class RealmAdapter implements RealmModel { if (!entity.getIdentityProviderAlias().equals(brokerAlias)) { continue; } - IdentityProviderMapperModel mapping = new IdentityProviderMapperModel(); - mapping.setId(entity.getId()); - mapping.setName(entity.getName()); - mapping.setIdentityProviderAlias(entity.getIdentityProviderAlias()); - mapping.setIdentityProviderMapper(entity.getIdentityProviderMapper()); - Map config = new HashMap(); - if (entity.getConfig() != null) { - config.putAll(entity.getConfig()); - } - mapping.setConfig(config); + IdentityProviderMapperModel mapping = entityToModel(entity); mappings.add(mapping); } return mappings; @@ -1265,7 +1274,7 @@ public class RealmAdapter implements RealmModel { @Override public IdentityProviderMapperModel addIdentityProviderMapper(IdentityProviderMapperModel model) { if (getIdentityProviderMapperByName(model.getIdentityProviderAlias(), model.getIdentityProviderMapper()) != null) { - throw new RuntimeException("protocol mapper name must be unique per protocol"); + throw new RuntimeException("identity provider mapper name must be unique per identity provider"); } String id = KeycloakModelUtils.generateId(); IdentityProviderMapperEntity entity = new IdentityProviderMapperEntity(); @@ -1353,8 +1362,122 @@ public class RealmAdapter implements RealmModel { } @Override - public List getUserFederationMappers() { - throw new IllegalStateException("Not yet implemented"); + public Set getUserFederationMappers() { + Set mappers = new HashSet(); + for (UserFederationMapperEntity entity : this.realm.getUserFederationMappers()) { + UserFederationMapperModel mapper = entityToModel(entity); + mappers.add(mapper); + } + return mappers; + } + + @Override + public Set getUserFederationMappersByFederationProvider(String federationProviderId) { + Set mappers = new HashSet(); + Set mapperEntities = getUserFederationMapperEntitiesByFederationProvider(federationProviderId); + for (UserFederationMapperEntity entity : mapperEntities) { + UserFederationMapperModel mapper = entityToModel(entity); + mappers.add(mapper); + } + return mappers; + } + + @Override + public UserFederationMapperModel addUserFederationMapper(UserFederationMapperModel model) { + if (getUserFederationMapperByName(model.getFederationProviderId(), model.getName()) != null) { + throw new ModelDuplicateException("User federation mapper must be unique per federation provider"); + } + String id = KeycloakModelUtils.generateId(); + UserFederationMapperEntity entity = new UserFederationMapperEntity(); + entity.setId(id); + entity.setName(model.getName()); + entity.setFederationProvider(getUserFederationProviderEntityById(model.getFederationProviderId())); + entity.setFederationMapperType(model.getFederationMapperType()); + entity.setRealm(this.realm); + entity.setConfig(model.getConfig()); + + em.persist(entity); + this.realm.getUserFederationMappers().add(entity); + return entityToModel(entity); + } + + @Override + public void removeUserFederationMapper(UserFederationMapperModel mapper) { + UserFederationMapperEntity toDelete = getUserFederationMapperEntity(mapper.getId()); + if (toDelete != null) { + this.realm.getUserFederationMappers().remove(toDelete); + em.remove(toDelete); + } + } + + protected UserFederationMapperEntity getUserFederationMapperEntity(String id) { + for (UserFederationMapperEntity entity : this.realm.getUserFederationMappers()) { + if (entity.getId().equals(id)) { + return entity; + } + } + return null; + + } + + protected UserFederationMapperEntity getUserFederationMapperEntityByName(String federationProviderId, String name) { + for (UserFederationMapperEntity entity : this.realm.getUserFederationMappers()) { + if (federationProviderId.equals(entity.getFederationProvider().getId()) && entity.getName().equals(name)) { + return entity; + } + } + return null; + + } + + protected Set getUserFederationMapperEntitiesByFederationProvider(String federationProviderId) { + Set mappers = new HashSet(); + for (UserFederationMapperEntity entity : this.realm.getUserFederationMappers()) { + if (federationProviderId.equals(entity.getFederationProvider().getId())) { + mappers.add(entity); + } + } + return mappers; + } + + @Override + public void updateUserFederationMapper(UserFederationMapperModel mapper) { + UserFederationMapperEntity entity = getUserFederationMapperEntity(mapper.getId()); + entity.setFederationProvider(getUserFederationProviderEntityById(mapper.getFederationProviderId())); + entity.setFederationMapperType(mapper.getFederationMapperType()); + if (entity.getConfig() == null) { + entity.setConfig(mapper.getConfig()); + } else { + entity.getConfig().clear(); + entity.getConfig().putAll(mapper.getConfig()); + } + em.flush(); + } + + @Override + public UserFederationMapperModel getUserFederationMapperById(String id) { + UserFederationMapperEntity entity = getUserFederationMapperEntity(id); + if (entity == null) return null; + return entityToModel(entity); + } + + @Override + public UserFederationMapperModel getUserFederationMapperByName(String federationProviderId, String name) { + UserFederationMapperEntity entity = getUserFederationMapperEntityByName(federationProviderId, name); + if (entity == null) return null; + return entityToModel(entity); + } + + protected UserFederationMapperModel entityToModel(UserFederationMapperEntity entity) { + UserFederationMapperModel mapper = new UserFederationMapperModel(); + mapper.setId(entity.getId()); + mapper.setName(entity.getName()); + mapper.setFederationProviderId(entity.getFederationProvider().getId()); + mapper.setFederationMapperType(entity.getFederationMapperType()); + Map config = new HashMap(); + if (entity.getConfig() != null) config.putAll(entity.getConfig()); + mapper.setConfig(config); + return mapper; } } \ No newline at end of file diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java index cdf314c17c..6c28b9f9fa 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/RealmEntity.java @@ -103,6 +103,9 @@ public class RealmEntity { @JoinTable(name="FED_PROVIDERS") List userFederationProviders = new ArrayList(); + @OneToMany(cascade ={CascadeType.REMOVE}, orphanRemoval = true, mappedBy = "realm") + Collection userFederationMappers = new ArrayList(); + @OneToMany(fetch = FetchType.LAZY, cascade ={CascadeType.REMOVE}, orphanRemoval = true) @JoinTable(name="REALM_CLIENT", joinColumns={ @JoinColumn(name="REALM_ID") }, inverseJoinColumns={ @JoinColumn(name="CLIENT_ID") }) Collection clients = new ArrayList<>(); @@ -475,6 +478,14 @@ public class RealmEntity { this.userFederationProviders = userFederationProviders; } + public Collection getUserFederationMappers() { + return userFederationMappers; + } + + public void setUserFederationMappers(Collection userFederationMappers) { + this.userFederationMappers = userFederationMappers; + } + public Collection getAttributes() { return attributes; } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationMapperEntity.java b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationMapperEntity.java new file mode 100644 index 0000000000..4aa5e6e72f --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/entities/UserFederationMapperEntity.java @@ -0,0 +1,113 @@ +package org.keycloak.models.jpa.entities; + +import java.util.Map; + +import javax.persistence.CollectionTable; +import javax.persistence.Column; +import javax.persistence.ElementCollection; +import javax.persistence.Entity; +import javax.persistence.FetchType; +import javax.persistence.Id; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; +import javax.persistence.MapKeyColumn; +import javax.persistence.NamedQueries; +import javax.persistence.NamedQuery; +import javax.persistence.Table; + +/** + * @author Marek Posolda + */ +@Entity +@Table(name="USER_FEDERATION_MAPPER") +public class UserFederationMapperEntity { + + @Id + @Column(name="ID", length = 36) + protected String id; + + @Column(name="NAME") + protected String name; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "FEDERATION_PROVIDER_ID") + protected UserFederationProviderEntity federationProvider; + + @Column(name = "FEDERATION_MAPPER_TYPE") + protected String federationMapperType; + + @ElementCollection + @MapKeyColumn(name="NAME") + @Column(name="VALUE") + @CollectionTable(name="USER_FEDERATION_MAPPER_CONFIG", joinColumns={ @JoinColumn(name="USER_FEDERATION_MAPPER_ID") }) + private Map config; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "REALM_ID") + private RealmEntity realm; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public UserFederationProviderEntity getFederationProvider() { + return federationProvider; + } + + public void setFederationProvider(UserFederationProviderEntity federationProvider) { + this.federationProvider = federationProvider; + } + + public String getFederationMapperType() { + return federationMapperType; + } + + public void setFederationMapperType(String federationMapperType) { + this.federationMapperType = federationMapperType; + } + + public Map getConfig() { + return config; + } + + public void setConfig(Map config) { + this.config = config; + } + + public RealmEntity getRealm() { + return realm; + } + + public void setRealm(RealmEntity realm) { + this.realm = realm; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + UserFederationMapperEntity that = (UserFederationMapperEntity) o; + + if (!id.equals(that.id)) return false; + + return true; + } + + @Override + public int hashCode() { + return id.hashCode(); + } +} diff --git a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java index 9f68d8fd16..a327a8d050 100755 --- a/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java +++ b/model/mongo/src/main/java/org/keycloak/models/mongo/keycloak/adapters/RealmAdapter.java @@ -9,6 +9,7 @@ import org.keycloak.models.ClientModel; import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.ModelDuplicateException; import org.keycloak.models.PasswordPolicy; import org.keycloak.models.RealmModel; import org.keycloak.models.RealmProvider; @@ -19,6 +20,7 @@ import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.entities.IdentityProviderEntity; import org.keycloak.models.entities.IdentityProviderMapperEntity; import org.keycloak.models.entities.RequiredCredentialEntity; +import org.keycloak.models.entities.UserFederationMapperEntity; import org.keycloak.models.entities.UserFederationProviderEntity; import org.keycloak.models.mongo.keycloak.entities.MongoClientEntity; import org.keycloak.models.mongo.keycloak.entities.MongoRealmEntity; @@ -833,6 +835,8 @@ public class RealmAdapter extends AbstractMongoAdapter impleme @Override public UserFederationProviderModel addUserFederationProvider(String providerName, Map config, int priority, String displayName, int fullSyncPeriod, int changedSyncPeriod, int lastSync) { + KeycloakModelUtils.ensureUniqueDisplayName(displayName, null, getUserFederationProviders()); + UserFederationProviderEntity entity = new UserFederationProviderEntity(); entity.setId(KeycloakModelUtils.generateId()); entity.setPriority(priority); @@ -859,6 +863,12 @@ public class RealmAdapter extends AbstractMongoAdapter impleme if (entity.getId().equals(provider.getId())) { session.users().preRemove(this, new UserFederationProviderModel(entity.getId(), entity.getProviderName(), entity.getConfig(), entity.getPriority(), entity.getDisplayName(), entity.getFullSyncPeriod(), entity.getChangedSyncPeriod(), entity.getLastSync())); + + Set mappers = getUserFederationMapperEntitiesByFederationProvider(provider.getId()); + for (UserFederationMapperEntity mapper : mappers) { + getMongoEntity().getUserFederationMappers().remove(mapper); + } + it.remove(); } } @@ -867,6 +877,8 @@ public class RealmAdapter extends AbstractMongoAdapter impleme @Override public void updateUserFederationProvider(UserFederationProviderModel model) { + KeycloakModelUtils.ensureUniqueDisplayName(model.getDisplayName(), model, getUserFederationProviders()); + Iterator it = realm.getUserFederationProviders().iterator(); while (it.hasNext()) { UserFederationProviderEntity entity = it.next(); @@ -913,6 +925,10 @@ public class RealmAdapter extends AbstractMongoAdapter impleme @Override public void setUserFederationProviders(List providers) { + for (UserFederationProviderModel currentProvider : providers) { + KeycloakModelUtils.ensureUniqueDisplayName(currentProvider.getDisplayName(), currentProvider, providers); + } + List entities = new LinkedList(); for (UserFederationProviderModel model : providers) { UserFederationProviderEntity entity = new UserFederationProviderEntity(); @@ -923,7 +939,7 @@ public class RealmAdapter extends AbstractMongoAdapter impleme entity.setPriority(model.getPriority()); String displayName = model.getDisplayName(); if (displayName == null) { - entity.setDisplayName(entity.getId()); + displayName = entity.getId(); } entity.setDisplayName(displayName); entity.setFullSyncPeriod(model.getFullSyncPeriod()); @@ -1089,16 +1105,7 @@ public class RealmAdapter extends AbstractMongoAdapter impleme public Set getIdentityProviderMappers() { Set mappings = new HashSet(); for (IdentityProviderMapperEntity entity : getMongoEntity().getIdentityProviderMappers()) { - IdentityProviderMapperModel mapping = new IdentityProviderMapperModel(); - mapping.setId(entity.getId()); - mapping.setName(entity.getName()); - mapping.setIdentityProviderAlias(entity.getIdentityProviderAlias()); - mapping.setIdentityProviderMapper(entity.getIdentityProviderMapper()); - Map config = new HashMap(); - if (entity.getConfig() != null) { - config.putAll(entity.getConfig()); - } - mapping.setConfig(config); + IdentityProviderMapperModel mapping = entityToModel(entity); mappings.add(mapping); } return mappings; @@ -1111,16 +1118,7 @@ public class RealmAdapter extends AbstractMongoAdapter impleme if (!entity.getIdentityProviderAlias().equals(brokerAlias)) { continue; } - IdentityProviderMapperModel mapping = new IdentityProviderMapperModel(); - mapping.setId(entity.getId()); - mapping.setName(entity.getName()); - mapping.setIdentityProviderAlias(entity.getIdentityProviderAlias()); - mapping.setIdentityProviderMapper(entity.getIdentityProviderMapper()); - Map config = new HashMap(); - if (entity.getConfig() != null) { - config.putAll(entity.getConfig()); - } - mapping.setConfig(config); + IdentityProviderMapperModel mapping = entityToModel(entity); mappings.add(mapping); } return mappings; @@ -1129,7 +1127,7 @@ public class RealmAdapter extends AbstractMongoAdapter impleme @Override public IdentityProviderMapperModel addIdentityProviderMapper(IdentityProviderMapperModel model) { if (getIdentityProviderMapperByName(model.getIdentityProviderAlias(), model.getIdentityProviderMapper()) != null) { - throw new RuntimeException("protocol mapper name must be unique per protocol"); + throw new RuntimeException("identity provider mapper name must be unique per identity provider"); } String id = KeycloakModelUtils.generateId(); IdentityProviderMapperEntity entity = new IdentityProviderMapperEntity(); @@ -1169,6 +1167,7 @@ public class RealmAdapter extends AbstractMongoAdapter impleme IdentityProviderMapperEntity toDelete = getIdentityProviderMapperEntity(mapping.getId()); if (toDelete != null) { this.realm.getIdentityProviderMappers().remove(toDelete); + updateMongoEntity(); } } @@ -1215,7 +1214,119 @@ public class RealmAdapter extends AbstractMongoAdapter impleme } @Override - public List getUserFederationMappers() { - throw new IllegalStateException("Not yet implemented"); + public Set getUserFederationMappers() { + Set mappers = new HashSet(); + for (UserFederationMapperEntity entity : getMongoEntity().getUserFederationMappers()) { + UserFederationMapperModel mapper = entityToModel(entity); + mappers.add(mapper); + } + return mappers; + } + + @Override + public Set getUserFederationMappersByFederationProvider(String federationProviderId) { + Set mappers = new HashSet(); + Set mapperEntities = getUserFederationMapperEntitiesByFederationProvider(federationProviderId); + for (UserFederationMapperEntity entity : mapperEntities) { + mappers.add(entityToModel(entity)); + } + return mappers; + } + + @Override + public UserFederationMapperModel addUserFederationMapper(UserFederationMapperModel model) { + if (getUserFederationMapperByName(model.getFederationProviderId(), model.getName()) != null) { + throw new ModelDuplicateException("User federation mapper must be unique per federation provider"); + } + String id = KeycloakModelUtils.generateId(); + UserFederationMapperEntity entity = new UserFederationMapperEntity(); + entity.setId(id); + entity.setName(model.getName()); + entity.setFederationProviderId(model.getFederationProviderId()); + entity.setFederationMapperType(model.getFederationMapperType()); + entity.setConfig(model.getConfig()); + + getMongoEntity().getUserFederationMappers().add(entity); + updateMongoEntity(); + return entityToModel(entity); + } + + protected UserFederationMapperEntity getUserFederationMapperEntity(String id) { + for (UserFederationMapperEntity entity : getMongoEntity().getUserFederationMappers()) { + if (entity.getId().equals(id)) { + return entity; + } + } + return null; + + } + + protected UserFederationMapperEntity getUserFederationMapperEntityByName(String federationProviderId, String name) { + for (UserFederationMapperEntity entity : getMongoEntity().getUserFederationMappers()) { + if (entity.getFederationProviderId().equals(federationProviderId) && entity.getName().equals(name)) { + return entity; + } + } + return null; + + } + + protected Set getUserFederationMapperEntitiesByFederationProvider(String federationProviderId) { + Set mappers = new HashSet(); + for (UserFederationMapperEntity entity : getMongoEntity().getUserFederationMappers()) { + if (federationProviderId.equals(entity.getFederationProviderId())) { + mappers.add(entity); + } + } + return mappers; + } + + @Override + public void removeUserFederationMapper(UserFederationMapperModel mapper) { + UserFederationMapperEntity toDelete = getUserFederationMapperEntity(mapper.getId()); + if (toDelete != null) { + this.realm.getUserFederationMappers().remove(toDelete); + updateMongoEntity(); + } + } + + @Override + public void updateUserFederationMapper(UserFederationMapperModel mapper) { + UserFederationMapperEntity entity = getUserFederationMapperEntity(mapper.getId()); + entity.setFederationProviderId(mapper.getFederationProviderId()); + entity.setFederationMapperType(mapper.getFederationMapperType()); + if (entity.getConfig() == null) { + entity.setConfig(mapper.getConfig()); + } else { + entity.getConfig().clear(); + entity.getConfig().putAll(mapper.getConfig()); + } + updateMongoEntity(); + } + + @Override + public UserFederationMapperModel getUserFederationMapperById(String id) { + UserFederationMapperEntity entity = getUserFederationMapperEntity(id); + if (entity == null) return null; + return entityToModel(entity); + } + + @Override + public UserFederationMapperModel getUserFederationMapperByName(String federationProviderId, String name) { + UserFederationMapperEntity entity = getUserFederationMapperEntityByName(federationProviderId, name); + if (entity == null) return null; + return entityToModel(entity); + } + + protected UserFederationMapperModel entityToModel(UserFederationMapperEntity entity) { + UserFederationMapperModel mapper = new UserFederationMapperModel(); + mapper.setId(entity.getId()); + mapper.setName(entity.getName()); + mapper.setFederationProviderId(entity.getFederationProviderId()); + mapper.setFederationMapperType(entity.getFederationMapperType()); + Map config = new HashMap(); + if (entity.getConfig() != null) config.putAll(entity.getConfig()); + mapper.setConfig(config); + return mapper; } } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/LDAPRoleMappingsTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/LDAPRoleMappingsTest.java index bdc3574ae8..eff3572bc1 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/LDAPRoleMappingsTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/LDAPRoleMappingsTest.java @@ -144,7 +144,7 @@ public class LDAPRoleMappingsTest { @Test public void test01_ldapOnlyRoleMappings() { // TODO: Remove me!!! - RealmAdapter.LDAP_MODE = "LDAP_ONLY"; + //RealmAdapter.LDAP_MODE = "LDAP_ONLY"; KeycloakSession session = keycloakRule.startSession(); try { @@ -231,7 +231,7 @@ public class LDAPRoleMappingsTest { @Test public void test02_readOnlyRoleMappings() { // TODO: Remove me!!! - RealmAdapter.LDAP_MODE = "READ_ONLY"; + //RealmAdapter.LDAP_MODE = "READ_ONLY"; KeycloakSession session = keycloakRule.startSession(); try { @@ -293,7 +293,7 @@ public class LDAPRoleMappingsTest { @Test public void test03_importRoleMappings() { // TODO: Remove me!!! - RealmAdapter.LDAP_MODE = "IMPORT"; + //RealmAdapter.LDAP_MODE = "IMPORT"; KeycloakSession session = keycloakRule.startSession(); try { @@ -346,7 +346,7 @@ public class LDAPRoleMappingsTest { } private static UserFederationMapperModel findRoleMapperModel(RealmModel appRealm) { - List fedMappers = appRealm.getUserFederationMappers(); + Set fedMappers = appRealm.getUserFederationMappers(); for (UserFederationMapperModel mapper : fedMappers) { if ("realmRoleMapper".equals(mapper.getName())) { return mapper; diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java index f7f48b3a74..8640d8e44a 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/AdapterTest.java @@ -13,6 +13,7 @@ import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserCredentialModel; import org.keycloak.models.UserCredentialValueModel; +import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; import org.keycloak.models.UserProvider; import org.keycloak.representations.idm.CredentialRepresentation; @@ -23,8 +24,11 @@ import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Set; import static org.junit.Assert.assertNotNull; @@ -741,6 +745,59 @@ public class AdapterTest extends AbstractModelTest { resetSession(); } + @Test + public void testUserFederationProviderDisplayNameCollisions() throws Exception { + RealmModel realm = realmManager.createRealm("JUGGLER1"); + Map cfg = Collections.emptyMap(); + realm.addUserFederationProvider("ldap", cfg, 1, "providerName1", -1, -1, 0); + realm.addUserFederationProvider("ldap", cfg, 1, "providerName2", -1, -1, 0); + + commit(); + + // Try to add federation provider with same display name + try { + realmManager.getRealmByName("JUGGLER1").addUserFederationProvider("ldap", cfg, 1, "providerName1", -1, -1, 0); + commit(); + Assert.fail("Expected exception"); + } catch (ModelDuplicateException e) { + } + commit(true); + + // Try to rename federation provider tu duplicate display name + try { + List fedProviders = realmManager.getRealmByName("JUGGLER1").getUserFederationProviders(); + for (UserFederationProviderModel fedProvider : fedProviders) { + if ("providerName1".equals(fedProvider.getDisplayName())) { + fedProvider.setDisplayName("providerName2"); + realm.updateUserFederationProvider(fedProvider); + break; + } + } + commit(); + Assert.fail("Expected exception"); + } catch (ModelDuplicateException e) { + } + commit(true); + + // Try to rename federation provider tu duplicate display name + try { + List fedProviders = realmManager.getRealmByName("JUGGLER1").getUserFederationProviders(); + for (UserFederationProviderModel fedProvider : fedProviders) { + if ("providerName1".equals(fedProvider.getDisplayName())) { + fedProvider.setDisplayName("providerName2"); + break; + } + } + + realm.setUserFederationProviders(fedProviders); + commit(); + Assert.fail("Expected exception"); + } catch (ModelDuplicateException e) { + } + commit(true); + + } + private KeyPair generateKeypair() throws NoSuchAlgorithmException { return KeyPairGenerator.getInstance("RSA").generateKeyPair(); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java index 317c101496..492aa1d2c0 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/ImportTest.java @@ -5,6 +5,8 @@ import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; import org.keycloak.constants.KerberosConstants; +import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapper; +import org.keycloak.federation.ldap.mappers.FullNameLDAPFederationMapperFactory; import org.keycloak.models.ClientModel; import org.keycloak.models.Constants; import org.keycloak.models.FederatedIdentityModel; @@ -15,6 +17,7 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.RequiredCredentialModel; import org.keycloak.models.RoleModel; import org.keycloak.models.UserConsentModel; +import org.keycloak.models.UserFederationMapperModel; import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderFactory; import org.keycloak.models.UserFederationProviderModel; @@ -220,6 +223,15 @@ public class ImportTest extends AbstractModelTest { Assert.assertEquals(1, ldap.getPriority()); Assert.assertEquals("ldap://foo", ldap.getConfig().get("important.config")); + // Test federation mappers + Set fedMappers = realm.getUserFederationMappers(); + Assert.assertTrue(fedMappers.size() == 1); + UserFederationMapperModel fullNameMapper = fedMappers.iterator().next(); + Assert.assertEquals("FullNameMapper", fullNameMapper.getName()); + Assert.assertEquals(FullNameLDAPFederationMapperFactory.ID, fullNameMapper.getFederationMapperType()); + Assert.assertEquals(ldap.getId(), fullNameMapper.getFederationProviderId()); + Assert.assertEquals("cn", fullNameMapper.getConfig().get(FullNameLDAPFederationMapper.LDAP_FULL_NAME_ATTRIBUTE)); + // Assert that federation link wasn't created during import UserFederationProviderFactory factory = (UserFederationProviderFactory)session.getKeycloakSessionFactory().getProviderFactory(UserFederationProvider.class, "dummy"); Assert.assertNull(factory.getInstance(session, null).getUserByUsername(realm, "wburke")); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserFederationModelTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserFederationModelTest.java new file mode 100644 index 0000000000..51fc446caf --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/model/UserFederationModelTest.java @@ -0,0 +1,118 @@ +package org.keycloak.testsuite.model; + +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import org.junit.Assert; +import org.junit.Test; +import org.keycloak.models.ModelDuplicateException; +import org.keycloak.models.RealmModel; +import org.keycloak.models.UserFederationMapperModel; +import org.keycloak.models.UserFederationProviderModel; + +/** + * @author Marek Posolda + */ +public class UserFederationModelTest extends AbstractModelTest { + + + @Test + public void federationMapperCrudTest() { + RealmModel realm = realmManager.createRealm("test-realm"); + UserFederationProviderModel fedProvider = realm.addUserFederationProvider("dummy", new TreeMap(), 1, "my-cool-provider", -1, -1, 0); + UserFederationProviderModel fedProvider2 = realm.addUserFederationProvider("dummy", new TreeMap(), 1, "my-cool-provider2", -1, -1, 0); + + UserFederationMapperModel mapperModel1 = createMapper("name1", fedProvider.getId(), "key1", "value1"); + UserFederationMapperModel mapperModel2 = createMapper("name2", fedProvider.getId(), "key2", "value2"); + UserFederationMapperModel mapperModel3 = createMapper("name1", fedProvider2.getId(), "key3", "value3"); + + mapperModel1 = realm.addUserFederationMapper(mapperModel1); + mapperModel2 = realm.addUserFederationMapper(mapperModel2); + mapperModel3 = realm.addUserFederationMapper(mapperModel3); + + commit(); + + try { + UserFederationMapperModel conflictMapper = createMapper("name1", fedProvider.getId(), "key4", "value4"); + realmManager.getRealmByName("test-realm").addUserFederationMapper(conflictMapper); + commit(); + Assert.fail("Don't expect to end here"); + } catch (ModelDuplicateException expected) { + } + + realm = realmManager.getRealmByName("test-realm"); + Set mappers = realm.getUserFederationMappers(); + Assert.assertEquals(3, mappers.size()); + Assert.assertTrue(mappers.contains(mapperModel1)); + Assert.assertTrue(mappers.contains(mapperModel2)); + Assert.assertTrue(mappers.contains(mapperModel3)); + + mappers = realm.getUserFederationMappersByFederationProvider(fedProvider.getId()); + Assert.assertEquals(2, mappers.size()); + Assert.assertTrue(mappers.contains(mapperModel1)); + Assert.assertTrue(mappers.contains(mapperModel2)); + + mapperModel3.getConfig().put("otherKey", "otherValue"); + realm.updateUserFederationMapper(mapperModel3); + + commit(); + + realm = realmManager.getRealmByName("test-realm"); + mapperModel3 = realm.getUserFederationMapperById(mapperModel3.getId()); + Assert.assertEquals(2, mapperModel3.getConfig().size()); + Assert.assertEquals("value3", mapperModel3.getConfig().get("key3")); + Assert.assertEquals("otherValue", mapperModel3.getConfig().get("otherKey")); + } + + + @Test + public void federationProviderRemovalTest() { + RealmModel realm = realmManager.createRealm("test-realm"); + UserFederationProviderModel fedProvider = realm.addUserFederationProvider("dummy", new TreeMap(), 1, "my-cool-provider", -1, -1, 0); + UserFederationProviderModel fedProvider2 = realm.addUserFederationProvider("dummy", new TreeMap(), 1, "my-cool-provider2", -1, -1, 0); + + UserFederationMapperModel mapperModel1 = createMapper("name1", fedProvider.getId(), "key1", "value1"); + UserFederationMapperModel mapperModel2 = createMapper("name2", fedProvider.getId(), "key2", "value2"); + UserFederationMapperModel mapperModel3 = createMapper("name1", fedProvider2.getId(), "key3", "value3"); + + mapperModel1 = realm.addUserFederationMapper(mapperModel1); + mapperModel2 = realm.addUserFederationMapper(mapperModel2); + mapperModel3 = realm.addUserFederationMapper(mapperModel3); + + commit(); + + realmManager.getRealmByName("test-realm").removeUserFederationProvider(fedProvider); + + commit(); + + realm = realmManager.getRealmByName("test-realm"); + Set mappers = realm.getUserFederationMappers(); + Assert.assertEquals(1, mappers.size()); + Assert.assertEquals(mapperModel3, mappers.iterator().next()); + + realm = realmManager.getRealmByName("test-realm"); + realmManager.removeRealm(realm); + + commit(); + } + + private UserFederationMapperModel createMapper(String name, String fedProviderId, String... config) { + UserFederationMapperModel mapperModel = new UserFederationMapperModel(); + mapperModel.setName(name); + mapperModel.setFederationMapperType("someType"); + mapperModel.setFederationProviderId(fedProviderId); + Map configMap = new TreeMap(); + String key = null; + for (String configEntry : config) { + if (key == null) { + key = configEntry; + } else { + configMap.put(key, configEntry); + key = null; + } + } + mapperModel.setConfig(configMap); + return mapperModel; + } +} diff --git a/testsuite/integration/src/test/resources/model/testrealm.json b/testsuite/integration/src/test/resources/model/testrealm.json index 50aee59e6a..43458c93c5 100755 --- a/testsuite/integration/src/test/resources/model/testrealm.json +++ b/testsuite/integration/src/test/resources/model/testrealm.json @@ -33,6 +33,16 @@ } } ], + "userFederationMappers": [ + { + "name": "FullNameMapper", + "federationProviderDisplayName": "MyLDAPProvider", + "federationMapperType": "full-name-ldap-mapper", + "config": { + "ldap.full.name.attribute": "cn" + } + } + ], "users": [ { "username": "wburke",