Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#1015 Added option to store checksum for each imported file #1017

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
- Upgrade to Spring Boot 3
- This affects the capability of the path matcher

- 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

Expand Down
40 changes: 21 additions & 19 deletions README.md

Large diffs are not rendered by default.

17 changes: 11 additions & 6 deletions src/main/java/de/adorsys/keycloak/config/model/RealmImport.java
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ public class RealmImport extends RealmRepresentation {
private Map<String, Map<String, String>> messageBundles;

private String checksum;
private String source;

@Override
@SuppressWarnings("java:S1168")
Expand All @@ -54,12 +55,6 @@ public void setAuthenticationFlowImports(List<AuthenticationFlowImport> authenti
this.authenticationFlowImports = authenticationFlowImports;
}

@SuppressWarnings("unused")
@JsonSetter("userProfile")
public void setUserProfile(Map<String, List<Map<String, Object>>> userProfile) {
this.userProfile = userProfile;
}

public Map<String, Map<String, String>> getMessageBundles() {
return messageBundles;
}
Expand All @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -342,10 +342,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() {
Expand All @@ -359,6 +368,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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,10 @@ private Pair<String, List<RealmImport>> 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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -51,25 +55,52 @@ public void doImport(RealmImport realmImport) {
Map<String, String> 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<String, String> 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
);
}

}
2 changes: 2 additions & 0 deletions src/main/resources/application.properties
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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 {

Expand Down Expand Up @@ -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)
Expand Down
Loading
Loading