diff --git a/README.md b/README.md index d1af99e..ca00877 100644 --- a/README.md +++ b/README.md @@ -40,7 +40,7 @@ mvn quarkus:dev "-Dquarkus.args=-h" Starting the import of a configuration can be done as follows: ```bash -mvn quarkus:dev "-Dquarkus.args=-s http://localhost:40800 -u keycloak -p root configure -c ../keycloak-configuration-eam" +mvn quarkus:dev "-Dquarkus.args=configure -s http://localhost:40800 -u keycloak -p root -c ../keycloak-configuration-eam" ``` The help of a sub-command is shown as follows: @@ -48,3 +48,32 @@ The help of a sub-command is shown as follows: ```bash mvn quarkus:dev "-Dquarkus.args=configure -h" ``` + +## Execution examples + +- Show help: + ```bash + mvn quarkus:dev "-Dquarkus.args=-h" + ``` +- Show help of the `configure` sub-command: + ```bash + mvn quarkus:dev "-Dquarkus.args=configure -h" + ``` +- Execute configuration import: + ```bash + mvn quarkus:dev "-Dquarkus.args=configure -s http://localhost:40800 -u keycloak -p root -c ../keycloak-configuration-eam" + ``` +- Show help of the `export-secrets` sub-command: + ```bash + mvn quarkus:dev "-Dquarkus.args=export-secrets -h" + ``` +- Export client secrets of all clients of the realm `eam`: + ```bash + mvn quarkus:dev "-Dquarkus.args=export-secrets -s http://localhost:40800 -u keycloak -p root -r eam" + ``` + +Specify the log level via `-Dquarkus.log.level`. For example, to set the log level to `INFO`: + +```bash +mvn quarkus:dev "-Dquarkus.args=export-secrets -s http://localhost:40800 -u keycloak -p root -r eam" "-Dquarkus.log.level=INFO" +``` diff --git a/pom.xml b/pom.xml index 5377301..7c1bced 100644 --- a/pom.xml +++ b/pom.xml @@ -31,6 +31,7 @@ 3.4.1 + 2.3 1.18.28 @@ -74,6 +75,12 @@ quarkus-picocli + + org.apache.velocity + velocity-engine-core + ${velocity.version} + + org.projectlombok lombok diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/boundary/AbstractImporter.java b/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/boundary/AbstractImporter.java index 2aecd95..5e39af0 100644 --- a/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/boundary/AbstractImporter.java +++ b/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/boundary/AbstractImporter.java @@ -12,6 +12,7 @@ import java.util.regex.Pattern; import java.util.stream.Stream; +import jakarta.annotation.PostConstruct; import jakarta.inject.Inject; import org.keycloak.admin.client.Keycloak; @@ -19,8 +20,8 @@ import com.cycrilabs.keycloak.configurator.commands.configure.control.EntityStore; import com.cycrilabs.keycloak.configurator.commands.configure.entity.ConfigureCommandConfiguration; import com.cycrilabs.keycloak.configurator.commands.configure.entity.EntityImportType; -import com.cycrilabs.keycloak.configurator.shared.control.JsonbConfigurator; -import com.cycrilabs.keycloak.configurator.shared.entity.KeycloakConfiguration; +import com.cycrilabs.keycloak.configurator.shared.control.JsonbFactory; +import com.cycrilabs.keycloak.configurator.shared.control.KeycloakFactory; import io.quarkus.logging.Log; @@ -28,14 +29,15 @@ public abstract class AbstractImporter { public static final String PATH_SEPARATOR = Pattern.quote(System.getProperty("file.separator")); @Inject - protected KeycloakConfiguration configuration; - @Inject - protected ConfigureCommandConfiguration subConfiguration; - @Inject + protected ConfigureCommandConfiguration configuration; protected Keycloak keycloak; - protected EntityStore entityStore; + @PostConstruct + public void init() { + keycloak = KeycloakFactory.create(configuration); + } + protected T loadEntity(final Path filepath, final Class dtoClass) { final String json = loadJsonFromResource(filepath); return fromJson(json, dtoClass); @@ -51,12 +53,12 @@ private String loadJsonFromResource(final Path filePath) { } private T fromJson(final String content, final Class dtoClass) { - return JsonbConfigurator.getJsonb().fromJson(content, dtoClass); + return JsonbFactory.getJsonb().fromJson(content, dtoClass); } public void runImport(final EntityStore entityStore) { - this.entityStore = entityStore; Log.infof("Executing importer %s.", getClass().getSimpleName()); + this.entityStore = entityStore; final List importFiles = getEntityFilePaths(getEntityDirectory()); for (final Path importFile : importFiles) { final Object o = importFile(importFile); @@ -65,7 +67,7 @@ public void runImport(final EntityStore entityStore) { } private List getEntityFilePaths(final String entityDir) { - final String dir = Paths.get(subConfiguration.getConfigDirectory(), entityDir).toString(); + final String dir = Paths.get(configuration.getConfigDirectory(), entityDir).toString(); try (final Stream stream = Files.walk(Paths.get(dir))) { return stream .filter(Files::isRegularFile) diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/control/ConfigureCommand.java b/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/control/ConfigureCommand.java index 3803f00..fc5779d 100644 --- a/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/control/ConfigureCommand.java +++ b/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/control/ConfigureCommand.java @@ -6,13 +6,16 @@ import jakarta.inject.Inject; import com.cycrilabs.keycloak.configurator.commands.configure.boundary.AbstractImporter; -import com.cycrilabs.keycloak.configurator.shared.entity.KeycloakConfiguration; +import com.cycrilabs.keycloak.configurator.commands.configure.entity.ConfigureCommandConfiguration; +import com.cycrilabs.keycloak.configurator.shared.control.KeycloakOptions; import io.quarkus.logging.Log; import picocli.CommandLine; @CommandLine.Command(name = "configure", mixinStandardHelpOptions = true) public class ConfigureCommand implements Runnable { + @CommandLine.Mixin + KeycloakOptions keycloakOptions; @CommandLine.Option(required = true, names = { "-c", "--config" }, description = "Directory containing the keycloak configuration files.") String configDirectory = ""; @@ -20,14 +23,14 @@ public class ConfigureCommand implements Runnable { @Inject EntityStore entityStore; @Inject - KeycloakConfiguration configuration; + ConfigureCommandConfiguration configuration; @Inject Instance importers; @Override public void run() { Log.infof("Running importers for server %s with configuration %s.", - configuration.getServer(), configDirectory); + configuration.getServer(), configuration.getConfigDirectory()); importers.stream() .sorted(Comparator.comparingInt(AbstractImporter::getPriority)) .forEach(importer -> importer.runImport(entityStore)); diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/control/ConfigureCommandConfigurationProducer.java b/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/control/ConfigureCommandConfigurationProducer.java index 41f0c06..f9f13f3 100644 --- a/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/control/ConfigureCommandConfigurationProducer.java +++ b/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/control/ConfigureCommandConfigurationProducer.java @@ -12,8 +12,6 @@ public class ConfigureCommandConfigurationProducer { @Produces @ApplicationScoped ConfigureCommandConfiguration createConfiguration(final CommandLine.ParseResult parseResult) { - return new ConfigureCommandConfiguration( - parseResult.subcommand().matchedOption("c").getValue().toString() - ); + return new ConfigureCommandConfiguration(parseResult); } } diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/entity/ConfigureCommandConfiguration.java b/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/entity/ConfigureCommandConfiguration.java index cd2fd9c..4ff92e5 100644 --- a/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/entity/ConfigureCommandConfiguration.java +++ b/src/main/java/com/cycrilabs/keycloak/configurator/commands/configure/entity/ConfigureCommandConfiguration.java @@ -1,10 +1,17 @@ package com.cycrilabs.keycloak.configurator.commands.configure.entity; -import lombok.AllArgsConstructor; import lombok.Getter; -@AllArgsConstructor +import com.cycrilabs.keycloak.configurator.shared.entity.KeycloakConfiguration; + +import picocli.CommandLine.ParseResult; + @Getter -public class ConfigureCommandConfiguration { - String configDirectory; +public class ConfigureCommandConfiguration extends KeycloakConfiguration { + private final String configDirectory; + + public ConfigureCommandConfiguration(final ParseResult parseResult) { + super(parseResult); + configDirectory = getMatchedOption(parseResult, "-c"); + } } diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/commands/secrets/boundary/ExportSecrets.java b/src/main/java/com/cycrilabs/keycloak/configurator/commands/secrets/boundary/ExportSecrets.java new file mode 100644 index 0000000..c9d283c --- /dev/null +++ b/src/main/java/com/cycrilabs/keycloak/configurator/commands/secrets/boundary/ExportSecrets.java @@ -0,0 +1,99 @@ +package com.cycrilabs.keycloak.configurator.commands.secrets.boundary; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.net.URISyntaxException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.apache.commons.io.FileUtils; +import org.apache.velocity.Template; +import org.apache.velocity.runtime.parser.ParseException; +import org.keycloak.admin.client.Keycloak; +import org.keycloak.representations.idm.ClientRepresentation; + +import com.cycrilabs.keycloak.configurator.commands.secrets.entity.ExportSecretsCommandConfiguration; +import com.cycrilabs.keycloak.configurator.shared.control.KeycloakFactory; +import com.cycrilabs.keycloak.configurator.shared.control.VelocityUtils; + +import io.quarkus.logging.Log; + +@ApplicationScoped +public class ExportSecrets { + private static final String TEMPLATE_NAME_PLACEHOLDER = "client-name"; + + @Inject + ExportSecretsCommandConfiguration configuration; + Keycloak keycloak; + + @PostConstruct + public void init() { + keycloak = KeycloakFactory.create(configuration); + } + + public void export() throws IOException, ParseException, URISyntaxException { + final Collection