From a39898615194bb5f81c141198490b1be61ce3705 Mon Sep 17 00:00:00 2001 From: Marc Scheib Date: Sun, 24 Sep 2023 16:12:34 +0200 Subject: [PATCH] feat: add command to generate new secrets (#14) --- .../generate/boundary/GenerateSecrets.java | 57 +++++++++++++++++++ .../control/GenerateSecretsCommand.java | 38 +++++++++++++ ...teSecretsCommandConfigurationProducer.java | 18 ++++++ .../GenerateSecretsCommandConfiguration.java | 19 +++++++ .../shared/control/EntryCommand.java | 4 +- 5 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/cycrilabs/keycloak/configurator/commands/generate/boundary/GenerateSecrets.java create mode 100644 src/main/java/com/cycrilabs/keycloak/configurator/commands/generate/control/GenerateSecretsCommand.java create mode 100644 src/main/java/com/cycrilabs/keycloak/configurator/commands/generate/control/GenerateSecretsCommandConfigurationProducer.java create mode 100644 src/main/java/com/cycrilabs/keycloak/configurator/commands/generate/entity/GenerateSecretsCommandConfiguration.java diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/commands/generate/boundary/GenerateSecrets.java b/src/main/java/com/cycrilabs/keycloak/configurator/commands/generate/boundary/GenerateSecrets.java new file mode 100644 index 0000000..ad55ba5 --- /dev/null +++ b/src/main/java/com/cycrilabs/keycloak/configurator/commands/generate/boundary/GenerateSecrets.java @@ -0,0 +1,57 @@ +package com.cycrilabs.keycloak.configurator.commands.generate.boundary; + +import java.util.List; +import java.util.stream.Stream; + +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; + +import org.keycloak.admin.client.Keycloak; +import org.keycloak.representations.idm.ClientRepresentation; + +import com.cycrilabs.keycloak.configurator.commands.generate.entity.GenerateSecretsCommandConfiguration; +import com.cycrilabs.keycloak.configurator.shared.control.KeycloakFactory; + +import io.quarkus.logging.Log; + +@ApplicationScoped +public class GenerateSecrets { + @Inject + GenerateSecretsCommandConfiguration configuration; + Keycloak keycloak; + + @PostConstruct + public void init() { + keycloak = KeycloakFactory.create(configuration); + } + + public void run() { + final List generatedIds = getClients() + .filter(client -> client.getSecret() != null) + .map(ClientRepresentation::getId) + .map(this::generateSecret) + .toList(); + Log.infof("Generated secrets for %d clients.", Integer.valueOf(generatedIds.size())); + } + + private Stream getClients() { + return configuration.getClientId() == null + ? keycloak.realm(configuration.getRealmName()) + .clients() + .findAll() + .stream() + : keycloak.realm(configuration.getRealmName()) + .clients() + .findByClientId(configuration.getClientId()) + .stream(); + } + + private String generateSecret(final String id) { + return keycloak.realm(configuration.getRealmName()) + .clients() + .get(id) + .generateNewSecret() + .getId(); + } +} diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/commands/generate/control/GenerateSecretsCommand.java b/src/main/java/com/cycrilabs/keycloak/configurator/commands/generate/control/GenerateSecretsCommand.java new file mode 100644 index 0000000..bc478c6 --- /dev/null +++ b/src/main/java/com/cycrilabs/keycloak/configurator/commands/generate/control/GenerateSecretsCommand.java @@ -0,0 +1,38 @@ +package com.cycrilabs.keycloak.configurator.commands.generate.control; + +import jakarta.inject.Inject; + +import com.cycrilabs.keycloak.configurator.commands.generate.boundary.GenerateSecrets; +import com.cycrilabs.keycloak.configurator.commands.generate.entity.GenerateSecretsCommandConfiguration; +import com.cycrilabs.keycloak.configurator.shared.control.KeycloakOptions; + +import io.quarkus.logging.Log; +import picocli.CommandLine; + +@CommandLine.Command(name = "rotate-secrets", mixinStandardHelpOptions = true) +public class GenerateSecretsCommand implements Runnable { + @CommandLine.Mixin + KeycloakOptions keycloakOptions; + @CommandLine.Option(required = true, names = { "-r", "--realm" }, + description = "Realm name to generate secrets for.") + String realm; + @CommandLine.Option(names = { "-c", "--client" }, + description = "Specific client to generate new secret.") + String clientId; + + @Inject + GenerateSecretsCommandConfiguration configuration; + @Inject + GenerateSecrets command; + + @Override + public void run() { + try { + Log.infof("Generating secrets of realm '%s'.", configuration.getRealmName()); + command.run(); + } catch (final Exception e) { + Log.errorf(e, "Failed to generate secrets of realm '%s'.", + configuration.getRealmName()); + } + } +} diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/commands/generate/control/GenerateSecretsCommandConfigurationProducer.java b/src/main/java/com/cycrilabs/keycloak/configurator/commands/generate/control/GenerateSecretsCommandConfigurationProducer.java new file mode 100644 index 0000000..835acf5 --- /dev/null +++ b/src/main/java/com/cycrilabs/keycloak/configurator/commands/generate/control/GenerateSecretsCommandConfigurationProducer.java @@ -0,0 +1,18 @@ +package com.cycrilabs.keycloak.configurator.commands.generate.control; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.inject.Produces; + +import com.cycrilabs.keycloak.configurator.commands.generate.entity.GenerateSecretsCommandConfiguration; + +import picocli.CommandLine; + +@ApplicationScoped +public class GenerateSecretsCommandConfigurationProducer { + @Produces + @ApplicationScoped + GenerateSecretsCommandConfiguration createConfiguration( + final CommandLine.ParseResult parseResult) { + return new GenerateSecretsCommandConfiguration(parseResult); + } +} diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/commands/generate/entity/GenerateSecretsCommandConfiguration.java b/src/main/java/com/cycrilabs/keycloak/configurator/commands/generate/entity/GenerateSecretsCommandConfiguration.java new file mode 100644 index 0000000..3e7f598 --- /dev/null +++ b/src/main/java/com/cycrilabs/keycloak/configurator/commands/generate/entity/GenerateSecretsCommandConfiguration.java @@ -0,0 +1,19 @@ +package com.cycrilabs.keycloak.configurator.commands.generate.entity; + +import lombok.Getter; + +import com.cycrilabs.keycloak.configurator.shared.entity.KeycloakConfiguration; + +import picocli.CommandLine.ParseResult; + +@Getter +public class GenerateSecretsCommandConfiguration extends KeycloakConfiguration { + private final String realmName; + private final String clientId; + + public GenerateSecretsCommandConfiguration(final ParseResult parseResult) { + super(parseResult); + realmName = getMatchedOption(parseResult, "-r"); + clientId = getMatchedOption(parseResult, "-c"); + } +} diff --git a/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/EntryCommand.java b/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/EntryCommand.java index 2b39665..61763ce 100644 --- a/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/EntryCommand.java +++ b/src/main/java/com/cycrilabs/keycloak/configurator/shared/control/EntryCommand.java @@ -1,6 +1,7 @@ package com.cycrilabs.keycloak.configurator.shared.control; import com.cycrilabs.keycloak.configurator.commands.configure.control.ConfigureCommand; +import com.cycrilabs.keycloak.configurator.commands.generate.control.GenerateSecretsCommand; import com.cycrilabs.keycloak.configurator.commands.secrets.control.ExportSecretsCommand; import io.quarkus.picocli.runtime.annotations.TopCommand; @@ -9,6 +10,7 @@ @TopCommand @CommandLine.Command(mixinStandardHelpOptions = true, version = "0.1.0", - subcommands = { ConfigureCommand.class, ExportSecretsCommand.class }) + subcommands = { ConfigureCommand.class, ExportSecretsCommand.class, + GenerateSecretsCommand.class }) public class EntryCommand { }