From 8a5f817030f7e9aa116e301da51710e7fd2c5c04 Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 10 Nov 2016 16:52:18 -0500 Subject: [PATCH] ldap jpa migration --- dependencies/server-all/pom.xml | 4 + .../keycloak-ldap-federation/main/module.xml | 2 +- .../models/cache/infinispan/RealmAdapter.java | 7 + .../custom/AbstractUserFedToComponent.java | 182 +++++ .../PortLdapUserFedToComponentModel.java | 46 ++ .../org/keycloak/models/jpa/RealmAdapter.java | 10 +- .../META-INF/db2-jpa-changelog-master.xml | 1 + .../META-INF/jpa-changelog-2.4.0.xml | 25 + .../META-INF/jpa-changelog-master.xml | 1 + .../impl/DefaultMongoUpdaterProvider.java | 4 +- .../updater/impl/updates/Update2_4_0.java | 172 +++++ .../mongo/keycloak/adapters/RealmAdapter.java | 8 +- .../java/org/keycloak/models/RealmModel.java | 15 + .../models/utils/ModelToRepresentation.java | 30 +- .../models/utils/RepresentationToModel.java | 138 +++- .../FederationProvidersIntegrationTest.java | 18 + .../federation/storage/UserStorageTest.java | 2 +- .../storage/ldap/LDAPLegacyImportTest.java | 184 ++++++ .../resources/ldap/fed-provider-export.json | 622 ++++++++++++++++++ 19 files changed, 1419 insertions(+), 52 deletions(-) create mode 100644 model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/AbstractUserFedToComponent.java create mode 100644 model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/PortLdapUserFedToComponentModel.java create mode 100755 model/jpa/src/main/resources/META-INF/jpa-changelog-2.4.0.xml create mode 100644 model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update2_4_0.java create mode 100755 testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPLegacyImportTest.java create mode 100644 testsuite/integration/src/test/resources/ldap/fed-provider-export.json diff --git a/dependencies/server-all/pom.xml b/dependencies/server-all/pom.xml index 526a0cf057..3dcb6e5dd3 100755 --- a/dependencies/server-all/pom.xml +++ b/dependencies/server-all/pom.xml @@ -67,6 +67,10 @@ org.keycloak keycloak-ldap-federation + + org.keycloak + keycloak-ldap-storage + org.keycloak keycloak-kerberos-federation diff --git a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-ldap-federation/main/module.xml b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-ldap-federation/main/module.xml index 43a1d64c09..58977daa1e 100755 --- a/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-ldap-federation/main/module.xml +++ b/distribution/feature-packs/server-feature-pack/src/main/resources/modules/system/layers/keycloak/org/keycloak/keycloak-ldap-federation/main/module.xml @@ -21,7 +21,7 @@ - + diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java index 8252f6b572..069b34aec5 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/RealmAdapter.java @@ -1341,6 +1341,13 @@ public class RealmAdapter implements CachedRealmModel { return updated.addComponentModel(model); } + @Override + public ComponentModel importComponentModel(ComponentModel model) { + getDelegateForUpdate(); + evictUsers(model); + return updated.importComponentModel(model); + } + public void evictUsers(ComponentModel model) { String parentId = model.getParentId(); evictUsers(parentId); diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/AbstractUserFedToComponent.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/AbstractUserFedToComponent.java new file mode 100644 index 0000000000..e86788b75f --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/AbstractUserFedToComponent.java @@ -0,0 +1,182 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.keycloak.connections.jpa.updater.liquibase.custom; + +import liquibase.exception.CustomChangeException; +import liquibase.statement.core.DeleteStatement; +import liquibase.statement.core.InsertStatement; +import liquibase.structure.core.Table; +import org.jboss.logging.Logger; +import org.keycloak.models.LDAPConstants; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.storage.UserStorageProvider; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; + +/** + * @author Bill Burke + * @version $Revision: 1 $ + */ +public abstract class AbstractUserFedToComponent extends CustomKeycloakTask { + private final Logger logger = Logger.getLogger(getClass()); + protected void convertFedProviderToComponent(String providerId, String newMapperType) throws CustomChangeException { + try { + PreparedStatement statement = jdbcConnection.prepareStatement("select ID, REALM_ID, PRIORITY, DISPLAY_NAME, FULL_SYNC_PERIOD, CHANGED_SYNC_PERIOD, LAST_SYNC from " + getTableName("USER_FEDERATION_PROVIDER") + " WHERE PROVIDER_NAME='" + providerId + "'"); + + try { + ResultSet resultSet = statement.executeQuery(); + try { + while (resultSet.next()) { + int index = 1; + String id = resultSet.getString(index++); + String realmId = resultSet.getString(index++); + int priority = resultSet.getInt(index++); + String displayName = resultSet.getString(index++); + int fullSyncPeriod = resultSet.getInt(index++); + int changedSyncPeriod = resultSet.getInt(index++); + int lastSync = resultSet.getInt(index++); + + + InsertStatement insertComponent = new InsertStatement(null, null, database.correctObjectName("COMPONENT", Table.class)) + .addColumnValue("ID", id) + .addColumnValue("REALM_ID", realmId) + .addColumnValue("PARENT_ID", realmId) + .addColumnValue("NAME", displayName) + .addColumnValue("PROVIDER_ID", LDAPConstants.LDAP_PROVIDER) + .addColumnValue("PROVIDER_TYPE", UserStorageProvider.class.getName()); + + statements.add(insertComponent); + + statements.add(componentConfigStatement(id, "priority", Integer.toString(priority))); + statements.add(componentConfigStatement(id, "fullSyncPeriod", Integer.toString(fullSyncPeriod))); + statements.add(componentConfigStatement(id, "changedSyncPeriod", Integer.toString(changedSyncPeriod))); + statements.add(componentConfigStatement(id, "lastSync", Integer.toString(lastSync))); + PreparedStatement configStatement = jdbcConnection.prepareStatement("select name, VALUE from " + getTableName("USER_FEDERATION_CONFIG") + " WHERE USER_FEDERATION_PROVIDER_ID=?"); + configStatement.setString(1, id); + try { + ResultSet configSet = configStatement.executeQuery(); + try { + while (configSet.next()) { + String name = configSet.getString(1); + String value = configSet.getString(2); + //logger.info("adding component config: " + name + ": " + value); + statements.add(componentConfigStatement(id, name, value)); + } + } finally { + configSet.close(); + } + } finally { + configStatement.close(); + } + + if (newMapperType != null) { + convertFedMapperToComponent(realmId, id, newMapperType); + } + + DeleteStatement configDelete = new DeleteStatement(null, null, database.correctObjectName("USER_FEDERATION_CONFIG", Table.class)); + configDelete.setWhere("USER_FEDERATION_PROVIDER_ID='" + id + "'"); + statements.add(configDelete); + DeleteStatement deleteStatement = new DeleteStatement(null, null, database.correctObjectName("USER_FEDERATION_PROVIDER", Table.class)); + deleteStatement.setWhere("ID='" + id + "'"); + statements.add(deleteStatement); + + } + } finally { + resultSet.close(); + } + } finally { + statement.close(); + } + + confirmationMessage.append("Updated " + statements.size() + " records in USER_FEDERATION_PROVIDER table for " + providerId + " conversion to component model"); + } catch (Exception e) { + throw new CustomChangeException(getTaskId() + ": Exception when updating data from previous version", e); + } + } + + protected InsertStatement componentConfigStatement(String componentId, String name, String value) { + return new InsertStatement(null, null, database.correctObjectName("COMPONENT_CONFIG", Table.class)) + .addColumnValue("ID", KeycloakModelUtils.generateId()) + .addColumnValue("COMPONENT_ID", componentId) + .addColumnValue("NAME", name) + .addColumnValue("VALUE", value); + } + + protected void convertFedMapperToComponent(String realmId, String parentId, String newMapperType) throws CustomChangeException { + try { + PreparedStatement statement = jdbcConnection.prepareStatement("select ID, NAME, FEDERATION_MAPPER_TYPE from " + getTableName("USER_FEDERATION_MAPPER") + " WHERE FEDERATION_PROVIDER_ID='" + parentId + "'"); + + try { + ResultSet resultSet = statement.executeQuery(); + try { + while (resultSet.next()) { + String id = resultSet.getString(1); + String mapperName = resultSet.getString(2); + String fedMapperType = resultSet.getString(3); + + InsertStatement insertComponent = new InsertStatement(null, null, database.correctObjectName("COMPONENT", Table.class)) + .addColumnValue("ID", id) + .addColumnValue("REALM_ID", realmId) + .addColumnValue("PARENT_ID", parentId) + .addColumnValue("NAME", mapperName) + .addColumnValue("PROVIDER_ID", fedMapperType) + .addColumnValue("PROVIDER_TYPE", newMapperType); + + statements.add(insertComponent); + + + + PreparedStatement configStatement = jdbcConnection.prepareStatement("select name, VALUE from " + getTableName("USER_FEDERATION_MAPPER_CONFIG") + " WHERE USER_FEDERATION_MAPPER_ID=?"); + configStatement.setString(1, id); + try { + ResultSet configSet = configStatement.executeQuery(); + try { + while (configSet.next()) { + String name = configSet.getString(1); + String value = configSet.getString(2); + statements.add(componentConfigStatement(id, name, value)); + } + } finally { + configSet.close(); + } + } finally { + configStatement.close(); + } + DeleteStatement configDelete = new DeleteStatement(null, null, database.correctObjectName("USER_FEDERATION_MAPPER_CONFIG", Table.class)); + configDelete.setWhere("USER_FEDERATION_MAPPER_ID='" + id + "'"); + statements.add(configDelete); + DeleteStatement deleteStatement = new DeleteStatement(null, null, database.correctObjectName("USER_FEDERATION_MAPPER", Table.class)); + deleteStatement.setWhere("ID='" + id + "'"); + statements.add(deleteStatement); + + + } + } finally { + resultSet.close(); + } + } finally { + statement.close(); + } + + confirmationMessage.append("Updated " + statements.size() + " records in USER_FEDERATION_MAPPER table for " + parentId + " conversion to component model"); + } catch (Exception e) { + throw new CustomChangeException(getTaskId() + ": Exception when updating data from previous version", e); + } + } + +} diff --git a/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/PortLdapUserFedToComponentModel.java b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/PortLdapUserFedToComponentModel.java new file mode 100644 index 0000000000..cf224b2eb1 --- /dev/null +++ b/model/jpa/src/main/java/org/keycloak/connections/jpa/updater/liquibase/custom/PortLdapUserFedToComponentModel.java @@ -0,0 +1,46 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.connections.jpa.updater.liquibase.custom; + +import liquibase.exception.CustomChangeException; +import liquibase.statement.core.InsertStatement; +import liquibase.structure.core.Table; +import org.keycloak.keys.KeyProvider; +import org.keycloak.models.LDAPConstants; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.storage.UserStorageProvider; + +import java.sql.PreparedStatement; +import java.sql.ResultSet; + +/** + * @author Marek Posolda + */ +public class PortLdapUserFedToComponentModel extends AbstractUserFedToComponent { + + @Override + protected void generateStatementsImpl() throws CustomChangeException { + String providerId = LDAPConstants.LDAP_PROVIDER; + convertFedProviderToComponent(LDAPConstants.LDAP_PROVIDER, "org.keycloak.storage.ldap.mappers.LDAPStorageMapper"); + } + + @Override + protected String getTaskId() { + return "Update 2.4.0.Final"; + } +} 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 6de22a9200..97aa4bd55d 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 @@ -2031,6 +2031,14 @@ public class RealmAdapter implements RealmModel, JpaModel { @Override public ComponentModel addComponentModel(ComponentModel model) { + model = importComponentModel(model); + ComponentUtil.notifyCreated(session, this, model); + + return model; + } + + @Override + public ComponentModel importComponentModel(ComponentModel model) { ComponentFactory componentFactory = ComponentUtil.getComponentFactory(session, model); if (componentFactory == null) { throw new IllegalArgumentException("Invalid component type"); @@ -2057,8 +2065,6 @@ public class RealmAdapter implements RealmModel, JpaModel { em.persist(c); setConfig(model, c); model.setId(c.getId()); - ComponentUtil.notifyCreated(session, this, model); - return model; } diff --git a/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-master.xml index cc4c9ff016..588caf7898 100644 --- a/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-master.xml +++ b/model/jpa/src/main/resources/META-INF/db2-jpa-changelog-master.xml @@ -37,4 +37,5 @@ + diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-2.4.0.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.4.0.xml new file mode 100755 index 0000000000..0aebd9a0d2 --- /dev/null +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-2.4.0.xml @@ -0,0 +1,25 @@ + + + + + + + + + + \ No newline at end of file diff --git a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml index 8990fc438c..194b2d51ca 100755 --- a/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml +++ b/model/jpa/src/main/resources/META-INF/jpa-changelog-master.xml @@ -37,4 +37,5 @@ + diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java index 3e429e2cf5..c7f5be8d52 100755 --- a/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java +++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/DefaultMongoUpdaterProvider.java @@ -34,6 +34,7 @@ import org.keycloak.connections.mongo.updater.impl.updates.Update1_7_0; import org.keycloak.connections.mongo.updater.impl.updates.Update1_8_0; import org.keycloak.connections.mongo.updater.impl.updates.Update1_9_2; import org.keycloak.connections.mongo.updater.impl.updates.Update2_3_0; +import org.keycloak.connections.mongo.updater.impl.updates.Update2_4_0; import org.keycloak.models.KeycloakSession; import java.util.Date; @@ -59,7 +60,8 @@ public class DefaultMongoUpdaterProvider implements MongoUpdaterProvider { Update1_7_0.class, Update1_8_0.class, Update1_9_2.class, - Update2_3_0.class + Update2_3_0.class, + Update2_4_0.class }; @Override diff --git a/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update2_4_0.java b/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update2_4_0.java new file mode 100644 index 0000000000..f92eaaa3da --- /dev/null +++ b/model/mongo/src/main/java/org/keycloak/connections/mongo/updater/impl/updates/Update2_4_0.java @@ -0,0 +1,172 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.connections.mongo.updater.impl.updates; + +import com.mongodb.BasicDBList; +import com.mongodb.BasicDBObject; +import com.mongodb.DBCollection; +import com.mongodb.DBCursor; +import org.keycloak.keys.KeyProvider; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.LDAPConstants; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.storage.UserStorageProvider; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; +import java.util.Set; + +/** + * @author Marek Posolda + */ +public class Update2_4_0 extends Update { + + @Override + public String getId() { + return "2.4.0"; + } + + @Override + public void update(KeycloakSession session) { + portUserFedToComponent(LDAPConstants.LDAP_PROVIDER); + portUserFedMappersToComponent(LDAPConstants.LDAP_PROVIDER, "org.keycloak.storage.ldap.mappers.LDAPStorageMapper"); + } + + public void portUserFedToComponent(String providerId) { + DBCollection realms = db.getCollection("realms"); + DBCursor cursor = realms.find(); + while (cursor.hasNext()) { + BasicDBObject realm = (BasicDBObject) cursor.next(); + + String realmId = realm.getString("_id"); + Set removedProviders = new HashSet<>(); + + BasicDBList componentEntities = (BasicDBList) realm.get("componentEntities"); + BasicDBList federationProviders = (BasicDBList) realm.get("userFederationProviders"); + for (Object obj : federationProviders) { + BasicDBObject fedProvider = (BasicDBObject)obj; + if (fedProvider.getString("providerName").equals(providerId)) { + String id = fedProvider.getString("id"); + int priority = fedProvider.getInt("priority"); + String displayName = fedProvider.getString("displayName"); + int fullSyncPeriod = fedProvider.getInt("fullSyncPeriod"); + int changedSyncPeriod = fedProvider.getInt("changedSyncPeriod"); + int lastSync = fedProvider.getInt("lastSync"); + BasicDBObject component = new BasicDBObject(); + component.put("id", id); + component.put("name", displayName); + component.put("providerType", UserStorageProvider.class.getName()); + component.put("providerId", providerId); + component.put("parentId", realmId); + + BasicDBObject config = new BasicDBObject(); + config.put("priority", Collections.singletonList(Integer.toString(priority))); + config.put("fullSyncPeriod", Collections.singletonList(Integer.toString(fullSyncPeriod))); + config.put("changedSyncPeriod", Collections.singletonList(Integer.toString(changedSyncPeriod))); + config.put("lastSync", Collections.singletonList(Integer.toString(lastSync))); + + BasicDBObject fedConfig = (BasicDBObject)fedProvider.get("config"); + if (fedConfig != null) { + for (Map.Entry attr : new HashSet<>(fedConfig.entrySet())) { + String attrName = attr.getKey(); + String attrValue = attr.getValue().toString(); + config.put(attrName, Collections.singletonList(attrValue)); + + } + } + + + component.put("config", config); + + componentEntities.add(component); + + } + } + Iterator it = federationProviders.iterator(); + while (it.hasNext()) { + BasicDBObject fedProvider = (BasicDBObject)it.next(); + String id = fedProvider.getString("id"); + if (removedProviders.contains(id)) { + it.remove(); + } + + } + realms.update(new BasicDBObject().append("_id", realmId), realm); + } + } + public void portUserFedMappersToComponent(String providerId, String mapperType) { + DBCollection realms = db.getCollection("realms"); + DBCursor cursor = realms.find(); + while (cursor.hasNext()) { + BasicDBObject realm = (BasicDBObject) cursor.next(); + + String realmId = realm.getString("_id"); + Set removedProviders = new HashSet<>(); + + BasicDBList componentEntities = (BasicDBList) realm.get("componentEntities"); + BasicDBList federationProviders = (BasicDBList) realm.get("userFederationProviders"); + BasicDBList fedMappers = (BasicDBList) realm.get("userFederationMappers"); + for (Object obj : federationProviders) { + BasicDBObject fedProvider = (BasicDBObject)obj; + if (fedProvider.getString("providerName").equals(providerId)) { + String id = fedProvider.getString("id"); + for (Object obj2 : fedMappers) { + BasicDBObject fedMapper = (BasicDBObject)obj2; + if (fedMapper.getString("federationProviderId").equals(id)) { + String name = fedMapper.getString("name"); + String mapperId = fedMapper.getString("id"); + removedProviders.add(mapperId); + String mapperProviderId = fedMapper.getString("federationMapperType"); + BasicDBObject component = new BasicDBObject(); + component.put("id", mapperId); + component.put("name", name); + component.put("providerType", mapperType); + component.put("providerId", mapperProviderId); + component.put("parentId", providerId); + + BasicDBObject fedConfig = (BasicDBObject)fedMapper.get("config"); + BasicDBObject config = new BasicDBObject(); + if (fedConfig != null) { + for (Map.Entry attr : new HashSet<>(fedConfig.entrySet())) { + String attrName = attr.getKey(); + String attrValue = attr.getValue().toString(); + config.put(attrName, Collections.singletonList(attrValue)); + + } + } + component.put("config", config); + componentEntities.add(component); + } + } + } + } + Iterator it = fedMappers.iterator(); + while (it.hasNext()) { + BasicDBObject fedMapper = (BasicDBObject)it.next(); + String id = fedMapper.getString("id"); + if (removedProviders.contains(id)) { + it.remove(); + } + + } + realms.update(new BasicDBObject().append("_id", realmId), realm); + } + } +} 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 205f6b3091..a79c478bc7 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 @@ -1954,6 +1954,13 @@ public class RealmAdapter extends AbstractMongoAdapter impleme @Override public ComponentModel addComponentModel(ComponentModel model) { + model = importComponentModel(model); + ComponentUtil.notifyCreated(session, this, model); + return model; + } + + @Override + public ComponentModel importComponentModel(ComponentModel model) { ComponentUtil.getComponentFactory(session, model).validateConfiguration(session, this, model); ComponentEntity entity = new ComponentEntity(); @@ -1970,7 +1977,6 @@ public class RealmAdapter extends AbstractMongoAdapter impleme } realm.getComponentEntities().add(entity); updateRealm(); - ComponentUtil.notifyCreated(session, this, model); return model; } diff --git a/server-spi/src/main/java/org/keycloak/models/RealmModel.java b/server-spi/src/main/java/org/keycloak/models/RealmModel.java index aa8b440021..e9df047418 100755 --- a/server-spi/src/main/java/org/keycloak/models/RealmModel.java +++ b/server-spi/src/main/java/org/keycloak/models/RealmModel.java @@ -274,7 +274,22 @@ public interface RealmModel extends RoleContainerModel { public IdentityProviderMapperModel getIdentityProviderMapperByName(String brokerAlias, String name); + /** + * Adds component model. Will call onCreate() method of ComponentFactory + * + * @param model + * @return + */ ComponentModel addComponentModel(ComponentModel model); + + /** + * Adds component model. Will NOT call onCreate() method of ComponentFactory + * + * @param model + * @return + */ + ComponentModel importComponentModel(ComponentModel model); + void updateComponent(ComponentModel component); void removeComponent(ComponentModel component); void removeComponents(String parentId); diff --git a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java index e35da61510..bf4a6dc29e 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java @@ -352,19 +352,7 @@ public class ModelToRepresentation { } } - List fedProviderModels = realm.getUserFederationProviders(); - if (fedProviderModels.size() > 0) { - List fedProviderReps = new ArrayList(); - for (UserFederationProviderModel model : fedProviderModels) { - UserFederationProviderRepresentation fedProvRep = toRepresentation(model); - fedProviderReps.add(fedProvRep); - } - rep.setUserFederationProviders(fedProviderReps); - } - - for (UserFederationMapperModel mapper : realm.getUserFederationMappers()) { - rep.addUserFederationMapper(toRepresentation(realm, mapper)); - } + exportUserFederationProvidersAndMappers(realm, rep); for (IdentityProviderModel provider : realm.getIdentityProviders()) { rep.addIdentityProvider(toRepresentation(realm, provider)); @@ -396,6 +384,22 @@ public class ModelToRepresentation { return rep; } + public static void exportUserFederationProvidersAndMappers(RealmModel realm, RealmRepresentation rep) { + List fedProviderModels = realm.getUserFederationProviders(); + if (fedProviderModels.size() > 0) { + List fedProviderReps = new ArrayList(); + for (UserFederationProviderModel model : fedProviderModels) { + UserFederationProviderRepresentation fedProvRep = toRepresentation(model); + fedProviderReps.add(fedProvRep); + } + rep.setUserFederationProviders(fedProviderReps); + } + + for (UserFederationMapperModel mapper : realm.getUserFederationMappers()) { + rep.addUserFederationMapper(toRepresentation(realm, mapper)); + } + } + public static void exportGroups(RealmModel realm, RealmRepresentation rep) { List groups = toGroupHierarchy(realm, true); rep.setGroups(groups); diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java index c2e654efac..8be4a9bcd6 100755 --- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java +++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java @@ -54,6 +54,7 @@ import org.keycloak.models.GroupModel; import org.keycloak.models.IdentityProviderMapperModel; import org.keycloak.models.IdentityProviderModel; import org.keycloak.models.KeycloakSession; +import org.keycloak.models.LDAPConstants; import org.keycloak.models.ModelException; import org.keycloak.models.OTPPolicy; import org.keycloak.models.PasswordPolicy; @@ -101,6 +102,8 @@ import org.keycloak.representations.idm.authorization.ResourceOwnerRepresentatio import org.keycloak.representations.idm.authorization.ResourceRepresentation; import org.keycloak.representations.idm.authorization.ResourceServerRepresentation; import org.keycloak.representations.idm.authorization.ScopeRepresentation; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.UserStorageProviderModel; import org.keycloak.storage.federated.UserFederatedStorageProvider; import org.keycloak.util.JsonSerialization; @@ -305,36 +308,8 @@ public class RepresentationToModel { String parentId = newRealm.getId(); importComponents(newRealm, components, parentId); } + importUserFederationProvidersAndMappers(rep, newRealm); - List providerModels = null; - if (rep.getUserFederationProviders() != null) { - providerModels = convertFederationProviders(rep.getUserFederationProviders()); - newRealm.setUserFederationProviders(providerModels); - } - if (rep.getUserFederationMappers() != null) { - - // Remove builtin mappers for federation providers, which have some mappers already provided in JSON (likely due to previous export) - if (rep.getUserFederationProviders() != null) { - Set providerNames = new TreeSet(); - for (UserFederationMapperRepresentation representation : rep.getUserFederationMappers()) { - providerNames.add(representation.getFederationProviderDisplayName()); - } - for (String providerName : providerNames) { - for (UserFederationProviderModel providerModel : providerModels) { - if (providerName.equals(providerModel.getDisplayName())) { - Set toDelete = newRealm.getUserFederationMappersByFederationProvider(providerModel.getId()); - for (UserFederationMapperModel mapperModel : toDelete) { - newRealm.removeUserFederationMapper(mapperModel); - } - } - } - } - } - - for (UserFederationMapperRepresentation representation : rep.getUserFederationMappers()) { - newRealm.addUserFederationMapper(toModel(newRealm, representation)); - } - } if (rep.getGroups() != null) { importGroups(newRealm, rep); @@ -390,6 +365,64 @@ public class RepresentationToModel { } } + public static void importUserFederationProvidersAndMappers(RealmRepresentation rep, RealmModel newRealm) { + // providers to convert to component model + Set convertSet = new HashSet<>(); + convertSet.add(LDAPConstants.LDAP_PROVIDER); + Map mapperConvertSet = new HashMap<>(); + mapperConvertSet.put(LDAPConstants.LDAP_PROVIDER, "org.keycloak.storage.ldap.mappers.LDAPStorageMapper"); + + + List providerModels = null; + Map userStorageModels = new HashMap<>(); + + if (rep.getUserFederationProviders() != null) { + providerModels = new LinkedList<>(); + for (UserFederationProviderRepresentation fedRep : rep.getUserFederationProviders()) { + if (convertSet.contains(fedRep.getProviderName())) { + ComponentModel component = convertFedProviderToComponent(newRealm.getId(), fedRep); + userStorageModels.put(fedRep.getDisplayName(), newRealm.importComponentModel(component)); + } else { + providerModels.add(convertFederationProvider(fedRep)); + } + + } + newRealm.setUserFederationProviders(providerModels); + } + if (rep.getUserFederationMappers() != null) { + + // Remove builtin mappers for federation providers, which have some mappers already provided in JSON (likely due to previous export) + if (rep.getUserFederationProviders() != null) { + Set providerNames = new TreeSet(); + for (UserFederationMapperRepresentation representation : rep.getUserFederationMappers()) { + providerNames.add(representation.getFederationProviderDisplayName()); + } + for (String providerName : providerNames) { + for (UserFederationProviderModel providerModel : providerModels) { + if (providerName.equals(providerModel.getDisplayName())) { + Set toDelete = newRealm.getUserFederationMappersByFederationProvider(providerModel.getId()); + for (UserFederationMapperModel mapperModel : toDelete) { + newRealm.removeUserFederationMapper(mapperModel); + } + } + } + } + } + + for (UserFederationMapperRepresentation representation : rep.getUserFederationMappers()) { + if (userStorageModels.containsKey(representation.getFederationProviderDisplayName())) { + ComponentModel parent = userStorageModels.get(representation.getFederationProviderDisplayName()); + String newMapperType = mapperConvertSet.get(parent.getProviderId()); + ComponentModel mapper = convertFedMapperToComponent(newRealm, parent, representation, newMapperType); + newRealm.importComponentModel(mapper); + + } else { + newRealm.addUserFederationMapper(toModel(newRealm, representation)); + } + } + } + } + protected static void importComponents(RealmModel newRealm, MultivaluedHashMap components, String parentId) { for (Map.Entry> entry : components.entrySet()) { String providerType = entry.getKey(); @@ -402,7 +435,7 @@ public class RepresentationToModel { component.setProviderId(compRep.getProviderId()); component.setSubType(compRep.getSubType()); component.setParentId(parentId); - component = newRealm.addComponentModel(component); + component = newRealm.importComponentModel(component); if (compRep.getSubComponents() != null) { importComponents(newRealm, compRep.getSubComponents(), component.getId()); } @@ -865,14 +898,53 @@ public class RepresentationToModel { List result = new ArrayList(); for (UserFederationProviderRepresentation representation : providers) { - UserFederationProviderModel model = new UserFederationProviderModel(representation.getId(), representation.getProviderName(), - representation.getConfig(), representation.getPriority(), representation.getDisplayName(), - representation.getFullSyncPeriod(), representation.getChangedSyncPeriod(), representation.getLastSync()); + UserFederationProviderModel model = convertFederationProvider(representation); result.add(model); } return result; } + private static UserFederationProviderModel convertFederationProvider(UserFederationProviderRepresentation representation) { + return new UserFederationProviderModel(representation.getId(), representation.getProviderName(), + representation.getConfig(), representation.getPriority(), representation.getDisplayName(), + representation.getFullSyncPeriod(), representation.getChangedSyncPeriod(), representation.getLastSync()); + } + + public static ComponentModel convertFedProviderToComponent(String realmId, UserFederationProviderRepresentation fedModel) { + UserStorageProviderModel model = new UserStorageProviderModel(); + model.setId(fedModel.getId()); + model.setName(fedModel.getDisplayName()); + model.setParentId(realmId); + model.setProviderId(fedModel.getProviderName()); + model.setProviderType(UserStorageProvider.class.getName()); + model.setFullSyncPeriod(fedModel.getFullSyncPeriod()); + model.setPriority(fedModel.getPriority()); + model.setChangedSyncPeriod(fedModel.getChangedSyncPeriod()); + model.setLastSync(fedModel.getLastSync()); + if (fedModel.getConfig() != null) { + for (Map.Entry entry : fedModel.getConfig().entrySet()) { + model.getConfig().putSingle(entry.getKey(), entry.getValue()); + } + } + return model; + } + + public static ComponentModel convertFedMapperToComponent(RealmModel realm, ComponentModel parent, UserFederationMapperRepresentation rep, String newMapperType) { + ComponentModel mapper = new ComponentModel(); + mapper.setId(rep.getId()); + mapper.setName(rep.getName()); + mapper.setProviderId(rep.getFederationMapperType()); + mapper.setProviderType(newMapperType); + mapper.setParentId(parent.getId()); + if (rep.getConfig() != null) { + for (Map.Entry entry : rep.getConfig().entrySet()) { + mapper.getConfig().putSingle(entry.getKey(), entry.getValue()); + } + } + return mapper; + } + + public static UserFederationMapperModel toModel(RealmModel realm, UserFederationMapperRepresentation rep) { UserFederationMapperModel model = new UserFederationMapperModel(); model.setId(rep.getId()); diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/FederationProvidersIntegrationTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/FederationProvidersIntegrationTest.java index 2809b97bc5..11ed2f6343 100755 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/FederationProvidersIntegrationTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/ldap/base/FederationProvidersIntegrationTest.java @@ -48,7 +48,9 @@ import org.keycloak.models.UserFederationProvider; import org.keycloak.models.UserFederationProviderModel; import org.keycloak.models.UserModel; import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.models.utils.ModelToRepresentation; import org.keycloak.representations.AccessToken; +import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.services.managers.RealmManager; import org.keycloak.testsuite.OAuthClient; import org.keycloak.testsuite.federation.ldap.FederationTestUtils; @@ -61,8 +63,10 @@ import org.keycloak.testsuite.rule.KeycloakRule; import org.keycloak.testsuite.rule.LDAPRule; import org.keycloak.testsuite.rule.WebResource; import org.keycloak.testsuite.rule.WebRule; +import org.keycloak.util.JsonSerialization; import org.openqa.selenium.WebDriver; +import java.io.FileOutputStream; import java.util.List; import java.util.Map; @@ -104,6 +108,20 @@ public class FederationProvidersIntegrationTest { } }); + /* + @Test + public void exportJson() throws Exception { + KeycloakSession session = keycloakRule.startSession(); + FileOutputStream os = new FileOutputStream("/Users/williamburke/jboss/keycloak/p1b-repo/keycloak/fed-provider.json"); + RealmManager manager = new RealmManager(session); + RealmModel appRealm = manager.getRealm("test"); + RealmRepresentation rep = ModelToRepresentation.toRepresentation(appRealm, true); + JsonSerialization.writeValueToStream(os, rep); + os.close(); + + } + */ + @ClassRule public static TestRule chain = RuleChain .outerRule(ldapRule) diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageTest.java index 6e79d69aa1..a2b2636c1c 100644 --- a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageTest.java +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/UserStorageTest.java @@ -132,7 +132,7 @@ public class UserStorageTest { } - @Test + //@Test public void testIDE() throws Exception { Thread.sleep(100000000); } diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPLegacyImportTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPLegacyImportTest.java new file mode 100755 index 0000000000..fc34c86007 --- /dev/null +++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/federation/storage/ldap/LDAPLegacyImportTest.java @@ -0,0 +1,184 @@ +/* + * Copyright 2016 Red Hat, Inc. and/or its affiliates + * and other contributors as indicated by the @author tags. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.keycloak.testsuite.federation.storage.ldap; + +import org.junit.Assert; +import org.junit.ClassRule; +import org.junit.FixMethodOrder; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.RuleChain; +import org.junit.rules.TestRule; +import org.junit.runners.MethodSorters; +import org.keycloak.OAuth2Constants; +import org.keycloak.common.util.MultivaluedHashMap; +import org.keycloak.component.ComponentModel; +import org.keycloak.credential.CredentialModel; +import org.keycloak.models.KeycloakSession; +import org.keycloak.models.LDAPConstants; +import org.keycloak.models.ModelException; +import org.keycloak.models.ModelReadOnlyException; +import org.keycloak.models.RealmModel; +import org.keycloak.models.RoleModel; +import org.keycloak.models.UserCredentialModel; +import org.keycloak.models.UserModel; +import org.keycloak.models.utils.KeycloakModelUtils; +import org.keycloak.models.utils.RepresentationToModel; +import org.keycloak.representations.AccessToken; +import org.keycloak.representations.idm.RealmRepresentation; +import org.keycloak.services.managers.RealmManager; +import org.keycloak.storage.UserStorageProvider; +import org.keycloak.storage.UserStorageProviderModel; +import org.keycloak.storage.ldap.LDAPConfig; +import org.keycloak.storage.ldap.LDAPStorageProvider; +import org.keycloak.storage.ldap.LDAPStorageProviderFactory; +import org.keycloak.storage.ldap.idm.model.LDAPObject; +import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapper; +import org.keycloak.storage.ldap.mappers.FullNameLDAPStorageMapperFactory; +import org.keycloak.storage.ldap.mappers.HardcodedLDAPRoleStorageMapper; +import org.keycloak.storage.ldap.mappers.HardcodedLDAPRoleStorageMapperFactory; +import org.keycloak.storage.ldap.mappers.LDAPStorageMapper; +import org.keycloak.storage.ldap.mappers.UserAttributeLDAPStorageMapper; +import org.keycloak.testsuite.OAuthClient; +import org.keycloak.testsuite.pages.AccountPasswordPage; +import org.keycloak.testsuite.pages.AccountUpdateProfilePage; +import org.keycloak.testsuite.pages.AppPage; +import org.keycloak.testsuite.pages.LoginPage; +import org.keycloak.testsuite.pages.RegisterPage; +import org.keycloak.testsuite.rule.KeycloakRule; +import org.keycloak.testsuite.rule.LDAPRule; +import org.keycloak.testsuite.rule.WebResource; +import org.keycloak.testsuite.rule.WebRule; +import org.keycloak.util.JsonSerialization; +import org.openqa.selenium.WebDriver; + +import java.io.IOException; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * Tests that legacy UserFederationProvider json export is converted to ComponentModel + * + * @author Marek Posolda + */ +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class LDAPLegacyImportTest { + + private static LDAPRule ldapRule = new LDAPRule(); + + private static ComponentModel ldapModel = null; + + + private static KeycloakRule keycloakRule = new KeycloakRule(new KeycloakRule.KeycloakSetup() { + + @Override + public void config(RealmManager manager, RealmModel adminstrationRealm, RealmModel appRealm) { + LDAPTestUtils.addLocalUser(manager.getSession(), appRealm, "marykeycloak", "mary@test.com", "password-app"); + + RealmRepresentation imported = null; + try { + imported = JsonSerialization.readValue(getClass().getResourceAsStream("/ldap/fed-provider-export.json"), RealmRepresentation.class); + } catch (IOException e) { + throw new RuntimeException(e); + } + RepresentationToModel.importUserFederationProvidersAndMappers(imported, appRealm); + ldapModel = appRealm.getComponents(appRealm.getId(), UserStorageProvider.class.getName()).get(0); + // Delete all LDAP users and add some new for testing + LDAPStorageProvider ldapFedProvider = LDAPTestUtils.getLdapProvider(session, ldapModel); + LDAPTestUtils.removeAllLDAPUsers(ldapFedProvider, appRealm); + + LDAPObject john = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "johnkeycloak", "John", "Doe", "john@email.org", null, "1234"); + LDAPTestUtils.updateLDAPPassword(ldapFedProvider, john, "Password1"); + + LDAPObject existing = LDAPTestUtils.addLDAPUser(ldapFedProvider, appRealm, "existing", "Existing", "Foo", "existing@email.org", null, "5678"); + + appRealm.getClientByClientId("test-app").setDirectAccessGrantsEnabled(true); + } + }); + + @ClassRule + public static TestRule chain = RuleChain + .outerRule(ldapRule) + .around(keycloakRule); + + @Rule + public WebRule webRule = new WebRule(this); + + @WebResource + protected OAuthClient oauth; + + @WebResource + protected WebDriver driver; + + @WebResource + protected AppPage appPage; + + @WebResource + protected RegisterPage registerPage; + + @WebResource + protected LoginPage loginPage; + + @WebResource + protected AccountUpdateProfilePage profilePage; + + @WebResource + protected AccountPasswordPage changePasswordPage; + + //@Test + public void runit() throws Exception { + Thread.sleep(10000000); + + } + + private void loginSuccessAndLogout(String username, String password) { + loginPage.open(); + loginPage.login(username, password); + Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); + Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); + oauth.openLogout(); + } + + + @Test + public void loginClassic() { + loginPage.open(); + loginPage.login("marykeycloak", "password-app"); + + Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); + Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); + + } + + @Test + public void loginLdap() { + loginPage.open(); + loginPage.login("johnkeycloak", "Password1"); + + Assert.assertEquals(AppPage.RequestType.AUTH_RESPONSE, appPage.getRequestType()); + Assert.assertNotNull(oauth.getCurrentQuery().get(OAuth2Constants.CODE)); + + profilePage.open(); + Assert.assertEquals("John", profilePage.getFirstName()); + Assert.assertEquals("Doe", profilePage.getLastName()); + Assert.assertEquals("john@email.org", profilePage.getEmail()); + } + + +} diff --git a/testsuite/integration/src/test/resources/ldap/fed-provider-export.json b/testsuite/integration/src/test/resources/ldap/fed-provider-export.json new file mode 100644 index 0000000000..4bbe374dbb --- /dev/null +++ b/testsuite/integration/src/test/resources/ldap/fed-provider-export.json @@ -0,0 +1,622 @@ +{ + "id": "test", + "realm": "test", + "notBefore": 0, + "revokeRefreshToken": false, + "accessTokenLifespan": 300, + "accessTokenLifespanForImplicitFlow": 900, + "ssoSessionIdleTimeout": 1800, + "ssoSessionMaxLifespan": 36000, + "offlineSessionIdleTimeout": 2592000, + "accessCodeLifespan": 60, + "accessCodeLifespanUserAction": 300, + "accessCodeLifespanLogin": 1800, + "enabled": true, + "sslRequired": "external", + "registrationAllowed": true, + "registrationEmailAsUsername": false, + "rememberMe": false, + "verifyEmail": false, + "resetPasswordAllowed": true, + "editUsernameAllowed": true, + "bruteForceProtected": false, + "maxFailureWaitSeconds": 900, + "minimumQuickLoginWaitSeconds": 60, + "waitIncrementSeconds": 60, + "quickLoginCheckMilliSeconds": 1000, + "maxDeltaTimeSeconds": 43200, + "failureFactor": 30, + "groups": [ + { + "id": "2aa57ddd-e48f-4a62-bb8e-53ebe2ff1057", + "name": "topGroup", + "path": "/topGroup", + "attributes": { + "topAttribute": [ + "true" + ] + }, + "realmRoles": [ + "user" + ], + "clientRoles": {}, + "subGroups": [ + { + "id": "8e91afd4-b8e4-4de4-ba37-1edc7298d518", + "name": "level2group", + "path": "/topGroup/level2group", + "attributes": { + "level2Attribute": [ + "true" + ] + }, + "realmRoles": [ + "admin" + ], + "clientRoles": { + "test-app": [ + "customer-user" + ] + }, + "subGroups": [] + } + ] + } + ], + "defaultRoles": [ + "user", + "offline_access", + "uma_authorization" + ], + "requiredCredentials": [ + "password" + ], + "passwordPolicy": "hashIterations(20000)", + "otpPolicyType": "totp", + "otpPolicyAlgorithm": "HmacSHA1", + "otpPolicyInitialCounter": 0, + "otpPolicyDigits": 6, + "otpPolicyLookAheadWindow": 1, + "otpPolicyPeriod": 30, + "browserSecurityHeaders": { + "xContentTypeOptions": "nosniff", + "xFrameOptions": "SAMEORIGIN", + "contentSecurityPolicy": "frame-src 'self'" + }, + "smtpServer": { + "host": "localhost", + "from": "auto@keycloak.org", + "port": "3025" + }, + "userFederationProviders": [ + { + "id": "1fc3afd2-4c18-48dd-9055-b4bbae9229b7", + "displayName": "test-ldap", + "providerName": "ldap", + "config": { + "serverPrincipal": "HTTP/localhost@KEYCLOAK.ORG", + "debug": "true", + "pagination": "true", + "keyTab": "/Users/williamburke/jboss/keycloak/p1b-repo/keycloak/testsuite/integration/target/test-classes/kerberos/http.keytab", + "connectionPooling": "true", + "usersDn": "ou=People,dc=keycloak,dc=org", + "useKerberosForPasswordAuthentication": "false", + "kerberosRealm": "KEYCLOAK.ORG", + "bindCredential": "secret", + "bindDn": "uid=admin,ou=system", + "allowPasswordAuthentication": "true", + "vendor": "other", + "editMode": "WRITABLE", + "allowKerberosAuthentication": "false", + "connectionUrl": "ldap://localhost:10389", + "syncRegistrations": "true", + "baseDn": "dc=keycloak,dc=org", + "batchSizeForSync": "3", + "updateProfileFirstLogin": "true" + }, + "priority": 0, + "fullSyncPeriod": -1, + "changedSyncPeriod": -1, + "lastSync": 0 + } + ], + "userFederationMappers": [ + { + "id": "b2fc2d9c-2ea8-417f-96db-2565be62a646", + "name": "last name", + "federationProviderDisplayName": "test-ldap", + "federationMapperType": "user-attribute-ldap-mapper", + "config": { + "always.read.value.from.ldap": "true", + "read.only": "false", + "ldap.attribute": "sn", + "is.mandatory.in.ldap": "true", + "user.model.attribute": "lastName" + } + }, + { + "id": "6dc25318-dc20-4927-ba19-9293ab31aa28", + "name": "zipCodeMapper", + "federationProviderDisplayName": "test-ldap", + "federationMapperType": "user-attribute-ldap-mapper", + "config": { + "always.read.value.from.ldap": "false", + "read.only": "false", + "ldap.attribute": "postalCode", + "is.mandatory.in.ldap": "false", + "user.model.attribute": "postal_code" + } + }, + { + "id": "7afa12a2-f36e-4f87-b715-e941773c8534", + "name": "username", + "federationProviderDisplayName": "test-ldap", + "federationMapperType": "user-attribute-ldap-mapper", + "config": { + "always.read.value.from.ldap": "false", + "read.only": "false", + "ldap.attribute": "uid", + "is.mandatory.in.ldap": "true", + "user.model.attribute": "username" + } + }, + { + "id": "abfe054c-6d2a-4870-a239-1a312c3e5a94", + "name": "creation date", + "federationProviderDisplayName": "test-ldap", + "federationMapperType": "user-attribute-ldap-mapper", + "config": { + "always.read.value.from.ldap": "true", + "read.only": "true", + "ldap.attribute": "createTimestamp", + "is.mandatory.in.ldap": "false", + "user.model.attribute": "createTimestamp" + } + }, + { + "id": "6aef95e5-736e-4b1e-98d0-332f61f94ff9", + "name": "first name", + "federationProviderDisplayName": "test-ldap", + "federationMapperType": "user-attribute-ldap-mapper", + "config": { + "always.read.value.from.ldap": "true", + "read.only": "false", + "ldap.attribute": "cn", + "is.mandatory.in.ldap": "true", + "user.model.attribute": "firstName" + } + }, + { + "id": "0601e4a2-fd63-4f6a-ae3b-13cc6f4f4f1c", + "name": "email", + "federationProviderDisplayName": "test-ldap", + "federationMapperType": "user-attribute-ldap-mapper", + "config": { + "always.read.value.from.ldap": "false", + "read.only": "false", + "ldap.attribute": "mail", + "is.mandatory.in.ldap": "false", + "user.model.attribute": "email" + } + }, + { + "id": "fa308910-3be9-4bd8-8256-66cf04d8fcd2", + "name": "modify date", + "federationProviderDisplayName": "test-ldap", + "federationMapperType": "user-attribute-ldap-mapper", + "config": { + "always.read.value.from.ldap": "true", + "read.only": "true", + "ldap.attribute": "modifyTimestamp", + "is.mandatory.in.ldap": "false", + "user.model.attribute": "modifyTimestamp" + } + } + ], + "eventsEnabled": false, + "eventsListeners": [ + "jboss-logging" + ], + "enabledEventTypes": [], + "adminEventsEnabled": false, + "adminEventsDetailsEnabled": false, + "internationalizationEnabled": true, + "supportedLocales": [ + "de", + "en" + ], + "defaultLocale": "en", + "authenticationFlows": [ + { + "id": "b12463a9-5d33-4f27-b010-4005db77e602", + "alias": "Handle Existing Account", + "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-confirm-link", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "idp-email-verification", + "requirement": "ALTERNATIVE", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "ALTERNATIVE", + "priority": 30, + "flowAlias": "Verify Existing Account by Re-authentication", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "c1684fc8-a99d-4e19-a795-478e4d793fb5", + "alias": "Verify Existing Account by Re-authentication", + "description": "Reauthentication of existing account", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "idp-username-password-form", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "requirement": "OPTIONAL", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "09af30d8-8c2a-45a4-a2be-b7617e9d0185", + "alias": "browser", + "description": "browser based authentication", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-cookie", + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-spnego", + "requirement": "DISABLED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "identity-provider-redirector", + "requirement": "ALTERNATIVE", + "priority": 25, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "ALTERNATIVE", + "priority": 30, + "flowAlias": "forms", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "6cdf31d0-9c91-4ea6-8e37-da6e8fa7544c", + "alias": "clients", + "description": "Base authentication for clients", + "providerId": "client-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "client-secret", + "requirement": "ALTERNATIVE", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "client-jwt", + "requirement": "ALTERNATIVE", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "c9a38de8-4c0c-496a-9936-b9753f73bfcc", + "alias": "direct grant", + "description": "OpenID Connect Resource Owner Grant", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "direct-grant-validate-username", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "direct-grant-validate-password", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "direct-grant-validate-otp", + "requirement": "OPTIONAL", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "3755e297-7907-4c14-8c5f-d77e2bfe4b5d", + "alias": "first broker login", + "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticatorConfig": "review profile config", + "authenticator": "idp-review-profile", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticatorConfig": "create unique user config", + "authenticator": "idp-create-user-if-unique", + "requirement": "ALTERNATIVE", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "requirement": "ALTERNATIVE", + "priority": 30, + "flowAlias": "Handle Existing Account", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "f35b2f00-3e84-4f2e-b48e-3e4159d88a06", + "alias": "forms", + "description": "Username, password, otp and other auth forms.", + "providerId": "basic-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "auth-username-password-form", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "auth-otp-form", + "requirement": "OPTIONAL", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "441b4480-1ace-483a-bffb-f0cb6659fe32", + "alias": "registration", + "description": "registration flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-page-form", + "requirement": "REQUIRED", + "priority": 10, + "flowAlias": "registration form", + "userSetupAllowed": false, + "autheticatorFlow": true + } + ] + }, + { + "id": "c7de2a37-29a1-471a-9b51-699a69032b00", + "alias": "registration form", + "description": "registration form", + "providerId": "form-flow", + "topLevel": false, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "registration-user-creation", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-profile-action", + "requirement": "REQUIRED", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-password-action", + "requirement": "REQUIRED", + "priority": 50, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "registration-recaptcha-action", + "requirement": "DISABLED", + "priority": 60, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "d362be0a-df20-4ce7-9288-f8448e0c4647", + "alias": "reset credentials", + "description": "Reset credentials for a user if they forgot their password or something", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "reset-credentials-choose-user", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-credential-email", + "requirement": "REQUIRED", + "priority": 20, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-password", + "requirement": "REQUIRED", + "priority": 30, + "userSetupAllowed": false, + "autheticatorFlow": false + }, + { + "authenticator": "reset-otp", + "requirement": "OPTIONAL", + "priority": 40, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + }, + { + "id": "c2d7a1ae-57c9-4f3b-a4ce-55c3f0d9869f", + "alias": "saml ecp", + "description": "SAML ECP Profile Authentication Flow", + "providerId": "basic-flow", + "topLevel": true, + "builtIn": true, + "authenticationExecutions": [ + { + "authenticator": "http-basic-authenticator", + "requirement": "REQUIRED", + "priority": 10, + "userSetupAllowed": false, + "autheticatorFlow": false + } + ] + } + ], + "authenticatorConfig": [ + { + "id": "a2490828-becb-435f-9c3c-318b3939bf64", + "alias": "create unique user config", + "config": { + "require.password.update.after.registration": "false" + } + }, + { + "id": "78421671-f733-4901-82bc-58bf50c43206", + "alias": "review profile config", + "config": { + "update.profile.on.first.login": "missing" + } + } + ], + "requiredActions": [ + { + "alias": "CONFIGURE_TOTP", + "name": "Configure OTP", + "providerId": "CONFIGURE_TOTP", + "enabled": true, + "defaultAction": false, + "config": {} + }, + { + "alias": "UPDATE_PASSWORD", + "name": "Update Password", + "providerId": "UPDATE_PASSWORD", + "enabled": true, + "defaultAction": false, + "config": {} + }, + { + "alias": "UPDATE_PROFILE", + "name": "Update Profile", + "providerId": "UPDATE_PROFILE", + "enabled": true, + "defaultAction": false, + "config": {} + }, + { + "alias": "VERIFY_EMAIL", + "name": "Verify Email", + "providerId": "VERIFY_EMAIL", + "enabled": true, + "defaultAction": false, + "config": {} + }, + { + "alias": "terms_and_conditions", + "name": "Terms and Conditions", + "providerId": "terms_and_conditions", + "enabled": false, + "defaultAction": false, + "config": {} + } + ], + "browserFlow": "browser", + "registrationFlow": "registration", + "directGrantFlow": "direct grant", + "resetCredentialsFlow": "reset credentials", + "clientAuthenticationFlow": "clients", + "attributes": { + "_browser_header.xFrameOptions": "SAMEORIGIN", + "failureFactor": "30", + "quickLoginCheckMilliSeconds": "1000", + "maxDeltaTimeSeconds": "43200", + "_browser_header.xContentTypeOptions": "nosniff", + "bruteForceProtected": "false", + "maxFailureWaitSeconds": "900", + "_browser_header.contentSecurityPolicy": "frame-src 'self'", + "minimumQuickLoginWaitSeconds": "60", + "waitIncrementSeconds": "60" + } +} \ No newline at end of file