Rework the Import SPI to be configurable via the Config API
Also rework the export/import CLI for Quarkus, so that runtime options are available. Closes #17663
This commit is contained in:
parent
d29f3e4dfc
commit
251f6151e8
|
@ -28,14 +28,15 @@ import org.keycloak.models.KeycloakSessionFactory;
|
|||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.platform.Platform;
|
||||
import org.keycloak.representations.idm.RealmRepresentation;
|
||||
import org.keycloak.services.ServicesLogger;
|
||||
import org.keycloak.util.JsonSerialization;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FilenameFilter;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import org.keycloak.services.managers.RealmManager;
|
||||
|
||||
|
@ -44,54 +45,73 @@ import org.keycloak.services.managers.RealmManager;
|
|||
*/
|
||||
public class DirImportProvider extends AbstractFileBasedImportProvider {
|
||||
|
||||
private final Strategy strategy;
|
||||
private final KeycloakSessionFactory factory;
|
||||
|
||||
private static final Logger logger = Logger.getLogger(DirImportProvider.class);
|
||||
|
||||
private final File rootDirectory;
|
||||
private File rootDirectory;
|
||||
|
||||
public DirImportProvider() {
|
||||
// Determine platform tmp directory
|
||||
this.rootDirectory = new File(Platform.getPlatform().getTmpDirectory(), "keycloak-export");
|
||||
if (!this.rootDirectory .exists()) {
|
||||
throw new IllegalStateException("Directory " + this.rootDirectory + " doesn't exist");
|
||||
}
|
||||
private String realmName;
|
||||
|
||||
logger.infof("Importing from directory %s", this.rootDirectory.getAbsolutePath());
|
||||
public DirImportProvider(KeycloakSessionFactory factory, Strategy strategy) {
|
||||
this.factory = factory;
|
||||
this.strategy = strategy;
|
||||
}
|
||||
|
||||
public DirImportProvider(File rootDirectory) {
|
||||
this.rootDirectory = rootDirectory;
|
||||
public DirImportProvider withDir(String dir) {
|
||||
this.rootDirectory = new File(dir);
|
||||
|
||||
if (!this.rootDirectory.exists()) {
|
||||
throw new IllegalStateException("Directory " + this.rootDirectory + " doesn't exist");
|
||||
}
|
||||
|
||||
logger.infof("Importing from directory %s", this.rootDirectory.getAbsolutePath());
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importModel(KeycloakSessionFactory factory, Strategy strategy) throws IOException {
|
||||
List<String> realmNames = getRealmsToImport();
|
||||
public DirImportProvider withRealmName(String realmName) {
|
||||
this.realmName = realmName;
|
||||
return this;
|
||||
}
|
||||
|
||||
for (String realmName : realmNames) {
|
||||
importRealm(factory, realmName, strategy);
|
||||
private File getRootDirectory() {
|
||||
if (rootDirectory == null) {
|
||||
this.rootDirectory = new File(Platform.getPlatform().getTmpDirectory(), "keycloak-export");
|
||||
if (!this.rootDirectory.exists()) {
|
||||
throw new IllegalStateException("Directory " + this.rootDirectory + " doesn't exist");
|
||||
}
|
||||
|
||||
logger.infof("Importing from directory %s", this.rootDirectory.getAbsolutePath());
|
||||
}
|
||||
return rootDirectory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMasterRealmExported() throws IOException {
|
||||
public void importModel() throws IOException {
|
||||
if (realmName != null) {
|
||||
ServicesLogger.LOGGER.realmImportRequested(realmName, strategy.toString());
|
||||
importRealm(realmName, strategy);
|
||||
} else {
|
||||
ServicesLogger.LOGGER.fullModelImport(strategy.toString());
|
||||
List<String> realmNames = getRealmsToImport();
|
||||
|
||||
for (String realmName : realmNames) {
|
||||
importRealm(realmName, strategy);
|
||||
}
|
||||
}
|
||||
ServicesLogger.LOGGER.importSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isMasterRealmExported() {
|
||||
List<String> realmNames = getRealmsToImport();
|
||||
return realmNames.contains(Config.getAdminRealm());
|
||||
}
|
||||
|
||||
private List<String> getRealmsToImport() throws IOException {
|
||||
File[] realmFiles = this.rootDirectory.listFiles(new FilenameFilter() {
|
||||
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
return (name.endsWith("-realm.json"));
|
||||
}
|
||||
});
|
||||
|
||||
private List<String> getRealmsToImport() {
|
||||
File[] realmFiles = getRootDirectory().listFiles((dir, name) -> (name.endsWith("-realm.json")));
|
||||
Objects.requireNonNull(realmFiles, "Directory not found: " + getRootDirectory().getName());
|
||||
List<String> realmNames = new ArrayList<>();
|
||||
for (File file : realmFiles) {
|
||||
String fileName = file.getName();
|
||||
|
@ -108,23 +128,12 @@ public class DirImportProvider extends AbstractFileBasedImportProvider {
|
|||
return realmNames;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importRealm(KeycloakSessionFactory factory, final String realmName, final Strategy strategy) throws IOException {
|
||||
File realmFile = new File(this.rootDirectory + File.separator + realmName + "-realm.json");
|
||||
File[] userFiles = this.rootDirectory.listFiles(new FilenameFilter() {
|
||||
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
return name.matches(realmName + "-users-[0-9]+\\.json");
|
||||
}
|
||||
});
|
||||
File[] federatedUserFiles = this.rootDirectory.listFiles(new FilenameFilter() {
|
||||
|
||||
@Override
|
||||
public boolean accept(File dir, String name) {
|
||||
return name.matches(realmName + "-federated-users-[0-9]+\\.json");
|
||||
}
|
||||
});
|
||||
public void importRealm(final String realmName, final Strategy strategy) throws IOException {
|
||||
File realmFile = new File(getRootDirectory() + File.separator + realmName + "-realm.json");
|
||||
File[] userFiles = getRootDirectory().listFiles((dir, name) -> name.matches(realmName + "-users-[0-9]+\\.json"));
|
||||
Objects.requireNonNull(userFiles, "directory not found: " + getRootDirectory().getName());
|
||||
File[] federatedUserFiles = getRootDirectory().listFiles((dir, name) -> name.matches(realmName + "-federated-users-[0-9]+\\.json"));
|
||||
Objects.requireNonNull(federatedUserFiles, "directory not found: " + getRootDirectory().getName());
|
||||
|
||||
// Import realm first
|
||||
InputStream is = parseFile(realmFile);
|
||||
|
@ -134,7 +143,7 @@ public class DirImportProvider extends AbstractFileBasedImportProvider {
|
|||
KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
|
||||
|
||||
@Override
|
||||
public void runExportImportTask(KeycloakSession session) throws IOException {
|
||||
public void runExportImportTask(KeycloakSession session) {
|
||||
boolean imported = ImportUtils.importRealm(session, realmRep, strategy, true);
|
||||
realmImported.set(imported);
|
||||
}
|
||||
|
@ -144,24 +153,26 @@ public class DirImportProvider extends AbstractFileBasedImportProvider {
|
|||
if (realmImported.get()) {
|
||||
// Import users
|
||||
for (final File userFile : userFiles) {
|
||||
final InputStream fis = parseFile(userFile);
|
||||
KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
|
||||
@Override
|
||||
protected void runExportImportTask(KeycloakSession session) throws IOException {
|
||||
ImportUtils.importUsersFromStream(session, realmName, JsonSerialization.mapper, fis);
|
||||
logger.infof("Imported users from %s", userFile.getAbsolutePath());
|
||||
}
|
||||
});
|
||||
try (InputStream fis = parseFile(userFile)) {
|
||||
KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
|
||||
@Override
|
||||
protected void runExportImportTask(KeycloakSession session) throws IOException {
|
||||
ImportUtils.importUsersFromStream(session, realmName, JsonSerialization.mapper, fis);
|
||||
logger.infof("Imported users from %s", userFile.getAbsolutePath());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
for (final File userFile : federatedUserFiles) {
|
||||
final InputStream fis = parseFile(userFile);
|
||||
KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
|
||||
@Override
|
||||
protected void runExportImportTask(KeycloakSession session) throws IOException {
|
||||
ImportUtils.importFederatedUsersFromStream(session, realmName, JsonSerialization.mapper, fis);
|
||||
logger.infof("Imported federated users from %s", userFile.getAbsolutePath());
|
||||
}
|
||||
});
|
||||
try (InputStream fis = parseFile(userFile)) {
|
||||
KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
|
||||
@Override
|
||||
protected void runExportImportTask(KeycloakSession session) throws IOException {
|
||||
ImportUtils.importFederatedUsersFromStream(session, realmName, JsonSerialization.mapper, fis);
|
||||
logger.infof("Imported federated users from %s", userFile.getAbsolutePath());
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -170,7 +181,7 @@ public class DirImportProvider extends AbstractFileBasedImportProvider {
|
|||
KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
|
||||
|
||||
@Override
|
||||
public void runExportImportTask(KeycloakSession session) throws IOException {
|
||||
public void runExportImportTask(KeycloakSession session) {
|
||||
RealmManager realmManager = new RealmManager(session);
|
||||
realmManager.setupClientServiceAccountsAndAuthorizationOnImport(realmRep, false);
|
||||
}
|
||||
|
|
|
@ -21,24 +21,42 @@ import org.keycloak.Config;
|
|||
import org.keycloak.exportimport.ExportImportConfig;
|
||||
import org.keycloak.exportimport.ImportProvider;
|
||||
import org.keycloak.exportimport.ImportProviderFactory;
|
||||
import org.keycloak.exportimport.Strategy;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import static org.keycloak.exportimport.ExportImportConfig.DEFAULT_STRATEGY;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class DirImportProviderFactory implements ImportProviderFactory {
|
||||
|
||||
public static final String REALM_NAME = "realmName";
|
||||
public static final String DIR = "dir";
|
||||
private static final String STRATEGY = "strategy";
|
||||
|
||||
public static final String PROVIDER_ID = DirExportProviderFactory.PROVIDER_ID;
|
||||
|
||||
private Config.Scope config;
|
||||
|
||||
@Override
|
||||
public ImportProvider create(KeycloakSession session) {
|
||||
String dir = ExportImportConfig.getDir();
|
||||
return dir!=null ? new DirImportProvider(new File(dir)) : new DirImportProvider();
|
||||
Strategy strategy = Enum.valueOf(Strategy.class, System.getProperty(ExportImportConfig.STRATEGY, config.get(STRATEGY, DEFAULT_STRATEGY.toString())));
|
||||
String realmName = System.getProperty(ExportImportConfig.REALM_NAME, config.get(REALM_NAME));
|
||||
String dir = System.getProperty(ExportImportConfig.DIR, config.get(DIR));
|
||||
return new DirImportProvider(session.getKeycloakSessionFactory(), strategy)
|
||||
.withDir(dir)
|
||||
.withRealmName(realmName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -52,6 +70,30 @@ public class DirImportProviderFactory implements ImportProviderFactory {
|
|||
|
||||
@Override
|
||||
public String getId() {
|
||||
return DirExportProviderFactory.PROVIDER_ID;
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
public List<ProviderConfigProperty> getConfigMetadata() {
|
||||
return ProviderConfigurationBuilder.create()
|
||||
.property()
|
||||
.name(REALM_NAME)
|
||||
.type("string")
|
||||
.helpText("Realm to export")
|
||||
.add()
|
||||
|
||||
.property()
|
||||
.name(DIR)
|
||||
.type("string")
|
||||
.helpText("Directory to import from")
|
||||
.add()
|
||||
|
||||
.property()
|
||||
.name(STRATEGY)
|
||||
.type("string")
|
||||
.helpText("Strategy for import: " + Strategy.IGNORE_EXISTING.name() + ", " + Strategy.OVERWRITE_EXISTING)
|
||||
.add()
|
||||
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -40,25 +40,28 @@ import java.util.Map;
|
|||
public class SingleFileImportProvider extends AbstractFileBasedImportProvider {
|
||||
|
||||
private static final Logger logger = Logger.getLogger(SingleFileImportProvider.class);
|
||||
private final KeycloakSessionFactory factory;
|
||||
|
||||
private File file;
|
||||
private final File file;
|
||||
private final Strategy strategy;
|
||||
|
||||
// Allows to cache representation per provider to avoid parsing them twice
|
||||
protected Map<String, RealmRepresentation> realmReps;
|
||||
|
||||
public SingleFileImportProvider(File file) {
|
||||
public SingleFileImportProvider(KeycloakSessionFactory factory, File file, Strategy strategy) {
|
||||
this.factory = factory;
|
||||
this.file = file;
|
||||
this.strategy = strategy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importModel(KeycloakSessionFactory factory, final Strategy strategy) throws IOException {
|
||||
public void importModel() throws IOException {
|
||||
logger.infof("Full importing from file %s", this.file.getAbsolutePath());
|
||||
checkRealmReps();
|
||||
|
||||
KeycloakModelUtils.runJobInTransaction(factory, new ExportImportSessionTask() {
|
||||
|
||||
@Override
|
||||
protected void runExportImportTask(KeycloakSession session) throws IOException {
|
||||
protected void runExportImportTask(KeycloakSession session) {
|
||||
ImportUtils.importRealms(session, realmReps.values(), strategy);
|
||||
}
|
||||
|
||||
|
@ -78,12 +81,6 @@ public class SingleFileImportProvider extends AbstractFileBasedImportProvider {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void importRealm(KeycloakSessionFactory factory, String realmName, Strategy strategy) throws IOException {
|
||||
// TODO: import just that single realm in case that file contains many realms?
|
||||
importModel(factory, strategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() {
|
||||
|
||||
|
|
|
@ -21,27 +21,44 @@ import org.keycloak.Config;
|
|||
import org.keycloak.exportimport.ExportImportConfig;
|
||||
import org.keycloak.exportimport.ImportProvider;
|
||||
import org.keycloak.exportimport.ImportProviderFactory;
|
||||
import org.keycloak.exportimport.Strategy;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.ProviderConfigProperty;
|
||||
import org.keycloak.provider.ProviderConfigurationBuilder;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
import static org.keycloak.exportimport.ExportImportConfig.DEFAULT_STRATEGY;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
|
||||
*/
|
||||
public class SingleFileImportProviderFactory implements ImportProviderFactory {
|
||||
|
||||
public static final String PROVIDER_ID = SingleFileExportProviderFactory.PROVIDER_ID;
|
||||
|
||||
public static final String REALM_NAME = "realmName";
|
||||
public static final String STRATEGY = "strategy";
|
||||
|
||||
public static final String FILE = "file";
|
||||
|
||||
private Config.Scope config;
|
||||
|
||||
@Override
|
||||
public ImportProvider create(KeycloakSession session) {
|
||||
String fileName = ExportImportConfig.getFile();
|
||||
Strategy strategy = Enum.valueOf(Strategy.class, System.getProperty(ExportImportConfig.STRATEGY, config.get(STRATEGY, DEFAULT_STRATEGY.toString())));
|
||||
String fileName = System.getProperty(ExportImportConfig.FILE, config.get(FILE));
|
||||
if (fileName == null) {
|
||||
throw new IllegalArgumentException("Property " + ExportImportConfig.FILE + " needs to be provided!");
|
||||
throw new IllegalArgumentException("Property " + FILE + " needs to be provided!");
|
||||
}
|
||||
return new SingleFileImportProvider(new File(fileName));
|
||||
return new SingleFileImportProvider(session.getKeycloakSessionFactory(), new File(fileName), strategy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void init(Config.Scope config) {
|
||||
this.config = config;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -55,6 +72,30 @@ public class SingleFileImportProviderFactory implements ImportProviderFactory {
|
|||
|
||||
@Override
|
||||
public String getId() {
|
||||
return SingleFileExportProviderFactory.PROVIDER_ID;
|
||||
return PROVIDER_ID;
|
||||
}
|
||||
|
||||
public List<ProviderConfigProperty> getConfigMetadata() {
|
||||
return ProviderConfigurationBuilder.create()
|
||||
.property()
|
||||
.name(REALM_NAME)
|
||||
.type("string")
|
||||
.helpText("Realm to export")
|
||||
.add()
|
||||
|
||||
.property()
|
||||
.name(FILE)
|
||||
.type("string")
|
||||
.helpText("File to import from")
|
||||
.add()
|
||||
|
||||
.property()
|
||||
.name(STRATEGY)
|
||||
.type("string")
|
||||
.helpText("Strategy for import: " + Strategy.IGNORE_EXISTING.name() + ", " + Strategy.OVERWRITE_EXISTING)
|
||||
.add()
|
||||
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,13 +21,13 @@ public class ExportOptions {
|
|||
|
||||
public static final Option<String> FILE = new OptionBuilder<>("file", String.class)
|
||||
.category(OptionCategory.EXPORT)
|
||||
.hidden() // hidden for now until we refactor the import command
|
||||
.description("Set the path to a file that will be created with the exported data.")
|
||||
.buildTime(false)
|
||||
.build();
|
||||
|
||||
public static final Option<String> DIR = new OptionBuilder<>("dir", String.class)
|
||||
.category(OptionCategory.EXPORT)
|
||||
.hidden() // hidden for now until we refactor the import command
|
||||
.description("Set the path to a directory where files will be created with the exported data.")
|
||||
.buildTime(false)
|
||||
.build();
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright 2023 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.keycloak.config;
|
||||
|
||||
public class ImportOptions {
|
||||
|
||||
public static final Option<String> FILE = new OptionBuilder<>("file", String.class)
|
||||
.category(OptionCategory.IMPORT)
|
||||
.description("Set the path to a file that will be read.")
|
||||
.buildTime(false)
|
||||
.build();
|
||||
|
||||
public static final Option<String> DIR = new OptionBuilder<>("dir", String.class)
|
||||
.category(OptionCategory.IMPORT)
|
||||
.description("Set the path to a directory where files will be read from.")
|
||||
.buildTime(false)
|
||||
.build();
|
||||
|
||||
public static final Option<Boolean> OVERRIDE = new OptionBuilder<>("override", Boolean.class)
|
||||
.category(OptionCategory.IMPORT)
|
||||
.defaultValue(Boolean.TRUE)
|
||||
.description("Set if existing data should be overwritten. If set to false, data will be ignored.")
|
||||
.buildTime(false)
|
||||
.build();
|
||||
|
||||
}
|
|
@ -16,9 +16,10 @@ public enum OptionCategory {
|
|||
LOGGING("Logging", 110, ConfigSupportLevel.SUPPORTED),
|
||||
SECURITY("Security", 120, ConfigSupportLevel.PREVIEW),
|
||||
EXPORT("Export", 130, ConfigSupportLevel.SUPPORTED),
|
||||
IMPORT("Import", 140, ConfigSupportLevel.SUPPORTED),
|
||||
GENERAL("General", 999, ConfigSupportLevel.SUPPORTED);
|
||||
|
||||
private String heading;
|
||||
private final String heading;
|
||||
//Categories with a lower number are shown before groups with a higher number
|
||||
private final int order;
|
||||
private final ConfigSupportLevel supportLevel;
|
||||
|
|
|
@ -50,6 +50,7 @@ import java.util.stream.Collectors;
|
|||
import org.eclipse.microprofile.config.spi.ConfigSource;
|
||||
import org.keycloak.config.MultiOption;
|
||||
import org.keycloak.config.OptionCategory;
|
||||
import org.keycloak.quarkus.runtime.cli.command.AbstractCommand;
|
||||
import org.keycloak.quarkus.runtime.cli.command.AbstractStartCommand;
|
||||
import org.keycloak.quarkus.runtime.cli.command.Build;
|
||||
import org.keycloak.quarkus.runtime.cli.command.Export;
|
||||
|
@ -347,7 +348,7 @@ public final class Picocli {
|
|||
|
||||
if (isRebuildCheck()) {
|
||||
// build command should be available when running re-aug
|
||||
addCommandOptions(cliArgs, spec.subcommands().get(Build.NAME).getCommandSpec());
|
||||
addCommandOptions(cliArgs, spec.subcommands().get(Build.NAME));
|
||||
}
|
||||
|
||||
CommandLine cmd = new CommandLine(spec);
|
||||
|
@ -361,16 +362,22 @@ public final class Picocli {
|
|||
return cmd;
|
||||
}
|
||||
|
||||
private static void addCommandOptions(List<String> cliArgs, CommandSpec command) {
|
||||
private static void addCommandOptions(List<String> cliArgs, CommandLine command) {
|
||||
if (command != null) {
|
||||
boolean includeBuildTime = false;
|
||||
boolean includeRuntime = false;
|
||||
|
||||
if (Start.NAME.equals(command.name()) || StartDev.NAME.equals(command.name()) || Export.NAME.equals(command.name())) {
|
||||
if (command.getCommand() instanceof AbstractCommand) {
|
||||
AbstractCommand abstractCommand = command.getCommand();
|
||||
includeRuntime = abstractCommand.includeRuntime();
|
||||
includeBuildTime = abstractCommand.includeBuildTime();
|
||||
}
|
||||
|
||||
if (!includeBuildTime && !includeRuntime) {
|
||||
return;
|
||||
} else if (includeRuntime && !includeBuildTime && (Start.NAME.equals(command.getCommandName())) || StartDev.NAME.equals(command.getCommandName())) {
|
||||
includeBuildTime = isRebuilt() || !cliArgs.contains(OPTIMIZED_BUILD_OPTION_LONG);
|
||||
includeRuntime = true;
|
||||
} else if (Build.NAME.equals(command.name())) {
|
||||
includeBuildTime = true;
|
||||
} else if (includeBuildTime && !includeRuntime) {
|
||||
includeRuntime = isRebuildCheck();
|
||||
}
|
||||
|
||||
|
@ -378,19 +385,19 @@ public final class Picocli {
|
|||
}
|
||||
}
|
||||
|
||||
private static CommandSpec getCurrentCommandSpec(List<String> cliArgs, CommandSpec spec) {
|
||||
private static CommandLine getCurrentCommandSpec(List<String> cliArgs, CommandSpec spec) {
|
||||
for (String arg : cliArgs) {
|
||||
CommandLine command = spec.subcommands().get(arg);
|
||||
|
||||
if (command != null) {
|
||||
return command.getCommandSpec();
|
||||
return command;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static void addOptionsToCli(CommandSpec commandSpec, boolean includeBuildTime, boolean includeRuntime) {
|
||||
private static void addOptionsToCli(CommandLine commandLine, boolean includeBuildTime, boolean includeRuntime) {
|
||||
Map<OptionCategory, List<PropertyMapper>> mappers = new EnumMap<>(OptionCategory.class);
|
||||
|
||||
if (includeRuntime) {
|
||||
|
@ -408,23 +415,12 @@ public final class Picocli {
|
|||
}
|
||||
}
|
||||
|
||||
addMappedOptionsToArgGroups(commandSpec, mappers);
|
||||
addMappedOptionsToArgGroups(commandLine, mappers);
|
||||
}
|
||||
|
||||
private static void addMappedOptionsToArgGroups(CommandSpec cSpec, Map<OptionCategory, List<PropertyMapper>> propertyMappers) {
|
||||
for(OptionCategory category : OptionCategory.values()) {
|
||||
if (cSpec.name().equals(Export.NAME)) {
|
||||
// The export command should show only export options
|
||||
if (category != OptionCategory.EXPORT) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
// No other command should have the export options
|
||||
if (category == OptionCategory.EXPORT) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
private static void addMappedOptionsToArgGroups(CommandLine commandLine, Map<OptionCategory, List<PropertyMapper>> propertyMappers) {
|
||||
CommandSpec cSpec = commandLine.getCommandSpec();
|
||||
for(OptionCategory category : ((AbstractCommand) commandLine.getCommand()).getOptionCategories()) {
|
||||
List<PropertyMapper> mappersInCategory = propertyMappers.get(category);
|
||||
|
||||
if (mappersInCategory == null) {
|
||||
|
|
|
@ -19,10 +19,14 @@ package org.keycloak.quarkus.runtime.cli.command;
|
|||
|
||||
import static org.keycloak.quarkus.runtime.Messages.cliExecutionError;
|
||||
|
||||
import org.keycloak.config.OptionCategory;
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Model.CommandSpec;
|
||||
import picocli.CommandLine.Spec;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public abstract class AbstractCommand {
|
||||
|
||||
@Spec
|
||||
|
@ -35,4 +39,25 @@ public abstract class AbstractCommand {
|
|||
protected void executionError(CommandLine cmd, String message, Throwable cause) {
|
||||
cliExecutionError(cmd, message, cause);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this command should include runtime options for the CLI.
|
||||
*/
|
||||
public boolean includeRuntime() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if this command should include build time options for the CLI.
|
||||
*/
|
||||
public boolean includeBuildTime() {
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all option categories which are available for this command.
|
||||
*/
|
||||
public List<OptionCategory> getOptionCategories() {
|
||||
return Arrays.asList(OptionCategory.values());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,28 +17,19 @@
|
|||
|
||||
package org.keycloak.quarkus.runtime.cli.command;
|
||||
|
||||
import org.keycloak.config.OptionCategory;
|
||||
import org.keycloak.quarkus.runtime.Environment;
|
||||
import picocli.CommandLine;
|
||||
|
||||
import picocli.CommandLine.Option;
|
||||
|
||||
import static org.keycloak.exportimport.ExportImportConfig.ACTION_EXPORT;
|
||||
import static org.keycloak.exportimport.ExportImportConfig.ACTION_IMPORT;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public abstract class AbstractExportImportCommand extends AbstractStartCommand implements Runnable {
|
||||
|
||||
private final String action;
|
||||
|
||||
@Option(names = "--dir",
|
||||
arity = "1",
|
||||
description = "Set the path to a directory where files will be read from when importing or created with the exported data.",
|
||||
paramLabel = "<path>")
|
||||
String toDir;
|
||||
|
||||
@Option(names = "--file",
|
||||
arity = "1",
|
||||
description = "Set the path to a file that will be read when importing or created with the exported data.",
|
||||
paramLabel = "<path>")
|
||||
String toFile;
|
||||
@CommandLine.Mixin
|
||||
HelpAllMixin helpAllMixin;
|
||||
|
||||
protected AbstractExportImportCommand(String action) {
|
||||
this.action = action;
|
||||
|
@ -48,25 +39,23 @@ public abstract class AbstractExportImportCommand extends AbstractStartCommand i
|
|||
public void run() {
|
||||
System.setProperty("keycloak.migration.action", action);
|
||||
|
||||
if (action.equals(ACTION_IMPORT)) {
|
||||
if (toDir != null) {
|
||||
System.setProperty("keycloak.migration.provider", "dir");
|
||||
System.setProperty("keycloak.migration.dir", toDir);
|
||||
} else if (toFile != null) {
|
||||
System.setProperty("keycloak.migration.provider", "singleFile");
|
||||
System.setProperty("keycloak.migration.file", toFile);
|
||||
} else {
|
||||
executionError(spec.commandLine(), "Must specify either --dir or --file options.");
|
||||
}
|
||||
} else if (action.equals(ACTION_EXPORT)) {
|
||||
// for export, the properties are no longer needed and will be passed by the property mappers
|
||||
if (toDir == null && toFile == null) {
|
||||
executionError(spec.commandLine(), "Must specify either --dir or --file options.");
|
||||
}
|
||||
}
|
||||
|
||||
Environment.setProfile(Environment.IMPORT_EXPORT_MODE);
|
||||
|
||||
super.run();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OptionCategory> getOptionCategories() {
|
||||
return super.getOptionCategories().stream().filter(optionCategory ->
|
||||
optionCategory != OptionCategory.HTTP &&
|
||||
optionCategory != OptionCategory.PROXY &&
|
||||
optionCategory != OptionCategory.HOSTNAME &&
|
||||
optionCategory != OptionCategory.METRICS &&
|
||||
optionCategory != OptionCategory.HEALTH).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeRuntime() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@ import static org.keycloak.quarkus.runtime.Environment.isDevMode;
|
|||
import static org.keycloak.quarkus.runtime.cli.Picocli.println;
|
||||
import static org.keycloak.quarkus.runtime.configuration.ConfigArgsConfigSource.getAllCliArgs;
|
||||
|
||||
import org.keycloak.config.OptionCategory;
|
||||
import org.keycloak.quarkus.runtime.Environment;
|
||||
import org.keycloak.quarkus.runtime.Messages;
|
||||
|
||||
|
@ -32,6 +33,9 @@ import io.quarkus.runtime.configuration.ProfileManager;
|
|||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Command;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Command(name = Build.NAME,
|
||||
header = "Creates a new and optimized server image.",
|
||||
description = {
|
||||
|
@ -81,6 +85,15 @@ public final class Build extends AbstractCommand implements Runnable {
|
|||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeBuildTime() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public List<OptionCategory> getOptionCategories() {
|
||||
return super.getOptionCategories().stream().filter(optionCategory -> optionCategory != OptionCategory.EXPORT && optionCategory != OptionCategory.IMPORT).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private void exitWithErrorIfDevProfileIsSetAndNotStartDev() {
|
||||
if (Environment.isDevProfile() && !getAllCliArgs().contains(StartDev.NAME)) {
|
||||
executionError(spec.commandLine(), Messages.devProfileNotAllowedError(NAME));
|
||||
|
|
|
@ -19,8 +19,11 @@ package org.keycloak.quarkus.runtime.cli.command;
|
|||
|
||||
import static org.keycloak.exportimport.ExportImportConfig.ACTION_EXPORT;
|
||||
|
||||
import org.keycloak.config.OptionCategory;
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Option;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Command(name = Export.NAME,
|
||||
header = "Export data from realms to a file or directory.",
|
||||
|
@ -33,4 +36,10 @@ public final class Export extends AbstractExportImportCommand implements Runnabl
|
|||
super(ACTION_EXPORT);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OptionCategory> getOptionCategories() {
|
||||
return super.getOptionCategories().stream().filter(optionCategory ->
|
||||
optionCategory != OptionCategory.IMPORT).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,11 +18,12 @@
|
|||
package org.keycloak.quarkus.runtime.cli.command;
|
||||
|
||||
import static org.keycloak.exportimport.ExportImportConfig.ACTION_IMPORT;
|
||||
import static org.keycloak.exportimport.Strategy.IGNORE_EXISTING;
|
||||
import static org.keycloak.exportimport.Strategy.OVERWRITE_EXISTING;
|
||||
|
||||
import org.keycloak.config.OptionCategory;
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Option;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Command(name = Import.NAME,
|
||||
header = "Import data from a directory or a file.",
|
||||
|
@ -31,19 +32,14 @@ public final class Import extends AbstractExportImportCommand implements Runnabl
|
|||
|
||||
public static final String NAME = "import";
|
||||
|
||||
@Option(names = "--override",
|
||||
arity = "1",
|
||||
description = "Set if existing data should be skipped or overridden.",
|
||||
paramLabel = "false",
|
||||
defaultValue = "true")
|
||||
boolean override;
|
||||
|
||||
public Import() {
|
||||
super(ACTION_IMPORT);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doBeforeRun() {
|
||||
System.setProperty("keycloak.migration.strategy", override ? OVERWRITE_EXISTING.name() : IGNORE_EXISTING.name());
|
||||
public List<OptionCategory> getOptionCategories() {
|
||||
return super.getOptionCategories().stream().filter(optionCategory ->
|
||||
optionCategory != OptionCategory.EXPORT).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import static org.keycloak.quarkus.runtime.Environment.setProfile;
|
|||
import static org.keycloak.quarkus.runtime.cli.Picocli.NO_PARAM_LABEL;
|
||||
import static org.keycloak.quarkus.runtime.configuration.Configuration.getRawPersistedProperty;
|
||||
|
||||
import org.keycloak.config.OptionCategory;
|
||||
import org.keycloak.quarkus.runtime.Environment;
|
||||
import org.keycloak.quarkus.runtime.Messages;
|
||||
|
||||
|
@ -29,6 +30,7 @@ import picocli.CommandLine.Command;
|
|||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Command(name = Start.NAME,
|
||||
header = "Start the server.",
|
||||
|
@ -85,4 +87,13 @@ public final class Start extends AbstractStartCommand implements Runnable {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
public List<OptionCategory> getOptionCategories() {
|
||||
return super.getOptionCategories().stream().filter(optionCategory -> optionCategory != OptionCategory.EXPORT && optionCategory != OptionCategory.IMPORT).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeRuntime() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
|
||||
package org.keycloak.quarkus.runtime.cli.command;
|
||||
|
||||
import org.keycloak.config.OptionCategory;
|
||||
import org.keycloak.quarkus.runtime.Environment;
|
||||
|
||||
import picocli.CommandLine;
|
||||
|
@ -24,6 +25,9 @@ import picocli.CommandLine.Command;
|
|||
import picocli.CommandLine.Mixin;
|
||||
import picocli.CommandLine.Option;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Command(name = StartDev.NAME,
|
||||
header = "Start the server in development mode.",
|
||||
description = {
|
||||
|
@ -48,4 +52,14 @@ public final class StartDev extends AbstractStartCommand implements Runnable {
|
|||
protected void doBeforeRun() {
|
||||
Environment.forceDevProfile();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<OptionCategory> getOptionCategories() {
|
||||
return super.getOptionCategories().stream().filter(optionCategory -> optionCategory != OptionCategory.EXPORT && optionCategory != OptionCategory.IMPORT).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean includeRuntime() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,9 +20,11 @@ package org.keycloak.quarkus.runtime.configuration.mappers;
|
|||
import io.smallrye.config.ConfigSourceInterceptorContext;
|
||||
import io.smallrye.config.ConfigValue;
|
||||
import org.keycloak.config.ExportOptions;
|
||||
import picocli.CommandLine;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.keycloak.exportimport.ExportImportConfig.PROVIDER;
|
||||
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
|
||||
|
||||
final class ExportPropertyMappers {
|
||||
|
@ -65,6 +67,10 @@ final class ExportPropertyMappers {
|
|||
}
|
||||
|
||||
private static Optional<String> transformExporter(Optional<String> option, ConfigSourceInterceptorContext context) {
|
||||
ConfigValue exporter = context.proceed("kc.spi-export-exporter");
|
||||
if (exporter != null) {
|
||||
return Optional.of(exporter.getValue());
|
||||
}
|
||||
if (option.isPresent()) {
|
||||
return Optional.of("singleFile");
|
||||
}
|
||||
|
@ -72,6 +78,13 @@ final class ExportPropertyMappers {
|
|||
if (dirConfigValue != null && dirConfigValue.getValue() != null) {
|
||||
return Optional.of("dir");
|
||||
}
|
||||
ConfigValue dirValue = context.proceed("kc.dir");
|
||||
if (dirValue != null && dirValue.getValue() != null) {
|
||||
return Optional.of("dir");
|
||||
}
|
||||
if (System.getProperty(PROVIDER) == null) {
|
||||
throw new CommandLine.PicocliException("Must specify either --dir or --file options.");
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
* Copyright 2023 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.keycloak.quarkus.runtime.configuration.mappers;
|
||||
|
||||
import io.smallrye.config.ConfigSourceInterceptorContext;
|
||||
import io.smallrye.config.ConfigValue;
|
||||
import org.keycloak.config.ImportOptions;
|
||||
import org.keycloak.exportimport.Strategy;
|
||||
import picocli.CommandLine;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.keycloak.exportimport.ExportImportConfig.PROVIDER;
|
||||
import static org.keycloak.quarkus.runtime.configuration.mappers.PropertyMapper.fromOption;
|
||||
|
||||
final class ImportPropertyMappers {
|
||||
|
||||
private ImportPropertyMappers() {
|
||||
}
|
||||
|
||||
public static PropertyMapper<?>[] getMappers() {
|
||||
return new PropertyMapper[] {
|
||||
fromOption(ImportOptions.FILE)
|
||||
.to("kc.spi-import-importer")
|
||||
.transformer(ImportPropertyMappers::transformImporter)
|
||||
.paramLabel("file")
|
||||
.build(),
|
||||
fromOption(ImportOptions.FILE)
|
||||
.to("kc.spi-import-single-file-file")
|
||||
.paramLabel("file")
|
||||
.build(),
|
||||
fromOption(ImportOptions.DIR)
|
||||
.to("kc.spi-import-dir-dir")
|
||||
.paramLabel("dir")
|
||||
.build(),
|
||||
fromOption(ImportOptions.OVERRIDE)
|
||||
.to("kc.spi-import-single-file-strategy")
|
||||
.transformer(ImportPropertyMappers::transformOverride)
|
||||
.build(),
|
||||
fromOption(ImportOptions.OVERRIDE)
|
||||
.to("kc.spi-import-dir-strategy")
|
||||
.transformer(ImportPropertyMappers::transformOverride)
|
||||
.build(),
|
||||
};
|
||||
}
|
||||
|
||||
private static Optional<String> transformOverride(Optional<String> option, ConfigSourceInterceptorContext context) {
|
||||
if (option.isPresent() && Boolean.parseBoolean(option.get())) {
|
||||
return Optional.of(Strategy.OVERWRITE_EXISTING.name());
|
||||
} else {
|
||||
return Optional.of(Strategy.IGNORE_EXISTING.name());
|
||||
}
|
||||
}
|
||||
|
||||
private static Optional<String> transformImporter(Optional<String> option, ConfigSourceInterceptorContext context) {
|
||||
ConfigValue importer = context.proceed("kc.spi-import-importer");
|
||||
if (importer != null) {
|
||||
return Optional.of(importer.getValue());
|
||||
}
|
||||
if (option.isPresent()) {
|
||||
return Optional.of("singleFile");
|
||||
}
|
||||
ConfigValue dirConfigValue = context.proceed("kc.spi-import-dir-dir");
|
||||
if (dirConfigValue != null && dirConfigValue.getValue() != null) {
|
||||
return Optional.of("dir");
|
||||
}
|
||||
ConfigValue dirValue = context.proceed("kc.dir");
|
||||
if (dirConfigValue != null && dirValue.getValue() != null) {
|
||||
return Optional.of("dir");
|
||||
}
|
||||
if (System.getProperty(PROVIDER) == null) {
|
||||
throw new CommandLine.PicocliException("Must specify either --dir or --file options.");
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
}
|
|
@ -39,6 +39,7 @@ public final class PropertyMappers {
|
|||
MAPPERS.addAll(ClassLoaderPropertyMappers.getMappers());
|
||||
MAPPERS.addAll(SecurityPropertyMappers.getMappers());
|
||||
MAPPERS.addAll(ExportPropertyMappers.getMappers());
|
||||
MAPPERS.addAll(ImportPropertyMappers.getMappers());
|
||||
}
|
||||
|
||||
public static ConfigValue getValue(ConfigSourceInterceptorContext context, String name) {
|
||||
|
|
|
@ -38,6 +38,6 @@ public class ExportDistTest {
|
|||
cliResult.assertNoMessage("Listening on:");
|
||||
|
||||
cliResult = dist.run("export", "--realm=master");
|
||||
cliResult.assertError("Must specify either --dir or --file options.");
|
||||
cliResult.assertMessage("Must specify either --dir or --file options.");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -28,6 +28,8 @@ import org.keycloak.it.junit5.extension.DistributionTest;
|
|||
import org.keycloak.it.junit5.extension.RawDistOnly;
|
||||
import org.keycloak.it.utils.KeycloakDistribution;
|
||||
import org.keycloak.quarkus.runtime.cli.command.Build;
|
||||
import org.keycloak.quarkus.runtime.cli.command.Export;
|
||||
import org.keycloak.quarkus.runtime.cli.command.Import;
|
||||
import org.keycloak.quarkus.runtime.cli.command.Start;
|
||||
import org.keycloak.quarkus.runtime.cli.command.StartDev;
|
||||
|
||||
|
@ -110,6 +112,34 @@ public class HelpCommandDistTest {
|
|||
cliResult.assertHelp();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Launch({ Export.NAME, "--help" })
|
||||
void testExportHelp(LaunchResult result) {
|
||||
CLIResult cliResult = (CLIResult) result;
|
||||
cliResult.assertHelp();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Launch({ Export.NAME, "--help-all" })
|
||||
void testExportHelpAll(LaunchResult result) {
|
||||
CLIResult cliResult = (CLIResult) result;
|
||||
cliResult.assertHelp();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Launch({ Import.NAME, "--help" })
|
||||
void testImportHelp(LaunchResult result) {
|
||||
CLIResult cliResult = (CLIResult) result;
|
||||
cliResult.assertHelp();
|
||||
}
|
||||
|
||||
@Test
|
||||
@Launch({ Import.NAME, "--help-all" })
|
||||
void testImportHelpAll(LaunchResult result) {
|
||||
CLIResult cliResult = (CLIResult) result;
|
||||
cliResult.assertHelp();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHelpDoesNotStartReAugJvm(KeycloakDistribution dist) {
|
||||
for (String helpCmd : List.of("-h", "--help", "--help-all")) {
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
HelpCommandDistTest.*.received.txt
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -17,7 +17,6 @@
|
|||
|
||||
package org.keycloak.exportimport;
|
||||
|
||||
import org.keycloak.models.KeycloakSessionFactory;
|
||||
import org.keycloak.provider.Provider;
|
||||
|
||||
import java.io.IOException;
|
||||
|
@ -27,13 +26,10 @@ import java.io.IOException;
|
|||
*/
|
||||
public interface ImportProvider extends Provider {
|
||||
|
||||
void importModel(KeycloakSessionFactory factory, Strategy strategy) throws IOException;
|
||||
|
||||
void importRealm(KeycloakSessionFactory factory, String realmName, Strategy strategy) throws IOException;
|
||||
void importModel() throws IOException;
|
||||
|
||||
/**
|
||||
* @return true if master realm was previously exported and is available in the data to be imported
|
||||
* @throws IOException
|
||||
* @return true, if master realm was previously exported and is available in the data to be imported
|
||||
*/
|
||||
boolean isMasterRealmExported() throws IOException;
|
||||
}
|
||||
|
|
|
@ -62,18 +62,10 @@ public class ExportImportConfig {
|
|||
System.setProperty(ACTION, exportImportAction);
|
||||
}
|
||||
|
||||
public static String getProvider() {
|
||||
return System.getProperty(PROVIDER, PROVIDER_DEFAULT);
|
||||
}
|
||||
|
||||
public static void setProvider(String exportImportProvider) {
|
||||
System.setProperty(PROVIDER, exportImportProvider);
|
||||
}
|
||||
|
||||
public static String getRealmName() {
|
||||
return System.getProperty(REALM_NAME);
|
||||
}
|
||||
|
||||
public static void setRealmName(String realmName) {
|
||||
if (realmName != null) {
|
||||
System.setProperty(REALM_NAME, realmName);
|
||||
|
@ -82,27 +74,14 @@ public class ExportImportConfig {
|
|||
}
|
||||
}
|
||||
|
||||
public static String getDir() {
|
||||
return System.getProperty(DIR);
|
||||
}
|
||||
|
||||
public static String setDir(String dir) {
|
||||
return System.setProperty(DIR, dir);
|
||||
}
|
||||
|
||||
public static String getFile() {
|
||||
return System.getProperty(FILE);
|
||||
public static void setDir(String dir) {
|
||||
System.setProperty(DIR, dir);
|
||||
}
|
||||
|
||||
public static void setFile(String file) {
|
||||
System.setProperty(FILE, file);
|
||||
}
|
||||
|
||||
public static Strategy getStrategy() {
|
||||
String strategy = System.getProperty(STRATEGY, DEFAULT_STRATEGY.toString());
|
||||
return Enum.valueOf(Strategy.class, strategy);
|
||||
}
|
||||
|
||||
public static boolean isReplacePlaceholders() {
|
||||
return Boolean.getBoolean(REPLACE_PLACEHOLDERS);
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
|
||||
package org.keycloak.exportimport;
|
||||
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
import org.keycloak.Config;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -25,7 +24,6 @@ import org.keycloak.models.KeycloakSessionFactory;
|
|||
import org.keycloak.models.KeycloakSessionTask;
|
||||
import org.keycloak.models.utils.KeycloakModelUtils;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
import org.keycloak.services.ServicesLogger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
@ -33,6 +31,7 @@ import java.nio.file.Files;
|
|||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.HashSet;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
@ -47,10 +46,8 @@ public class ExportImportManager {
|
|||
|
||||
private static final Logger logger = Logger.getLogger(ExportImportManager.class);
|
||||
|
||||
private KeycloakSessionFactory sessionFactory;
|
||||
private KeycloakSession session;
|
||||
|
||||
private final String realmName;
|
||||
private final KeycloakSessionFactory sessionFactory;
|
||||
private final KeycloakSession session;
|
||||
|
||||
private ExportProvider exportProvider;
|
||||
private ImportProvider importProvider;
|
||||
|
@ -59,9 +56,6 @@ public class ExportImportManager {
|
|||
this.sessionFactory = session.getKeycloakSessionFactory();
|
||||
this.session = session;
|
||||
|
||||
realmName = ExportImportConfig.getRealmName();
|
||||
|
||||
String providerId = ExportImportConfig.getProvider();
|
||||
String exportImportAction = ExportImportConfig.getAction();
|
||||
|
||||
if (ExportImportConfig.ACTION_EXPORT.equals(exportImportAction)) {
|
||||
|
@ -70,12 +64,13 @@ public class ExportImportManager {
|
|||
// Setting this to "provider" doesn't work yet when instrumenting Keycloak with Quarkus as it leads to
|
||||
// "java.lang.NullPointerException: Cannot invoke "String.indexOf(String)" because "value" is null"
|
||||
// when calling "Config.getProvider()" from "KeycloakProcessor.loadFactories()"
|
||||
providerId = Config.scope("export").get("exporter", System.getProperty(PROVIDER, PROVIDER_DEFAULT));
|
||||
String providerId = System.getProperty(PROVIDER, Config.scope("export").get("exporter", PROVIDER_DEFAULT));
|
||||
exportProvider = session.getProvider(ExportProvider.class, providerId);
|
||||
if (exportProvider == null) {
|
||||
throw new RuntimeException("Export provider '" + providerId + "' not found");
|
||||
}
|
||||
} else if (ExportImportConfig.ACTION_IMPORT.equals(exportImportAction)) {
|
||||
String providerId = System.getProperty(PROVIDER, Config.scope("import").get("importer", PROVIDER_DEFAULT));
|
||||
importProvider = session.getProvider(ImportProvider.class, providerId);
|
||||
if (importProvider == null) {
|
||||
throw new RuntimeException("Import provider '" + providerId + "' not found");
|
||||
|
@ -104,21 +99,13 @@ public class ExportImportManager {
|
|||
|
||||
public void runImport() {
|
||||
try {
|
||||
Strategy strategy = ExportImportConfig.getStrategy();
|
||||
if (realmName == null) {
|
||||
ServicesLogger.LOGGER.fullModelImport(strategy.toString());
|
||||
importProvider.importModel(sessionFactory, strategy);
|
||||
} else {
|
||||
ServicesLogger.LOGGER.realmImportRequested(realmName, strategy.toString());
|
||||
importProvider.importRealm(sessionFactory, realmName, strategy);
|
||||
}
|
||||
ServicesLogger.LOGGER.importSuccess();
|
||||
importProvider.importModel();
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to run import", e);
|
||||
}
|
||||
}
|
||||
|
||||
public void runImportAtStartup(String dir, Strategy strategy) throws IOException {
|
||||
public void runImportAtStartup(String dir) throws IOException {
|
||||
ExportImportConfig.setReplacePlaceholders(true);
|
||||
ExportImportConfig.setAction("import");
|
||||
|
||||
|
@ -130,11 +117,13 @@ public class ExportImportManager {
|
|||
if ("dir".equals(providerId)) {
|
||||
ExportImportConfig.setDir(dir);
|
||||
ImportProvider importProvider = session.getProvider(ImportProvider.class, providerId);
|
||||
importProvider.importModel(sessionFactory, strategy);
|
||||
importProvider.importModel();
|
||||
} else if ("singleFile".equals(providerId)) {
|
||||
Set<String> filesToImport = new HashSet<>();
|
||||
|
||||
for (File file : Paths.get(dir).toFile().listFiles()) {
|
||||
File[] files = Paths.get(dir).toFile().listFiles();
|
||||
Objects.requireNonNull(files, "directory not found");
|
||||
for (File file : files) {
|
||||
Path filePath = file.toPath();
|
||||
|
||||
if (!(Files.exists(filePath) && Files.isRegularFile(filePath) && filePath.toString().endsWith(".json"))) {
|
||||
|
@ -158,7 +147,7 @@ public class ExportImportManager {
|
|||
public void run(KeycloakSession session) {
|
||||
ImportProvider importProvider = session.getProvider(ImportProvider.class, providerId);
|
||||
try {
|
||||
importProvider.importModel(sessionFactory, strategy);
|
||||
importProvider.importModel();
|
||||
} catch (IOException cause) {
|
||||
throw new RuntimeException(cause);
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ import org.keycloak.common.Profile;
|
|||
import org.keycloak.common.crypto.CryptoIntegration;
|
||||
import org.keycloak.common.util.Resteasy;
|
||||
import org.keycloak.config.ConfigProviderFactory;
|
||||
import org.keycloak.exportimport.ExportImportConfig;
|
||||
import org.keycloak.exportimport.ExportImportManager;
|
||||
import org.keycloak.exportimport.Strategy;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
|
@ -256,7 +257,8 @@ public class KeycloakApplication extends Application {
|
|||
String dir = System.getProperty("keycloak.import");
|
||||
if (dir != null) {
|
||||
try {
|
||||
exportImportManager.runImportAtStartup(dir, Strategy.IGNORE_EXISTING);
|
||||
System.setProperty(ExportImportConfig.STRATEGY, Strategy.IGNORE_EXISTING.toString());
|
||||
exportImportManager.runImportAtStartup(dir);
|
||||
} catch (IOException cause) {
|
||||
throw new RuntimeException("Failed to import realms", cause);
|
||||
}
|
||||
|
|
|
@ -30,8 +30,6 @@ import org.keycloak.common.profile.PropertiesProfileConfigResolver;
|
|||
import org.keycloak.common.util.Time;
|
||||
import org.keycloak.component.ComponentFactoryProviderFactory;
|
||||
import org.keycloak.component.ComponentFactorySpi;
|
||||
import org.keycloak.device.DeviceRepresentationProviderFactoryImpl;
|
||||
import org.keycloak.device.DeviceRepresentationSpi;
|
||||
import org.keycloak.events.EventStoreSpi;
|
||||
import org.keycloak.executors.DefaultExecutorsProviderFactory;
|
||||
import org.keycloak.executors.ExecutorsSpi;
|
||||
|
@ -279,9 +277,9 @@ public abstract class KeycloakModelTest {
|
|||
Stream.of(basicParameters),
|
||||
Stream.of(System.getProperty("keycloak.model.parameters", "").split("\\s*,\\s*"))
|
||||
.filter(s -> s != null && ! s.trim().isEmpty())
|
||||
.map(cn -> { try { return Class.forName(cn.indexOf('.') >= 0 ? cn : ("org.keycloak.testsuite.model.parameters." + cn)); } catch (Exception e) { LOG.error("Cannot find " + cn); return null; }})
|
||||
.map(cn -> { try { return Class.forName(cn.indexOf('.') >= 0 ? cn : ("org.keycloak.testsuite.model.parameters." + cn)); } catch (Exception e) { throw new RuntimeException("Cannot find class " + cn, e); }})
|
||||
.filter(Objects::nonNull)
|
||||
.map(c -> { try { return c.getDeclaredConstructor().newInstance(); } catch (Exception e) { LOG.error("Cannot instantiate " + c); return null; }} )
|
||||
.map(c -> { try { return c.getDeclaredConstructor().newInstance(); } catch (Exception e) { throw new RuntimeException("Cannot instantiate class " + c, e); }} )
|
||||
.filter(KeycloakModelParameters.class::isInstance)
|
||||
.map(KeycloakModelParameters.class::cast)
|
||||
)
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.model.export;
|
||||
package org.keycloak.testsuite.model.exportimport;
|
||||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.junit.Assert;
|
||||
|
@ -76,11 +76,10 @@ public class ExportModelTest extends KeycloakModelTest {
|
|||
.provider(SingleFileExportProviderFactory.PROVIDER_ID)
|
||||
.config(SingleFileExportProviderFactory.REALM_NAME, REALM_NAME);
|
||||
|
||||
withRealm(realmId, (session, realm) -> {
|
||||
inComittedTransaction(session -> {
|
||||
ExportImportConfig.setAction(ExportImportConfig.ACTION_EXPORT);
|
||||
ExportImportManager exportImportManager = new ExportImportManager(session);
|
||||
exportImportManager.runExport();
|
||||
return null;
|
||||
});
|
||||
|
||||
// file will exist if export was successful
|
||||
|
@ -112,11 +111,10 @@ public class ExportModelTest extends KeycloakModelTest {
|
|||
.provider(DirExportProviderFactory.PROVIDER_ID)
|
||||
.config(DirExportProviderFactory.REALM_NAME, REALM_NAME);
|
||||
|
||||
withRealm(realmId, (session, realm) -> {
|
||||
inComittedTransaction(session -> {
|
||||
ExportImportConfig.setAction(ExportImportConfig.ACTION_EXPORT);
|
||||
ExportImportManager exportImportManager = new ExportImportManager(session);
|
||||
exportImportManager.runExport();
|
||||
return null;
|
||||
});
|
||||
|
||||
// file will exist if export was successful
|
|
@ -0,0 +1,129 @@
|
|||
/*
|
||||
* Copyright 2023 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
package org.keycloak.testsuite.model.exportimport;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.keycloak.exportimport.ExportImportConfig;
|
||||
import org.keycloak.exportimport.ExportImportManager;
|
||||
import org.keycloak.exportimport.ExportProvider;
|
||||
import org.keycloak.exportimport.ImportProvider;
|
||||
import org.keycloak.exportimport.dir.DirExportProviderFactory;
|
||||
import org.keycloak.exportimport.dir.DirImportProviderFactory;
|
||||
import org.keycloak.exportimport.singlefile.SingleFileImportProviderFactory;
|
||||
import org.keycloak.models.KeycloakSession;
|
||||
import org.keycloak.models.RealmModel;
|
||||
import org.keycloak.services.managers.ApplianceBootstrap;
|
||||
import org.keycloak.testsuite.model.KeycloakModelTest;
|
||||
import org.keycloak.testsuite.model.RequireProvider;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
@RequireProvider(value = ImportProvider.class)
|
||||
public class ImportModelTest extends KeycloakModelTest {
|
||||
|
||||
public static final String SPI_NAME = "import";
|
||||
|
||||
@Override
|
||||
public void createEnvironment(KeycloakSession s) {
|
||||
// Master realm is needed for importing a realm
|
||||
if (s.realms().getRealmByName("master") == null) {
|
||||
new ApplianceBootstrap(s).createMasterRealm();
|
||||
}
|
||||
// clean-up test realm which might be left-over from a previous run
|
||||
RealmModel test = s.realms().getRealmByName("test");
|
||||
if (test != null) {
|
||||
s.realms().removeRealm(test.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanEnvironment(KeycloakSession s) {
|
||||
RealmModel master = s.realms().getRealmByName("master");
|
||||
if (master != null) {
|
||||
s.realms().removeRealm(master.getId());
|
||||
}
|
||||
RealmModel test = s.realms().getRealmByName("test");
|
||||
if (test != null) {
|
||||
s.realms().removeRealm(test.getId());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequireProvider(value = ExportProvider.class, only = SingleFileImportProviderFactory.PROVIDER_ID)
|
||||
public void testImportSingleFile() {
|
||||
try {
|
||||
Path singleFileExport = Paths.get("src/test/resources/exportimport/singleFile/testrealm.json");
|
||||
|
||||
CONFIG.spi(SPI_NAME)
|
||||
.config("importer", new SingleFileImportProviderFactory().getId());
|
||||
CONFIG.spi(SPI_NAME)
|
||||
.provider(SingleFileImportProviderFactory.PROVIDER_ID)
|
||||
.config(SingleFileImportProviderFactory.FILE, singleFileExport.toAbsolutePath().toString());
|
||||
|
||||
inComittedTransaction(session -> {
|
||||
ExportImportConfig.setAction(ExportImportConfig.ACTION_IMPORT);
|
||||
ExportImportManager exportImportManager = new ExportImportManager(session);
|
||||
exportImportManager.runImport();
|
||||
});
|
||||
|
||||
inComittedTransaction(session -> {
|
||||
Assert.assertNotNull(session.realms().getRealmByName("test"));
|
||||
});
|
||||
|
||||
} finally {
|
||||
CONFIG.spi(SPI_NAME)
|
||||
.config("importer", null);
|
||||
CONFIG.spi(SPI_NAME)
|
||||
.provider(SingleFileImportProviderFactory.PROVIDER_ID)
|
||||
.config(SingleFileImportProviderFactory.FILE, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@RequireProvider(value = ExportProvider.class, only = DirImportProviderFactory.PROVIDER_ID)
|
||||
public void testImportDirectory() {
|
||||
try {
|
||||
Path importFolder = Paths.get("src/test/resources/exportimport/dir");
|
||||
CONFIG.spi(SPI_NAME)
|
||||
.config("importer", new DirImportProviderFactory().getId());
|
||||
CONFIG.spi(SPI_NAME)
|
||||
.provider(DirImportProviderFactory.PROVIDER_ID)
|
||||
.config(DirImportProviderFactory.DIR, importFolder.toAbsolutePath().toString());
|
||||
|
||||
inComittedTransaction(session -> {
|
||||
ExportImportConfig.setAction(ExportImportConfig.ACTION_IMPORT);
|
||||
ExportImportManager exportImportManager = new ExportImportManager(session);
|
||||
exportImportManager.runImport();
|
||||
});
|
||||
|
||||
inComittedTransaction(session -> {
|
||||
Assert.assertNotNull(session.realms().getRealmByName("test"));
|
||||
});
|
||||
|
||||
} finally {
|
||||
CONFIG.spi(SPI_NAME)
|
||||
.config("importer", null);
|
||||
CONFIG.spi(SPI_NAME)
|
||||
.provider(DirImportProviderFactory.PROVIDER_ID)
|
||||
.config(DirExportProviderFactory.DIR, null);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -16,12 +16,22 @@
|
|||
*/
|
||||
package org.keycloak.testsuite.model.parameters;
|
||||
|
||||
import org.keycloak.common.crypto.CryptoIntegration;
|
||||
import org.keycloak.common.crypto.CryptoProvider;
|
||||
import org.keycloak.exportimport.ExportSpi;
|
||||
import org.keycloak.exportimport.ImportSpi;
|
||||
import org.keycloak.exportimport.dir.DirExportProviderFactory;
|
||||
import org.keycloak.exportimport.dir.DirImportProviderFactory;
|
||||
import org.keycloak.exportimport.singlefile.SingleFileExportProviderFactory;
|
||||
import org.keycloak.exportimport.singlefile.SingleFileImportProviderFactory;
|
||||
import org.keycloak.keys.KeyProviderFactory;
|
||||
import org.keycloak.keys.KeySpi;
|
||||
import org.keycloak.models.ClientScopeSpi;
|
||||
import org.keycloak.models.map.storage.MapStorageSpi;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyManagerFactory;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyManagerSpi;
|
||||
import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicyFactory;
|
||||
import org.keycloak.services.clientregistration.policy.ClientRegistrationPolicySpi;
|
||||
import org.keycloak.testsuite.model.KeycloakModelParameters;
|
||||
import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory;
|
||||
import org.keycloak.provider.ProviderFactory;
|
||||
|
@ -39,13 +49,30 @@ public class ConcurrentHashMapStorage extends KeycloakModelParameters {
|
|||
static final Set<Class<? extends Spi>> ALLOWED_SPIS = ImmutableSet.<Class<? extends Spi>>builder()
|
||||
.add(ExportSpi.class)
|
||||
.add(ClientPolicyManagerSpi.class)
|
||||
.add(ImportSpi.class)
|
||||
.add(ClientRegistrationPolicySpi.class)
|
||||
.add(ClientScopeSpi.class)
|
||||
.add(KeySpi.class)
|
||||
.build();
|
||||
|
||||
static {
|
||||
// CryptoIntegration needed for import of realms
|
||||
CryptoIntegration.init(CryptoProvider.class.getClassLoader());
|
||||
}
|
||||
|
||||
static final Set<Class<? extends ProviderFactory>> ALLOWED_FACTORIES = ImmutableSet.<Class<? extends ProviderFactory>>builder()
|
||||
.add(ConcurrentHashMapStorageProviderFactory.class)
|
||||
// start providers needed for export
|
||||
.add(SingleFileExportProviderFactory.class)
|
||||
.add(DirExportProviderFactory.class)
|
||||
.add(ClientPolicyManagerFactory.class)
|
||||
// end providers needed for export
|
||||
// start providers needed for import
|
||||
.add(SingleFileImportProviderFactory.class)
|
||||
.add(DirImportProviderFactory.class)
|
||||
.add(ClientRegistrationPolicyFactory.class)
|
||||
.add(KeyProviderFactory.class)
|
||||
// end providers needed for import
|
||||
.build();
|
||||
|
||||
@Override
|
||||
|
|
|
@ -18,9 +18,6 @@ package org.keycloak.testsuite.model.parameters;
|
|||
|
||||
import org.keycloak.authorization.store.StoreFactorySpi;
|
||||
import org.keycloak.events.EventStoreSpi;
|
||||
import org.keycloak.exportimport.ExportSpi;
|
||||
import org.keycloak.exportimport.dir.DirExportProviderFactory;
|
||||
import org.keycloak.exportimport.singlefile.SingleFileExportProviderFactory;
|
||||
import org.keycloak.keys.PublicKeyStorageSpi;
|
||||
import org.keycloak.models.DeploymentStateSpi;
|
||||
import org.keycloak.models.SingleUseObjectProviderFactory;
|
||||
|
@ -37,8 +34,6 @@ import org.keycloak.models.map.loginFailure.MapUserLoginFailureProviderFactory;
|
|||
import org.keycloak.models.map.singleUseObject.MapSingleUseObjectProviderFactory;
|
||||
import org.keycloak.models.map.storage.chm.ConcurrentHashMapStorageProviderFactory;
|
||||
import org.keycloak.models.map.userSession.MapUserSessionProviderFactory;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyManagerFactory;
|
||||
import org.keycloak.services.clientpolicy.ClientPolicyManagerSpi;
|
||||
import org.keycloak.sessions.AuthenticationSessionSpi;
|
||||
import org.keycloak.testsuite.model.KeycloakModelParameters;
|
||||
import org.keycloak.models.map.client.MapClientProviderFactory;
|
||||
|
|
|
@ -0,0 +1,682 @@
|
|||
{
|
||||
"id": "4b390336-f32b-4b49-b0a5-e7c865637496",
|
||||
"realm": "test",
|
||||
"enabled": true,
|
||||
"sslRequired": "external",
|
||||
"registrationAllowed": true,
|
||||
"resetPasswordAllowed": true,
|
||||
"editUsernameAllowed" : true,
|
||||
"ssoSessionIdleTimeout": 1800,
|
||||
"ssoSessionMaxLifespan": 36000,
|
||||
"offlineSessionIdleTimeout": 2592000,
|
||||
"offlineSessionMaxLifespan": 5184000,
|
||||
"requiredCredentials": [ "password" ],
|
||||
"defaultRoles": [ "user" ],
|
||||
"smtpServer": {
|
||||
"from": "auto@keycloak.org",
|
||||
"host": "localhost",
|
||||
"port":"3025",
|
||||
"fromDisplayName": "Keycloak SSO",
|
||||
"replyTo":"reply-to@keycloak.org",
|
||||
"replyToDisplayName": "Keycloak no-reply",
|
||||
"envelopeFrom": "auto+bounces@keycloak.org"
|
||||
},
|
||||
"users" : [
|
||||
{
|
||||
"username" : "test-user@localhost",
|
||||
"enabled": true,
|
||||
"email" : "test-user@localhost",
|
||||
"firstName": "Tom",
|
||||
"lastName": "Brady",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"realmRoles": ["user", "offline_access"],
|
||||
"clientRoles": {
|
||||
"test-app": [ "customer-user" ],
|
||||
"account": [ "view-profile", "manage-account" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"username" : "john-doh@localhost",
|
||||
"enabled": true,
|
||||
"email" : "john-doh@localhost",
|
||||
"firstName": "John",
|
||||
"lastName": "Doh",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"realmRoles": ["user"],
|
||||
"clientRoles": {
|
||||
"test-app": [ "customer-user" ],
|
||||
"account": [ "view-profile", "manage-account" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"username" : "keycloak-user@localhost",
|
||||
"enabled": true,
|
||||
"email" : "keycloak-user@localhost",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"realmRoles": ["user"],
|
||||
"clientRoles": {
|
||||
"test-app": [ "customer-user" ],
|
||||
"account": [ "view-profile", "manage-account" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"username" : "topGroupUser",
|
||||
"enabled": true,
|
||||
"email" : "top@redhat.com",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"groups": [
|
||||
"/topGroup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"username" : "level2GroupUser",
|
||||
"enabled": true,
|
||||
"email" : "level2@redhat.com",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"groups": [
|
||||
"/topGroup/level2group"
|
||||
]
|
||||
},
|
||||
{
|
||||
"username" : "roleRichUser",
|
||||
"enabled": true,
|
||||
"email" : "rich.roles@redhat.com",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"groups": [
|
||||
"/roleRichGroup/level2group"
|
||||
],
|
||||
"clientRoles": {
|
||||
"test-app-scope": [ "test-app-allowed-by-scope", "test-app-disallowed-by-scope" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"username" : "non-duplicate-email-user",
|
||||
"enabled": true,
|
||||
"email" : "non-duplicate-email-user@localhost",
|
||||
"firstName": "Brian",
|
||||
"lastName": "Cohen",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"realmRoles": ["user", "offline_access"],
|
||||
"clientRoles": {
|
||||
"test-app": [ "customer-user" ],
|
||||
"account": [ "view-profile", "manage-account" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"username" : "user-with-one-configured-otp",
|
||||
"enabled": true,
|
||||
"email" : "otp1@redhat.com",
|
||||
"credentials" : [
|
||||
{
|
||||
"type" : "password",
|
||||
"value" : "password"
|
||||
},
|
||||
{
|
||||
"id" : "unique",
|
||||
"type" : "otp",
|
||||
"secretData" : "{\"value\":\"DJmQfC73VGFhw7D4QJ8A\"}",
|
||||
"credentialData" : "{\"digits\":6,\"counter\":0,\"period\":30,\"algorithm\":\"HmacSHA1\",\"subType\":\"totp\"}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"username" : "user-with-two-configured-otp",
|
||||
"enabled": true,
|
||||
"email" : "otp2@redhat.com",
|
||||
"realmRoles": ["user"],
|
||||
"credentials" : [
|
||||
{
|
||||
"id" : "first",
|
||||
"userLabel" : "first",
|
||||
"type" : "otp",
|
||||
"secretData" : "{\"value\":\"DJmQfC73VGFhw7D4QJ8A\"}",
|
||||
"credentialData" : "{\"digits\":6,\"counter\":0,\"period\":30,\"algorithm\":\"HmacSHA1\",\"subType\":\"totp\"}"
|
||||
},
|
||||
{
|
||||
"type" : "password",
|
||||
"value" : "password"
|
||||
},
|
||||
{
|
||||
"id" : "second",
|
||||
"type" : "otp",
|
||||
"secretData" : "{\"value\":\"ABCQfC73VGFhw7D4QJ8A\"}",
|
||||
"credentialData" : "{\"digits\":6,\"counter\":0,\"period\":30,\"algorithm\":\"HmacSHA1\",\"subType\":\"totp\"}"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"scopeMappings": [
|
||||
{
|
||||
"client": "third-party",
|
||||
"roles": ["user"]
|
||||
},
|
||||
{
|
||||
"client": "test-app",
|
||||
"roles": ["user"]
|
||||
},
|
||||
{
|
||||
"client": "test-app-scope",
|
||||
"roles": ["user", "admin"]
|
||||
}
|
||||
],
|
||||
"clients": [
|
||||
{
|
||||
"clientId": "test-app",
|
||||
"enabled": true,
|
||||
"baseUrl": "http://localhost:8180/auth/realms/master/app/auth",
|
||||
"redirectUris": [
|
||||
"http://localhost:8180/auth/realms/master/app/auth/*",
|
||||
"https://localhost:8543/auth/realms/master/app/auth/*",
|
||||
"http://localhost:8180/auth/realms/test/app/auth/*",
|
||||
"https://localhost:8543/auth/realms/test/app/auth/*"
|
||||
],
|
||||
"adminUrl": "http://localhost:8180/auth/realms/master/app/admin",
|
||||
"secret": "password"
|
||||
},
|
||||
{
|
||||
"clientId": "root-url-client",
|
||||
"enabled": true,
|
||||
"rootUrl": "http://localhost:8180/foo/bar",
|
||||
"adminUrl": "http://localhost:8180/foo/bar",
|
||||
"baseUrl": "/baz",
|
||||
"redirectUris": [
|
||||
"http://localhost:8180/foo/bar/*",
|
||||
"https://localhost:8543/foo/bar/*"
|
||||
],
|
||||
"directAccessGrantsEnabled": true,
|
||||
"secret": "password"
|
||||
},
|
||||
{
|
||||
"clientId" : "test-app-scope",
|
||||
"enabled": true,
|
||||
|
||||
"redirectUris": [
|
||||
"http://localhost:8180/auth/realms/master/app/*",
|
||||
"https://localhost:8543/auth/realms/master/app/*"
|
||||
],
|
||||
"secret": "password",
|
||||
"fullScopeAllowed": "false"
|
||||
},
|
||||
{
|
||||
"clientId" : "third-party",
|
||||
"description" : "A third party application",
|
||||
"enabled": true,
|
||||
"consentRequired": true,
|
||||
|
||||
"baseUrl": "http://localhost:8180/auth/realms/master/app/auth",
|
||||
"redirectUris": [
|
||||
"http://localhost:8180/auth/realms/master/app/*",
|
||||
"https://localhost:8543/auth/realms/master/app/*"
|
||||
],
|
||||
"secret": "password"
|
||||
},
|
||||
{
|
||||
"clientId": "test-app-authz",
|
||||
"enabled": true,
|
||||
"baseUrl": "/test-app-authz",
|
||||
"adminUrl": "/test-app-authz",
|
||||
"bearerOnly": false,
|
||||
"authorizationSettings": {
|
||||
"allowRemoteResourceManagement": true,
|
||||
"policyEnforcementMode": "ENFORCING",
|
||||
"resources": [
|
||||
{
|
||||
"name": "Admin Resource",
|
||||
"uri": "/protected/admin/*",
|
||||
"type": "http://test-app-authz/protected/admin",
|
||||
"scopes": [
|
||||
{
|
||||
"name": "admin-access"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Protected Resource",
|
||||
"uri": "/*",
|
||||
"type": "http://test-app-authz/protected/resource",
|
||||
"scopes": [
|
||||
{
|
||||
"name": "resource-access"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Premium Resource",
|
||||
"uri": "/protected/premium/*",
|
||||
"type": "urn:test-app-authz:protected:resource",
|
||||
"scopes": [
|
||||
{
|
||||
"name": "premium-access"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Main Page",
|
||||
"type": "urn:test-app-authz:protected:resource",
|
||||
"scopes": [
|
||||
{
|
||||
"name": "urn:test-app-authz:page:main:actionForAdmin"
|
||||
},
|
||||
{
|
||||
"name": "urn:test-app-authz:page:main:actionForUser"
|
||||
},
|
||||
{
|
||||
"name": "urn:test-app-authz:page:main:actionForPremiumUser"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"policies": [
|
||||
{
|
||||
"name": "Any Admin Policy",
|
||||
"description": "Defines that adminsitrators can do something",
|
||||
"type": "role",
|
||||
"config": {
|
||||
"roles": "[{\"id\":\"admin\"}]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Any User Policy",
|
||||
"description": "Defines that any user can do something",
|
||||
"type": "role",
|
||||
"config": {
|
||||
"roles": "[{\"id\":\"user\"}]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Only Premium User Policy",
|
||||
"description": "Defines that only premium users can do something",
|
||||
"type": "role",
|
||||
"logic": "POSITIVE",
|
||||
"config": {
|
||||
"roles": "[{\"id\":\"customer-user-premium\"}]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "All Users Policy",
|
||||
"description": "Defines that all users can do something",
|
||||
"type": "aggregate",
|
||||
"decisionStrategy": "AFFIRMATIVE",
|
||||
"config": {
|
||||
"applyPolicies": "[\"Any User Policy\",\"Any Admin Policy\",\"Only Premium User Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Premium Resource Permission",
|
||||
"description": "A policy that defines access to premium resources",
|
||||
"type": "resource",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"resources": "[\"Premium Resource\"]",
|
||||
"applyPolicies": "[\"Only Premium User Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Administrative Resource Permission",
|
||||
"description": "A policy that defines access to administrative resources",
|
||||
"type": "resource",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"resources": "[\"Admin Resource\"]",
|
||||
"applyPolicies": "[\"Any Admin Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Protected Resource Permission",
|
||||
"description": "A policy that defines access to any protected resource",
|
||||
"type": "resource",
|
||||
"decisionStrategy": "AFFIRMATIVE",
|
||||
"config": {
|
||||
"resources": "[\"Protected Resource\"]",
|
||||
"applyPolicies": "[\"All Users Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Action 1 on Main Page Resource Permission",
|
||||
"description": "A policy that defines access to action 1 on the main page",
|
||||
"type": "scope",
|
||||
"decisionStrategy": "AFFIRMATIVE",
|
||||
"config": {
|
||||
"scopes": "[\"urn:test-app-authz:page:main:actionForAdmin\"]",
|
||||
"applyPolicies": "[\"Any Admin Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Action 2 on Main Page Resource Permission",
|
||||
"description": "A policy that defines access to action 2 on the main page",
|
||||
"type": "scope",
|
||||
"decisionStrategy": "AFFIRMATIVE",
|
||||
"config": {
|
||||
"scopes": "[\"urn:test-app-authz:page:main:actionForUser\"]",
|
||||
"applyPolicies": "[\"Any User Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Action 3 on Main Page Resource Permission",
|
||||
"description": "A policy that defines access to action 3 on the main page",
|
||||
"type": "scope",
|
||||
"decisionStrategy": "AFFIRMATIVE",
|
||||
"config": {
|
||||
"scopes": "[\"urn:test-app-authz:page:main:actionForPremiumUser\"]",
|
||||
"applyPolicies": "[\"Only Premium User Policy\"]"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"redirectUris": [
|
||||
"/test-app-authz/*"
|
||||
],
|
||||
"secret": "secret"
|
||||
},
|
||||
{
|
||||
"clientId": "named-test-app",
|
||||
"name": "My Named Test App",
|
||||
"enabled": true,
|
||||
"baseUrl": "http://localhost:8180/namedapp/base",
|
||||
"redirectUris": [
|
||||
"http://localhost:8180/namedapp/base/*",
|
||||
"https://localhost:8543/namedapp/base/*"
|
||||
],
|
||||
"adminUrl": "http://localhost:8180/namedapp/base/admin",
|
||||
"secret": "password"
|
||||
},
|
||||
{
|
||||
"clientId": "var-named-test-app",
|
||||
"name": "Test App Named - ${client_account}",
|
||||
"enabled": true,
|
||||
"baseUrl": "http://localhost:8180/varnamedapp/base",
|
||||
"redirectUris": [
|
||||
"http://localhost:8180/varnamedapp/base/*",
|
||||
"https://localhost:8543/varnamedapp/base/*"
|
||||
],
|
||||
"adminUrl": "http://localhost:8180/varnamedapp/base/admin",
|
||||
"secret": "password"
|
||||
},
|
||||
{
|
||||
"clientId": "direct-grant",
|
||||
"enabled": true,
|
||||
"directAccessGrantsEnabled": true,
|
||||
"secret": "password",
|
||||
"webOrigins": [ "http://localtest.me:8180" ],
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "aud-account",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-audience-mapper",
|
||||
"config": {
|
||||
"included.client.audience": "account",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "aud-admin",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-audience-mapper",
|
||||
"config": {
|
||||
"included.client.audience": "security-admin-console",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"clientId": "custom-audience",
|
||||
"enabled": true,
|
||||
"directAccessGrantsEnabled": true,
|
||||
"secret": "password",
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "aud",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-audience-mapper",
|
||||
"config": {
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"included.custom.audience": "foo-bar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "client roles",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-client-role-mapper",
|
||||
"config": {
|
||||
"user.attribute": "foo",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "resource_access.${client_id}.roles",
|
||||
"jsonType.label": "String",
|
||||
"multivalued": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "realm roles",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-realm-role-mapper",
|
||||
"config": {
|
||||
"user.attribute": "foo",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "realm_access.roles",
|
||||
"jsonType.label": "String",
|
||||
"multivalued": "true"
|
||||
}
|
||||
}
|
||||
],
|
||||
"defaultClientScopes": [
|
||||
"web-origins",
|
||||
"profile",
|
||||
"email"
|
||||
]
|
||||
}
|
||||
],
|
||||
"roles" : {
|
||||
"realm" : [
|
||||
{
|
||||
"name": "user",
|
||||
"description": "Have User privileges"
|
||||
},
|
||||
{
|
||||
"name": "admin",
|
||||
"description": "Have Administrator privileges"
|
||||
},
|
||||
{
|
||||
"name": "customer-user-premium",
|
||||
"description": "Have User Premium privileges"
|
||||
},
|
||||
{
|
||||
"name": "sample-realm-role",
|
||||
"description": "Sample realm role"
|
||||
},
|
||||
{
|
||||
"name": "attribute-role",
|
||||
"description": "has attributes assigned",
|
||||
"attributes": {
|
||||
"hello": [
|
||||
"world",
|
||||
"keycloak"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "realm-composite-role",
|
||||
"description": "Realm composite role containing client role",
|
||||
"composite" : true,
|
||||
"composites" : {
|
||||
"realm" : [ "sample-realm-role" ],
|
||||
"client" : {
|
||||
"test-app" : [ "sample-client-role" ],
|
||||
"account" : [ "view-profile" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"client" : {
|
||||
"test-app" : [
|
||||
{
|
||||
"name": "manage-account",
|
||||
"description": "Allows application-initiated actions."
|
||||
},
|
||||
{
|
||||
"name": "customer-user",
|
||||
"description": "Have Customer User privileges"
|
||||
},
|
||||
{
|
||||
"name": "customer-admin",
|
||||
"description": "Have Customer Admin privileges"
|
||||
},
|
||||
{
|
||||
"name": "sample-client-role",
|
||||
"description": "Sample client role",
|
||||
"attributes": {
|
||||
"sample-client-role-attribute": [
|
||||
"sample-client-role-attribute-value"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "customer-admin-composite-role",
|
||||
"description": "Have Customer Admin privileges via composite role",
|
||||
"composite" : true,
|
||||
"composites" : {
|
||||
"realm" : [ "customer-user-premium" ],
|
||||
"client" : {
|
||||
"test-app" : [ "customer-admin" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"test-app-scope" : [
|
||||
{
|
||||
"name": "test-app-allowed-by-scope",
|
||||
"description": "Role allowed by scope in test-app-scope"
|
||||
},
|
||||
{
|
||||
"name": "test-app-disallowed-by-scope",
|
||||
"description": "Role disallowed by scope in test-app-scope"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
},
|
||||
"groups" : [
|
||||
{
|
||||
"name": "topGroup",
|
||||
"attributes": {
|
||||
"topAttribute": ["true"]
|
||||
|
||||
},
|
||||
"realmRoles": ["user"],
|
||||
|
||||
"subGroups": [
|
||||
{
|
||||
"name": "level2group",
|
||||
"realmRoles": ["admin"],
|
||||
"clientRoles": {
|
||||
"test-app": ["customer-user"]
|
||||
},
|
||||
"attributes": {
|
||||
"level2Attribute": ["true"]
|
||||
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "level2group2",
|
||||
"realmRoles": ["admin"],
|
||||
"clientRoles": {
|
||||
"test-app": ["customer-user"]
|
||||
},
|
||||
"attributes": {
|
||||
"level2Attribute": ["true"]
|
||||
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "roleRichGroup",
|
||||
"attributes": {
|
||||
"topAttribute": ["true"]
|
||||
|
||||
},
|
||||
"realmRoles": ["user", "realm-composite-role"],
|
||||
"clientRoles": {
|
||||
"account": ["manage-account"]
|
||||
},
|
||||
|
||||
"subGroups": [
|
||||
{
|
||||
"name": "level2group",
|
||||
"realmRoles": ["admin"],
|
||||
"clientRoles": {
|
||||
"test-app": ["customer-user", "customer-admin-composite-role"]
|
||||
},
|
||||
"attributes": {
|
||||
"level2Attribute": ["true"]
|
||||
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "level2group2",
|
||||
"realmRoles": ["admin"],
|
||||
"clientRoles": {
|
||||
"test-app": ["customer-user"]
|
||||
},
|
||||
"attributes": {
|
||||
"level2Attribute": ["true"]
|
||||
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "sample-realm-group"
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
"clientScopeMappings": {
|
||||
"test-app": [
|
||||
{
|
||||
"client": "third-party",
|
||||
"roles": ["customer-user"]
|
||||
},
|
||||
{
|
||||
"client": "test-app-scope",
|
||||
"roles": ["customer-admin-composite-role"]
|
||||
}
|
||||
],
|
||||
"test-app-scope": [
|
||||
{
|
||||
"client": "test-app-scope",
|
||||
"roles": ["test-app-allowed-by-scope"]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"internationalizationEnabled": true,
|
||||
"supportedLocales": ["en", "de"],
|
||||
"defaultLocale": "en",
|
||||
"eventsListeners": ["jboss-logging", "event-queue"]
|
||||
}
|
|
@ -0,0 +1,682 @@
|
|||
{
|
||||
"id": "4b390336-f32b-4b49-b0a5-e7c865637496",
|
||||
"realm": "test",
|
||||
"enabled": true,
|
||||
"sslRequired": "external",
|
||||
"registrationAllowed": true,
|
||||
"resetPasswordAllowed": true,
|
||||
"editUsernameAllowed" : true,
|
||||
"ssoSessionIdleTimeout": 1800,
|
||||
"ssoSessionMaxLifespan": 36000,
|
||||
"offlineSessionIdleTimeout": 2592000,
|
||||
"offlineSessionMaxLifespan": 5184000,
|
||||
"requiredCredentials": [ "password" ],
|
||||
"defaultRoles": [ "user" ],
|
||||
"smtpServer": {
|
||||
"from": "auto@keycloak.org",
|
||||
"host": "localhost",
|
||||
"port":"3025",
|
||||
"fromDisplayName": "Keycloak SSO",
|
||||
"replyTo":"reply-to@keycloak.org",
|
||||
"replyToDisplayName": "Keycloak no-reply",
|
||||
"envelopeFrom": "auto+bounces@keycloak.org"
|
||||
},
|
||||
"users" : [
|
||||
{
|
||||
"username" : "test-user@localhost",
|
||||
"enabled": true,
|
||||
"email" : "test-user@localhost",
|
||||
"firstName": "Tom",
|
||||
"lastName": "Brady",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"realmRoles": ["user", "offline_access"],
|
||||
"clientRoles": {
|
||||
"test-app": [ "customer-user" ],
|
||||
"account": [ "view-profile", "manage-account" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"username" : "john-doh@localhost",
|
||||
"enabled": true,
|
||||
"email" : "john-doh@localhost",
|
||||
"firstName": "John",
|
||||
"lastName": "Doh",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"realmRoles": ["user"],
|
||||
"clientRoles": {
|
||||
"test-app": [ "customer-user" ],
|
||||
"account": [ "view-profile", "manage-account" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"username" : "keycloak-user@localhost",
|
||||
"enabled": true,
|
||||
"email" : "keycloak-user@localhost",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"realmRoles": ["user"],
|
||||
"clientRoles": {
|
||||
"test-app": [ "customer-user" ],
|
||||
"account": [ "view-profile", "manage-account" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"username" : "topGroupUser",
|
||||
"enabled": true,
|
||||
"email" : "top@redhat.com",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"groups": [
|
||||
"/topGroup"
|
||||
]
|
||||
},
|
||||
{
|
||||
"username" : "level2GroupUser",
|
||||
"enabled": true,
|
||||
"email" : "level2@redhat.com",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"groups": [
|
||||
"/topGroup/level2group"
|
||||
]
|
||||
},
|
||||
{
|
||||
"username" : "roleRichUser",
|
||||
"enabled": true,
|
||||
"email" : "rich.roles@redhat.com",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"groups": [
|
||||
"/roleRichGroup/level2group"
|
||||
],
|
||||
"clientRoles": {
|
||||
"test-app-scope": [ "test-app-allowed-by-scope", "test-app-disallowed-by-scope" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"username" : "non-duplicate-email-user",
|
||||
"enabled": true,
|
||||
"email" : "non-duplicate-email-user@localhost",
|
||||
"firstName": "Brian",
|
||||
"lastName": "Cohen",
|
||||
"credentials" : [
|
||||
{ "type" : "password",
|
||||
"value" : "password" }
|
||||
],
|
||||
"realmRoles": ["user", "offline_access"],
|
||||
"clientRoles": {
|
||||
"test-app": [ "customer-user" ],
|
||||
"account": [ "view-profile", "manage-account" ]
|
||||
}
|
||||
},
|
||||
{
|
||||
"username" : "user-with-one-configured-otp",
|
||||
"enabled": true,
|
||||
"email" : "otp1@redhat.com",
|
||||
"credentials" : [
|
||||
{
|
||||
"type" : "password",
|
||||
"value" : "password"
|
||||
},
|
||||
{
|
||||
"id" : "unique",
|
||||
"type" : "otp",
|
||||
"secretData" : "{\"value\":\"DJmQfC73VGFhw7D4QJ8A\"}",
|
||||
"credentialData" : "{\"digits\":6,\"counter\":0,\"period\":30,\"algorithm\":\"HmacSHA1\",\"subType\":\"totp\"}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"username" : "user-with-two-configured-otp",
|
||||
"enabled": true,
|
||||
"email" : "otp2@redhat.com",
|
||||
"realmRoles": ["user"],
|
||||
"credentials" : [
|
||||
{
|
||||
"id" : "first",
|
||||
"userLabel" : "first",
|
||||
"type" : "otp",
|
||||
"secretData" : "{\"value\":\"DJmQfC73VGFhw7D4QJ8A\"}",
|
||||
"credentialData" : "{\"digits\":6,\"counter\":0,\"period\":30,\"algorithm\":\"HmacSHA1\",\"subType\":\"totp\"}"
|
||||
},
|
||||
{
|
||||
"type" : "password",
|
||||
"value" : "password"
|
||||
},
|
||||
{
|
||||
"id" : "second",
|
||||
"type" : "otp",
|
||||
"secretData" : "{\"value\":\"ABCQfC73VGFhw7D4QJ8A\"}",
|
||||
"credentialData" : "{\"digits\":6,\"counter\":0,\"period\":30,\"algorithm\":\"HmacSHA1\",\"subType\":\"totp\"}"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"scopeMappings": [
|
||||
{
|
||||
"client": "third-party",
|
||||
"roles": ["user"]
|
||||
},
|
||||
{
|
||||
"client": "test-app",
|
||||
"roles": ["user"]
|
||||
},
|
||||
{
|
||||
"client": "test-app-scope",
|
||||
"roles": ["user", "admin"]
|
||||
}
|
||||
],
|
||||
"clients": [
|
||||
{
|
||||
"clientId": "test-app",
|
||||
"enabled": true,
|
||||
"baseUrl": "http://localhost:8180/auth/realms/master/app/auth",
|
||||
"redirectUris": [
|
||||
"http://localhost:8180/auth/realms/master/app/auth/*",
|
||||
"https://localhost:8543/auth/realms/master/app/auth/*",
|
||||
"http://localhost:8180/auth/realms/test/app/auth/*",
|
||||
"https://localhost:8543/auth/realms/test/app/auth/*"
|
||||
],
|
||||
"adminUrl": "http://localhost:8180/auth/realms/master/app/admin",
|
||||
"secret": "password"
|
||||
},
|
||||
{
|
||||
"clientId": "root-url-client",
|
||||
"enabled": true,
|
||||
"rootUrl": "http://localhost:8180/foo/bar",
|
||||
"adminUrl": "http://localhost:8180/foo/bar",
|
||||
"baseUrl": "/baz",
|
||||
"redirectUris": [
|
||||
"http://localhost:8180/foo/bar/*",
|
||||
"https://localhost:8543/foo/bar/*"
|
||||
],
|
||||
"directAccessGrantsEnabled": true,
|
||||
"secret": "password"
|
||||
},
|
||||
{
|
||||
"clientId" : "test-app-scope",
|
||||
"enabled": true,
|
||||
|
||||
"redirectUris": [
|
||||
"http://localhost:8180/auth/realms/master/app/*",
|
||||
"https://localhost:8543/auth/realms/master/app/*"
|
||||
],
|
||||
"secret": "password",
|
||||
"fullScopeAllowed": "false"
|
||||
},
|
||||
{
|
||||
"clientId" : "third-party",
|
||||
"description" : "A third party application",
|
||||
"enabled": true,
|
||||
"consentRequired": true,
|
||||
|
||||
"baseUrl": "http://localhost:8180/auth/realms/master/app/auth",
|
||||
"redirectUris": [
|
||||
"http://localhost:8180/auth/realms/master/app/*",
|
||||
"https://localhost:8543/auth/realms/master/app/*"
|
||||
],
|
||||
"secret": "password"
|
||||
},
|
||||
{
|
||||
"clientId": "test-app-authz",
|
||||
"enabled": true,
|
||||
"baseUrl": "/test-app-authz",
|
||||
"adminUrl": "/test-app-authz",
|
||||
"bearerOnly": false,
|
||||
"authorizationSettings": {
|
||||
"allowRemoteResourceManagement": true,
|
||||
"policyEnforcementMode": "ENFORCING",
|
||||
"resources": [
|
||||
{
|
||||
"name": "Admin Resource",
|
||||
"uri": "/protected/admin/*",
|
||||
"type": "http://test-app-authz/protected/admin",
|
||||
"scopes": [
|
||||
{
|
||||
"name": "admin-access"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Protected Resource",
|
||||
"uri": "/*",
|
||||
"type": "http://test-app-authz/protected/resource",
|
||||
"scopes": [
|
||||
{
|
||||
"name": "resource-access"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Premium Resource",
|
||||
"uri": "/protected/premium/*",
|
||||
"type": "urn:test-app-authz:protected:resource",
|
||||
"scopes": [
|
||||
{
|
||||
"name": "premium-access"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Main Page",
|
||||
"type": "urn:test-app-authz:protected:resource",
|
||||
"scopes": [
|
||||
{
|
||||
"name": "urn:test-app-authz:page:main:actionForAdmin"
|
||||
},
|
||||
{
|
||||
"name": "urn:test-app-authz:page:main:actionForUser"
|
||||
},
|
||||
{
|
||||
"name": "urn:test-app-authz:page:main:actionForPremiumUser"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"policies": [
|
||||
{
|
||||
"name": "Any Admin Policy",
|
||||
"description": "Defines that adminsitrators can do something",
|
||||
"type": "role",
|
||||
"config": {
|
||||
"roles": "[{\"id\":\"admin\"}]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Any User Policy",
|
||||
"description": "Defines that any user can do something",
|
||||
"type": "role",
|
||||
"config": {
|
||||
"roles": "[{\"id\":\"user\"}]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Only Premium User Policy",
|
||||
"description": "Defines that only premium users can do something",
|
||||
"type": "role",
|
||||
"logic": "POSITIVE",
|
||||
"config": {
|
||||
"roles": "[{\"id\":\"customer-user-premium\"}]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "All Users Policy",
|
||||
"description": "Defines that all users can do something",
|
||||
"type": "aggregate",
|
||||
"decisionStrategy": "AFFIRMATIVE",
|
||||
"config": {
|
||||
"applyPolicies": "[\"Any User Policy\",\"Any Admin Policy\",\"Only Premium User Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Premium Resource Permission",
|
||||
"description": "A policy that defines access to premium resources",
|
||||
"type": "resource",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"resources": "[\"Premium Resource\"]",
|
||||
"applyPolicies": "[\"Only Premium User Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Administrative Resource Permission",
|
||||
"description": "A policy that defines access to administrative resources",
|
||||
"type": "resource",
|
||||
"decisionStrategy": "UNANIMOUS",
|
||||
"config": {
|
||||
"resources": "[\"Admin Resource\"]",
|
||||
"applyPolicies": "[\"Any Admin Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Protected Resource Permission",
|
||||
"description": "A policy that defines access to any protected resource",
|
||||
"type": "resource",
|
||||
"decisionStrategy": "AFFIRMATIVE",
|
||||
"config": {
|
||||
"resources": "[\"Protected Resource\"]",
|
||||
"applyPolicies": "[\"All Users Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Action 1 on Main Page Resource Permission",
|
||||
"description": "A policy that defines access to action 1 on the main page",
|
||||
"type": "scope",
|
||||
"decisionStrategy": "AFFIRMATIVE",
|
||||
"config": {
|
||||
"scopes": "[\"urn:test-app-authz:page:main:actionForAdmin\"]",
|
||||
"applyPolicies": "[\"Any Admin Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Action 2 on Main Page Resource Permission",
|
||||
"description": "A policy that defines access to action 2 on the main page",
|
||||
"type": "scope",
|
||||
"decisionStrategy": "AFFIRMATIVE",
|
||||
"config": {
|
||||
"scopes": "[\"urn:test-app-authz:page:main:actionForUser\"]",
|
||||
"applyPolicies": "[\"Any User Policy\"]"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "Action 3 on Main Page Resource Permission",
|
||||
"description": "A policy that defines access to action 3 on the main page",
|
||||
"type": "scope",
|
||||
"decisionStrategy": "AFFIRMATIVE",
|
||||
"config": {
|
||||
"scopes": "[\"urn:test-app-authz:page:main:actionForPremiumUser\"]",
|
||||
"applyPolicies": "[\"Only Premium User Policy\"]"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"redirectUris": [
|
||||
"/test-app-authz/*"
|
||||
],
|
||||
"secret": "secret"
|
||||
},
|
||||
{
|
||||
"clientId": "named-test-app",
|
||||
"name": "My Named Test App",
|
||||
"enabled": true,
|
||||
"baseUrl": "http://localhost:8180/namedapp/base",
|
||||
"redirectUris": [
|
||||
"http://localhost:8180/namedapp/base/*",
|
||||
"https://localhost:8543/namedapp/base/*"
|
||||
],
|
||||
"adminUrl": "http://localhost:8180/namedapp/base/admin",
|
||||
"secret": "password"
|
||||
},
|
||||
{
|
||||
"clientId": "var-named-test-app",
|
||||
"name": "Test App Named - ${client_account}",
|
||||
"enabled": true,
|
||||
"baseUrl": "http://localhost:8180/varnamedapp/base",
|
||||
"redirectUris": [
|
||||
"http://localhost:8180/varnamedapp/base/*",
|
||||
"https://localhost:8543/varnamedapp/base/*"
|
||||
],
|
||||
"adminUrl": "http://localhost:8180/varnamedapp/base/admin",
|
||||
"secret": "password"
|
||||
},
|
||||
{
|
||||
"clientId": "direct-grant",
|
||||
"enabled": true,
|
||||
"directAccessGrantsEnabled": true,
|
||||
"secret": "password",
|
||||
"webOrigins": [ "http://localtest.me:8180" ],
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "aud-account",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-audience-mapper",
|
||||
"config": {
|
||||
"included.client.audience": "account",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "aud-admin",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-audience-mapper",
|
||||
"config": {
|
||||
"included.client.audience": "security-admin-console",
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
{
|
||||
"clientId": "custom-audience",
|
||||
"enabled": true,
|
||||
"directAccessGrantsEnabled": true,
|
||||
"secret": "password",
|
||||
"protocolMappers": [
|
||||
{
|
||||
"name": "aud",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-audience-mapper",
|
||||
"config": {
|
||||
"id.token.claim": "true",
|
||||
"access.token.claim": "true",
|
||||
"included.custom.audience": "foo-bar"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "client roles",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-client-role-mapper",
|
||||
"config": {
|
||||
"user.attribute": "foo",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "resource_access.${client_id}.roles",
|
||||
"jsonType.label": "String",
|
||||
"multivalued": "true"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "realm roles",
|
||||
"protocol": "openid-connect",
|
||||
"protocolMapper": "oidc-usermodel-realm-role-mapper",
|
||||
"config": {
|
||||
"user.attribute": "foo",
|
||||
"access.token.claim": "true",
|
||||
"claim.name": "realm_access.roles",
|
||||
"jsonType.label": "String",
|
||||
"multivalued": "true"
|
||||
}
|
||||
}
|
||||
],
|
||||
"defaultClientScopes": [
|
||||
"web-origins",
|
||||
"profile",
|
||||
"email"
|
||||
]
|
||||
}
|
||||
],
|
||||
"roles" : {
|
||||
"realm" : [
|
||||
{
|
||||
"name": "user",
|
||||
"description": "Have User privileges"
|
||||
},
|
||||
{
|
||||
"name": "admin",
|
||||
"description": "Have Administrator privileges"
|
||||
},
|
||||
{
|
||||
"name": "customer-user-premium",
|
||||
"description": "Have User Premium privileges"
|
||||
},
|
||||
{
|
||||
"name": "sample-realm-role",
|
||||
"description": "Sample realm role"
|
||||
},
|
||||
{
|
||||
"name": "attribute-role",
|
||||
"description": "has attributes assigned",
|
||||
"attributes": {
|
||||
"hello": [
|
||||
"world",
|
||||
"keycloak"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "realm-composite-role",
|
||||
"description": "Realm composite role containing client role",
|
||||
"composite" : true,
|
||||
"composites" : {
|
||||
"realm" : [ "sample-realm-role" ],
|
||||
"client" : {
|
||||
"test-app" : [ "sample-client-role" ],
|
||||
"account" : [ "view-profile" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"client" : {
|
||||
"test-app" : [
|
||||
{
|
||||
"name": "manage-account",
|
||||
"description": "Allows application-initiated actions."
|
||||
},
|
||||
{
|
||||
"name": "customer-user",
|
||||
"description": "Have Customer User privileges"
|
||||
},
|
||||
{
|
||||
"name": "customer-admin",
|
||||
"description": "Have Customer Admin privileges"
|
||||
},
|
||||
{
|
||||
"name": "sample-client-role",
|
||||
"description": "Sample client role",
|
||||
"attributes": {
|
||||
"sample-client-role-attribute": [
|
||||
"sample-client-role-attribute-value"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "customer-admin-composite-role",
|
||||
"description": "Have Customer Admin privileges via composite role",
|
||||
"composite" : true,
|
||||
"composites" : {
|
||||
"realm" : [ "customer-user-premium" ],
|
||||
"client" : {
|
||||
"test-app" : [ "customer-admin" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
],
|
||||
"test-app-scope" : [
|
||||
{
|
||||
"name": "test-app-allowed-by-scope",
|
||||
"description": "Role allowed by scope in test-app-scope"
|
||||
},
|
||||
{
|
||||
"name": "test-app-disallowed-by-scope",
|
||||
"description": "Role disallowed by scope in test-app-scope"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
},
|
||||
"groups" : [
|
||||
{
|
||||
"name": "topGroup",
|
||||
"attributes": {
|
||||
"topAttribute": ["true"]
|
||||
|
||||
},
|
||||
"realmRoles": ["user"],
|
||||
|
||||
"subGroups": [
|
||||
{
|
||||
"name": "level2group",
|
||||
"realmRoles": ["admin"],
|
||||
"clientRoles": {
|
||||
"test-app": ["customer-user"]
|
||||
},
|
||||
"attributes": {
|
||||
"level2Attribute": ["true"]
|
||||
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "level2group2",
|
||||
"realmRoles": ["admin"],
|
||||
"clientRoles": {
|
||||
"test-app": ["customer-user"]
|
||||
},
|
||||
"attributes": {
|
||||
"level2Attribute": ["true"]
|
||||
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "roleRichGroup",
|
||||
"attributes": {
|
||||
"topAttribute": ["true"]
|
||||
|
||||
},
|
||||
"realmRoles": ["user", "realm-composite-role"],
|
||||
"clientRoles": {
|
||||
"account": ["manage-account"]
|
||||
},
|
||||
|
||||
"subGroups": [
|
||||
{
|
||||
"name": "level2group",
|
||||
"realmRoles": ["admin"],
|
||||
"clientRoles": {
|
||||
"test-app": ["customer-user", "customer-admin-composite-role"]
|
||||
},
|
||||
"attributes": {
|
||||
"level2Attribute": ["true"]
|
||||
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "level2group2",
|
||||
"realmRoles": ["admin"],
|
||||
"clientRoles": {
|
||||
"test-app": ["customer-user"]
|
||||
},
|
||||
"attributes": {
|
||||
"level2Attribute": ["true"]
|
||||
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "sample-realm-group"
|
||||
}
|
||||
],
|
||||
|
||||
|
||||
"clientScopeMappings": {
|
||||
"test-app": [
|
||||
{
|
||||
"client": "third-party",
|
||||
"roles": ["customer-user"]
|
||||
},
|
||||
{
|
||||
"client": "test-app-scope",
|
||||
"roles": ["customer-admin-composite-role"]
|
||||
}
|
||||
],
|
||||
"test-app-scope": [
|
||||
{
|
||||
"client": "test-app-scope",
|
||||
"roles": ["test-app-allowed-by-scope"]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
"internationalizationEnabled": true,
|
||||
"supportedLocales": ["en", "de"],
|
||||
"defaultLocale": "en",
|
||||
"eventsListeners": ["jboss-logging", "event-queue"]
|
||||
}
|
Loading…
Reference in New Issue