From 6818c0a6457456cd6ac1162e07ea9deb71a97ed5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan-Otto=20Kr=C3=B6pke?= Date: Wed, 10 Jun 2020 13:56:39 +0200 Subject: [PATCH] Manage components and subComponents --- CHANGELOG.md | 4 +- docs/FEATURES.md | 6 +- docs/MANAGED.md | 14 +- .../properties/ImportConfigProperties.java | 22 +- .../repository/ComponentRepository.java | 68 ++- .../service/ComponentImportService.java | 83 +++- src/main/resources/application.properties | 2 + .../keycloak/config/ImportComponentsIT.java | 436 +++++++++++++++++- .../config/ImportManagedNoDeleteIT.java | 61 +-- .../ImportConfigPropertiesTest.java | 4 + ...m__update_component_skip_subcomponent.json | 130 ++++++ ..._update_component_remove_subcomponent.json | 157 +++++++ ...ate_component_remove_all_subcomponent.json | 131 ++++++ ...__with_component_without_subcomponent.json | 26 ++ ...lm__update_component_add_subcomponent.json | 26 ++ ...pdate_component_add_more_subcomponent.json | 31 +- .../97_update_realm__skip_components.json | 4 + .../98_update_realm__remove_component.json | 51 ++ ...9_update_realm__remove_all_components.json | 5 + ..._update_component_update_subcomponent.json | 29 ++ .../0_create_simple-realm.json | 195 +++++++- .../1_update-realm_not-delete-one.json | 169 ++++++- .../2_update-realm_not-delete-all.json | 3 +- 23 files changed, 1562 insertions(+), 95 deletions(-) create mode 100644 src/test/resources/import-files/components/10_update_realm__update_component_skip_subcomponent.json create mode 100644 src/test/resources/import-files/components/11_update_realm__update_component_remove_subcomponent.json create mode 100644 src/test/resources/import-files/components/12_update_realm__update_component_remove_all_subcomponent.json create mode 100644 src/test/resources/import-files/components/97_update_realm__skip_components.json create mode 100644 src/test/resources/import-files/components/98_update_realm__remove_component.json create mode 100644 src/test/resources/import-files/components/99_update_realm__remove_all_components.json diff --git a/CHANGELOG.md b/CHANGELOG.md index d2d67a089..df7207ef8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,7 +12,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - `import.file` is removed. Use `import.path` instead for files and directories. - `keycloak.migrationKey` is removed. Use `import.cache-key` instead. - `keycloak.realm` is removed. Use `import.login-realm` to define the realm to login. -- If you have defined requiredActions in your realm configure, make sure you have defined all in your json files. All not defined +- If you have defined requiredActions, components or subcomponents in your realm configure, make sure you have defined all in your json files. All not defined actions will removed now by keycloak-config-cli. See: [docs/MANAGED.md](docs/MANAGED.md) ### Added @@ -22,6 +22,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Throw errors on unknown properties in config files - Add, update and remove clientScopes - Removed required actions if they not defined in import json. +- Removed components if they not defined in import json. +- Removed subcomponents if they not defined in import json. - Contrib behaivor of purging ressource via `import.manage.` property. See: [docs/MANAGED.md](docs/MANAGED.md) ### Changed diff --git a/docs/FEATURES.md b/docs/FEATURES.md index 562747a41..93cb39b5d 100644 --- a/docs/FEATURES.md +++ b/docs/FEATURES.md @@ -2,8 +2,8 @@ | Feature | Since | Description | | ------------------------------------------ | ----- | -------------------------------------------------------------------------------------------------------- | -| Create clients | 1.0.0 | Create client configuration (inclusive protocolMappers) while creating or updating realms | -| Update clients | 1.0.0 | Update client configuration (inclusive protocolMappers) while updating realms | +| Create clients | 1.0.0 | Create client configuration (inclusive protocolMappers) while creating or updating realms | +| Update clients | 1.0.0 | Update client configuration (inclusive protocolMappers) while updating realms | | Add roles | 1.0.0 | Add roles while creating or updating realms | | Update roles | 1.0.0 | Update role properties while updating realms | | Add composites to roles | 1.3.0 | Add role with realm-level and client-level composite roles while creating or updating realms | @@ -18,7 +18,9 @@ | Update authentication flows and executions | 1.0.0 | Update authentication flow properties and executions while updating realms | | Add components | 1.0.0 | Add components while creating or updating realms | | Update components | 1.0.0 | Update components properties while updating realms | +| Remove components | 2.0.0 | Remove existing sub-components while creating or updating realms | | Update sub-components | 1.0.0 | Add sub-components properties while creating or updating realms | +| Remove sub-components | 2.0.0 | Remove existing sub-components while creating or updating realms | | Add groups | 1.3.0 | Add groups (inclusive subgroups!) to realm while creating or updating realms | | Update groups | 1.3.0 | Update existing group properties and attributes while creating or updating realms | | Remove groups | 1.3.0 | Remove existing groups while updating realms | diff --git a/docs/MANAGED.md b/docs/MANAGED.md index cf02b2bc4..01bbdcaa8 100644 --- a/docs/MANAGED.md +++ b/docs/MANAGED.md @@ -24,12 +24,14 @@ For example if you define `groups` but set an empty array, keycloak will delete ## Supported full managed entities -| Type | Additional Information | -| ---------------- | ----------------------------------------------------- | -| Groups | - | -| Required Actions | You have to copy the default one to you import json. | -| Client Scopes | - | -| Scope Mappings | - | +| Type | Additional Information | +| ---------------- | ----------------------------------------------------------- | +| Groups | - | +| Required Actions | You have to copy the default one to you import json. | +| Client Scopes | - | +| Scope Mappings | - | +| Components | You have to copy the default components to you import json. | +| Sub Components | You have to copy the default components to you import json. | ## Disable deletion of managed entities diff --git a/src/main/java/de/adorsys/keycloak/config/properties/ImportConfigProperties.java b/src/main/java/de/adorsys/keycloak/config/properties/ImportConfigProperties.java index 71a79a010..c09625a34 100644 --- a/src/main/java/de/adorsys/keycloak/config/properties/ImportConfigProperties.java +++ b/src/main/java/de/adorsys/keycloak/config/properties/ImportConfigProperties.java @@ -78,11 +78,23 @@ public static class ImportManagedProperties { @NotNull private final ImportManagedPropertiesValues scopeMapping; - private ImportManagedProperties(ImportManagedPropertiesValues group, ImportManagedPropertiesValues requiredAction, ImportManagedPropertiesValues clientScope, @NotNull ImportManagedPropertiesValues scopeMapping) { + @NotNull + private final ImportManagedPropertiesValues component; + + @NotNull + private final ImportManagedPropertiesValues subComponent; + + private ImportManagedProperties( + ImportManagedPropertiesValues group, ImportManagedPropertiesValues requiredAction, + ImportManagedPropertiesValues clientScope, ImportManagedPropertiesValues scopeMapping, + ImportManagedPropertiesValues component, ImportManagedPropertiesValues subComponent + ) { this.group = group; this.requiredAction = requiredAction; this.clientScope = clientScope; this.scopeMapping = scopeMapping; + this.component = component; + this.subComponent = subComponent; } public ImportManagedPropertiesValues getGroup() { @@ -101,6 +113,14 @@ public ImportManagedPropertiesValues getScopeMapping() { return scopeMapping; } + public ImportManagedPropertiesValues getComponent() { + return component; + } + + public ImportManagedPropertiesValues getSubComponent() { + return subComponent; + } + public enum ImportManagedPropertiesValues { full, noDelete diff --git a/src/main/java/de/adorsys/keycloak/config/repository/ComponentRepository.java b/src/main/java/de/adorsys/keycloak/config/repository/ComponentRepository.java index f707bdc5a..0b38a5dd6 100644 --- a/src/main/java/de/adorsys/keycloak/config/repository/ComponentRepository.java +++ b/src/main/java/de/adorsys/keycloak/config/repository/ComponentRepository.java @@ -27,6 +27,7 @@ import org.springframework.stereotype.Service; import javax.ws.rs.core.Response; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.Optional; @@ -41,21 +42,28 @@ public ComponentRepository(RealmRepository realmRepository) { this.realmRepository = realmRepository; } - public void create(String realm, ComponentRepresentation componentToCreate) { + public void create(String realm, ComponentRepresentation component) { RealmResource realmResource = realmRepository.loadRealm(realm); - Response response = realmResource.components().add(componentToCreate); + Response response = realmResource.components().add(component); ResponseUtil.throwOnError(response); } - public void update(String realm, ComponentRepresentation componentToUpdate) { + public void update(String realm, ComponentRepresentation component) { RealmResource realmResource = realmRepository.loadRealm(realm); - ComponentResource componentResource = realmResource.components().component(componentToUpdate.getId()); + ComponentResource componentResource = realmResource.components().component(component.getId()); - componentResource.update(componentToUpdate); + componentResource.update(component); } - public ComponentRepresentation get(String realm, String subType, String name) { + public void delete(String realm, ComponentRepresentation component) { + RealmResource realmResource = realmRepository.loadRealm(realm); + ComponentResource componentResource = realmResource.components().component(component.getId()); + + componentResource.remove(); + } + + public ComponentRepresentation get(String realm, String providerType, String name) { RealmResource realmResource = realmRepository.loadRealm(realm); List realmComponents = realmResource.components().query(); @@ -63,30 +71,39 @@ public ComponentRepresentation get(String realm, String subType, String name) { Optional maybeComponent = realmComponents .stream() .filter(c -> Objects.equals(c.getName(), name)) - .filter(c -> Objects.equals(c.getProviderType(), subType)) + .filter(c -> Objects.equals(c.getProviderType(), providerType)) .findFirst(); if (maybeComponent.isPresent()) { return maybeComponent.get(); } - throw new KeycloakRepositoryException("Cannot find component by name '" + name + "' and subtype '" + subType + "' in realm '" + realm + "' "); + throw new KeycloakRepositoryException("Cannot find component by name '" + name + "' and subtype '" + providerType + "' in realm '" + realm + "' "); } - public Optional tryToGet(String realm, String parentId, String subType, String name) { + public List getAllComponents(String realm) { RealmResource realmResource = realmRepository.loadRealm(realm); + String realmId = realmResource.toRepresentation().getId(); - Optional maybeComponent; - List existingComponents = realmResource.components() - .query(parentId, subType, name); + List subComponents = realmResource.components().query(realmId); - if (existingComponents.isEmpty()) { - maybeComponent = Optional.empty(); - } else { - maybeComponent = Optional.of(existingComponents.get(0)); + if (subComponents == null) { + return Collections.emptyList(); } - return maybeComponent; + return subComponents; + } + + public List getAllSubComponentsByParentId(String realm, String parentId) { + RealmResource realmResource = realmRepository.loadRealm(realm); + + List subComponents = realmResource.components().query(parentId); + + if (subComponents == null) { + return Collections.emptyList(); + } + + return subComponents; } /** @@ -101,9 +118,24 @@ public Optional tryToGetComponent(String realm, String .query(); return existingComponents.stream() - .filter(c -> c.getName().equals(name)) .filter(c -> Objects.equals(c.getName(), name)) .filter(c -> Objects.equals(c.getSubType(), subType)) .findFirst(); } + + public Optional tryToGetSubComponent(String realm, String parentId, String subType, String name) { + RealmResource realmResource = realmRepository.loadRealm(realm); + + Optional maybeComponent; + List existingComponents = realmResource.components() + .query(parentId, subType, name); + + if (existingComponents.isEmpty()) { + maybeComponent = Optional.empty(); + } else { + maybeComponent = Optional.of(existingComponents.get(0)); + } + + return maybeComponent; + } } diff --git a/src/main/java/de/adorsys/keycloak/config/service/ComponentImportService.java b/src/main/java/de/adorsys/keycloak/config/service/ComponentImportService.java index b4a9d5ce1..a578512fe 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/ComponentImportService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/ComponentImportService.java @@ -21,6 +21,8 @@ import de.adorsys.keycloak.config.exception.ImportProcessingException; import de.adorsys.keycloak.config.exception.KeycloakRepositoryException; import de.adorsys.keycloak.config.model.RealmImport; +import de.adorsys.keycloak.config.properties.ImportConfigProperties; +import de.adorsys.keycloak.config.properties.ImportConfigProperties.ImportManagedProperties.ImportManagedPropertiesValues; import de.adorsys.keycloak.config.repository.ComponentRepository; import de.adorsys.keycloak.config.util.CloneUtil; import org.keycloak.common.util.MultivaluedHashMap; @@ -40,10 +42,12 @@ public class ComponentImportService { private static final Logger logger = LoggerFactory.getLogger(ComponentImportService.class); private final ComponentRepository componentRepository; + private final ImportConfigProperties importConfigProperties; @Autowired - public ComponentImportService(ComponentRepository componentRepository) { + public ComponentImportService(ComponentRepository componentRepository, ImportConfigProperties importConfigProperties) { this.componentRepository = componentRepository; + this.importConfigProperties = importConfigProperties; } public void doImport(RealmImport realmImport) { @@ -53,10 +57,14 @@ public void doImport(RealmImport realmImport) { return; } - createOrUpdateComponents(realmImport.getRealm(), components); + importComponents(realmImport.getRealm(), components); + + if (importConfigProperties.getManaged().getComponent() == ImportManagedPropertiesValues.full) { + deleteComponentsMissingInImport(realmImport.getRealm(), components); + } } - private void createOrUpdateComponents(String realm, Map> componentsToImport) { + private void importComponents(String realm, Map> componentsToImport) { for (Map.Entry> entry : componentsToImport.entrySet()) { createOrUpdateComponents(realm, entry.getKey(), entry.getValue()); } @@ -111,10 +119,19 @@ private void createComponent( MultivaluedHashMap subComponents = component.getSubComponents(); - if (subComponents != null && !subComponents.isEmpty()) { - ComponentRepresentation exitingComponent = componentRepository.get(realm, providerType, component.getName()); + if (subComponents == null) { + return; + } + + ComponentRepresentation exitingComponent = componentRepository.get(realm, providerType, component.getName()); + + if (!subComponents.isEmpty()) { createOrUpdateSubComponents(realm, subComponents, exitingComponent.getId()); } + + if (importConfigProperties.getManaged().getComponent() == ImportManagedPropertiesValues.full) { + deleteSubComponentsMissingInImport(realm, subComponents, exitingComponent.getId()); + } } private void updateComponentIfNeeded( @@ -150,8 +167,14 @@ private void updateComponent( MultivaluedHashMap subComponents = componentToImport.getSubComponents(); - if (subComponents != null && !subComponents.isEmpty()) { - createOrUpdateSubComponents(realm, subComponents, patchedComponent.getId()); + if (subComponents != null) { + if (!subComponents.isEmpty()) { + createOrUpdateSubComponents(realm, subComponents, patchedComponent.getId()); + } + + if (importConfigProperties.getManaged().getSubComponent() == ImportManagedPropertiesValues.full) { + deleteSubComponentsMissingInImport(realm, subComponents, patchedComponent.getId()); + } } } @@ -168,7 +191,7 @@ private void createOrUpdateSubComponents(String realm, String providerType, List } private void createOrUpdateSubComponent(String realm, String parentId, String providerType, ComponentExportRepresentation subComponent) { - Optional maybeComponent = componentRepository.tryToGet(realm, parentId, subComponent.getSubType(), subComponent.getName()); + Optional maybeComponent = componentRepository.tryToGetSubComponent(realm, parentId, subComponent.getSubType(), subComponent.getName()); if (maybeComponent.isPresent()) { updateComponentIfNeeded(realm, providerType, subComponent, maybeComponent.get()); @@ -176,4 +199,48 @@ private void createOrUpdateSubComponent(String realm, String parentId, String pr createComponent(realm, providerType, subComponent, parentId); } } + + private void deleteComponentsMissingInImport(String realm, MultivaluedHashMap componentsToImport) { + List existingComponents = componentRepository.getAllComponents(realm); + + for (ComponentRepresentation existingComponent : existingComponents) { + if (checkIfComponentMissingImport(existingComponent, componentsToImport)) { + logger.debug("Delete component: {}/{}", existingComponent.getProviderType(), existingComponent.getName()); + componentRepository.delete(realm, existingComponent); + } + } + } + + private void deleteSubComponentsMissingInImport(String realm, MultivaluedHashMap subComponentsToImport, String parentId) { + List existingSubComponents = componentRepository.getAllSubComponentsByParentId(realm, parentId); + + for (ComponentRepresentation existingSubComponent : existingSubComponents) { + if (checkIfComponentMissingImport(existingSubComponent, subComponentsToImport)) { + logger.debug("Delete component: {}/{}", existingSubComponent.getProviderType(), existingSubComponent.getName()); + componentRepository.delete(realm, existingSubComponent); + } + } + } + + private boolean checkIfComponentMissingImport(ComponentRepresentation existingComponent, MultivaluedHashMap componentsToImport) { + String existingComponentProviderType = existingComponent.getProviderType(); + String existingComponentName = existingComponent.getName(); + + for (Map.Entry> entry : componentsToImport.entrySet()) { + String providerType = entry.getKey(); + List componentToImport = entry.getValue(); + + if (!existingComponentProviderType.equals(providerType)) { + continue; + } + + boolean isInImport = componentToImport.stream().anyMatch((component) -> existingComponentName.equals(component.getName())); + + if (isInImport) { + return false; + } + } + + return true; + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 5f1e10817..b086b4b31 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -18,3 +18,5 @@ import.managed.group=full import.managed.required-action=full import.managed.client-scope=full import.managed.scope-mapping=full +import.managed.component=nodelete +import.managed.sub-component=full diff --git a/src/test/java/de/adorsys/keycloak/config/ImportComponentsIT.java b/src/test/java/de/adorsys/keycloak/config/ImportComponentsIT.java index e3e734c88..6c8edef4c 100644 --- a/src/test/java/de/adorsys/keycloak/config/ImportComponentsIT.java +++ b/src/test/java/de/adorsys/keycloak/config/ImportComponentsIT.java @@ -25,6 +25,7 @@ import org.keycloak.representations.idm.ComponentExportRepresentation; import org.keycloak.representations.idm.ComponentRepresentation; import org.keycloak.representations.idm.RealmRepresentation; +import org.springframework.test.context.TestPropertySource; import java.util.List; import java.util.Objects; @@ -35,6 +36,9 @@ import static org.hamcrest.Matchers.*; import static org.hamcrest.core.Is.is; +@TestPropertySource(properties = { + "import.managed.component=full" +}) public class ImportComponentsIT extends AbstractImportTest { private static final String REALM_NAME = "realmWithComponents"; @@ -52,14 +56,14 @@ public void shouldCreateRealmWithComponent() { assertThat(createdRealm.getRealm(), is(REALM_NAME)); assertThat(createdRealm.isEnabled(), is(true)); - ComponentRepresentation createdComponent = getComponent( + ComponentRepresentation rsaComponent = getComponent( "org.keycloak.keys.KeyProvider", "rsa-generated" ); - assertThat(createdComponent.getName(), is("rsa-generated")); - assertThat(createdComponent.getProviderId(), is("rsa-generated")); - MultivaluedHashMap componentConfig = createdComponent.getConfig(); + assertThat(rsaComponent.getName(), is("rsa-generated")); + assertThat(rsaComponent.getProviderId(), is("rsa-generated")); + MultivaluedHashMap componentConfig = rsaComponent.getConfig(); List keySize = componentConfig.get("keySize"); assertThat(keySize, hasSize(1)); @@ -76,14 +80,14 @@ public void shouldUpdateComponentsConfig() { assertThat(createdRealm.getRealm(), is(REALM_NAME)); assertThat(createdRealm.isEnabled(), is(true)); - ComponentRepresentation createdComponent = getComponent( + ComponentRepresentation rsaComponent = getComponent( "org.keycloak.keys.KeyProvider", "rsa-generated" ); - assertThat(createdComponent.getName(), is("rsa-generated")); - assertThat(createdComponent.getProviderId(), is("rsa-generated")); - MultivaluedHashMap componentConfig = createdComponent.getConfig(); + assertThat(rsaComponent.getName(), is("rsa-generated")); + assertThat(rsaComponent.getProviderId(), is("rsa-generated")); + MultivaluedHashMap componentConfig = rsaComponent.getConfig(); List keySize = componentConfig.get("keySize"); assertThat(keySize, hasSize(1)); @@ -161,6 +165,7 @@ public void shouldAddComponentWithSubComponent() { "my-realm-userstorage" ); + assertThat(createdComponent, is(notNullValue())); assertThat(createdComponent.getName(), is("my-realm-userstorage")); assertThat(createdComponent.getProviderId(), is("ldap")); @@ -200,6 +205,7 @@ public void shouldUpdateConfigOfSubComponent() { "my-realm-userstorage" ); + assertThat(createdComponent, is(notNullValue())); assertThat(createdComponent.getName(), is("my-realm-userstorage")); assertThat(createdComponent.getProviderId(), is("ldap")); @@ -210,6 +216,7 @@ public void shouldUpdateConfigOfSubComponent() { "my-realm-role-mapper" ); + assertThat(subComponent, is(notNullValue())); assertThat(subComponent.getName(), is(equalTo("my-realm-role-mapper"))); assertThat(subComponent.getProviderId(), is(equalTo("role-ldap-mapper"))); @@ -235,6 +242,216 @@ public void shouldUpdateComponentAddSubComponent() { doImport("6_create_realm__with_component_without_subcomponent.json"); doImport("7_update_realm__update_component_add_subcomponent.json"); + ComponentRepresentation rsaComponent = getComponent( + "org.keycloak.keys.KeyProvider", + "rsa-generated" + ); + + assertThat(rsaComponent.getName(), is("rsa-generated")); + assertThat(rsaComponent.getProviderId(), is("rsa-generated")); + MultivaluedHashMap componentConfig = rsaComponent.getConfig(); + + List keySize = componentConfig.get("keySize"); + assertThat(keySize, hasSize(1)); + assertThat(keySize.get(0), is("2048")); + + ComponentExportRepresentation createdComponent = exportComponent( + "realmWithSubComponents", + "org.keycloak.storage.UserStorageProvider", + "my-realm-userstorage" + ); + + assertThat(createdComponent, is(notNullValue())); + assertThat(createdComponent.getName(), is("my-realm-userstorage")); + assertThat(createdComponent.getProviderId(), is("ldap")); + + MultivaluedHashMap subComponentsMap = createdComponent.getSubComponents(); + ComponentExportRepresentation subComponent = getSubComponent( + subComponentsMap, + "org.keycloak.storage.ldap.mappers.LDAPStorageMapper", + "my-realm-role-mapper" + ); + + assertThat(subComponent.getName(), is(equalTo("my-realm-role-mapper"))); + assertThat(subComponent.getProviderId(), is(equalTo("role-ldap-mapper"))); + + MultivaluedHashMap config = subComponent.getConfig(); + assertThat(config.size(), is(10)); + + assertConfigHasValue(config, "mode", "LDAP_ONLY"); + assertConfigHasValue(config, "membership.attribute.type", "DN"); + assertConfigHasValue(config, "user.roles.retrieve.strategy", "LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY"); + assertConfigHasValue(config, "roles.dn", "someDN"); + assertConfigHasValue(config, "membership.ldap.attribute", "member"); + assertConfigHasValue(config, "membership.user.ldap.attribute", "userPrincipalName"); + assertConfigHasValue(config, "memberof.ldap.attribute", "memberOf"); + assertConfigHasValue(config, "role.name.ldap.attribute", "cn"); + assertConfigHasValue(config, "use.realm.roles.mapping", "true"); + assertConfigHasValue(config, "role.object.classes", "group"); + } + + @Test + @Order(8) + public void shouldUpdateComponentAddMoreSubComponent() { + doImport("8_update_realm__update_component_add_more_subcomponent.json"); + + ComponentRepresentation rsaComponent = getComponent( + "org.keycloak.keys.KeyProvider", + "rsa-generated" + ); + + assertThat(rsaComponent.getName(), is("rsa-generated")); + assertThat(rsaComponent.getProviderId(), is("rsa-generated")); + MultivaluedHashMap componentConfig = rsaComponent.getConfig(); + + List keySize = componentConfig.get("keySize"); + assertThat(keySize, hasSize(1)); + assertThat(keySize.get(0), is("2048")); + + ComponentExportRepresentation createdComponent = exportComponent( + "realmWithSubComponents", + "org.keycloak.storage.UserStorageProvider", + "my-realm-userstorage" + ); + + assertThat(createdComponent, is(notNullValue())); + assertThat(createdComponent.getName(), is("my-realm-userstorage")); + assertThat(createdComponent.getProviderId(), is("ldap")); + + MultivaluedHashMap subComponentsMap = createdComponent.getSubComponents(); + ComponentExportRepresentation subComponent = getSubComponent( + subComponentsMap, + "org.keycloak.storage.ldap.mappers.LDAPStorageMapper", + "my-realm-role-mapper" + ); + + assertThat(subComponent.getName(), is(equalTo("my-realm-role-mapper"))); + assertThat(subComponent.getProviderId(), is(equalTo("role-ldap-mapper"))); + + MultivaluedHashMap config = subComponent.getConfig(); + assertThat(config.size(), is(10)); + + assertConfigHasValue(config, "mode", "LDAP_ONLY"); + assertConfigHasValue(config, "membership.attribute.type", "DN"); + assertConfigHasValue(config, "user.roles.retrieve.strategy", "LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY"); + assertConfigHasValue(config, "roles.dn", "someDN"); + assertConfigHasValue(config, "membership.ldap.attribute", "member"); + assertConfigHasValue(config, "membership.user.ldap.attribute", "userPrincipalName"); + assertConfigHasValue(config, "memberof.ldap.attribute", "memberOf"); + assertConfigHasValue(config, "role.name.ldap.attribute", "cn"); + assertConfigHasValue(config, "use.realm.roles.mapping", "true"); + assertConfigHasValue(config, "role.object.classes", "group"); + + ComponentExportRepresentation subComponent2 = getSubComponent( + subComponentsMap, + "org.keycloak.storage.ldap.mappers.LDAPStorageMapper", + "picture" + ); + + assertThat(subComponent2.getName(), is(equalTo("picture"))); + assertThat(subComponent2.getProviderId(), is(equalTo("user-attribute-ldap-mapper"))); + + MultivaluedHashMap config2 = subComponent2.getConfig(); + assertThat(config2.size(), is(5)); + + assertConfigHasValue(config2, "ldap.attribute", "jpegPhoto"); + assertConfigHasValue(config2, "is.mandatory.in.ldap", "true"); + assertConfigHasValue(config2, "is.binary.attribute", "true"); + assertConfigHasValue(config2, "always.read.value.from.ldap", "true"); + assertConfigHasValue(config2, "user.model.attribute", "picture"); + } + + @Test + @Order(9) + public void shouldUpdateComponentUpdateSubComponent() { + doImport("9_update_realm__update_component_update_subcomponent.json"); + + ComponentRepresentation rsaComponent = getComponent( + "org.keycloak.keys.KeyProvider", + "rsa-generated" + ); + + assertThat(rsaComponent.getName(), is("rsa-generated")); + assertThat(rsaComponent.getProviderId(), is("rsa-generated")); + MultivaluedHashMap componentConfig = rsaComponent.getConfig(); + + List keySize = componentConfig.get("keySize"); + assertThat(keySize, hasSize(1)); + assertThat(keySize.get(0), is("2048")); + + ComponentExportRepresentation createdComponent = exportComponent( + "realmWithSubComponents", + "org.keycloak.storage.UserStorageProvider", + "my-realm-userstorage" + ); + + assertThat(createdComponent, is(notNullValue())); + assertThat(createdComponent.getName(), is("my-realm-userstorage")); + assertThat(createdComponent.getProviderId(), is("ldap")); + + MultivaluedHashMap subComponentsMap = createdComponent.getSubComponents(); + ComponentExportRepresentation subComponent = getSubComponent( + subComponentsMap, + "org.keycloak.storage.ldap.mappers.LDAPStorageMapper", + "my-realm-role-mapper" + ); + + assertThat(subComponent.getName(), is(equalTo("my-realm-role-mapper"))); + assertThat(subComponent.getProviderId(), is(equalTo("role-ldap-mapper"))); + + MultivaluedHashMap config = subComponent.getConfig(); + assertThat(config.size(), is(10)); + + assertConfigHasValue(config, "mode", "LDAP_ONLY"); + assertConfigHasValue(config, "membership.attribute.type", "DN"); + assertConfigHasValue(config, "user.roles.retrieve.strategy", "LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY"); + assertConfigHasValue(config, "roles.dn", "someDN"); + assertConfigHasValue(config, "membership.ldap.attribute", "member"); + assertConfigHasValue(config, "membership.user.ldap.attribute", "userPrincipalName"); + assertConfigHasValue(config, "memberof.ldap.attribute", "memberOf"); + assertConfigHasValue(config, "role.name.ldap.attribute", "cn"); + assertConfigHasValue(config, "use.realm.roles.mapping", "true"); + assertConfigHasValue(config, "role.object.classes", "group"); + + ComponentExportRepresentation subComponent2 = getSubComponent( + subComponentsMap, + "org.keycloak.storage.ldap.mappers.LDAPStorageMapper", + "picture" + ); + + assertThat(subComponent2.getName(), is(equalTo("picture"))); + assertThat(subComponent2.getProviderId(), is(equalTo("user-attribute-ldap-mapper"))); + + MultivaluedHashMap config2 = subComponent2.getConfig(); + assertThat(config2.size(), is(6)); + + assertConfigHasValue(config2, "ldap.attribute", "jpegPhoto"); + assertConfigHasValue(config2, "is.mandatory.in.ldap", "false"); + assertConfigHasValue(config2, "is.binary.attribute", "true"); + assertConfigHasValue(config2, "read.only", "true"); + assertConfigHasValue(config2, "always.read.value.from.ldap", "true"); + assertConfigHasValue(config2, "user.model.attribute", "picture"); + } + + /* + @Test + @Order(10) + public void shouldUpdateComponentSkipSubComponent() { + doImport("10_update_realm__update_component_skip_subcomponent.json"); + + ComponentRepresentation rsaComponent = getComponent( + "org.keycloak.keys.KeyProvider", + "rsa-generated" + ); + + assertThat(rsaComponent.getName(), is("rsa-generated")); + assertThat(rsaComponent.getProviderId(), is("rsa-generated")); + MultivaluedHashMap componentConfig = rsaComponent.getConfig(); + + List keySize = componentConfig.get("keySize"); + assertThat(keySize, hasSize(1)); + assertThat(keySize.get(0), is("2048")); + ComponentExportRepresentation createdComponent = exportComponent( "realmWithSubComponents", "org.keycloak.storage.UserStorageProvider", @@ -267,6 +484,200 @@ public void shouldUpdateComponentAddSubComponent() { assertConfigHasValue(config, "role.name.ldap.attribute", "cn"); assertConfigHasValue(config, "use.realm.roles.mapping", "true"); assertConfigHasValue(config, "role.object.classes", "group"); + + ComponentExportRepresentation subComponent2 = getSubComponent( + subComponentsMap, + "org.keycloak.storage.ldap.mappers.LDAPStorageMapper", + "picture" + ); + + assertThat(subComponent2.getName(), is(equalTo("picture"))); + assertThat(subComponent2.getProviderId(), is(equalTo("user-attribute-ldap-mapper"))); + + MultivaluedHashMap config2 = subComponent2.getConfig(); + assertThat(config2.size(), is(6)); + + assertConfigHasValue(config2, "ldap.attribute", "jpegPhoto"); + assertConfigHasValue(config2, "is.mandatory.in.ldap", "false"); + assertConfigHasValue(config2, "is.binary.attribute", "true"); + assertConfigHasValue(config2, "read.only", "true"); + assertConfigHasValue(config2, "always.read.value.from.ldap", "true"); + assertConfigHasValue(config2, "user.model.attribute", "picture"); + } +*/ + @Test + @Order(11) + public void shouldUpdateComponentRemoveSubComponent() { + doImport("11_update_realm__update_component_remove_subcomponent.json"); + + ComponentRepresentation rsaComponent = getComponent( + "org.keycloak.keys.KeyProvider", + "rsa-generated" + ); + + assertThat(rsaComponent.getName(), is("rsa-generated")); + assertThat(rsaComponent.getProviderId(), is("rsa-generated")); + MultivaluedHashMap componentConfig = rsaComponent.getConfig(); + + List keySize = componentConfig.get("keySize"); + assertThat(keySize, hasSize(1)); + assertThat(keySize.get(0), is("2048")); + + ComponentExportRepresentation createdComponent = exportComponent( + "realmWithSubComponents", + "org.keycloak.storage.UserStorageProvider", + "my-realm-userstorage" + ); + + assertThat(createdComponent, is(notNullValue())); + assertThat(createdComponent.getName(), is("my-realm-userstorage")); + assertThat(createdComponent.getProviderId(), is("ldap")); + + MultivaluedHashMap subComponentsMap = createdComponent.getSubComponents(); + ComponentExportRepresentation subComponent = getSubComponent( + subComponentsMap, + "org.keycloak.storage.ldap.mappers.LDAPStorageMapper", + "my-realm-role-mapper" + ); + + assertThat(subComponent, is(nullValue())); + + ComponentExportRepresentation subComponent2 = getSubComponent( + subComponentsMap, + "org.keycloak.storage.ldap.mappers.LDAPStorageMapper", + "picture" + ); + + assertThat(subComponent2.getName(), is(equalTo("picture"))); + assertThat(subComponent2.getProviderId(), is(equalTo("user-attribute-ldap-mapper"))); + + MultivaluedHashMap config2 = subComponent2.getConfig(); + assertThat(config2.size(), is(6)); + + assertConfigHasValue(config2, "ldap.attribute", "jpegPhoto"); + assertConfigHasValue(config2, "is.mandatory.in.ldap", "false"); + assertConfigHasValue(config2, "is.binary.attribute", "true"); + assertConfigHasValue(config2, "read.only", "true"); + assertConfigHasValue(config2, "always.read.value.from.ldap", "true"); + assertConfigHasValue(config2, "user.model.attribute", "picture"); + } + + @Test + @Order(12) + public void shouldUpdateComponentRemoveAllSubComponent() { + doImport("12_update_realm__update_component_remove_all_subcomponent.json"); + + ComponentRepresentation rsaComponent = getComponent( + "org.keycloak.keys.KeyProvider", + "rsa-generated" + ); + + assertThat(rsaComponent.getName(), is("rsa-generated")); + assertThat(rsaComponent.getProviderId(), is("rsa-generated")); + MultivaluedHashMap componentConfig = rsaComponent.getConfig(); + + List keySize = componentConfig.get("keySize"); + assertThat(keySize, hasSize(1)); + assertThat(keySize.get(0), is("2048")); + + ComponentExportRepresentation createdComponent = exportComponent( + "realmWithSubComponents", + "org.keycloak.storage.UserStorageProvider", + "my-realm-userstorage" + ); + + assertThat(createdComponent, is(notNullValue())); + assertThat(createdComponent.getName(), is("my-realm-userstorage")); + assertThat(createdComponent.getProviderId(), is("ldap")); + + MultivaluedHashMap subComponentsMap = createdComponent.getSubComponents(); + + assertThat(subComponentsMap, is(anEmptyMap())); + } + + @Test + @Order(97) + public void shouldUpdateSkipComponents() { + doImport("97_update_realm__skip_components.json"); + + ComponentRepresentation rsaComponent = getComponent( + "org.keycloak.keys.KeyProvider", + "rsa-generated" + ); + + assertThat(rsaComponent.getName(), is("rsa-generated")); + assertThat(rsaComponent.getProviderId(), is("rsa-generated")); + MultivaluedHashMap componentConfig = rsaComponent.getConfig(); + + List keySize = componentConfig.get("keySize"); + assertThat(keySize, hasSize(1)); + assertThat(keySize.get(0), is("2048")); + + ComponentExportRepresentation createdComponent = exportComponent( + "realmWithSubComponents", + "org.keycloak.storage.UserStorageProvider", + "my-realm-userstorage" + ); + + assertThat(createdComponent, is(notNullValue())); + assertThat(createdComponent.getName(), is("my-realm-userstorage")); + assertThat(createdComponent.getProviderId(), is("ldap")); + + MultivaluedHashMap subComponentsMap = createdComponent.getSubComponents(); + + assertThat(subComponentsMap, is(anEmptyMap())); + } + + @Test + @Order(98) + public void shouldUpdateRemoveComponents() { + doImport("98_update_realm__remove_component.json"); + + ComponentExportRepresentation createdComponent = exportComponent( + "realmWithSubComponents", + "org.keycloak.storage.UserStorageProvider", + "my-realm-userstorage" + ); + + assertThat(createdComponent, is(nullValue())); + + ComponentRepresentation clientRegistrationPolicyComponent = getComponent( + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy", + "Allowed Protocol Mapper Types", + "authenticated" + ); + + assertThat(clientRegistrationPolicyComponent.getName(), is("Allowed Protocol Mapper Types")); + assertThat(clientRegistrationPolicyComponent.getProviderId(), is("allowed-protocol-mappers")); + assertThat(clientRegistrationPolicyComponent.getSubType(), is("authenticated")); + MultivaluedHashMap componentConfig = clientRegistrationPolicyComponent.getConfig(); + + List mapperTypes = componentConfig.get("allowed-protocol-mapper-types"); + assertThat(mapperTypes, hasSize(8)); + assertThat(mapperTypes, containsInAnyOrder( + "oidc-full-name-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-address-mapper", + "saml-user-property-mapper", + "oidc-usermodel-property-mapper", + "saml-role-list-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-attribute-mapper" + )); + } + + @Test + @Order(99) + public void shouldUpdateRemoveAllComponents() { + doImport("99_update_realm__remove_all_components.json"); + + ComponentExportRepresentation createdComponent = exportComponent( + "realmWithSubComponents", + "org.keycloak.storage.UserStorageProvider", + "my-realm-userstorage" + ); + + assertThat(createdComponent, is(nullValue())); } private void assertConfigHasValue(MultivaluedHashMap config, String configKey, String expectedConfigValue) { @@ -290,8 +701,13 @@ private ComponentRepresentation getComponent(String providerType, String name) { private ComponentExportRepresentation exportComponent(String realm, String providerType, String name) { RealmRepresentation exportedRealm = keycloakProvider.get().realm(realm).partialExport(true, true); - return exportedRealm.getComponents() - .get(providerType) + List components = exportedRealm.getComponents().get(providerType); + + if (components == null) { + return null; + } + + return components .stream() .filter(c -> c.getName().equals(name)) .findFirst() diff --git a/src/test/java/de/adorsys/keycloak/config/ImportManagedNoDeleteIT.java b/src/test/java/de/adorsys/keycloak/config/ImportManagedNoDeleteIT.java index bbb40c99c..1c49b7837 100644 --- a/src/test/java/de/adorsys/keycloak/config/ImportManagedNoDeleteIT.java +++ b/src/test/java/de/adorsys/keycloak/config/ImportManagedNoDeleteIT.java @@ -34,6 +34,8 @@ "import.managed.required-action=no-delete", "import.managed.client-scope=no-delete", "import.managed.scope-mapping=no-delete", + "import.managed.component=no-delete", + "import.managed.sub-component=no-delete", }) public class ImportManagedNoDeleteIT extends AbstractImportTest { private static final String REALM_NAME = "realmWithNoDelete"; @@ -46,62 +48,27 @@ public class ImportManagedNoDeleteIT extends AbstractImportTest { @Order(0) public void shouldCreateSimpleRealm() { doImport("0_create_simple-realm.json"); - RealmRepresentation createdRealm = keycloakProvider.get().realm(REALM_NAME).partialExport(true, true); - - List createdGroup = createdRealm.getGroups(); - assertThat(createdGroup, hasSize(2)); - List createdRequiredActions = createdRealm.getRequiredActions() - .stream() - .filter((action) -> action.getAlias().equals("MY_CONFIGURE_TOTP") || action.getAlias().equals("my_terms_and_conditions")) - .collect(Collectors.toList()); - assertThat(createdRequiredActions, hasSize(2)); - - List createdClientScopes = createdRealm.getClientScopes() - .stream() - .filter((clientScope) -> clientScope.getName().equals("my_clientScope") || clientScope.getName().equals("my_other_clientScope")) - .collect(Collectors.toList()); - assertThat(createdClientScopes, hasSize(2)); - - List createdScopeMappings = createdRealm.getScopeMappings() - .stream() - .filter((scopeMapping) -> scopeMapping.getClientScope().equals("offline_access")) - .collect(Collectors.toList()); - assertThat(createdScopeMappings, hasSize(1)); + assertRealm(); } @Test @Order(1) public void shouldUpdateRealmNotDeleteOne() { doImport("1_update-realm_not-delete-one.json"); - RealmRepresentation createdRealm = keycloakProvider.get().realm(REALM_NAME).partialExport(true, true); - - List createdGroup = createdRealm.getGroups(); - assertThat(createdGroup, hasSize(2)); - List createdRequiredActions = createdRealm.getRequiredActions() - .stream() - .filter((action) -> action.getAlias().equals("MY_CONFIGURE_TOTP") || action.getAlias().equals("my_terms_and_conditions")) - .collect(Collectors.toList()); - assertThat(createdRequiredActions, hasSize(2)); - - List createdClientScopes = createdRealm.getClientScopes() - .stream() - .filter((clientScope) -> clientScope.getName().equals("my_clientScope") || clientScope.getName().equals("my_other_clientScope")) - .collect(Collectors.toList()); - assertThat(createdClientScopes, hasSize(2)); - - List createdScopeMappings = createdRealm.getScopeMappings() - .stream() - .filter((scopeMapping) -> scopeMapping.getClientScope().equals("offline_access")) - .collect(Collectors.toList()); - assertThat(createdScopeMappings, hasSize(1)); + assertRealm(); } @Test @Order(2) public void shouldUpdateRealmNotDeleteAll() { doImport("2_update-realm_not-delete-all.json"); + + assertRealm(); + } + + private void assertRealm() { RealmRepresentation createdRealm = keycloakProvider.get().realm(REALM_NAME).partialExport(true, true); List createdGroup = createdRealm.getGroups(); @@ -124,5 +91,15 @@ public void shouldUpdateRealmNotDeleteAll() { .filter((scopeMapping) -> scopeMapping.getClientScope().equals("offline_access")) .collect(Collectors.toList()); assertThat(createdScopeMappings, hasSize(1)); + + List createdComponents = createdRealm.getComponents().get("org.keycloak.storage.UserStorageProvider") + .stream() + .filter(c -> c.getName().equals("my-realm-userstorage")) + .collect(Collectors.toList()); + assertThat(createdComponents, hasSize(1)); + + List createdSubComponents = createdComponents.get(0) + .getSubComponents().getList("org.keycloak.storage.ldap.mappers.LDAPStorageMapper"); + assertThat(createdSubComponents, hasSize(10)); } } diff --git a/src/test/java/de/adorsys/keycloak/config/properties/ImportConfigPropertiesTest.java b/src/test/java/de/adorsys/keycloak/config/properties/ImportConfigPropertiesTest.java index 1cda0a5ad..f8ac28da7 100644 --- a/src/test/java/de/adorsys/keycloak/config/properties/ImportConfigPropertiesTest.java +++ b/src/test/java/de/adorsys/keycloak/config/properties/ImportConfigPropertiesTest.java @@ -42,6 +42,8 @@ "import.managed.required-action=no-delete", "import.managed.client-scope=no-delete", "import.managed.scope-mapping=no-delete", + "import.managed.component=no-delete", + "import.managed.sub-component=no-delete", }) public class ImportConfigPropertiesTest { @@ -57,6 +59,8 @@ public void shouldPopulateConfigurationProperties() { assertThat(properties.getManaged().getRequiredAction(), is(ImportManagedPropertiesValues.noDelete)); assertThat(properties.getManaged().getClientScope(), is(ImportManagedPropertiesValues.noDelete)); assertThat(properties.getManaged().getScopeMapping(), is(ImportManagedPropertiesValues.noDelete)); + assertThat(properties.getManaged().getComponent(), is(ImportManagedPropertiesValues.noDelete)); + assertThat(properties.getManaged().getSubComponent(), is(ImportManagedPropertiesValues.noDelete)); } @EnableConfigurationProperties(ImportConfigProperties.class) diff --git a/src/test/resources/import-files/components/10_update_realm__update_component_skip_subcomponent.json b/src/test/resources/import-files/components/10_update_realm__update_component_skip_subcomponent.json new file mode 100644 index 000000000..905a97146 --- /dev/null +++ b/src/test/resources/import-files/components/10_update_realm__update_component_skip_subcomponent.json @@ -0,0 +1,130 @@ +{ + "enabled": true, + "realm": "realmWithSubComponents", + "components": { + "org.keycloak.keys.KeyProvider": [ + { + "name": "rsa-generated", + "providerId": "rsa-generated", + "config": { + "keySize": [ + "2048" + ], + "priority": [ + "100" + ] + } + }, + { + "name": "hmac-generated", + "providerId": "hmac-generated", + "config": { + "secretSize": [ + "32" + ], + "priority": [ + "100" + ] + } + } + ], + "org.keycloak.storage.UserStorageProvider": [ + { + "name": "my-realm-userstorage", + "providerId": "ldap", + "config": { + "pagination": [ + "true" + ], + "fullSyncPeriod": [ + "-1" + ], + "connectionPooling": [ + "true" + ], + "usersDn": [ + "someDN" + ], + "cachePolicy": [ + "DEFAULT" + ], + "useKerberosForPasswordAuthentication": [ + "false" + ], + "importEnabled": [ + "true" + ], + "enabled": [ + "true" + ], + "changedSyncPeriod": [ + "-1" + ], + "bindCredential": [ + "adadasdasdasdasdasdasdasd" + ], + "bindDn": [ + "someBindDN" + ], + "usernameLDAPAttribute": [ + "userPrincipalName" + ], + "lastSync": [ + "1539695370" + ], + "vendor": [ + "ad" + ], + "uuidLDAPAttribute": [ + "objectGUID" + ], + "allowKerberosAuthentication": [ + "false" + ], + "connectionUrl": [ + "ldaps://1.something.local ldaps://1.something.local" + ], + "syncRegistrations": [ + "false" + ], + "authType": [ + "simple" + ], + "connectionTimeout": [ + "60000" + ], + "debug": [ + "false" + ], + "searchScope": [ + "2" + ], + "useTruststoreSpi": [ + "never" + ], + "priority": [ + "0" + ], + "userObjectClasses": [ + "person, organizationalPerson, user" + ], + "rdnLDAPAttribute": [ + "cn" + ], + "readTimeout": [ + "60000" + ], + "editMode": [ + "WRITABLE" + ], + "validatePasswordPolicy": [ + "false" + ], + "batchSizeForSync": [ + "1000" + ] + } + } + ] + } +} diff --git a/src/test/resources/import-files/components/11_update_realm__update_component_remove_subcomponent.json b/src/test/resources/import-files/components/11_update_realm__update_component_remove_subcomponent.json new file mode 100644 index 000000000..42952b26d --- /dev/null +++ b/src/test/resources/import-files/components/11_update_realm__update_component_remove_subcomponent.json @@ -0,0 +1,157 @@ +{ + "enabled": true, + "realm": "realmWithSubComponents", + "components": { + "org.keycloak.keys.KeyProvider": [ + { + "name": "rsa-generated", + "providerId": "rsa-generated", + "config": { + "keySize": [ + "2048" + ], + "priority": [ + "100" + ] + } + }, + { + "name": "hmac-generated", + "providerId": "hmac-generated", + "config": { + "secretSize": [ + "32" + ], + "priority": [ + "100" + ] + } + } + ], + "org.keycloak.storage.UserStorageProvider": [ + { + "name": "my-realm-userstorage", + "providerId": "ldap", + "subComponents": { + "org.keycloak.storage.ldap.mappers.LDAPStorageMapper": [ + { + "name": "picture", + "providerId": "user-attribute-ldap-mapper", + "subComponents": { + }, + "config": { + "ldap.attribute": [ + "jpegPhoto" + ], + "is.binary.attribute": [ + "true" + ], + "read.only": [ + "true" + ], + "always.read.value.from.ldap": [ + "true" + ], + "user.model.attribute": [ + "picture" + ] + } + } + ] + }, + "config": { + "pagination": [ + "true" + ], + "fullSyncPeriod": [ + "-1" + ], + "connectionPooling": [ + "true" + ], + "usersDn": [ + "someDN" + ], + "cachePolicy": [ + "DEFAULT" + ], + "useKerberosForPasswordAuthentication": [ + "false" + ], + "importEnabled": [ + "true" + ], + "enabled": [ + "true" + ], + "changedSyncPeriod": [ + "-1" + ], + "bindCredential": [ + "adadasdasdasdasdasdasdasd" + ], + "bindDn": [ + "someBindDN" + ], + "usernameLDAPAttribute": [ + "userPrincipalName" + ], + "lastSync": [ + "1539695370" + ], + "vendor": [ + "ad" + ], + "uuidLDAPAttribute": [ + "objectGUID" + ], + "allowKerberosAuthentication": [ + "false" + ], + "connectionUrl": [ + "ldaps://1.something.local ldaps://1.something.local" + ], + "syncRegistrations": [ + "false" + ], + "authType": [ + "simple" + ], + "connectionTimeout": [ + "60000" + ], + "debug": [ + "false" + ], + "searchScope": [ + "2" + ], + "useTruststoreSpi": [ + "never" + ], + "priority": [ + "0" + ], + "userObjectClasses": [ + "person, organizationalPerson, user" + ], + "rdnLDAPAttribute": [ + "cn" + ], + "readTimeout": [ + "60000" + ], + "editMode": [ + "WRITABLE" + ], + "validatePasswordPolicy": [ + "false" + ], + "batchSizeForSync": [ + "1000" + ] + } + } + ] + } +} diff --git a/src/test/resources/import-files/components/12_update_realm__update_component_remove_all_subcomponent.json b/src/test/resources/import-files/components/12_update_realm__update_component_remove_all_subcomponent.json new file mode 100644 index 000000000..f6964e41d --- /dev/null +++ b/src/test/resources/import-files/components/12_update_realm__update_component_remove_all_subcomponent.json @@ -0,0 +1,131 @@ +{ + "enabled": true, + "realm": "realmWithSubComponents", + "components": { + "org.keycloak.keys.KeyProvider": [ + { + "name": "rsa-generated", + "providerId": "rsa-generated", + "config": { + "keySize": [ + "2048" + ], + "priority": [ + "100" + ] + } + }, + { + "name": "hmac-generated", + "providerId": "hmac-generated", + "config": { + "secretSize": [ + "32" + ], + "priority": [ + "100" + ] + } + } + ], + "org.keycloak.storage.UserStorageProvider": [ + { + "name": "my-realm-userstorage", + "providerId": "ldap", + "subComponents": {}, + "config": { + "pagination": [ + "true" + ], + "fullSyncPeriod": [ + "-1" + ], + "connectionPooling": [ + "true" + ], + "usersDn": [ + "someDN" + ], + "cachePolicy": [ + "DEFAULT" + ], + "useKerberosForPasswordAuthentication": [ + "false" + ], + "importEnabled": [ + "true" + ], + "enabled": [ + "true" + ], + "changedSyncPeriod": [ + "-1" + ], + "bindCredential": [ + "adadasdasdasdasdasdasdasd" + ], + "bindDn": [ + "someBindDN" + ], + "usernameLDAPAttribute": [ + "userPrincipalName" + ], + "lastSync": [ + "1539695370" + ], + "vendor": [ + "ad" + ], + "uuidLDAPAttribute": [ + "objectGUID" + ], + "allowKerberosAuthentication": [ + "false" + ], + "connectionUrl": [ + "ldaps://1.something.local ldaps://1.something.local" + ], + "syncRegistrations": [ + "false" + ], + "authType": [ + "simple" + ], + "connectionTimeout": [ + "60000" + ], + "debug": [ + "false" + ], + "searchScope": [ + "2" + ], + "useTruststoreSpi": [ + "never" + ], + "priority": [ + "0" + ], + "userObjectClasses": [ + "person, organizationalPerson, user" + ], + "rdnLDAPAttribute": [ + "cn" + ], + "readTimeout": [ + "60000" + ], + "editMode": [ + "WRITABLE" + ], + "validatePasswordPolicy": [ + "false" + ], + "batchSizeForSync": [ + "1000" + ] + } + } + ] + } +} diff --git a/src/test/resources/import-files/components/6_create_realm__with_component_without_subcomponent.json b/src/test/resources/import-files/components/6_create_realm__with_component_without_subcomponent.json index fb9a8cdbc..f6964e41d 100644 --- a/src/test/resources/import-files/components/6_create_realm__with_component_without_subcomponent.json +++ b/src/test/resources/import-files/components/6_create_realm__with_component_without_subcomponent.json @@ -2,6 +2,32 @@ "enabled": true, "realm": "realmWithSubComponents", "components": { + "org.keycloak.keys.KeyProvider": [ + { + "name": "rsa-generated", + "providerId": "rsa-generated", + "config": { + "keySize": [ + "2048" + ], + "priority": [ + "100" + ] + } + }, + { + "name": "hmac-generated", + "providerId": "hmac-generated", + "config": { + "secretSize": [ + "32" + ], + "priority": [ + "100" + ] + } + } + ], "org.keycloak.storage.UserStorageProvider": [ { "name": "my-realm-userstorage", diff --git a/src/test/resources/import-files/components/7_update_realm__update_component_add_subcomponent.json b/src/test/resources/import-files/components/7_update_realm__update_component_add_subcomponent.json index ec0debae4..37e756204 100644 --- a/src/test/resources/import-files/components/7_update_realm__update_component_add_subcomponent.json +++ b/src/test/resources/import-files/components/7_update_realm__update_component_add_subcomponent.json @@ -2,6 +2,32 @@ "enabled": true, "realm": "realmWithSubComponents", "components": { + "org.keycloak.keys.KeyProvider": [ + { + "name": "rsa-generated", + "providerId": "rsa-generated", + "config": { + "keySize": [ + "2048" + ], + "priority": [ + "100" + ] + } + }, + { + "name": "hmac-generated", + "providerId": "hmac-generated", + "config": { + "secretSize": [ + "32" + ], + "priority": [ + "100" + ] + } + } + ], "org.keycloak.storage.UserStorageProvider": [ { "name": "my-realm-userstorage", diff --git a/src/test/resources/import-files/components/8_update_realm__update_component_add_more_subcomponent.json b/src/test/resources/import-files/components/8_update_realm__update_component_add_more_subcomponent.json index d06085c1f..ecea57d37 100644 --- a/src/test/resources/import-files/components/8_update_realm__update_component_add_more_subcomponent.json +++ b/src/test/resources/import-files/components/8_update_realm__update_component_add_more_subcomponent.json @@ -2,6 +2,32 @@ "enabled": true, "realm": "realmWithSubComponents", "components": { + "org.keycloak.keys.KeyProvider": [ + { + "name": "rsa-generated", + "providerId": "rsa-generated", + "config": { + "keySize": [ + "2048" + ], + "priority": [ + "100" + ] + } + }, + { + "name": "hmac-generated", + "providerId": "hmac-generated", + "config": { + "secretSize": [ + "32" + ], + "priority": [ + "100" + ] + } + } + ], "org.keycloak.storage.UserStorageProvider": [ { "name": "my-realm-userstorage", @@ -18,14 +44,11 @@ "jpegPhoto" ], "is.mandatory.in.ldap": [ - "false" + "true" ], "is.binary.attribute": [ "true" ], - "read.only": [ - "false" - ], "always.read.value.from.ldap": [ "true" ], diff --git a/src/test/resources/import-files/components/97_update_realm__skip_components.json b/src/test/resources/import-files/components/97_update_realm__skip_components.json new file mode 100644 index 000000000..3190c2dd0 --- /dev/null +++ b/src/test/resources/import-files/components/97_update_realm__skip_components.json @@ -0,0 +1,4 @@ +{ + "enabled": true, + "realm": "realmWithSubComponents" +} diff --git a/src/test/resources/import-files/components/98_update_realm__remove_component.json b/src/test/resources/import-files/components/98_update_realm__remove_component.json new file mode 100644 index 000000000..6f7f16f50 --- /dev/null +++ b/src/test/resources/import-files/components/98_update_realm__remove_component.json @@ -0,0 +1,51 @@ +{ + "enabled": true, + "realm": "realmWithSubComponents", + "components": { + "org.keycloak.keys.KeyProvider": [ + { + "name": "rsa-generated", + "providerId": "rsa-generated", + "config": { + "keySize": [ + "2048" + ], + "priority": [ + "100" + ] + } + }, + { + "name": "hmac-generated", + "providerId": "hmac-generated", + "config": { + "secretSize": [ + "32" + ], + "priority": [ + "100" + ] + } + } + ], + "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ + { + "name": "Allowed Protocol Mapper Types", + "providerId": "allowed-protocol-mappers", + "subType": "authenticated", + "config": { + "allowed-protocol-mapper-types": [ + "oidc-full-name-mapper", + "oidc-sha256-pairwise-sub-mapper", + "oidc-address-mapper", + "saml-user-property-mapper", + "oidc-usermodel-property-mapper", + "saml-role-list-mapper", + "saml-user-attribute-mapper", + "oidc-usermodel-attribute-mapper" + ] + } + } + ] + } +} diff --git a/src/test/resources/import-files/components/99_update_realm__remove_all_components.json b/src/test/resources/import-files/components/99_update_realm__remove_all_components.json new file mode 100644 index 000000000..f51a92976 --- /dev/null +++ b/src/test/resources/import-files/components/99_update_realm__remove_all_components.json @@ -0,0 +1,5 @@ +{ + "enabled": true, + "realm": "realmWithSubComponents", + "components": {} +} diff --git a/src/test/resources/import-files/components/9_update_realm__update_component_update_subcomponent.json b/src/test/resources/import-files/components/9_update_realm__update_component_update_subcomponent.json index ab3a64851..0e1ab4d49 100644 --- a/src/test/resources/import-files/components/9_update_realm__update_component_update_subcomponent.json +++ b/src/test/resources/import-files/components/9_update_realm__update_component_update_subcomponent.json @@ -2,6 +2,32 @@ "enabled": true, "realm": "realmWithSubComponents", "components": { + "org.keycloak.keys.KeyProvider": [ + { + "name": "rsa-generated", + "providerId": "rsa-generated", + "config": { + "keySize": [ + "2048" + ], + "priority": [ + "100" + ] + } + }, + { + "name": "hmac-generated", + "providerId": "hmac-generated", + "config": { + "secretSize": [ + "32" + ], + "priority": [ + "100" + ] + } + } + ], "org.keycloak.storage.UserStorageProvider": [ { "name": "my-realm-userstorage", @@ -17,6 +43,9 @@ "ldap.attribute": [ "jpegPhoto" ], + "is.mandatory.in.ldap": [ + "false" + ], "is.binary.attribute": [ "true" ], diff --git a/src/test/resources/import-files/managed-no-delete/0_create_simple-realm.json b/src/test/resources/import-files/managed-no-delete/0_create_simple-realm.json index 753a8e20f..7eba07681 100644 --- a/src/test/resources/import-files/managed-no-delete/0_create_simple-realm.json +++ b/src/test/resources/import-files/managed-no-delete/0_create_simple-realm.json @@ -56,5 +56,198 @@ "offline_access" ] } - ] + ], + "components": { + "org.keycloak.keys.KeyProvider": [ + { + "name": "rsa-generated", + "providerId": "rsa-generated", + "config": { + "keySize": [ + "2048" + ], + "priority": [ + "100" + ] + } + }, + { + "name": "hmac-generated", + "providerId": "hmac-generated", + "config": { + "secretSize": [ + "32" + ], + "priority": [ + "100" + ] + } + } + ], + "org.keycloak.storage.UserStorageProvider": [ + { + "name": "my-realm-userstorage", + "providerId": "ldap", + "subComponents": { + "org.keycloak.storage.ldap.mappers.LDAPStorageMapper": [ + { + "name": "picture", + "providerId": "user-attribute-ldap-mapper", + "subComponents": { + }, + "config": { + "ldap.attribute": [ + "jpegPhoto" + ], + "is.mandatory.in.ldap": [ + "false" + ], + "is.binary.attribute": [ + "true" + ], + "read.only": [ + "true" + ], + "always.read.value.from.ldap": [ + "true" + ], + "user.model.attribute": [ + "picture" + ] + } + }, + { + "name": "my-realm-role-mapper", + "providerId": "role-ldap-mapper", + "subComponents": {}, + "config": { + "mode": [ + "LDAP_ONLY" + ], + "membership.attribute.type": [ + "DN" + ], + "user.roles.retrieve.strategy": [ + "LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY" + ], + "roles.dn": [ + "someDN" + ], + "membership.ldap.attribute": [ + "member" + ], + "membership.user.ldap.attribute": [ + "userPrincipalName" + ], + "memberof.ldap.attribute": [ + "memberOf" + ], + "role.name.ldap.attribute": [ + "cn" + ], + "use.realm.roles.mapping": [ + "true" + ], + "role.object.classes": [ + "group" + ] + } + } + ] + }, + "config": { + "pagination": [ + "true" + ], + "fullSyncPeriod": [ + "-1" + ], + "connectionPooling": [ + "true" + ], + "usersDn": [ + "someDN" + ], + "cachePolicy": [ + "DEFAULT" + ], + "useKerberosForPasswordAuthentication": [ + "false" + ], + "importEnabled": [ + "true" + ], + "enabled": [ + "true" + ], + "changedSyncPeriod": [ + "-1" + ], + "bindCredential": [ + "adadasdasdasdasdasdasdasd" + ], + "bindDn": [ + "someBindDN" + ], + "usernameLDAPAttribute": [ + "userPrincipalName" + ], + "lastSync": [ + "1539695370" + ], + "vendor": [ + "ad" + ], + "uuidLDAPAttribute": [ + "objectGUID" + ], + "allowKerberosAuthentication": [ + "false" + ], + "connectionUrl": [ + "ldaps://1.something.local ldaps://1.something.local" + ], + "syncRegistrations": [ + "false" + ], + "authType": [ + "simple" + ], + "connectionTimeout": [ + "60000" + ], + "debug": [ + "false" + ], + "searchScope": [ + "2" + ], + "useTruststoreSpi": [ + "never" + ], + "priority": [ + "0" + ], + "userObjectClasses": [ + "person, organizationalPerson, user" + ], + "rdnLDAPAttribute": [ + "cn" + ], + "readTimeout": [ + "60000" + ], + "editMode": [ + "WRITABLE" + ], + "validatePasswordPolicy": [ + "false" + ], + "batchSizeForSync": [ + "1000" + ] + } + } + ] + } } diff --git a/src/test/resources/import-files/managed-no-delete/1_update-realm_not-delete-one.json b/src/test/resources/import-files/managed-no-delete/1_update-realm_not-delete-one.json index 95b88ec69..b377f6af4 100644 --- a/src/test/resources/import-files/managed-no-delete/1_update-realm_not-delete-one.json +++ b/src/test/resources/import-files/managed-no-delete/1_update-realm_not-delete-one.json @@ -35,5 +35,172 @@ "offline_access" ] } - ] + ], + "components": { + "org.keycloak.keys.KeyProvider": [ + { + "name": "rsa-generated", + "providerId": "rsa-generated", + "config": { + "keySize": [ + "2048" + ], + "priority": [ + "100" + ] + } + }, + { + "name": "hmac-generated", + "providerId": "hmac-generated", + "config": { + "secretSize": [ + "32" + ], + "priority": [ + "100" + ] + } + } + ], + "org.keycloak.storage.UserStorageProvider": [ + { + "name": "my-realm-userstorage", + "providerId": "ldap", + "subComponents": { + "org.keycloak.storage.ldap.mappers.LDAPStorageMapper": [ + { + "name": "my-realm-role-mapper", + "providerId": "role-ldap-mapper", + "subComponents": {}, + "config": { + "mode": [ + "LDAP_ONLY" + ], + "membership.attribute.type": [ + "DN" + ], + "user.roles.retrieve.strategy": [ + "LOAD_ROLES_BY_MEMBER_ATTRIBUTE_RECURSIVELY" + ], + "roles.dn": [ + "someDN" + ], + "membership.ldap.attribute": [ + "member" + ], + "membership.user.ldap.attribute": [ + "userPrincipalName" + ], + "memberof.ldap.attribute": [ + "memberOf" + ], + "role.name.ldap.attribute": [ + "cn" + ], + "use.realm.roles.mapping": [ + "true" + ], + "role.object.classes": [ + "group" + ] + } + } + ] + }, + "config": { + "pagination": [ + "true" + ], + "fullSyncPeriod": [ + "-1" + ], + "connectionPooling": [ + "true" + ], + "usersDn": [ + "someDN" + ], + "cachePolicy": [ + "DEFAULT" + ], + "useKerberosForPasswordAuthentication": [ + "false" + ], + "importEnabled": [ + "true" + ], + "enabled": [ + "true" + ], + "changedSyncPeriod": [ + "-1" + ], + "bindCredential": [ + "adadasdasdasdasdasdasdasd" + ], + "bindDn": [ + "someBindDN" + ], + "usernameLDAPAttribute": [ + "userPrincipalName" + ], + "lastSync": [ + "1539695370" + ], + "vendor": [ + "ad" + ], + "uuidLDAPAttribute": [ + "objectGUID" + ], + "allowKerberosAuthentication": [ + "false" + ], + "connectionUrl": [ + "ldaps://1.something.local ldaps://1.something.local" + ], + "syncRegistrations": [ + "false" + ], + "authType": [ + "simple" + ], + "connectionTimeout": [ + "60000" + ], + "debug": [ + "false" + ], + "searchScope": [ + "2" + ], + "useTruststoreSpi": [ + "never" + ], + "priority": [ + "0" + ], + "userObjectClasses": [ + "person, organizationalPerson, user" + ], + "rdnLDAPAttribute": [ + "cn" + ], + "readTimeout": [ + "60000" + ], + "editMode": [ + "WRITABLE" + ], + "validatePasswordPolicy": [ + "false" + ], + "batchSizeForSync": [ + "1000" + ] + } + } + ] + } } diff --git a/src/test/resources/import-files/managed-no-delete/2_update-realm_not-delete-all.json b/src/test/resources/import-files/managed-no-delete/2_update-realm_not-delete-all.json index 0a8e27e68..0375660cb 100644 --- a/src/test/resources/import-files/managed-no-delete/2_update-realm_not-delete-all.json +++ b/src/test/resources/import-files/managed-no-delete/2_update-realm_not-delete-all.json @@ -4,5 +4,6 @@ "groups": [], "requiredActions": [], "clientScopes": [], - "scopeMappings": [] + "scopeMappings": [], + "components": {} }