Skip to content

Commit

Permalink
Support for multiple realm definitions inside one YAML file.
Browse files Browse the repository at this point in the history
  • Loading branch information
jkroepke committed Dec 16, 2021
1 parent d3ea7bd commit 8101bca
Show file tree
Hide file tree
Showing 18 changed files with 153 additions and 97 deletions.
4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
## [Unreleased]

### Added
- Workaround for creating client authorization resources, if a username is defined an owner through `owner.name`. Keycloak [excepts](https://github.com/keycloak/keycloak/blob/bfce612641a70e106b20b136431f0e4046b5c37f/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java#L2647-L2649) `owner.id` here instead `owner.name`.
See [#589](https://github.com/adorsys/keycloak-config-cli/pull/589)
- Support for multiple realm definitions inside one YAML file.
- Workaround for creating client authorization resources, if a username is defined an owner through `owner.name`. Keycloak [excepts](https://github.com/keycloak/keycloak/blob/bfce612641a70e106b20b136431f0e4046b5c37f/server-spi-private/src/main/java/org/keycloak/models/utils/RepresentationToModel.java#L2647-L2649) `owner.id` here instead `owner.name`. See [#589](https://github.com/adorsys/keycloak-config-cli/pull/589)

## [4.4.0] - 2021-12-04

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.Map;

@Component
Expand Down Expand Up @@ -64,11 +65,13 @@ public void run(String... args) {
try {
KeycloakImport keycloakImport = keycloakImportProvider.get();

Map<String, RealmImport> realmImports = keycloakImport.getRealmImports();
Map<String, List<RealmImport>> realmImports = keycloakImport.getRealmImports();

for (Map.Entry<String, RealmImport> realmImport : realmImports.entrySet()) {
for (Map.Entry<String, List<RealmImport>> realmImport : realmImports.entrySet()) {
logger.info("Importing file '{}'", realmImport.getKey());
realmImportService.doImport(realmImport.getValue());
for (RealmImport realmImportParts : realmImport.getValue()) {
realmImportService.doImport(realmImportParts);
}
}
} catch (NullPointerException e) {
throw e;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,17 +22,18 @@

import org.springframework.stereotype.Component;

import java.util.List;
import java.util.Map;

@Component
public class KeycloakImport {
private final Map<String, RealmImport> realmImports;
private final Map<String, List<RealmImport>> realmImports;

public KeycloakImport(Map<String, RealmImport> realmImports) {
public KeycloakImport(Map<String, List<RealmImport>> realmImports) {
this.realmImports = realmImports;
}

public Map<String, RealmImport> getRealmImports() {
public Map<String, List<RealmImport>> getRealmImports() {
return realmImports;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@

package de.adorsys.keycloak.config.provider;

import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
Expand Down Expand Up @@ -51,9 +54,10 @@ public class KeycloakImportProvider {

private StringSubstitutor interpolator = null;

private static final ObjectMapper OBJECT_MAPPER_JSON = new ObjectMapper()
.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
private static final ObjectMapper OBJECT_MAPPER_YAML = new ObjectMapper(new YAMLFactory())
private static final JsonFactory JSON_FACTORY = new JsonFactory();
private static final YAMLFactory YAML_FACTORY = new YAMLFactory();

private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

@Autowired
Expand Down Expand Up @@ -116,7 +120,7 @@ public KeycloakImport readFromPath(String path) {
}

private KeycloakImport readRealmImportsFromResource(Collection<File> importResources) {
Map<String, RealmImport> realmImports = importResources.stream()
Map<String, List<RealmImport>> realmImports = importResources.stream()
// https://stackoverflow.com/a/52130074/8087167
.collect(Collectors.toMap(
File::getAbsolutePath,
Expand All @@ -130,35 +134,49 @@ private KeycloakImport readRealmImportsFromResource(Collection<File> importResou
}

public KeycloakImport readRealmImportFromFile(File importFile) {
Map<String, RealmImport> realmImports = new HashMap<>();
Map<String, List<RealmImport>> realmImports = new HashMap<>();

RealmImport realmImport = readRealmImport(importFile);
List<RealmImport> realmImport = readRealmImport(importFile);
realmImports.put(importFile.getAbsolutePath(), realmImport);

return new KeycloakImport(realmImports);
}

private RealmImport readRealmImport(File importFile) {
private List<RealmImport> readRealmImport(File importFile) {
String importConfig;

try {
importConfig = FileUtils.readFileToString(importFile, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new InvalidImportException(e);
}

if (importConfigProperties.isVarSubstitution()) {
importConfig = interpolator.replace(importConfig);
}

String checksum = ChecksumUtil.checksum(importConfig.getBytes(StandardCharsets.UTF_8));

ImportConfigProperties.ImportFileType fileType = importConfigProperties.getFileType();

ObjectMapper objectMapper;
JsonFactory factory;

switch (fileType) {
case YAML:
objectMapper = OBJECT_MAPPER_YAML;
factory = YAML_FACTORY;
break;
case JSON:
objectMapper = OBJECT_MAPPER_JSON;
factory = JSON_FACTORY;
break;
case AUTO:
String fileExt = FilenameUtils.getExtension(importFile.getName());
switch (fileExt) {
case "yaml":
case "yml":
objectMapper = OBJECT_MAPPER_YAML;
factory = YAML_FACTORY;
break;
case "json":
objectMapper = OBJECT_MAPPER_JSON;
factory = JSON_FACTORY;
break;
default:
throw new InvalidImportException("Unknown file extension: " + fileExt);
Expand All @@ -167,25 +185,16 @@ private RealmImport readRealmImport(File importFile) {
default:
throw new InvalidImportException("Unknown import file type: " + fileType);
}
String importConfig;

try {
importConfig = FileUtils.readFileToString(importFile, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new InvalidImportException(e);
}
factory.setCodec(OBJECT_MAPPER);
JsonParser parser = factory.createParser(importConfig);
List<RealmImport> realmImports = OBJECT_MAPPER.readValues(parser, new TypeReference<RealmImport>() {
}).readAll();

if (importConfigProperties.isVarSubstitution()) {
importConfig = interpolator.replace(importConfig);
}

String checksum = ChecksumUtil.checksum(importConfig.getBytes(StandardCharsets.UTF_8));

try {
RealmImport realmImport = objectMapper.readValue(importConfig, RealmImport.class);
realmImport.setChecksum(checksum);
realmImports.forEach(realmImport -> realmImport.setChecksum(checksum));

return realmImport;
return realmImports;
} catch (IOException e) {
throw new InvalidImportException(e);
}
Expand Down
13 changes: 10 additions & 3 deletions src/test/java/de/adorsys/keycloak/config/AbstractImportTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import java.io.File;
import java.io.IOException;
import java.time.Duration;
import java.util.List;

import static java.util.concurrent.TimeUnit.SECONDS;

Expand Down Expand Up @@ -128,12 +129,18 @@ public void doImport(String fileName) throws IOException {
}

public void doImport(String fileName, RealmImportService _realmImportService) throws IOException {
RealmImport realmImport = getImport(fileName);
List<RealmImport> realmImports = getImport(fileName);

_realmImportService.doImport(realmImport);
for (RealmImport realmImport : realmImports) {
_realmImportService.doImport(realmImport);
}
}

public RealmImport getFirstImport(String fileName) throws IOException {
return getImport(fileName).get(0);
}

public RealmImport getImport(String fileName) throws IOException {
public List<RealmImport> getImport(String fileName) throws IOException {
File realmImportFile = new ClassPathResource(this.resourcePath + '/' + fileName).getFile();

return keycloakImportProvider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@

import java.io.File;
import java.io.IOException;
import java.util.List;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
Expand Down Expand Up @@ -90,12 +91,14 @@ void run() throws Exception {
return KeycloakMock.serverInfo(request);
});

RealmImport realmImport = getRealmImport("import-files/simple-realm/00_create_simple-realm.json");
realmImportService.doImport(realmImport);
List<RealmImport> realmImports = getRealmImport("import-files/simple-realm/00_create_simple-realm.json");
for (RealmImport realmImport : realmImports) {
realmImportService.doImport(realmImport);
}
}

@SuppressWarnings("SameParameterValue")
private RealmImport getRealmImport(String file) throws IOException {
private List<RealmImport> getRealmImport(String file) throws IOException {
File realmImportFile = new ClassPathResource(file).getFile();

return keycloakImportProvider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ void shouldAddFlowWithExecutionFlow() throws IOException {
@Test
@Order(5)
void shouldFailWhenTryAddFlowWithDefectiveExecutionFlow() throws IOException {
RealmImport foundImport = getImport("05_try_to_update_realm__add_flow_with_defective_execution_flow.json");
RealmImport foundImport = getFirstImport("05_try_to_update_realm__add_flow_with_defective_execution_flow.json");

InvalidImportException thrown = assertThrows(InvalidImportException.class, () -> realmImportService.doImport(foundImport));

Expand Down Expand Up @@ -281,7 +281,7 @@ void shouldChangeFlowRequirementWithExecutionFlow() throws IOException {
@Test
@Order(7)
void shouldFailWhenTryToUpdateDefectiveFlowRequirementWithExecutionFlow() throws IOException {
RealmImport foundImport = getImport("06_try_to_update_realm__change_requirement_in_defective_flow_with_execution_flow.json");
RealmImport foundImport = getFirstImport("06_try_to_update_realm__change_requirement_in_defective_flow_with_execution_flow.json");

InvalidImportException thrown = assertThrows(InvalidImportException.class, () -> realmImportService.doImport(foundImport));

Expand All @@ -291,7 +291,7 @@ void shouldFailWhenTryToUpdateDefectiveFlowRequirementWithExecutionFlow() throws
@Test
@Order(8)
void shouldFailWhenTryToUpdateFlowRequirementWithExecutionFlowWithNotExistingExecution() throws IOException {
RealmImport foundImport = getImport("07_try_to_update_realm__change_requirement_flow_with_execution_flow_with_not_existing_execution.json");
RealmImport foundImport = getFirstImport("07_try_to_update_realm__change_requirement_flow_with_execution_flow_with_not_existing_execution.json");

ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport));

Expand All @@ -301,7 +301,7 @@ void shouldFailWhenTryToUpdateFlowRequirementWithExecutionFlowWithNotExistingExe
@Test
@Order(9)
void shouldFailWhenTryToUpdateFlowRequirementWithExecutionFlowWithDefectiveExecution() throws IOException {
RealmImport foundImport = getImport("08_try_to_update_realm__change_requirement_flow_with_execution_flow_with_defective_execution.json");
RealmImport foundImport = getFirstImport("08_try_to_update_realm__change_requirement_flow_with_execution_flow_with_defective_execution.json");

ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport));

Expand All @@ -311,7 +311,7 @@ void shouldFailWhenTryToUpdateFlowRequirementWithExecutionFlowWithDefectiveExecu
@Test
@Order(10)
void shouldFailWhenTryToUpdateFlowRequirementWithDefectiveExecutionFlow() throws IOException {
RealmImport foundImport = getImport("09_try_to_update_realm__change_requirement_flow_with_defective_execution_flow.json");
RealmImport foundImport = getFirstImport("09_try_to_update_realm__change_requirement_flow_with_defective_execution_flow.json");

ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport));

Expand Down Expand Up @@ -638,7 +638,7 @@ void shouldUpdateSubFlowWithPseudoId() throws IOException {
@Order(27)
@DisabledIfSystemProperty(named = "keycloak.dockerImage", matches = ".*/keycloak-x")
void shouldNotUpdateSubFlowWithPseudoId() throws IOException {
RealmImport foundImport = getImport("27_update_realm__try-to-update-non-top-level-flow-with-pseudo-id.json");
RealmImport foundImport = getFirstImport("27_update_realm__try-to-update-non-top-level-flow-with-pseudo-id.json");

ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport));

Expand Down Expand Up @@ -669,7 +669,7 @@ void shouldUpdateSubFlowWithPseudoIdAndReUseTempFlow() throws IOException {
@Order(29)
@DisabledIfSystemProperty(named = "keycloak.dockerImage", matches = ".*/keycloak-x")
void shouldNotUpdateInvalidTopLevelFlow() throws IOException {
RealmImport foundImport = getImport("29_update_realm__try-to-update-invalid-top-level-flow.json");
RealmImport foundImport = getFirstImport("29_update_realm__try-to-update-invalid-top-level-flow.json");

ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport));

Expand Down Expand Up @@ -792,7 +792,7 @@ void shouldUpdateMultipleExecutionsWithSameAuthenticatorWithConfig() throws IOEx
@Test
@Order(40)
void shouldFailWhenTryingToUpdateBuiltInFlow() throws IOException {
RealmImport foundImport = getImport("40_update_realm__try-to-update-built-in-flow.json");
RealmImport foundImport = getFirstImport("40_update_realm__try-to-update-built-in-flow.json");

InvalidImportException thrown = assertThrows(InvalidImportException.class, () -> realmImportService.doImport(foundImport));

Expand All @@ -802,7 +802,7 @@ void shouldFailWhenTryingToUpdateBuiltInFlow() throws IOException {
@Test
@Order(41)
void shouldFailWhenTryingToUpdateWithNonExistingFlow() throws IOException {
RealmImport foundImport = getImport("41_update_realm__try-to-update-with-non-existing-flow.json");
RealmImport foundImport = getFirstImport("41_update_realm__try-to-update-with-non-existing-flow.json");

ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport));

Expand Down Expand Up @@ -857,7 +857,7 @@ void shouldUpdateSubBuiltinFLow() throws IOException {
@Test
@Order(44)
void shouldNotUpdateFlowWithBuiltInFalse() throws IOException {
RealmImport foundImport = getImport("44_update_realm__try-to-update-flow-set-builtin-false.json");
RealmImport foundImport = getFirstImport("44_update_realm__try-to-update-flow-set-builtin-false.json");

InvalidImportException thrown = assertThrows(InvalidImportException.class, () -> realmImportService.doImport(foundImport));

Expand All @@ -867,7 +867,7 @@ void shouldNotUpdateFlowWithBuiltInFalse() throws IOException {
@Test
@Order(45)
void shouldNotUpdateFlowWithBuiltInTrue() throws IOException {
RealmImport foundImport = getImport("45_update_realm__try-to-update-flow-set-builtin-true.json");
RealmImport foundImport = getFirstImport("45_update_realm__try-to-update-flow-set-builtin-true.json");

InvalidImportException thrown = assertThrows(InvalidImportException.class, () -> realmImportService.doImport(foundImport));

Expand All @@ -877,7 +877,7 @@ void shouldNotUpdateFlowWithBuiltInTrue() throws IOException {
@Test
@Order(46)
void shouldNotCreateBuiltInFlow() throws IOException {
RealmImport foundImport = getImport("46_update_realm__try-to-create-builtin-flow.json");
RealmImport foundImport = getFirstImport("46_update_realm__try-to-create-builtin-flow.json");

if (VersionUtil.ge(KEYCLOAK_VERSION, "11")) {
ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport));
Expand Down Expand Up @@ -1128,7 +1128,7 @@ void shouldChangeFirstBrokerLoginFlowForIdentityProvider() throws IOException {
@Test
@Order(64)
void shouldNotUpdateFlowWithAuthenticatorOnBasicFlow() throws IOException {
RealmImport foundImport = getImport("63_update-realm__try-to-set-authenticator-basic-flow.json");
RealmImport foundImport = getFirstImport("63_update-realm__try-to-set-authenticator-basic-flow.json");

InvalidImportException thrown = assertThrows(InvalidImportException.class, () -> realmImportService.doImport(foundImport));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,7 @@ void shouldUpdateRealmDeleteFlowAuthConfigInsideBuiltinSubFlow() throws IOExcept
@Test
@Order(9)
void shouldThrowInvalidAuthConfig() throws IOException {
RealmImport foundImport = getImport("9_update_realm__invalid_auth_config.json");
RealmImport foundImport = getFirstImport("9_update_realm__invalid_auth_config.json");

ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ void shouldChangeClientScopeWithReplaceProtocolMapper() throws IOException {
@Test
@Order(6)
void shouldNotUpdateRealmUpdateScopeMappingsWithError() throws IOException {
RealmImport foundImport = getImport("06_update_realm__try-to-change_clientScope_invalid_protocolMapper.json");
RealmImport foundImport = getFirstImport("06_update_realm__try-to-change_clientScope_invalid_protocolMapper.json");

ImportProcessingException thrown = assertThrows(ImportProcessingException.class, () -> realmImportService.doImport(foundImport));

Expand Down
Loading

0 comments on commit 8101bca

Please sign in to comment.