diff --git a/CHANGELOG.md b/CHANGELOG.md index 2377d749e..8deefd539 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), ## [Unreleased] +- Added option to calculate checksum for each import file ([#1015](https://github.com/adorsys/keycloak-config-cli/issues/1015)) + ## [5.12.0] - 2024-03-28 - Added support for managing message bundles diff --git a/README.md b/README.md index 76c9ad924..f69688c84 100644 --- a/README.md +++ b/README.md @@ -215,25 +215,27 @@ Checkout helm docs about [chart dependencies](https://helm.sh/docs/topics/charts ### Import options -| CLI Option | ENV Variable | Description | Default | Docs | -|-------------------------------------------------------|----------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|-----------|-------------------------------| -| --import.validate | `IMPORT_VALIDATE` | Validate configuration settings | `false` | | -| --import.parallel | `IMPORT_PARALLEL` | Enable parallel import of certain resources | `false` | | -| --import.files.locations | `IMPORT_FILES_LOCATIONS` | Location of config files (URL, file path, or Ant-style pattern) | - | [IMPORT.md](docs/IMPORT.md) | -| --import.files.include-hidden-files | `IMPORT_FILES_INCLUDE_HIDDEN_FILES` | Includes files that marked as hidden | `false` | | -| --import.files.excludes | `IMPORT_FILES_EXCLUDES` | Exclude files with Ant-style pattern | - | | -| --import.cache.enabled | `IMPORT_CACHE_ENABLED` | Enable caching of import file locations | `true` | | -| --import.cache.key | `IMPORT_CACHE_KEY` | Cache key for importing config. | `default` | | -| --import.remote-state.enabled | `IMPORT_REMOTESTATE_ENABLED` | Enable remote state management. Purge only resources managed by keycloak-config-cli. | `true` | [MANAGED.md](docs/MANAGED.md) | -| --import.remote-state.encryption-key | `IMPORT_REMOTESTATE_ENCRYPTIONKEY` | Enables remote state in encrypted format. If unset, state will be stored in plain | - | | -| --import.var-substitution.enabled | `IMPORT_VARSUBSTITUTION_ENABLED` | Enable variable substitution config files | `false` | | -| --import.var-substitution.nested | `IMPORT_VARSUBSTITUTION_NESTED` | Expand variables in variables. | `true` | | -| --import.var-substitution.undefined-is-error | `IMPORT_VARSUBSTITUTION_UNDEFINEDISTERROR` | Raise exceptions, if variables are not defined. | `true` | | -| --import.var-substitution.prefix | `IMPORT_VARSUBSTITUTION_PREFIX` | Configure the variable prefix, if `import.var-substitution.enabled` is `true`. | `$(` | | -| --import.var-substitution.suffix | `IMPORT_VARSUBSTITUTION_SUFFIX` | Configure the variable suffix, if `import.var-substitution.enabled` is `true`. | `)` | | -| --import.behaviors.sync-user-federation | `IMPORT_BEHAVIORS_SYNC_USER_FEDERATION` | Enable the synchronization of user federation. | `false` | | -| --import.behaviors.remove-default-role-from-user | `IMPORT_BEHAVIORS_REMOVEDEFAULTROLEFROMUSER` | The default setting of this flag prevents keycloak-config-cli from removing `default-roles-$REALM`, even if its not defined in the import json. To make keycloak-config-cli able to remove the `default-role-$REALM`, `import.remove-default-role-from-user` must be set to true. In conclusion, you have to add the `default-role-$REALM` to the realm import on certain users, if you want not remove the `default-role-$REALM`. | `false` | | -| --import.behaviors.skip-attributes-for-federated-user | `IMPORT_BEHAVIORS_SKIP_ATTRIBUTESFORFEDERATEDUSER` | Set attributes to null for federated users to avoid read only conflicts | `false` | | +| CLI Option | ENV Variable | Description | Default | Docs | +|-------------------------------------------------------|----------------------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|------------|-------------------------------| +| --import.validate | `IMPORT_VALIDATE` | Validate configuration settings | `false` | | +| --import.parallel | `IMPORT_PARALLEL` | Enable parallel import of certain resources | `false` | | +| --import.files.locations | `IMPORT_FILES_LOCATIONS` | Location of config files (URL, file path, or Ant-style pattern) | - | [IMPORT.md](docs/IMPORT.md) | +| --import.files.include-hidden-files | `IMPORT_FILES_INCLUDE_HIDDEN_FILES` | Includes files that marked as hidden | `false` | | +| --import.files.excludes | `IMPORT_FILES_EXCLUDES` | Exclude files with Ant-style pattern | - | | +| --import.cache.enabled | `IMPORT_CACHE_ENABLED` | Enable caching of import file locations | `true` | | +| --import.cache.key | `IMPORT_CACHE_KEY` | Cache key for importing config. | `default` | | +| --import.remote-state.enabled | `IMPORT_REMOTESTATE_ENABLED` | Enable remote state management. Purge only resources managed by keycloak-config-cli. | `true` | [MANAGED.md](docs/MANAGED.md) | +| --import.remote-state.encryption-key | `IMPORT_REMOTESTATE_ENCRYPTIONKEY` | Enables remote state in encrypted format. If unset, state will be stored in plain | - | | +| --import.var-substitution.enabled | `IMPORT_VARSUBSTITUTION_ENABLED` | Enable variable substitution config files | `false` | | +| --import.var-substitution.nested | `IMPORT_VARSUBSTITUTION_NESTED` | Expand variables in variables. | `true` | | +| --import.var-substitution.undefined-is-error | `IMPORT_VARSUBSTITUTION_UNDEFINEDISTERROR` | Raise exceptions, if variables are not defined. | `true` | | +| --import.var-substitution.prefix | `IMPORT_VARSUBSTITUTION_PREFIX` | Configure the variable prefix, if `import.var-substitution.enabled` is `true`. | `$(` | | +| --import.var-substitution.suffix | `IMPORT_VARSUBSTITUTION_SUFFIX` | Configure the variable suffix, if `import.var-substitution.enabled` is `true`. | `)` | | +| --import.behaviors.sync-user-federation | `IMPORT_BEHAVIORS_SYNC_USER_FEDERATION` | Enable the synchronization of user federation. | `false` | | +| --import.behaviors.remove-default-role-from-user | `IMPORT_BEHAVIORS_REMOVEDEFAULTROLEFROMUSER` | The default setting of this flag prevents keycloak-config-cli from removing `default-roles-$REALM`, even if its not defined in the import json. To make keycloak-config-cli able to remove the `default-role-$REALM`, `import.remove-default-role-from-user` must be set to true. In conclusion, you have to add the `default-role-$REALM` to the realm import on certain users, if you want not remove the `default-role-$REALM`. | `false` | | +| --import.behaviors.skip-attributes-for-federated-user | `IMPORT_BEHAVIORS_SKIP_ATTRIBUTESFORFEDERATEDUSER` | Set attributes to null for federated users to avoid read only conflicts | `false` | | +| --import.behaviors.checksum-with-cache-key | `IMPORT_BEHAVIORS_CHECKSUM_WITH_CACHE_KEY` | Use cache key to store the checksum, if set to `false` a checksum for each import file is stored | `true` | | +| --import.behaviors.checksum-changed | `IMPORT_BEHAVIORS_CHECKSUM_CHANGED` | Defines the behavior if the checksum of an imported file has changed. Set to `fail` when import should be aborted, `continue` reimport and update the checksum. | `continue` | | ## Spring boot options diff --git a/src/main/java/de/adorsys/keycloak/config/model/RealmImport.java b/src/main/java/de/adorsys/keycloak/config/model/RealmImport.java index a974136a5..8147fb780 100644 --- a/src/main/java/de/adorsys/keycloak/config/model/RealmImport.java +++ b/src/main/java/de/adorsys/keycloak/config/model/RealmImport.java @@ -39,6 +39,7 @@ public class RealmImport extends RealmRepresentation { private Map> messageBundles; private String checksum; + private String source; @Override @SuppressWarnings("java:S1168") @@ -54,12 +55,6 @@ public void setAuthenticationFlowImports(List authenti this.authenticationFlowImports = authenticationFlowImports; } - @SuppressWarnings("unused") - @JsonSetter("userProfile") - public void setUserProfile(Map>> userProfile) { - this.userProfile = userProfile; - } - public Map> getMessageBundles() { return messageBundles; } @@ -83,4 +78,14 @@ public String getChecksum() { public void setChecksum(String checksum) { this.checksum = checksum; } + + @JsonIgnore + public String getSource() { + return source; + } + + @JsonIgnore + public void setSource(String source) { + this.source = source; + } } 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 95802b857..014159b2b 100644 --- a/src/main/java/de/adorsys/keycloak/config/properties/ImportConfigProperties.java +++ b/src/main/java/de/adorsys/keycloak/config/properties/ImportConfigProperties.java @@ -343,10 +343,19 @@ public static class ImportBehaviorsProperties { @NotNull private final boolean skipAttributesForFederatedUser; - public ImportBehaviorsProperties(boolean syncUserFederation, boolean removeDefaultRoleFromUser, boolean skipAttributesForFederatedUser) { + @NotNull + private final boolean checksumWithCacheKey; + + @NotNull + private final ChecksumChangedOption checksumChanged; + + public ImportBehaviorsProperties(boolean syncUserFederation, boolean removeDefaultRoleFromUser, boolean skipAttributesForFederatedUser, + boolean checksumWithCacheKey, ChecksumChangedOption checksumChanged) { this.syncUserFederation = syncUserFederation; this.removeDefaultRoleFromUser = removeDefaultRoleFromUser; this.skipAttributesForFederatedUser = skipAttributesForFederatedUser; + this.checksumWithCacheKey = checksumWithCacheKey; + this.checksumChanged = checksumChanged; } public boolean isSyncUserFederation() { @@ -360,6 +369,18 @@ public boolean isRemoveDefaultRoleFromUser() { public boolean isSkipAttributesForFederatedUser() { return skipAttributesForFederatedUser; } + + public boolean isChecksumWithCacheKey() { + return checksumWithCacheKey; + } + + public ChecksumChangedOption getChecksumChanged() { + return checksumChanged; + } + + public enum ChecksumChangedOption { + CONTINUE, FAIL + } } @SuppressWarnings("unused") diff --git a/src/main/java/de/adorsys/keycloak/config/provider/KeycloakImportProvider.java b/src/main/java/de/adorsys/keycloak/config/provider/KeycloakImportProvider.java index 24dd458e1..13506aefa 100644 --- a/src/main/java/de/adorsys/keycloak/config/provider/KeycloakImportProvider.java +++ b/src/main/java/de/adorsys/keycloak/config/provider/KeycloakImportProvider.java @@ -211,7 +211,10 @@ private Pair> readRealmImportFromImportResource(Import } catch (Exception e) { throw new InvalidImportException("Unable to parse file '" + location + "': " + e.getMessage(), e); } - realmImports.forEach(realmImport -> realmImport.setChecksum(contentChecksum)); + realmImports.forEach(realmImport -> { + realmImport.setChecksum(contentChecksum); + realmImport.setSource(location); + }); return new ImmutablePair<>(location, realmImports); } diff --git a/src/main/java/de/adorsys/keycloak/config/service/checksum/ChecksumService.java b/src/main/java/de/adorsys/keycloak/config/service/checksum/ChecksumService.java index ab4525508..735e4b14d 100644 --- a/src/main/java/de/adorsys/keycloak/config/service/checksum/ChecksumService.java +++ b/src/main/java/de/adorsys/keycloak/config/service/checksum/ChecksumService.java @@ -20,9 +20,13 @@ package de.adorsys.keycloak.config.service.checksum; +import de.adorsys.keycloak.config.exception.InvalidImportException; import de.adorsys.keycloak.config.model.RealmImport; import de.adorsys.keycloak.config.properties.ImportConfigProperties; +import de.adorsys.keycloak.config.properties.ImportConfigProperties.ImportBehaviorsProperties.ChecksumChangedOption; import de.adorsys.keycloak.config.repository.RealmRepository; +import org.apache.commons.codec.digest.DigestUtils; +import org.apache.commons.io.FilenameUtils; import org.keycloak.representations.idm.RealmRepresentation; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -51,25 +55,52 @@ public void doImport(RealmImport realmImport) { Map customAttributes = existingRealm.getAttributes(); String importChecksum = realmImport.getChecksum(); - customAttributes.put(getCustomAttributeKey(), importChecksum); + String attributeKey = getCustomAttributeKey(realmImport); + customAttributes.put(attributeKey, importChecksum); realmRepository.update(existingRealm); - logger.debug("Updated import checksum of realm '{}' to '{}'", realmImport.getRealm(), importChecksum); + logger.debug("Updated import checksum of realm '{}' to '{}', attributeKey: '{}'", realmImport.getRealm(), importChecksum, attributeKey); } public boolean hasToBeUpdated(RealmImport realmImport) { RealmRepresentation existingRealm = realmRepository.get(realmImport.getRealm()); Map customAttributes = existingRealm.getAttributes(); - String readChecksum = customAttributes.get(getCustomAttributeKey()); + String readChecksum = customAttributes.get(getCustomAttributeKey(realmImport)); + if (readChecksum == null) { + return true; + } - return !Objects.equals(realmImport.getChecksum(), readChecksum); + if (Objects.equals(realmImport.getChecksum(), readChecksum)) { + return false; + } + + // checksum has changed + if (ChecksumChangedOption.CONTINUE.equals(importConfigProperties.getBehaviors().getChecksumChanged())) { + return true; + } else if (ChecksumChangedOption.FAIL.equals(importConfigProperties.getBehaviors().getChecksumChanged())) { + throw new InvalidImportException( + String.format("The checksum of import '%s' has changed from: '%s', to: '%s'", + realmImport.getSource(), readChecksum, realmImport.getChecksum()) + ); + } else { + throw new IllegalStateException("Unknown behavior constant: " + importConfigProperties.getBehaviors().getChecksumChanged()); + } } - private String getCustomAttributeKey() { + @SuppressWarnings("java:S4790") + private String getCustomAttributeKey(RealmImport realmImport) { + String attributeSuffix; + if (importConfigProperties.getBehaviors().isChecksumWithCacheKey()) { + attributeSuffix = importConfigProperties.getCache().getKey(); + } else { + attributeSuffix = FilenameUtils.getName(realmImport.getSource()) + "_" + DigestUtils.md5Hex(realmImport.getSource()); + } + return MessageFormat.format( ImportConfigProperties.REALM_CHECKSUM_ATTRIBUTE_PREFIX_KEY, - importConfigProperties.getCache().getKey() + attributeSuffix ); } + } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 22cf5e0ee..031fc6bbd 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -34,6 +34,8 @@ import.remote-state.encryption-salt=2B521C795FBE2F2425DB150CD3700BA9 import.behaviors.remove-default-role-from-user=false import.behaviors.skip-attributes-for-federated-user=false import.behaviors.sync-user-federation=false +import.behaviors.checksum-with-cache-key=true +import.behaviors.checksum-changed=continue import.managed.authentication-flow=full import.managed.group=full import.managed.required-action=full 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 f061c95ac..b6adfaeba 100644 --- a/src/test/java/de/adorsys/keycloak/config/properties/ImportConfigPropertiesTest.java +++ b/src/test/java/de/adorsys/keycloak/config/properties/ImportConfigPropertiesTest.java @@ -22,6 +22,7 @@ import de.adorsys.keycloak.config.extensions.GithubActionsExtension; import de.adorsys.keycloak.config.properties.ImportConfigProperties.ImportManagedProperties.ImportManagedPropertiesValues; +import de.adorsys.keycloak.config.properties.ImportConfigProperties.ImportBehaviorsProperties.ChecksumChangedOption; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.beans.factory.annotation.Autowired; @@ -72,6 +73,8 @@ "import.behaviors.sync-user-federation=true", "import.behaviors.remove-default-role-from-user=true", "import.behaviors.skip-attributes-for-federated-user=true", + "import.behaviors.checksum-with-cache-key=true", + "import.behaviors.checksum-changed=fail" }) class ImportConfigPropertiesTest { @@ -112,6 +115,8 @@ void shouldPopulateConfigurationProperties() { assertThat(properties.getBehaviors().isSyncUserFederation(), is(true)); assertThat(properties.getBehaviors().isRemoveDefaultRoleFromUser(), is(true)); assertThat(properties.getBehaviors().isSkipAttributesForFederatedUser(), is(true)); + assertThat(properties.getBehaviors().isChecksumWithCacheKey(), is(true)); + assertThat(properties.getBehaviors().getChecksumChanged(), is(ChecksumChangedOption.FAIL)); } @EnableConfigurationProperties(ImportConfigProperties.class) diff --git a/src/test/java/de/adorsys/keycloak/config/service/ChecksumServiceCasesIT.java b/src/test/java/de/adorsys/keycloak/config/service/ChecksumServiceCasesIT.java new file mode 100644 index 000000000..816392fb8 --- /dev/null +++ b/src/test/java/de/adorsys/keycloak/config/service/ChecksumServiceCasesIT.java @@ -0,0 +1,248 @@ +/*- + * ---license-start + * keycloak-config-cli + * --- + * Copyright (C) 2017 - 2024 adorsys GmbH & Co. KG @ https://adorsys.com + * --- + * 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. + * ---license-end + */ + +package de.adorsys.keycloak.config.service; + +import de.adorsys.keycloak.config.AbstractImportIT; +import de.adorsys.keycloak.config.exception.InvalidImportException; +import de.adorsys.keycloak.config.model.RealmImport; +import de.adorsys.keycloak.config.properties.ImportConfigProperties; +import de.adorsys.keycloak.config.service.checksum.ChecksumService; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.keycloak.representations.idm.RealmRepresentation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; + +import java.io.IOException; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Collectors; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.hasEntry; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.startsWith; + +class ChecksumServiceCasesIT { + + private static final String REALM_NAME = "simple"; + + abstract static class AbstractChecksumServiceIT extends AbstractImportIT { + + @Autowired + ChecksumService checksumService; + + @AfterEach + void clearRealms() { + keycloakProvider.getInstance().realms().findAll().forEach(realm -> { + if (!Objects.equals(realm.getRealm(), "master")) { + keycloakProvider.getInstance().realm(realm.getRealm()).remove(); + } + }); + } + + void verifyChecksum(RealmRepresentation realm, String checksum) { + Map attributes = realm.getAttributes(); + var prefix = ImportConfigProperties.REALM_CHECKSUM_ATTRIBUTE_PREFIX_KEY.substring(0, ImportConfigProperties.REALM_CHECKSUM_ATTRIBUTE_PREFIX_KEY.length() - 3); + assertThat(attributes, hasEntry(startsWith(prefix), is(checksum))); + } + + void verifyHasToBeUpdated(RealmImport realmImport, boolean expected) { + var hasToBe = checksumService.hasToBeUpdated(realmImport); + assertThat(hasToBe, is(expected)); + } + + void verifyHasToBeUpdated(String fileName, boolean expected) throws IOException { + var realmImport = getFirstImport(fileName); + var hasToBe = checksumService.hasToBeUpdated(realmImport); + assertThat(hasToBe, is(expected)); + } + + void importAndVerifyChecksum(String filename, String checksum) throws Exception { + doImport(filename); + + var createdRealm = keycloakProvider.getInstance().realm(REALM_NAME).toRepresentation(); + verifyChecksum(createdRealm, checksum); + } + + List importFromDirectory(String location) { + var keycloakImport = keycloakImportProvider.readFromLocations(location); + var realmImports = keycloakImport.getRealmImports().values().stream() + .flatMap(e -> e.values().stream()) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + + realmImports.forEach(realmImport -> realmImportService.doImport(realmImport)); + + return realmImports; + } + } + + @Nested + @ContextConfiguration() + @TestPropertySource(properties = { + "import.behaviors.checksum-with-cache-key=true", + "import.behaviors.checksum-changed=continue" + }) + class DefaultChecksumKeyIT extends AbstractChecksumServiceIT { + + @Test + void hasToBeUpdated_with_single_file() throws Exception { + this.resourcePath = "import-files/simple-realm"; + + var fileName = "00_create_simple-realm.json"; + importAndVerifyChecksum(fileName, "6292be0628c50ff8fc02bd4092f48a731133e4802e158e7bc2ba174524b4ccf1"); + + verifyHasToBeUpdated(fileName, false); + } + + @Test + void hasToBeUpdated_with_multiple_files() throws Exception { + this.resourcePath = "import-files/simple-realm"; + + var fileName01 = "00_create_simple-realm.json"; + importAndVerifyChecksum(fileName01, "6292be0628c50ff8fc02bd4092f48a731133e4802e158e7bc2ba174524b4ccf1"); + + var fileName02 = "01_update_login-theme_to_simple-realm.json"; + importAndVerifyChecksum(fileName02, "4ac94d3adb91122979e80816a8a355a01f9c7c90a25b6b529bf2a572e1158b1c"); + + verifyHasToBeUpdated(fileName01, true); + verifyHasToBeUpdated(fileName02, false); + } + + @Test + void hasToBeUpdated_with_multi_document() throws Exception { + this.resourcePath = "import-files/realm-file-type/auto"; + + var fileName = "2_multi_document.yaml"; + doImport(fileName); + + var createdRealm = keycloakProvider.getInstance().realm("realm-file-type-auto-0").toRepresentation(); + verifyChecksum(createdRealm, "950de6a46f669dbd6c42178fde9d9b6ab3315eed5226c7051ca02c5b35263996"); + + createdRealm = keycloakProvider.getInstance().realm("realm-file-type-auto-1").toRepresentation(); + verifyChecksum(createdRealm, "950de6a46f669dbd6c42178fde9d9b6ab3315eed5226c7051ca02c5b35263996"); + + getImport(fileName).forEach(realmImport -> verifyHasToBeUpdated(realmImport, false)); + } + + @Test + void hasToBeUpdated_with_same_filenames() { + var realmImports = importFromDirectory("classpath:import-files/import/same-names/**/*.yaml"); + realmImports.subList(0, realmImports.size() - 1).forEach(realmImport -> verifyHasToBeUpdated(realmImport, true)); + verifyHasToBeUpdated(realmImports.get(realmImports.size() - 1), false); + } + + } + + @Nested + @ContextConfiguration() + @TestPropertySource(properties = { + "import.behaviors.checksum-with-cache-key=false", + "import.behaviors.checksum-changed=continue" + }) + class PerResourceChecksumKeyIT extends AbstractChecksumServiceIT { + + @Test + void hasToBeUpdated_with_single_file() throws Exception { + this.resourcePath = "import-files/simple-realm"; + + var fileName = "00_create_simple-realm.json"; + importAndVerifyChecksum(fileName, "6292be0628c50ff8fc02bd4092f48a731133e4802e158e7bc2ba174524b4ccf1"); + + verifyHasToBeUpdated(fileName, false); + } + + @Test + void hasToBeUpdated_with_multiple_files() throws Exception { + this.resourcePath = "import-files/simple-realm"; + + var fileName01 = "00_create_simple-realm.json"; + importAndVerifyChecksum(fileName01, "6292be0628c50ff8fc02bd4092f48a731133e4802e158e7bc2ba174524b4ccf1"); + + var fileName02 = "01_update_login-theme_to_simple-realm.json"; + importAndVerifyChecksum(fileName02, "4ac94d3adb91122979e80816a8a355a01f9c7c90a25b6b529bf2a572e1158b1c"); + + verifyHasToBeUpdated(fileName01, false); + verifyHasToBeUpdated(fileName02, false); + } + + @Test + void hasToBeUpdated_with_multi_document() throws Exception { + this.resourcePath = "import-files/realm-file-type/auto"; + + var fileName = "2_multi_document.yaml"; + doImport(fileName); + + var createdRealm = keycloakProvider.getInstance().realm("realm-file-type-auto-0").toRepresentation(); + verifyChecksum(createdRealm, "950de6a46f669dbd6c42178fde9d9b6ab3315eed5226c7051ca02c5b35263996"); + + createdRealm = keycloakProvider.getInstance().realm("realm-file-type-auto-1").toRepresentation(); + verifyChecksum(createdRealm, "950de6a46f669dbd6c42178fde9d9b6ab3315eed5226c7051ca02c5b35263996"); + + getImport(fileName).forEach(realmImport -> verifyHasToBeUpdated(realmImport, false)); + } + + @Test + void hasToBeUpdated_with_same_filenames() { + this.resourcePath = null; + + var realmImports = importFromDirectory("classpath:import-files/import/same-names/**/*.yaml"); + realmImports.forEach(realmImport -> verifyHasToBeUpdated(realmImport, false)); + } + } + + @Nested + @ContextConfiguration() + @TestPropertySource(properties = { + "import.behaviors.checksum-with-cache-key=false", + "import.behaviors.checksum-changed=fail" + }) + class FailWhenChecksumChangedIT extends AbstractChecksumServiceIT { + + @Test + void hasToBeUpdated_with_multiple_files_fails() throws Exception { + this.resourcePath = "import-files/simple-realm"; + + var fileName = "00_create_simple-realm.json"; + importAndVerifyChecksum(fileName, "6292be0628c50ff8fc02bd4092f48a731133e4802e158e7bc2ba174524b4ccf1"); + + var realmImport = getFirstImport(fileName); + realmImport.setChecksum(""); + Assertions.assertThatThrownBy(() -> checksumService.hasToBeUpdated(realmImport)) + .isInstanceOf(InvalidImportException.class) + .hasMessageContaining("checksum", "changed"); + } + + @Test + void hasToBeUpdated_with_same_filenames() { + var realmImports = importFromDirectory("classpath:import-files/import/same-names/**/*.yaml"); + realmImports.forEach(realmImport -> verifyHasToBeUpdated(realmImport, false)); + } + + } + +} diff --git a/src/test/resources/import-files/import/same-names/01-lorem-ipsum/01-create-realm.yaml b/src/test/resources/import-files/import/same-names/01-lorem-ipsum/01-create-realm.yaml new file mode 100644 index 000000000..7363ef82b --- /dev/null +++ b/src/test/resources/import-files/import/same-names/01-lorem-ipsum/01-create-realm.yaml @@ -0,0 +1,2 @@ +realm: same-names +enabled: true diff --git a/src/test/resources/import-files/import/same-names/01-lorem-ipsum/02-update-realm.yaml b/src/test/resources/import-files/import/same-names/01-lorem-ipsum/02-update-realm.yaml new file mode 100644 index 000000000..3c2626c1c --- /dev/null +++ b/src/test/resources/import-files/import/same-names/01-lorem-ipsum/02-update-realm.yaml @@ -0,0 +1,2 @@ +realm: same-names +loginTheme: moped diff --git a/src/test/resources/import-files/import/same-names/02-lorem-ipsum/01-update-realm.yaml b/src/test/resources/import-files/import/same-names/02-lorem-ipsum/01-update-realm.yaml new file mode 100644 index 000000000..4f3e5cc26 --- /dev/null +++ b/src/test/resources/import-files/import/same-names/02-lorem-ipsum/01-update-realm.yaml @@ -0,0 +1,2 @@ +realm: same-names +loginTheme: moped-v2 diff --git a/src/test/resources/import-files/import/same-names/02-lorem-ipsum/02-update-realm.yaml b/src/test/resources/import-files/import/same-names/02-lorem-ipsum/02-update-realm.yaml new file mode 100644 index 000000000..4089c5d45 --- /dev/null +++ b/src/test/resources/import-files/import/same-names/02-lorem-ipsum/02-update-realm.yaml @@ -0,0 +1,2 @@ +realm: same-names +internationalizationEnabled: true