task: refactor overlap between cli clients

also repackaging to more clearly delineate code roles

closes: #28329

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
Steve Hawkins 2024-03-29 11:57:13 -04:00 committed by Pedro Igor
parent 808926b63e
commit 0be34d64e7
121 changed files with 1569 additions and 3927 deletions

View file

@ -14,38 +14,46 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.admin.cli;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.keycloak.client.admin.cli.common.AttributeOperation;
import org.keycloak.client.admin.cli.common.CmdStdinContext;
import org.keycloak.client.cli.common.AttributeOperation;
import org.keycloak.client.cli.util.AttributeException;
import org.keycloak.util.JsonSerialization;
import java.io.IOException;
import java.util.List;
import static org.keycloak.client.admin.cli.util.IoUtil.readFileOrStdin;
import static org.keycloak.client.admin.cli.util.ReflectionUtil.setAttributes;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import static org.keycloak.client.admin.cli.ReflectionUtil.setAttributes;
import static org.keycloak.client.cli.util.IoUtil.readFileOrStdin;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class ParseUtil {
public class CmdStdinContext<T> {
public static String[] parseKeyVal(String keyval) {
// we expect = as a separator
int pos = keyval.indexOf("=");
if (pos <= 0) {
throw new IllegalArgumentException("Invalid key=value parameter: [" + keyval + "]");
}
private T result;
private String content;
String [] parsed = new String[2];
parsed[0] = keyval.substring(0, pos);
parsed[1] = keyval.substring(pos+1);
public CmdStdinContext() {}
return parsed;
public T getResult() {
return result;
}
public void setResult(T result) {
this.result = result;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public static CmdStdinContext<JsonNode> parseFileOrStdin(String file) {

View file

@ -17,45 +17,37 @@
package org.keycloak.client.admin.cli;
import org.keycloak.client.admin.cli.commands.KcAdmCmd;
import org.keycloak.client.admin.cli.util.ClassLoaderUtil;
import org.keycloak.client.admin.cli.util.OsUtil;
import org.keycloak.common.crypto.CryptoIntegration;
import java.io.PrintWriter;
import picocli.CommandLine;
import picocli.CommandLine.Model.CommandSpec;
import org.keycloak.client.cli.common.CommandState;
import org.keycloak.client.cli.common.Globals;
import org.keycloak.client.cli.util.OsUtil;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class KcAdmMain {
public static void main(String [] args) {
String libDir = System.getProperty("kc.lib.dir");
if (libDir == null) {
throw new RuntimeException("System property kc.lib.dir needs to be set");
public static final String DEFAULT_CONFIG_FILE_PATH = System.getProperty("user.home") + "/.keycloak/kcadm.config";
public static final String DEFAULT_CONFIG_FILE_STRING = OsUtil.OS_ARCH.isWindows() ? "%HOMEDRIVE%%HOMEPATH%\\.keycloak\\kcadm.config" : "~/.keycloak/kcadm.config";
public static final String CMD = OsUtil.OS_ARCH.isWindows() ? "kcadm.bat" : "kcadm.sh";
public static final CommandState COMMAND_STATE = new CommandState() {
@Override
public String getCommand() {
return CMD;
}
ClassLoader cl = ClassLoaderUtil.resolveClassLoader(libDir);
Thread.currentThread().setContextClassLoader(cl);
CryptoIntegration.init(cl);
@Override
public String getDefaultConfigFilePath() {
return DEFAULT_CONFIG_FILE_PATH;
}
CommandLine cli = createCommandLine();
int exitCode = cli.execute(args);
System.exit(exitCode);
}
};
public static CommandLine createCommandLine() {
CommandSpec spec = CommandSpec.forAnnotatedObject(new KcAdmCmd()).name(OsUtil.CMD);
CommandLine cmd = new CommandLine(spec);
cmd.setExecutionExceptionHandler(new ExecutionExceptionHandler());
cmd.setParameterExceptionHandler(new ShortErrorMessageHandler());
cmd.setErr(new PrintWriter(System.err, true));
return cmd;
public static void main(String [] args) {
Globals.main(args, new KcAdmCmd(), CMD, DEFAULT_CONFIG_FILE_STRING);
}
}

View file

@ -14,22 +14,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.admin.cli;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.NullNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.fasterxml.jackson.databind.node.TextNode;
import org.keycloak.client.admin.cli.common.AttributeKey;
import org.keycloak.client.admin.cli.common.AttributeOperation;
import org.keycloak.client.cli.common.AttributeKey;
import org.keycloak.client.cli.common.AttributeOperation;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import static org.keycloak.client.admin.cli.common.AttributeOperation.Type.SET;
import static org.keycloak.client.admin.cli.util.OutputUtil.MAPPER;
import static org.keycloak.client.cli.common.AttributeOperation.Type.SET;
import static org.keycloak.client.cli.util.OutputUtil.MAPPER;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>

View file

@ -16,252 +16,35 @@
*/
package org.keycloak.client.admin.cli.commands;
import org.keycloak.OAuth2Constants;
import org.keycloak.client.admin.cli.config.ConfigData;
import org.keycloak.client.admin.cli.config.ConfigHandler;
import org.keycloak.client.admin.cli.config.FileConfigHandler;
import org.keycloak.client.admin.cli.config.InMemoryConfigHandler;
import org.keycloak.client.admin.cli.config.RealmConfigData;
import org.keycloak.client.admin.cli.util.ConfigUtil;
import org.keycloak.client.admin.cli.util.HttpUtil;
import org.keycloak.client.admin.cli.util.IoUtil;
import java.io.File;
import org.keycloak.client.admin.cli.KcAdmMain;
import org.keycloak.client.cli.common.BaseAuthOptionsCmd;
import org.keycloak.client.cli.config.ConfigData;
import picocli.CommandLine.Option;
import static org.keycloak.client.admin.cli.config.FileConfigHandler.setConfigFile;
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CLIENT;
import static org.keycloak.client.admin.cli.util.ConfigUtil.checkAuthInfo;
import static org.keycloak.client.admin.cli.util.ConfigUtil.checkServerInfo;
import static org.keycloak.client.admin.cli.util.ConfigUtil.loadConfig;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
public abstract class AbstractAuthOptionsCmd extends BaseAuthOptionsCmd implements GlobalOptionsCmdHelper {
@Option(names = {"-a", "--admin-root"}, description = "URL of Admin REST endpoint root if not default - e.g. http://localhost:8080/admin")
String adminRestRoot;
@Option(names = "--config", description = "Path to the config file (~/.keycloak/kcadm.config by default)")
String config;
@Option(names = "--no-config", description = "Don't use config file - no authentication info is loaded or saved")
boolean noconfig;
@Option(names = "--server", description = "Server endpoint url (e.g. 'http://localhost:8080')")
String server;
@Option(names = {"-r", "--target-realm"}, description = "Realm to target - when it's different than the realm we authenticate against")
String targetRealm;
@Option(names = "--realm", description = "Realm name to authenticate against")
String realm;
@Option(names = "--client", description = "Realm name to authenticate against")
String clientId;
@Option(names = "--user", description = "Username to login with")
String user;
@Option(names = "--password", description = "Password to login with (prompted for if not specified and --user is used)")
String password;
@Option(names = "--secret", description = "Secret to authenticate the client (prompted for if no --user or --keystore is specified)")
String secret;
@Option(names = "--keystore", description = "Path to a keystore containing private key")
String keystore;
@Option(names = "--storepass", description = "Keystore password (prompted for if not specified and --keystore is used)")
String storePass;
@Option(names = "--keypass", description = "Key password (prompted for if not specified and --keystore is used without --storepass, \n otherwise defaults to keystore password)")
String keyPass;
@Option(names = "--alias", description = "Alias of the key inside a keystore (defaults to the value of ClientId)")
String alias;
@Option(names = "--truststore", description = "Path to a truststore")
String trustStore;
@Option(names = "--trustpass", description = "Truststore password (prompted for if not specified and --truststore is used)")
String trustPass;
@Option(names = "--insecure", description = "Turns off TLS validation")
boolean insecure;
@Option(names = "--token", description = "Token to use for invocations. With this option set, every other authentication option is ignored")
String externalToken;
protected void initFromParent(AbstractAuthOptionsCmd parent) {
noconfig = parent.noconfig;
config = parent.config;
server = parent.server;
realm = parent.realm;
clientId = parent.clientId;
user = parent.user;
password = parent.password;
secret = parent.secret;
keystore = parent.keystore;
storePass = parent.storePass;
keyPass = parent.keyPass;
alias = parent.alias;
trustStore = parent.trustStore;
trustPass = parent.trustPass;
externalToken = parent.externalToken;
public void setToken(String token) {
this.externalToken = token;
}
protected void applyDefaultOptionValues() {
if (clientId == null) {
clientId = DEFAULT_CLIENT;
}
public AbstractAuthOptionsCmd() {
super(KcAdmMain.COMMAND_STATE);
}
@Override
protected boolean nothingToDo() {
return externalToken == null && server == null && realm == null && clientId == null && secret == null &&
user == null && password == null &&
keystore == null && storePass == null && keyPass == null && alias == null &&
trustStore == null && trustPass == null && config == null;
}
protected String getTargetRealm(ConfigData config) {
return targetRealm != null ? targetRealm : config.getRealm();
}
@Override
protected void processOptions() {
if (config != null && noconfig) {
throw new IllegalArgumentException("Options --config and --no-config are mutually exclusive");
}
if (!noconfig) {
setConfigFile(config != null ? config : ConfigUtil.DEFAULT_CONFIG_FILE_PATH);
ConfigUtil.setHandler(new FileConfigHandler());
} else {
InMemoryConfigHandler handler = new InMemoryConfigHandler();
ConfigData data = new ConfigData();
initConfigData(data);
handler.setConfigData(data);
ConfigUtil.setHandler(handler);
}
}
protected void setupTruststore(ConfigData configData) {
if (!configData.getServerUrl().startsWith("https:")) {
return;
}
String truststore = trustStore;
if (truststore == null) {
truststore = configData.getTruststore();
}
if (truststore != null) {
String pass = trustPass;
if (pass == null) {
pass = configData.getTrustpass();
}
if (pass == null) {
pass = IoUtil.readSecret("Enter truststore password: ");
}
try {
HttpUtil.setTruststore(new File(truststore), pass);
} catch (Exception e) {
throw new RuntimeException("Failed to load truststore: " + truststore, e);
}
}
if (insecure) {
HttpUtil.setSkipCertificateValidation();
}
}
protected ConfigData ensureAuthInfo(ConfigData config) {
if (requiresLogin()) {
// make sure current handler is in-memory handler
// restore it at the end
ConfigHandler old = ConfigUtil.getHandler();
try {
// make sure all defaults are initialized after this point
applyDefaultOptionValues();
initConfigData(config);
ConfigUtil.setupInMemoryHandler(config);
ConfigCredentialsCmd login = new ConfigCredentialsCmd();
login.initFromParent(this);
login.init(config);
login.process();
// this must be executed before finally block which restores config handler
return loadConfig();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
ConfigUtil.setHandler(old);
}
} else {
checkAuthInfo(config);
// make sure all defaults are initialized after this point
applyDefaultOptionValues();
return loadConfig();
}
}
protected boolean requiresLogin() {
return externalToken == null && (user != null || password != null || secret != null || keystore != null
|| keyPass != null || storePass != null || alias != null);
}
protected ConfigData copyWithServerInfo(ConfigData config) {
ConfigData result = config.deepcopy();
if (server != null) {
result.setServerUrl(server);
}
if (realm != null) {
result.setRealm(realm);
}
if (externalToken != null) {
result.setExternalToken(externalToken);
}
checkServerInfo(result);
return result;
}
private void initConfigData(ConfigData data) {
if (server != null)
data.setServerUrl(server);
if (realm != null)
data.setRealm(realm);
if (trustStore != null)
data.setTruststore(trustStore);
if (externalToken != null) {
data.setExternalToken(externalToken);
}
RealmConfigData rdata = data.sessionRealmConfigData();
if (clientId != null)
rdata.setClientId(clientId);
if (secret != null)
rdata.setSecret(secret);
String grantTypeForAuthentication = user == null ? OAuth2Constants.CLIENT_CREDENTIALS : OAuth2Constants.PASSWORD;
rdata.setGrantTypeForAuthentication(grantTypeForAuthentication);
}
}

View file

@ -1,136 +0,0 @@
/*
* Copyright 2016 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.client.admin.cli.commands;
import org.keycloak.client.admin.cli.Globals;
import org.keycloak.client.admin.cli.util.FilterUtil;
import org.keycloak.client.admin.cli.util.ReturnFields;
import java.io.IOException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import picocli.CommandLine;
import picocli.CommandLine.Option;
import static org.keycloak.client.admin.cli.util.HttpUtil.normalize;
import static org.keycloak.client.admin.cli.util.IoUtil.printOut;
public abstract class AbstractGlobalOptionsCmd implements Runnable {
@Option(names = "--help",
description = "Print command specific help")
public void setHelp(boolean help) {
Globals.help = help;
}
@Option(names = "-x",
description = "Print full stack trace when exiting with error")
public void setDumpTrace(boolean dumpTrace) {
Globals.dumpTrace = dumpTrace;
}
protected void printHelpIfNeeded() {
if (Globals.help) {
printOut(help());
System.exit(CommandLine.ExitCode.OK);
} else if (nothingToDo()) {
printOut(help());
System.exit(CommandLine.ExitCode.USAGE);
}
}
protected boolean nothingToDo() {
return false;
}
protected String help() {
return KcAdmCmd.usage();
}
protected String composeAdminRoot(String server) {
return normalize(server) + "admin";
}
protected String extractTypeNameFromUri(String resourceUrl) {
String type = extractLastComponentOfUri(resourceUrl);
if (type.endsWith("s")) {
type = type.substring(0, type.length()-1);
}
return type;
}
protected String extractLastComponentOfUri(String resourceUrl) {
int endPos = resourceUrl.endsWith("/") ? resourceUrl.length()-2 : resourceUrl.length()-1;
int pos = resourceUrl.lastIndexOf("/", endPos);
pos = pos == -1 ? 0 : pos;
return resourceUrl.substring(pos+1, endPos+1);
}
protected JsonNode applyFieldFilter(ObjectMapper mapper, JsonNode rootNode, ReturnFields returnFields) {
// construct new JsonNode that satisfies filtering specified by returnFields
try {
return FilterUtil.copyFilteredObject(rootNode, returnFields);
} catch (IOException e) {
throw new RuntimeException("Failed to apply fields filter", e);
}
}
@Override
public void run() {
printHelpIfNeeded();
checkUnsupportedOptions(getUnsupportedOptions());
processOptions();
process();
}
protected String[] getUnsupportedOptions() {
return new String[0];
}
protected void processOptions() {
}
protected void process() {
}
protected void checkUnsupportedOptions(String ... options) {
if (options.length % 2 != 0) {
throw new IllegalArgumentException("Even number of argument required");
}
for (int i = 0; i < options.length; i++) {
String name = options[i];
String value = options[++i];
if (value != null) {
throw new IllegalArgumentException("Unsupported option: " + name);
}
}
}
protected static String booleanOptionForCheck(boolean value) {
return value ? "true" : null;
}
}

View file

@ -17,18 +17,18 @@
package org.keycloak.client.admin.cli.commands;
import org.apache.http.entity.ContentType;
import org.keycloak.client.admin.cli.common.AttributeOperation;
import org.keycloak.client.admin.cli.common.CmdStdinContext;
import org.keycloak.client.admin.cli.config.ConfigData;
import org.keycloak.client.admin.cli.util.AccessibleBufferOutputStream;
import org.keycloak.client.admin.cli.util.Header;
import org.keycloak.client.admin.cli.util.Headers;
import org.keycloak.client.admin.cli.util.HeadersBody;
import org.keycloak.client.admin.cli.util.HeadersBodyStatus;
import org.keycloak.client.admin.cli.util.HttpUtil;
import org.keycloak.client.admin.cli.util.OutputFormat;
import org.keycloak.client.admin.cli.util.ReflectionUtil;
import org.keycloak.client.admin.cli.util.ReturnFields;
import org.keycloak.client.admin.cli.CmdStdinContext;
import org.keycloak.client.admin.cli.ReflectionUtil;
import org.keycloak.client.cli.common.AttributeOperation;
import org.keycloak.client.cli.config.ConfigData;
import org.keycloak.client.cli.util.AccessibleBufferOutputStream;
import org.keycloak.client.cli.util.Header;
import org.keycloak.client.cli.util.Headers;
import org.keycloak.client.cli.util.HeadersBody;
import org.keycloak.client.cli.util.HeadersBodyStatus;
import org.keycloak.client.cli.util.HttpUtil;
import org.keycloak.client.cli.util.OutputFormat;
import org.keycloak.client.cli.util.ReturnFields;
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
@ -52,22 +52,19 @@ import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import static org.keycloak.client.admin.cli.common.AttributeOperation.Type.DELETE;
import static org.keycloak.client.admin.cli.common.AttributeOperation.Type.SET;
import static org.keycloak.client.admin.cli.util.AuthUtil.ensureToken;
import static org.keycloak.client.admin.cli.util.ConfigUtil.credentialsAvailable;
import static org.keycloak.client.admin.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.admin.cli.util.HttpUtil.checkSuccess;
import static org.keycloak.client.admin.cli.util.HttpUtil.composeResourceUrl;
import static org.keycloak.client.admin.cli.util.HttpUtil.doGet;
import static org.keycloak.client.admin.cli.util.IoUtil.copyStream;
import static org.keycloak.client.admin.cli.util.IoUtil.printErr;
import static org.keycloak.client.admin.cli.util.IoUtil.printOut;
import static org.keycloak.client.admin.cli.util.OutputUtil.MAPPER;
import static org.keycloak.client.admin.cli.util.OutputUtil.printAsCsv;
import static org.keycloak.client.admin.cli.util.ParseUtil.mergeAttributes;
import static org.keycloak.client.admin.cli.util.ParseUtil.parseFileOrStdin;
import static org.keycloak.client.admin.cli.util.ParseUtil.parseKeyVal;
import static org.keycloak.client.cli.common.AttributeOperation.Type.DELETE;
import static org.keycloak.client.cli.common.AttributeOperation.Type.SET;
import static org.keycloak.client.cli.util.ConfigUtil.credentialsAvailable;
import static org.keycloak.client.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.cli.util.HttpUtil.checkSuccess;
import static org.keycloak.client.cli.util.HttpUtil.composeResourceUrl;
import static org.keycloak.client.cli.util.HttpUtil.doGet;
import static org.keycloak.client.cli.util.IoUtil.copyStream;
import static org.keycloak.client.cli.util.IoUtil.printErr;
import static org.keycloak.client.cli.util.IoUtil.printOut;
import static org.keycloak.client.cli.util.OutputUtil.MAPPER;
import static org.keycloak.client.cli.util.OutputUtil.printAsCsv;
import static org.keycloak.client.cli.util.ParseUtil.parseKeyVal;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -208,7 +205,7 @@ public abstract class AbstractRequestCmd extends AbstractAuthOptionsCmd {
}
}
} else {
ctx = parseFileOrStdin(file);
ctx = CmdStdinContext.parseFileOrStdin(file);
}
} else if (body != null) {
content = new ByteArrayInputStream(body.getBytes(StandardCharsets.UTF_8));
@ -280,7 +277,7 @@ public abstract class AbstractRequestCmd extends AbstractAuthOptionsCmd {
throw new RuntimeException("Can't set attributes on content of type other than application/json");
}
ctx = mergeAttributes(ctx, MAPPER.createObjectNode(), attrs);
ctx = CmdStdinContext.mergeAttributes(ctx, MAPPER.createObjectNode(), attrs);
}
if (content == null && ctx.getContent() != null) {

View file

@ -21,12 +21,13 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import org.keycloak.client.admin.cli.config.ConfigData;
import org.keycloak.client.admin.cli.KcAdmMain;
import org.keycloak.client.admin.cli.operations.ClientOperations;
import org.keycloak.client.admin.cli.operations.GroupOperations;
import org.keycloak.client.admin.cli.operations.RoleOperations;
import org.keycloak.client.admin.cli.operations.LocalSearch;
import org.keycloak.client.admin.cli.operations.UserOperations;
import org.keycloak.client.cli.config.ConfigData;
import java.io.PrintWriter;
import java.io.StringWriter;
@ -35,12 +36,10 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.keycloak.client.admin.cli.util.AuthUtil.ensureToken;
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.admin.cli.util.ConfigUtil.credentialsAvailable;
import static org.keycloak.client.admin.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.admin.cli.KcAdmMain.CMD;
import static org.keycloak.client.cli.util.ConfigUtil.credentialsAvailable;
import static org.keycloak.client.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.cli.util.OsUtil.PROMPT;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -290,7 +289,7 @@ public class AddRolesCmd extends AbstractAuthOptionsCmd {
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --config Path to the config file (" + KcAdmMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set.");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");

View file

@ -21,7 +21,7 @@ import java.io.StringWriter;
import picocli.CommandLine.Command;
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
import static org.keycloak.client.admin.cli.KcAdmMain.CMD;
/**

View file

@ -16,237 +16,20 @@
*/
package org.keycloak.client.admin.cli.commands;
import org.keycloak.OAuth2Constants;
import org.keycloak.client.admin.cli.config.ConfigData;
import org.keycloak.client.admin.cli.config.RealmConfigData;
import org.keycloak.client.admin.cli.util.AuthUtil;
import org.keycloak.representations.AccessTokenResponse;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import org.keycloak.client.admin.cli.KcAdmMain;
import org.keycloak.client.cli.common.BaseConfigCredentialsCmd;
import picocli.CommandLine.Command;
import static org.keycloak.client.admin.cli.util.AuthUtil.getAuthTokens;
import static org.keycloak.client.admin.cli.util.AuthUtil.getAuthTokensByJWT;
import static org.keycloak.client.admin.cli.util.AuthUtil.getAuthTokensBySecret;
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.admin.cli.util.ConfigUtil.getHandler;
import static org.keycloak.client.admin.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.admin.cli.util.ConfigUtil.saveTokens;
import static org.keycloak.client.admin.cli.util.IoUtil.printErr;
import static org.keycloak.client.admin.cli.util.IoUtil.readSecret;
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
import static org.keycloak.client.admin.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
@Command(name = "credentials", description = "--server SERVER_URL --realm REALM [ARGUMENTS]")
public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd {
public class ConfigCredentialsCmd extends BaseConfigCredentialsCmd {
private int sigLifetime = 600;
public void init(ConfigData configData) {
if (server == null) {
server = configData.getServerUrl();
}
if (realm == null) {
realm = configData.getRealm();
}
if (trustStore == null) {
trustStore = configData.getTruststore();
}
RealmConfigData rdata = configData.getRealmConfigData(server, realm);
if (rdata == null) {
return;
}
if (clientId == null) {
clientId = rdata.getClientId();
}
public ConfigCredentialsCmd() {
super(KcAdmMain.COMMAND_STATE);
}
@Override
protected String[] getUnsupportedOptions() {
return new String[] {"--no-config", booleanOptionForCheck(noconfig)};
}
@Override
public void process() {
// check server
if (server == null) {
throw new IllegalArgumentException("Required option not specified: --server");
}
try {
new URL(server);
} catch (Exception e) {
throw new RuntimeException("Invalid server endpoint url: " + server, e);
}
if (realm == null)
throw new IllegalArgumentException("Required option not specified: --realm");
String signedRequestToken = null;
boolean clientSet = clientId != null;
applyDefaultOptionValues();
String grantTypeForAuthentication = null;
if (user != null) {
grantTypeForAuthentication = OAuth2Constants.PASSWORD;
printErr("Logging into " + server + " as user " + user + " of realm " + realm);
// if user was set there needs to be a password so we can authenticate
if (password == null) {
password = readSecret("Enter password: ");
}
// if secret was set to be read from stdin, then ask for it
if ("-".equals(secret) && keystore == null) {
secret = readSecret("Enter client secret: ");
}
} else if (keystore != null || secret != null || clientSet) {
grantTypeForAuthentication = OAuth2Constants.CLIENT_CREDENTIALS;
printErr("Logging into " + server + " as " + "service-account-" + clientId + " of realm " + realm);
if (keystore == null) {
if (secret == null) {
secret = readSecret("Enter client secret: ");
}
}
}
if (keystore != null) {
if (secret != null) {
throw new IllegalArgumentException("Can't use both --keystore and --secret");
}
if (!new File(keystore).isFile()) {
throw new RuntimeException("No such keystore file: " + keystore);
}
if (storePass == null) {
storePass = readSecret("Enter keystore password: ");
keyPass = readSecret("Enter key password: ");
}
if (keyPass == null) {
keyPass = storePass;
}
if (alias == null) {
alias = clientId;
}
String realmInfoUrl = server + "/realms/" + realm;
signedRequestToken = AuthUtil.getSignedRequestToken(keystore, storePass, keyPass,
alias, sigLifetime, clientId, realmInfoUrl);
}
// if only server and realm are set, just save config and be done
if (user == null && secret == null && keystore == null) {
getHandler().saveMergeConfig(config -> {
config.setServerUrl(server);
config.setRealm(realm);
});
return;
}
setupTruststore(copyWithServerInfo(loadConfig()));
// now use the token endpoint to retrieve access token, and refresh token
AccessTokenResponse tokens = signedRequestToken != null ?
getAuthTokensByJWT(server, realm, user, password, clientId, signedRequestToken) :
secret != null ?
getAuthTokensBySecret(server, realm, user, password, clientId, secret) :
getAuthTokens(server, realm, user, password, clientId);
Long sigExpiresAt = signedRequestToken == null ? null : System.currentTimeMillis() + sigLifetime * 1000;
// save tokens to config file
saveTokens(tokens, server, realm, clientId, signedRequestToken, sigExpiresAt, secret, grantTypeForAuthentication);
}
@Override
protected String help() {
return usage();
}
public static String usage() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
out.println("Usage: " + CMD + " config credentials --server SERVER_URL --realm REALM --user USER [--password PASSWORD] [ARGUMENTS]");
out.println(" " + CMD + " config credentials --server SERVER_URL --realm REALM --client CLIENT_ID [--secret SECRET] [ARGUMENTS]");
out.println(" " + CMD + " config credentials --server SERVER_URL --realm REALM --client CLIENT_ID [--keystore KEYSTORE] [ARGUMENTS]");
out.println();
out.println("Command to establish an authenticated client session with the server. There are many authentication");
out.println("options available, and it depends on server side client authentication configuration how client can or should authenticate.");
out.println("The information always required includes --server, and --realm. Then, --user and / or --client need to be used to authenticate.");
out.println("If --client is not provided it defaults to 'admin-cli'. The authentication options / requirements depend on how this client is configured.");
out.println();
out.println("If confidential client authentication is also configured, you may have to specify a client id, and client credentials in addition to");
out.println("user credentials. Client credentials are either a client secret, or a keystore information to use Signed JWT mechanism.");
out.println("If only client credentials are provided, and no user credentials, then the service account is used for login.");
out.println();
out.println("Arguments:");
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to a config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");
out.println();
out.println(" Command specific options:");
out.println(" --server SERVER_URL Server endpoint url (e.g. 'http://localhost:8080')");
out.println(" --realm REALM Realm name to use");
out.println(" --user USER Username to login with");
out.println(" --password PASSWORD Password to login with (prompted for if not specified and --user is used)");
out.println(" --client CLIENT_ID ClientId used by this client tool ('admin-cli' by default)");
out.println(" --secret SECRET Secret to authenticate the client (prompted for if --client is specified, and no --keystore is specified)");
out.println(" --keystore PATH Path to a keystore containing private key");
out.println(" --storepass PASSWORD Keystore password (prompted for if not specified and --keystore is used)");
out.println(" --keypass PASSWORD Key password (prompted for if not specified and --keystore is used without --storepass,");
out.println(" otherwise defaults to keystore password)");
out.println(" --alias ALIAS Alias of the key inside a keystore (defaults to the value of ClientId)");
out.println();
out.println();
out.println("Examples:");
out.println();
out.println("Login as 'admin' user of 'master' realm to a local Keycloak server running on default port.");
out.println("You will be prompted for a password:");
out.println(" " + PROMPT + " " + CMD + " config credentials --server http://localhost:8080 --realm master --user admin");
out.println();
out.println("Login to Keycloak server at non-default endpoint passing the password via standard input:");
if (OS_ARCH.isWindows()) {
out.println(" " + PROMPT + " echo mypassword | " + CMD + " config credentials --server http://localhost:9080 --realm master --user admin");
} else {
out.println(" " + PROMPT + " " + CMD + " config credentials --server http://localhost:9080 --realm master --user admin << EOF");
out.println(" mypassword");
out.println(" EOF");
}
out.println();
out.println("Login specifying a password through command line:");
out.println(" " + PROMPT + " " + CMD + " config credentials --server http://localhost:9080 --realm master --user admin --password " + OS_ARCH.envVar("PASSWORD"));
out.println();
out.println("Login using a client service account of a custom client. You will be prompted for a client secret:");
out.println(" " + PROMPT + " " + CMD + " config credentials --server http://localhost:9080 --realm master --client reg-cli");
out.println();
out.println("Login using a client service account of a custom client, authenticating with signed JWT.");
out.println("You will be prompted for a keystore password, and a key password:");
out.println(" " + PROMPT + " " + CMD + " config credentials --server http://localhost:9080 --realm master --client reg-cli --keystore " + OS_ARCH.path("~/.keycloak/keystore.jks"));
out.println();
out.println("Login as 'user' while also authenticating a custom client with signed JWT.");
out.println("You will be prompted for a user password, a keystore password, and a key password:");
out.println(" " + PROMPT + " " + CMD + " config credentials --server http://localhost:9080 --realm master --user user --client reg-cli --keystore " + OS_ARCH.path("~/.keycloak/keystore.jks"));
out.println();
out.println();
out.println("Use '" + CMD + " help' for general information and a list of commands");
return sb.toString();
}
}

View file

@ -16,126 +16,19 @@
*/
package org.keycloak.client.admin.cli.commands;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.keycloak.client.admin.cli.KcAdmMain;
import org.keycloak.client.cli.common.BaseConfigTruststoreCmd;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.admin.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.admin.cli.util.IoUtil.readSecret;
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
import static org.keycloak.client.admin.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
@Command(name = "truststore", description = "PATH [ARGUMENTS]")
public class ConfigTruststoreCmd extends AbstractAuthOptionsCmd {
public class ConfigTruststoreCmd extends BaseConfigTruststoreCmd {
@Parameters(arity = "0..1")
private String store;
@Option(names = {"-d", "--delete"}, description = "Remove truststore configuration")
private boolean delete;
@Override
protected boolean nothingToDo() {
return super.nothingToDo() && store == null && !delete;
public ConfigTruststoreCmd() {
super(KcAdmMain.COMMAND_STATE);
}
@Override
protected String[] getUnsupportedOptions() {
return new String[] {"--server", server,
"--realm", realm,
"--client", clientId,
"--user", user,
"--password", password,
"--secret", secret,
"--truststore", trustStore,
"--keystore", keystore,
"--keypass", keyPass,
"--alias", alias,
"--no-config", booleanOptionForCheck(noconfig)};
}
@Override
protected void process() {
String pass;
if (!delete) {
if (store == null) {
throw new IllegalArgumentException("No truststore specified");
}
if (!new File(store).isFile()) {
throw new RuntimeException("Truststore file not found: " + store);
}
if ("-".equals(trustPass)) {
trustPass = readSecret("Enter truststore password: ");
}
pass = trustPass;
} else {
if (store != null) {
throw new IllegalArgumentException("Option --delete is mutually exclusive with specifying a TRUSTSTORE");
}
if (trustPass != null) {
throw new IllegalArgumentException("Options --trustpass and --delete are mutually exclusive");
}
pass = null;
}
saveMergeConfig(config -> {
config.setTruststore(store);
config.setTrustpass(pass);
});
}
@Override
protected String help() {
return usage();
}
public static String usage() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
out.println("Usage: " + CMD + " config truststore [TRUSTSTORE | --delete] [--trustpass PASSWORD] [ARGUMENTS]");
out.println();
out.println("Command to configure a global truststore to use when using https to connect to Keycloak server.");
out.println();
out.println("Arguments:");
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println();
out.println(" Command specific options:");
out.println(" TRUSTSTORE Path to truststore file");
out.println(" --trustpass PASSWORD Truststore password to unlock truststore (prompted for if set to '-')");
out.println(" -d, --delete Remove truststore configuration");
out.println();
out.println();
out.println("Examples:");
out.println();
out.println("Specify a truststore - you will be prompted for truststore password every time it is used:");
out.println(" " + PROMPT + " " + CMD + " config truststore " + OS_ARCH.path("~/.keycloak/truststore.jks"));
out.println();
out.println("Specify a truststore, and password - truststore will automatically be used without prompting for password:");
out.println(" " + PROMPT + " " + CMD + " config truststore --trustpass " + OS_ARCH.envVar("PASSWORD") + " " + OS_ARCH.path("~/.keycloak/truststore.jks"));
out.println();
out.println("Remove truststore configuration:");
out.println(" " + PROMPT + " " + CMD + " config truststore --delete");
out.println();
out.println();
out.println("Use '" + CMD + " help' for general information and a list of commands");
return sb.toString();
}
}

View file

@ -16,16 +16,17 @@
*/
package org.keycloak.client.admin.cli.commands;
import org.keycloak.client.admin.cli.KcAdmMain;
import java.io.PrintWriter;
import java.io.StringWriter;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
import static org.keycloak.client.admin.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.admin.cli.KcAdmMain.CMD;
import static org.keycloak.client.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.cli.util.OsUtil.PROMPT;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -91,7 +92,7 @@ public class CreateCmd extends AbstractRequestCmd {
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --config Path to the config file (" + KcAdmMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set.");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");

View file

@ -16,14 +16,15 @@
*/
package org.keycloak.client.admin.cli.commands;
import org.keycloak.client.admin.cli.KcAdmMain;
import java.io.PrintWriter;
import java.io.StringWriter;
import picocli.CommandLine.Command;
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.admin.cli.KcAdmMain.CMD;
import static org.keycloak.client.cli.util.OsUtil.PROMPT;
/**
@ -55,7 +56,7 @@ public class DeleteCmd extends CreateCmd {
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --config Path to the config file (" + KcAdmMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set.");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");

View file

@ -16,15 +16,16 @@
*/
package org.keycloak.client.admin.cli.commands;
import org.keycloak.client.admin.cli.KcAdmMain;
import java.io.PrintWriter;
import java.io.StringWriter;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.admin.cli.KcAdmMain.CMD;
import static org.keycloak.client.cli.util.OsUtil.PROMPT;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -91,7 +92,7 @@ public class GetCmd extends AbstractRequestCmd {
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --config Path to the config file (" + KcAdmMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set.");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");

View file

@ -16,11 +16,12 @@
*/
package org.keycloak.client.admin.cli.commands;
import org.keycloak.client.admin.cli.config.ConfigData;
import org.keycloak.client.admin.cli.KcAdmMain;
import org.keycloak.client.admin.cli.operations.ClientOperations;
import org.keycloak.client.admin.cli.operations.GroupOperations;
import org.keycloak.client.admin.cli.operations.RoleOperations;
import org.keycloak.client.admin.cli.operations.UserOperations;
import org.keycloak.client.cli.config.ConfigData;
import java.io.PrintWriter;
import java.io.StringWriter;
@ -28,13 +29,11 @@ import java.io.StringWriter;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import static org.keycloak.client.admin.cli.util.AuthUtil.ensureToken;
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.admin.cli.util.ConfigUtil.credentialsAvailable;
import static org.keycloak.client.admin.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.admin.cli.util.HttpUtil.composeResourceUrl;
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.admin.cli.KcAdmMain.CMD;
import static org.keycloak.client.cli.util.ConfigUtil.credentialsAvailable;
import static org.keycloak.client.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.cli.util.HttpUtil.composeResourceUrl;
import static org.keycloak.client.cli.util.OsUtil.PROMPT;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -325,7 +324,7 @@ public class GetRolesCmd extends GetCmd {
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --config Path to the config file (" + KcAdmMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set.");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");

View file

@ -0,0 +1,59 @@
/*
* Copyright 2016 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.client.admin.cli.commands;
import org.keycloak.client.cli.util.FilterUtil;
import org.keycloak.client.cli.util.ReturnFields;
import java.io.IOException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import static org.keycloak.client.cli.util.HttpUtil.normalize;
public interface GlobalOptionsCmdHelper {
default String composeAdminRoot(String server) {
return normalize(server) + "admin";
}
default String extractTypeNameFromUri(String resourceUrl) {
String type = extractLastComponentOfUri(resourceUrl);
if (type.endsWith("s")) {
type = type.substring(0, type.length()-1);
}
return type;
}
default String extractLastComponentOfUri(String resourceUrl) {
int endPos = resourceUrl.endsWith("/") ? resourceUrl.length()-2 : resourceUrl.length()-1;
int pos = resourceUrl.lastIndexOf("/", endPos);
pos = pos == -1 ? 0 : pos;
return resourceUrl.substring(pos+1, endPos+1);
}
default JsonNode applyFieldFilter(ObjectMapper mapper, JsonNode rootNode, ReturnFields returnFields) {
// construct new JsonNode that satisfies filtering specified by returnFields
try {
return FilterUtil.copyFilteredObject(rootNode, returnFields);
} catch (IOException e) {
throw new RuntimeException("Failed to apply fields filter", e);
}
}
}

View file

@ -21,7 +21,7 @@ import java.util.List;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
import static org.keycloak.client.admin.cli.util.IoUtil.printOut;
import static org.keycloak.client.cli.util.IoUtil.printOut;
@Command(name = "help", description = "This Help")
public class HelpCmd implements Runnable {
@ -39,11 +39,11 @@ public class HelpCmd implements Runnable {
if (args.size() > 1) {
switch (args.get(1)) {
case "credentials": {
printOut(ConfigCredentialsCmd.usage());
printOut(new ConfigCredentialsCmd().help());
break outer;
}
case "truststore": {
printOut(ConfigTruststoreCmd.usage());
printOut(new ConfigTruststoreCmd().help());
break outer;
}
}

View file

@ -16,14 +16,16 @@
*/
package org.keycloak.client.admin.cli.commands;
import org.keycloak.client.admin.cli.KcAdmMain;
import org.keycloak.client.cli.common.BaseGlobalOptionsCmd;
import java.io.PrintWriter;
import java.io.StringWriter;
import picocli.CommandLine.Command;
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.admin.cli.KcAdmMain.CMD;
import static org.keycloak.client.cli.util.OsUtil.PROMPT;
@Command(name = "kcadm",
header = {
@ -47,13 +49,18 @@ subcommands = {
GetRolesCmd.class,
SetPasswordCmd.class
})
public class KcAdmCmd extends AbstractGlobalOptionsCmd {
public class KcAdmCmd extends BaseGlobalOptionsCmd {
@Override
protected boolean nothingToDo() {
return true;
}
@Override
protected String help() {
return usage();
}
public static String usage() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
@ -76,7 +83,7 @@ public class KcAdmCmd extends AbstractGlobalOptionsCmd {
out.println("Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --help Print help for specific command");
out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --config Path to the config file (" + KcAdmMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println();
out.println("Commands: ");
out.println(" config Set up credentials, and other configuration settings using the config file");

View file

@ -21,9 +21,10 @@ import com.fasterxml.jackson.databind.JsonNode;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import org.keycloak.client.admin.cli.common.AttributeOperation;
import org.keycloak.client.admin.cli.common.CmdStdinContext;
import org.keycloak.client.admin.cli.util.AccessibleBufferOutputStream;
import org.keycloak.client.admin.cli.CmdStdinContext;
import org.keycloak.client.cli.common.AttributeOperation;
import org.keycloak.client.cli.common.BaseGlobalOptionsCmd;
import org.keycloak.client.cli.util.AccessibleBufferOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
@ -35,22 +36,20 @@ import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import static org.keycloak.client.admin.cli.common.AttributeOperation.Type.SET;
import static org.keycloak.client.admin.cli.util.IoUtil.copyStream;
import static org.keycloak.client.admin.cli.util.IoUtil.printErr;
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
import static org.keycloak.client.admin.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.admin.cli.util.OutputUtil.MAPPER;
import static org.keycloak.client.admin.cli.util.ParseUtil.mergeAttributes;
import static org.keycloak.client.admin.cli.util.ParseUtil.parseFileOrStdin;
import static org.keycloak.client.admin.cli.util.ParseUtil.parseKeyVal;
import static org.keycloak.client.cli.common.AttributeOperation.Type.SET;
import static org.keycloak.client.cli.util.IoUtil.copyStream;
import static org.keycloak.client.cli.util.IoUtil.printErr;
import static org.keycloak.client.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.cli.util.OutputUtil.MAPPER;
import static org.keycloak.client.cli.util.ParseUtil.parseKeyVal;
import static org.keycloak.client.admin.cli.KcAdmMain.CMD;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
@Command(name = "new-object", description = "Command to create new JSON objects locally")
public class NewObjectCmd extends AbstractGlobalOptionsCmd {
public class NewObjectCmd extends BaseGlobalOptionsCmd implements GlobalOptionsCmdHelper {
@Option(names = {"-f", "--file"}, description = "Read object from file or standard input if FILENAME is set to '-'")
String file;
@ -73,11 +72,11 @@ public class NewObjectCmd extends AbstractGlobalOptionsCmd {
CmdStdinContext<JsonNode> ctx = new CmdStdinContext<>();
if (file != null) {
ctx = parseFileOrStdin(file);
ctx = CmdStdinContext.parseFileOrStdin(file);
}
if (attrs.size() > 0) {
ctx = mergeAttributes(ctx, MAPPER.createObjectNode(), attrs);
ctx = CmdStdinContext.mergeAttributes(ctx, MAPPER.createObjectNode(), attrs);
}
if (body == null && ctx.getContent() != null) {

View file

@ -16,12 +16,13 @@
*/
package org.keycloak.client.admin.cli.commands;
import org.keycloak.client.admin.cli.config.ConfigData;
import org.keycloak.client.admin.cli.KcAdmMain;
import org.keycloak.client.admin.cli.operations.ClientOperations;
import org.keycloak.client.admin.cli.operations.GroupOperations;
import org.keycloak.client.admin.cli.operations.LocalSearch;
import org.keycloak.client.admin.cli.operations.RoleOperations;
import org.keycloak.client.admin.cli.operations.UserOperations;
import org.keycloak.client.cli.config.ConfigData;
import java.io.PrintWriter;
import java.io.StringWriter;
@ -35,12 +36,10 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import static org.keycloak.client.admin.cli.util.AuthUtil.ensureToken;
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.admin.cli.util.ConfigUtil.credentialsAvailable;
import static org.keycloak.client.admin.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.admin.cli.KcAdmMain.CMD;
import static org.keycloak.client.cli.util.ConfigUtil.credentialsAvailable;
import static org.keycloak.client.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.cli.util.OsUtil.PROMPT;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -297,7 +296,7 @@ public class RemoveRolesCmd extends AbstractAuthOptionsCmd {
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --config Path to the config file (" + KcAdmMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set.");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");

View file

@ -16,7 +16,8 @@
*/
package org.keycloak.client.admin.cli.commands;
import org.keycloak.client.admin.cli.config.ConfigData;
import org.keycloak.client.admin.cli.KcAdmMain;
import org.keycloak.client.cli.config.ConfigData;
import java.io.PrintWriter;
import java.io.StringWriter;
@ -26,13 +27,11 @@ import picocli.CommandLine.Option;
import static org.keycloak.client.admin.cli.operations.UserOperations.getIdFromUsername;
import static org.keycloak.client.admin.cli.operations.UserOperations.resetUserPassword;
import static org.keycloak.client.admin.cli.util.AuthUtil.ensureToken;
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.admin.cli.util.ConfigUtil.credentialsAvailable;
import static org.keycloak.client.admin.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.admin.cli.util.IoUtil.readSecret;
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.cli.util.ConfigUtil.credentialsAvailable;
import static org.keycloak.client.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.cli.util.IoUtil.readSecret;
import static org.keycloak.client.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.admin.cli.KcAdmMain.CMD;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -117,7 +116,7 @@ public class SetPasswordCmd extends AbstractAuthOptionsCmd {
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --config Path to the config file (" + KcAdmMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set.");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");

View file

@ -17,16 +17,17 @@
package org.keycloak.client.admin.cli.commands;
import org.keycloak.client.admin.cli.KcAdmMain;
import java.io.PrintWriter;
import java.io.StringWriter;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
import static org.keycloak.client.admin.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.admin.cli.KcAdmMain.CMD;
import static org.keycloak.client.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.cli.util.OsUtil.PROMPT;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -97,7 +98,7 @@ public class UpdateCmd extends AbstractRequestCmd {
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --config Path to the config file (" + KcAdmMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set.");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");

View file

@ -1,184 +0,0 @@
/*
* Copyright 2016 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.client.admin.cli.config;
import org.keycloak.util.JsonSerialization;
import java.io.IOException;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class RealmConfigData {
private String serverUrl;
private String realm;
private String clientId;
private String token;
private String refreshToken;
private String signingToken;
private String secret;
private String grantTypeForAuthentication;
private Long expiresAt;
private Long refreshExpiresAt;
private Long sigExpiresAt;
public String serverUrl() {
return serverUrl;
}
public void serverUrl(String serverUrl) {
this.serverUrl = serverUrl;
}
public String realm() {
return realm;
}
public void realm(String realm) {
this.realm = realm;
}
public String getClientId() {
return clientId;
}
public void setClientId(String clientId) {
this.clientId = clientId;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getRefreshToken() {
return refreshToken;
}
public void setRefreshToken(String refreshToken) {
this.refreshToken = refreshToken;
}
public String getSigningToken() {
return signingToken;
}
public void setSigningToken(String signingToken) {
this.signingToken = signingToken;
}
public String getSecret() {
return secret;
}
public void setSecret(String secret) {
this.secret = secret;
}
public String getGrantTypeForAuthentication() {
return grantTypeForAuthentication;
}
public void setGrantTypeForAuthentication(String grantTypeForAuthentication) {
this.grantTypeForAuthentication = grantTypeForAuthentication;
}
public Long getExpiresAt() {
return expiresAt;
}
public void setExpiresAt(Long expiresAt) {
this.expiresAt = expiresAt;
}
public Long getRefreshExpiresAt() {
return refreshExpiresAt;
}
public void setRefreshExpiresAt(Long refreshExpiresAt) {
this.refreshExpiresAt = refreshExpiresAt;
}
public Long getSigExpiresAt() {
return sigExpiresAt;
}
public void setSigExpiresAt(Long sigExpiresAt) {
this.sigExpiresAt = sigExpiresAt;
}
public void merge(RealmConfigData source) {
serverUrl = source.serverUrl;
realm = source.realm;
clientId = source.clientId;
token = source.token;
refreshToken = source.refreshToken;
signingToken = source.signingToken;
secret = source.secret;
grantTypeForAuthentication = source.grantTypeForAuthentication;
expiresAt = source.expiresAt;
refreshExpiresAt = source.refreshExpiresAt;
sigExpiresAt = source.sigExpiresAt;
}
public void mergeRefreshTokens(RealmConfigData source) {
token = source.token;
refreshToken = source.refreshToken;
expiresAt = source.expiresAt;
refreshExpiresAt = source.refreshExpiresAt;
}
@Override
public String toString() {
try {
return JsonSerialization.writeValueAsPrettyString(this);
} catch (IOException e) {
return super.toString() + " - Error: " + e.toString();
}
}
public RealmConfigData deepcopy() {
RealmConfigData data = new RealmConfigData();
data.serverUrl = serverUrl;
data.realm = realm;
data.clientId = clientId;
data.token = token;
data.refreshToken = refreshToken;
data.signingToken = signingToken;
data.secret = secret;
data.grantTypeForAuthentication = grantTypeForAuthentication;
data.expiresAt = expiresAt;
data.refreshExpiresAt = refreshExpiresAt;
data.sigExpiresAt = sigExpiresAt;
return data;
}
}

View file

@ -16,14 +16,12 @@
*/
package org.keycloak.client.admin.cli.operations;
import static org.keycloak.client.admin.cli.util.HttpUtil.getIdForType;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class ClientOperations {
public static String getIdFromClientId(String rootUrl, String realm, String auth, String clientId) {
return getIdForType(rootUrl, realm, auth, "clients", "clientId", clientId, "clientId");
return OperationUtils.getIdForType(rootUrl, realm, auth, "clients", "clientId", clientId, "clientId");
}
}

View file

@ -18,10 +18,9 @@ package org.keycloak.client.admin.cli.operations;
import java.util.List;
import static org.keycloak.client.admin.cli.util.HttpUtil.composeResourceUrl;
import static org.keycloak.client.admin.cli.util.HttpUtil.doDeleteJSON;
import static org.keycloak.client.admin.cli.util.HttpUtil.doPostJSON;
import static org.keycloak.client.admin.cli.util.HttpUtil.getIdForType;
import static org.keycloak.client.cli.util.HttpUtil.composeResourceUrl;
import static org.keycloak.client.cli.util.HttpUtil.doDeleteJSON;
import static org.keycloak.client.cli.util.HttpUtil.doPostJSON;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -29,11 +28,11 @@ import static org.keycloak.client.admin.cli.util.HttpUtil.getIdForType;
public class GroupOperations {
public static String getIdFromName(String rootUrl, String realm, String auth, String groupname) {
return getIdForType(rootUrl, realm, auth, "groups", "search", groupname, "name", () -> new String[] { "exact", "true" });
return OperationUtils.getIdForType(rootUrl, realm, auth, "groups", "search", groupname, "name", () -> new String[] { "exact", "true" });
}
public static String getIdFromPath(String rootUrl, String realm, String auth, String path) {
return getIdForType(rootUrl, realm, auth, "groups", "path", path, "path");
return OperationUtils.getIdForType(rootUrl, realm, auth, "groups", "path", path, "path");
}
public static void addRealmRoles(String rootUrl, String realm, String auth, String groupid, List<?> roles) {

View file

@ -0,0 +1,84 @@
/*
* Copyright 2024 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.client.admin.cli.operations;
import org.keycloak.client.cli.util.HttpUtil;
import java.util.List;
import java.util.function.Supplier;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import static org.keycloak.common.util.ObjectUtil.capitalize;
public class OperationUtils {
private static final String[] DEFAULT_QUERY_PARAMS = { "first", "0", "max", "2" };
public static String getIdForType(String rootUrl, String realm, String auth, String resourceEndpoint, String attrName, String attrValue, String inputAttrName) {
return getAttrForType(rootUrl, realm, auth, resourceEndpoint, attrName, attrValue, inputAttrName, "id", null);
}
public static String getIdForType(String rootUrl, String realm, String auth, String resourceEndpoint, String attrName, String attrValue, String inputAttrName, Supplier<String[]> endpointParams) {
return getAttrForType(rootUrl, realm, auth, resourceEndpoint, attrName, attrValue, inputAttrName, "id", endpointParams);
}
public static String getAttrForType(String rootUrl, String realm, String auth, String resourceEndpoint, String attrName, String attrValue, String inputAttrName, String returnAttrName) {
return getAttrForType(rootUrl, realm, auth, resourceEndpoint, attrName, attrValue, inputAttrName, returnAttrName, null);
}
public static String getAttrForType(String rootUrl, String realm, String auth, String resourceEndpoint, String attrName, String attrValue, String inputAttrName, String returnAttrName, Supplier<String[]> endpointParams) {
String resourceUrl = HttpUtil.composeResourceUrl(rootUrl, realm, resourceEndpoint);
String[] defaultParams;
if (endpointParams == null) {
defaultParams = DEFAULT_QUERY_PARAMS;
} else {
defaultParams = endpointParams.get();
}
resourceUrl = HttpUtil.addQueryParamsToUri(resourceUrl, attrName, attrValue);
resourceUrl = HttpUtil.addQueryParamsToUri(resourceUrl, defaultParams);
List<ObjectNode> results = HttpUtil.doGetJSON(RoleOperations.LIST_OF_NODES.class, resourceUrl, auth);
ObjectNode match;
try {
match = new LocalSearch(results).exactMatchOne(attrValue, inputAttrName);
} catch (Exception e) {
throw new RuntimeException("Multiple " + resourceEndpoint + " found for " + inputAttrName + ": " + attrValue, e);
}
String typeName = HttpUtil.singularize(resourceEndpoint);
if (match == null) {
if (results.size() > 1) {
throw new RuntimeException("Some matches, but not an exact match, found for " + capitalize(typeName) + " with " + inputAttrName + ": " + attrValue + ". Try using a more unique search, such as an id.");
}
throw new RuntimeException(capitalize(typeName) + " not found for " + inputAttrName + ": " + attrValue);
}
JsonNode attr = match.get(returnAttrName);
if (attr == null) {
throw new RuntimeException("Returned " + typeName + " info has no '" + returnAttrName + "' attribute");
}
return attr.asText();
}
}

View file

@ -17,18 +17,17 @@
package org.keycloak.client.admin.cli.operations;
import com.fasterxml.jackson.databind.node.ObjectNode;
import static org.keycloak.client.cli.util.HttpUtil.composeResourceUrl;
import static org.keycloak.client.cli.util.HttpUtil.doDeleteJSON;
import static org.keycloak.client.cli.util.HttpUtil.doGetJSON;
import static org.keycloak.client.cli.util.HttpUtil.doPostJSON;
import org.keycloak.representations.idm.RoleRepresentation;
import java.util.ArrayList;
import java.util.List;
import static org.keycloak.client.admin.cli.util.HttpUtil.composeResourceUrl;
import static org.keycloak.client.admin.cli.util.HttpUtil.doDeleteJSON;
import static org.keycloak.client.admin.cli.util.HttpUtil.doGetJSON;
import static org.keycloak.client.admin.cli.util.HttpUtil.doPostJSON;
import static org.keycloak.client.admin.cli.util.HttpUtil.getAttrForType;
import static org.keycloak.client.admin.cli.util.HttpUtil.getIdForType;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
@ -38,7 +37,7 @@ public class RoleOperations {
public static class LIST_OF_NODES extends ArrayList<ObjectNode>{};
public static String getIdFromRoleName(String adminRoot, String realm, String auth, String rname) {
return getIdForType(adminRoot, realm, auth, "roles", "search", rname, "name");
return OperationUtils.getIdForType(adminRoot, realm, auth, "roles", "search", rname, "name");
}
public static void addRealmRoles(String rootUrl, String realm, String auth, String roleid, List<?> roles) {
@ -60,11 +59,11 @@ public class RoleOperations {
}
public static String getRoleNameFromId(String adminRoot, String realm, String auth, String rid) {
return getAttrForType(adminRoot, realm, auth, "roles", "id", rid, "id","name");
return OperationUtils.getAttrForType(adminRoot, realm, auth, "roles", "id", rid, "id","name");
}
public static String getClientRoleNameFromId(String adminRoot, String realm, String auth, String cid, String rid) {
return getAttrForType(adminRoot, realm, auth, "clients/" + cid + "/roles", "id", rid, "id", "name");
return OperationUtils.getAttrForType(adminRoot, realm, auth, "clients/" + cid + "/roles", "id", rid, "id", "name");
}
public static List<RoleRepresentation> getRealmRoles(String rootUrl, String realm, String auth) {

View file

@ -16,10 +16,10 @@
*/
package org.keycloak.client.admin.cli.operations;
import org.keycloak.client.admin.cli.util.Headers;
import org.keycloak.client.admin.cli.util.HeadersBody;
import org.keycloak.client.admin.cli.util.HeadersBodyStatus;
import org.keycloak.client.admin.cli.util.HttpUtil;
import org.keycloak.client.cli.util.Headers;
import org.keycloak.client.cli.util.HeadersBody;
import org.keycloak.client.cli.util.HeadersBodyStatus;
import org.keycloak.client.cli.util.HttpUtil;
import org.keycloak.representations.idm.CredentialRepresentation;
import org.keycloak.util.JsonSerialization;
@ -27,10 +27,9 @@ import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.List;
import static org.keycloak.client.admin.cli.util.HttpUtil.composeResourceUrl;
import static org.keycloak.client.admin.cli.util.HttpUtil.doDeleteJSON;
import static org.keycloak.client.admin.cli.util.HttpUtil.doPostJSON;
import static org.keycloak.client.admin.cli.util.HttpUtil.getIdForType;
import static org.keycloak.client.cli.util.HttpUtil.composeResourceUrl;
import static org.keycloak.client.cli.util.HttpUtil.doDeleteJSON;
import static org.keycloak.client.cli.util.HttpUtil.doPostJSON;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -91,7 +90,7 @@ public class UserOperations {
}
public static String getIdFromUsername(String rootUrl, String realm, String auth, String username) {
return getIdForType(rootUrl, realm, auth, "users", "username", username, "username",
return OperationUtils.getIdForType(rootUrl, realm, auth, "users", "username", username, "username",
() -> new String[] {"exact", "true"});
}
}

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.common;
package org.keycloak.client.cli.common;
import java.util.Collections;
import java.util.LinkedList;

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.common;
package org.keycloak.client.cli.common;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>

View file

@ -0,0 +1,276 @@
/*
* Copyright 2016 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.client.cli.common;
import org.keycloak.OAuth2Constants;
import org.keycloak.client.cli.config.ConfigData;
import org.keycloak.client.cli.config.ConfigHandler;
import org.keycloak.client.cli.config.FileConfigHandler;
import org.keycloak.client.cli.config.InMemoryConfigHandler;
import org.keycloak.client.cli.config.RealmConfigData;
import org.keycloak.client.cli.util.AuthUtil;
import org.keycloak.client.cli.util.ConfigUtil;
import org.keycloak.client.cli.util.HttpUtil;
import org.keycloak.client.cli.util.IoUtil;
import java.io.File;
import picocli.CommandLine.Option;
import static org.keycloak.client.cli.config.FileConfigHandler.setConfigFile;
import static org.keycloak.client.cli.util.ConfigUtil.DEFAULT_CLIENT;
import static org.keycloak.client.cli.util.ConfigUtil.checkServerInfo;
import static org.keycloak.client.cli.util.ConfigUtil.loadConfig;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public abstract class BaseAuthOptionsCmd extends BaseGlobalOptionsCmd {
public static final String DEFAULT_CONFIG_PATH_STRING_KEY = "default.config.path.string";
@Option(names = "--config", description = "Path to the config file (${sys:"+DEFAULT_CONFIG_PATH_STRING_KEY+"} by default)")
protected String config;
@Option(names = "--no-config", description = "Don't use config file - no authentication info is loaded or saved")
protected boolean noconfig;
@Option(names = "--server", description = "Server endpoint url (e.g. 'http://localhost:8080')")
protected String server;
@Option(names = "--realm", description = "Realm name to authenticate against")
protected String realm;
@Option(names = "--client", description = "Realm name to authenticate against")
protected String clientId;
@Option(names = "--user", description = "Username to login with")
protected String user;
@Option(names = "--password", description = "Password to login with (prompted for if not specified and --user is used)")
protected String password;
@Option(names = "--secret", description = "Secret to authenticate the client (prompted for if no --user or --keystore is specified)")
protected String secret;
@Option(names = "--keystore", description = "Path to a keystore containing private key")
protected String keystore;
@Option(names = "--storepass", description = "Keystore password (prompted for if not specified and --keystore is used)")
protected String storePass;
@Option(names = "--keypass", description = "Key password (prompted for if not specified and --keystore is used without --storepass, \n otherwise defaults to keystore password)")
protected String keyPass;
@Option(names = "--alias", description = "Alias of the key inside a keystore (defaults to the value of ClientId)")
protected String alias;
@Option(names = "--truststore", description = "Path to a truststore")
protected String trustStore;
@Option(names = "--trustpass", description = "Truststore password (prompted for if not specified and --truststore is used)")
protected String trustPass;
@Option(names = "--insecure", description = "Turns off TLS validation")
protected boolean insecure;
// subclasses unfortunately use different options for this, so they must be declared elsewhere
protected String externalToken;
protected CommandState commandState;
public BaseAuthOptionsCmd(CommandState state) {
this.commandState = state;
}
protected String getCommand() {
return commandState.getCommand();
}
protected String getDefaultConfigFilePath() {
return commandState.getDefaultConfigFilePath();
}
protected void initFromParent(BaseAuthOptionsCmd parent) {
noconfig = parent.noconfig;
config = parent.config;
server = parent.server;
realm = parent.realm;
clientId = parent.clientId;
user = parent.user;
password = parent.password;
secret = parent.secret;
keystore = parent.keystore;
storePass = parent.storePass;
keyPass = parent.keyPass;
alias = parent.alias;
trustStore = parent.trustStore;
trustPass = parent.trustPass;
externalToken = parent.externalToken;
}
protected void applyDefaultOptionValues() {
if (clientId == null) {
clientId = DEFAULT_CLIENT;
}
}
@Override
protected boolean nothingToDo() {
return externalToken == null && server == null && realm == null && clientId == null && secret == null &&
user == null && password == null &&
keystore == null && storePass == null && keyPass == null && alias == null &&
trustStore == null && trustPass == null && config == null;
}
@Override
protected void processOptions() {
if (config != null && noconfig) {
throw new IllegalArgumentException("Options --config and --no-config are mutually exclusive");
}
if (!noconfig) {
setConfigFile(config != null ? config : getDefaultConfigFilePath());
ConfigUtil.setHandler(new FileConfigHandler());
} else {
InMemoryConfigHandler handler = new InMemoryConfigHandler();
ConfigData data = new ConfigData();
initConfigData(data);
handler.setConfigData(data);
ConfigUtil.setHandler(handler);
}
}
protected void setupTruststore(ConfigData configData) {
if (!configData.getServerUrl().startsWith("https:")) {
return;
}
String truststore = trustStore;
if (truststore == null) {
truststore = configData.getTruststore();
}
if (truststore != null) {
String pass = trustPass;
if (pass == null) {
pass = configData.getTrustpass();
}
if (pass == null) {
pass = IoUtil.readSecret("Enter truststore password: ");
}
try {
HttpUtil.setTruststore(new File(truststore), pass);
} catch (Exception e) {
throw new RuntimeException("Failed to load truststore: " + truststore, e);
}
}
if (insecure) {
HttpUtil.setSkipCertificateValidation();
}
}
protected ConfigData ensureAuthInfo(ConfigData config) {
if (requiresLogin()) {
// make sure current handler is in-memory handler
// restore it at the end
ConfigHandler old = ConfigUtil.getHandler();
try {
// make sure all defaults are initialized after this point
applyDefaultOptionValues();
initConfigData(config);
ConfigUtil.setupInMemoryHandler(config);
BaseConfigCredentialsCmd login = new BaseConfigCredentialsCmd(commandState);
login.initFromParent(this);
login.init(config);
login.process();
// this must be executed before finally block which restores config handler
return loadConfig();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
ConfigUtil.setHandler(old);
}
} else {
checkServerInfo(config, getCommand());
// make sure all defaults are initialized after this point
applyDefaultOptionValues();
return loadConfig();
}
}
protected boolean requiresLogin() {
return externalToken == null && (user != null || password != null || secret != null || keystore != null
|| keyPass != null || storePass != null || alias != null);
}
protected ConfigData copyWithServerInfo(ConfigData config) {
ConfigData result = config.deepcopy();
if (server != null) {
result.setServerUrl(server);
}
if (realm != null) {
result.setRealm(realm);
}
if (externalToken != null) {
result.setExternalToken(externalToken);
}
checkServerInfo(result, getCommand());
return result;
}
private void initConfigData(ConfigData data) {
if (server != null)
data.setServerUrl(server);
if (realm != null)
data.setRealm(realm);
if (trustStore != null)
data.setTruststore(trustStore);
if (externalToken != null) {
data.setExternalToken(externalToken);
}
RealmConfigData rdata = data.sessionRealmConfigData();
if (clientId != null)
rdata.setClientId(clientId);
if (secret != null)
rdata.setSecret(secret);
String grantTypeForAuthentication = user == null ? OAuth2Constants.CLIENT_CREDENTIALS : OAuth2Constants.PASSWORD;
rdata.setGrantTypeForAuthentication(grantTypeForAuthentication);
}
protected String ensureToken(ConfigData config) {
return AuthUtil.ensureToken(config, getCommand());
}
}

View file

@ -0,0 +1,248 @@
/*
* Copyright 2016 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.client.cli.common;
import org.keycloak.OAuth2Constants;
import org.keycloak.client.cli.config.ConfigData;
import org.keycloak.client.cli.config.RealmConfigData;
import org.keycloak.client.cli.util.AuthUtil;
import org.keycloak.representations.AccessTokenResponse;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import static org.keycloak.client.cli.util.AuthUtil.getAuthTokens;
import static org.keycloak.client.cli.util.AuthUtil.getAuthTokensByJWT;
import static org.keycloak.client.cli.util.AuthUtil.getAuthTokensBySecret;
import static org.keycloak.client.cli.util.ConfigUtil.getHandler;
import static org.keycloak.client.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.cli.util.ConfigUtil.saveTokens;
import static org.keycloak.client.cli.util.IoUtil.printErr;
import static org.keycloak.client.cli.util.IoUtil.readSecret;
import static org.keycloak.client.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.cli.util.OsUtil.PROMPT;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class BaseConfigCredentialsCmd extends BaseAuthOptionsCmd {
private int sigLifetime = 600;
public BaseConfigCredentialsCmd(CommandState commandState) {
super(commandState);
}
public void init(ConfigData configData) {
if (server == null) {
server = configData.getServerUrl();
}
if (realm == null) {
realm = configData.getRealm();
}
if (trustStore == null) {
trustStore = configData.getTruststore();
}
RealmConfigData rdata = configData.getRealmConfigData(server, realm);
if (rdata == null) {
return;
}
if (clientId == null) {
clientId = rdata.getClientId();
}
}
@Override
protected String[] getUnsupportedOptions() {
return new String[] {"--no-config", booleanOptionForCheck(noconfig)};
}
@Override
public void process() {
// check server
if (server == null) {
throw new IllegalArgumentException("Required option not specified: --server");
}
try {
new URL(server);
} catch (Exception e) {
throw new RuntimeException("Invalid server endpoint url: " + server, e);
}
if (realm == null)
throw new IllegalArgumentException("Required option not specified: --realm");
String signedRequestToken = null;
boolean clientSet = clientId != null;
applyDefaultOptionValues();
String grantTypeForAuthentication = null;
if (user != null) {
grantTypeForAuthentication = OAuth2Constants.PASSWORD;
printErr("Logging into " + server + " as user " + user + " of realm " + realm);
// if user was set there needs to be a password so we can authenticate
if (password == null) {
password = readSecret("Enter password: ");
}
// if secret was set to be read from stdin, then ask for it
if ("-".equals(secret) && keystore == null) {
secret = readSecret("Enter client secret: ");
}
} else if (keystore != null || secret != null || clientSet) {
grantTypeForAuthentication = OAuth2Constants.CLIENT_CREDENTIALS;
printErr("Logging into " + server + " as " + "service-account-" + clientId + " of realm " + realm);
if (keystore == null) {
if (secret == null) {
secret = readSecret("Enter client secret: ");
}
}
}
if (keystore != null) {
if (secret != null) {
throw new IllegalArgumentException("Can't use both --keystore and --secret");
}
if (!new File(keystore).isFile()) {
throw new RuntimeException("No such keystore file: " + keystore);
}
if (storePass == null) {
storePass = readSecret("Enter keystore password: ");
keyPass = readSecret("Enter key password: ");
}
if (keyPass == null) {
keyPass = storePass;
}
if (alias == null) {
alias = clientId;
}
String realmInfoUrl = server + "/realms/" + realm;
signedRequestToken = AuthUtil.getSignedRequestToken(keystore, storePass, keyPass,
alias, sigLifetime, clientId, realmInfoUrl);
}
// if only server and realm are set, just save config and be done
if (user == null && secret == null && keystore == null) {
getHandler().saveMergeConfig(config -> {
config.setServerUrl(server);
config.setRealm(realm);
});
return;
}
setupTruststore(copyWithServerInfo(loadConfig()));
// now use the token endpoint to retrieve access token, and refresh token
AccessTokenResponse tokens = signedRequestToken != null ?
getAuthTokensByJWT(server, realm, user, password, clientId, signedRequestToken) :
secret != null ?
getAuthTokensBySecret(server, realm, user, password, clientId, secret) :
getAuthTokens(server, realm, user, password, clientId);
Long sigExpiresAt = signedRequestToken == null ? null : System.currentTimeMillis() + sigLifetime * 1000;
// save tokens to config file
saveTokens(tokens, server, realm, clientId, signedRequestToken, sigExpiresAt, secret, grantTypeForAuthentication);
}
@Override
public String help() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
out.println("Usage: " + getCommand() + " config credentials --server SERVER_URL --realm REALM [ARGUMENTS]");
out.println(" " + getCommand() + " config credentials --server SERVER_URL --realm REALM --user USER [--password PASSWORD] [ARGUMENTS]");
out.println(" " + getCommand() + " config credentials --server SERVER_URL --realm REALM --client CLIENT_ID [--secret SECRET] [ARGUMENTS]");
out.println(" " + getCommand() + " config credentials --server SERVER_URL --realm REALM --client CLIENT_ID [--keystore KEYSTORE] [ARGUMENTS]");
out.println();
out.println("Command to establish an authenticated client session with the server. There are many authentication");
out.println("options available, and it depends on server side client authentication configuration how client can or should authenticate.");
out.println("The information always required includes --server, and --realm. Then, --user and / or --client need to be used to authenticate.");
out.println("If --client is not provided it defaults to 'admin-cli'. The authentication options / requirements depend on how this client is configured.");
out.println();
out.println("If confidential client authentication is also configured, you may have to specify a client id, and client credentials in addition to");
out.println("user credentials. Client credentials are either a client secret, or a keystore information to use Signed JWT mechanism.");
out.println("If only client credentials are provided, and no user credentials, then the service account is used for login.");
out.println();
out.println("Arguments:");
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to a config file (" + getDefaultConfigFilePath() + " by default)");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");
out.println();
out.println(" Command specific options:");
out.println(" --server SERVER_URL Server endpoint url (e.g. 'http://localhost:8080')");
out.println(" --realm REALM Realm name to use");
out.println(" --user USER Username to login with");
out.println(" --password PASSWORD Password to login with (prompted for if not specified and --user is used)");
out.println(" --client CLIENT_ID ClientId used by this client tool ('admin-cli' by default)");
out.println(" --secret SECRET Secret to authenticate the client (prompted for if --client is specified, and no --keystore is specified)");
out.println(" --keystore PATH Path to a keystore containing private key");
out.println(" --storepass PASSWORD Keystore password (prompted for if not specified and --keystore is used)");
out.println(" --keypass PASSWORD Key password (prompted for if not specified and --keystore is used without --storepass,");
out.println(" otherwise defaults to keystore password)");
out.println(" --alias ALIAS Alias of the key inside a keystore (defaults to the value of ClientId)");
out.println();
out.println();
out.println("Examples:");
out.println();
out.println("Login as 'admin' user of 'master' realm to a local Keycloak server running on default port.");
out.println("You will be prompted for a password:");
out.println(" " + PROMPT + " " + getCommand() + " config credentials --server http://localhost:8080 --realm master --user admin");
out.println();
out.println("Login to Keycloak server at non-default endpoint passing the password via standard input:");
if (OS_ARCH.isWindows()) {
out.println(" " + PROMPT + " echo mypassword | " + getCommand() + " config credentials --server http://localhost:9080 --realm master --user admin");
} else {
out.println(" " + PROMPT + " " + getCommand() + " config credentials --server http://localhost:9080 --realm master --user admin << EOF");
out.println(" mypassword");
out.println(" EOF");
}
out.println();
out.println("Login specifying a password through command line:");
out.println(" " + PROMPT + " " + getCommand() + " config credentials --server http://localhost:9080 --realm master --user admin --password " + OS_ARCH.envVar("PASSWORD"));
out.println();
out.println("Login using a client service account of a custom client. You will be prompted for a client secret:");
out.println(" " + PROMPT + " " + getCommand() + " config credentials --server http://localhost:9080 --realm master --client reg-cli");
out.println();
out.println("Login using a client service account of a custom client, authenticating with signed JWT.");
out.println("You will be prompted for a keystore password, and a key password:");
out.println(" " + PROMPT + " " + getCommand() + " config credentials --server http://localhost:9080 --realm master --client reg-cli --keystore " + OS_ARCH.path("~/.keycloak/keystore.jks"));
out.println();
out.println("Login as 'user' while also authenticating a custom client with signed JWT.");
out.println("You will be prompted for a user password, a keystore password, and a key password:");
out.println(" " + PROMPT + " " + getCommand() + " config credentials --server http://localhost:9080 --realm master --user user --client reg-cli --keystore " + OS_ARCH.path("~/.keycloak/keystore.jks"));
out.println();
out.println();
out.println("Use '" + getCommand() + " help' for general information and a list of commands");
return sb.toString();
}
}

View file

@ -0,0 +1,137 @@
/*
* Copyright 2016 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.client.cli.common;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import static org.keycloak.client.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.cli.util.IoUtil.readSecret;
import static org.keycloak.client.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.cli.util.OsUtil.PROMPT;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class BaseConfigTruststoreCmd extends BaseAuthOptionsCmd {
@Parameters(arity = "0..1")
private String store;
@Option(names = {"-d", "--delete"}, description = "Remove truststore configuration")
private boolean delete;
public BaseConfigTruststoreCmd(CommandState commandState) {
super(commandState);
}
@Override
protected boolean nothingToDo() {
return super.nothingToDo() && store == null && !delete;
}
@Override
protected String[] getUnsupportedOptions() {
return new String[] {"--server", server,
"--realm", realm,
"--client", clientId,
"--user", user,
"--password", password,
"--secret", secret,
"--truststore", trustStore,
"--keystore", keystore,
"--keypass", keyPass,
"--alias", alias,
"--no-config", booleanOptionForCheck(noconfig)};
}
@Override
protected void process() {
String pass;
if (!delete) {
if (store == null) {
throw new IllegalArgumentException("No truststore specified");
}
if (!new File(store).isFile()) {
throw new RuntimeException("Truststore file not found: " + store);
}
if ("-".equals(trustPass)) {
trustPass = readSecret("Enter truststore password: ");
}
pass = trustPass;
} else {
if (store != null) {
throw new IllegalArgumentException("Option --delete is mutually exclusive with specifying a TRUSTSTORE");
}
if (trustPass != null) {
throw new IllegalArgumentException("Options --trustpass and --delete are mutually exclusive");
}
pass = null;
}
saveMergeConfig(config -> {
config.setTruststore(store);
config.setTrustpass(pass);
});
}
@Override
public String help() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
out.println("Usage: " + getCommand() + " config truststore [TRUSTSTORE | --delete] [--trustpass PASSWORD] [ARGUMENTS]");
out.println();
out.println("Command to configure a global truststore to use when using https to connect to Keycloak server.");
out.println();
out.println("Arguments:");
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + getDefaultConfigFilePath() + " by default)");
out.println();
out.println(" Command specific options:");
out.println(" TRUSTSTORE Path to truststore file");
out.println(" --trustpass PASSWORD Truststore password to unlock truststore (prompted for if set to '-')");
out.println(" -d, --delete Remove truststore configuration");
out.println();
out.println();
out.println("Examples:");
out.println();
out.println("Specify a truststore - you will be prompted for truststore password every time it is used:");
out.println(" " + PROMPT + " " + getCommand() + " config truststore " + OS_ARCH.path("~/.keycloak/truststore.jks"));
out.println();
out.println("Specify a truststore, and password - truststore will automatically be used without prompting for password:");
out.println(" " + PROMPT + " " + getCommand() + " config truststore --trustpass " + OS_ARCH.envVar("PASSWORD") + " " + OS_ARCH.path("~/.keycloak/truststore.jks"));
out.println();
out.println("Remove truststore configuration:");
out.println(" " + PROMPT + " " + getCommand() + " config truststore --delete");
out.println();
out.println();
out.println("Use '" + getCommand() + " help' for general information and a list of commands");
return sb.toString();
}
}

View file

@ -1,16 +1,27 @@
package org.keycloak.client.registration.cli.commands;
import org.keycloak.client.registration.cli.Globals;
/*
* Copyright 2016 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.client.cli.common;
import picocli.CommandLine;
import picocli.CommandLine.Option;
import static org.keycloak.client.registration.cli.util.IoUtil.printOut;
import static org.keycloak.client.cli.util.IoUtil.printOut;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public abstract class AbstractGlobalOptionsCmd implements Runnable {
public abstract class BaseGlobalOptionsCmd implements Runnable {
@Option(names = "--help",
description = "Print command specific help")
@ -38,9 +49,7 @@ public abstract class AbstractGlobalOptionsCmd implements Runnable {
return false;
}
protected String help() {
return KcRegCmd.usage();
}
protected abstract String help();
@Override
public void run() {

View file

@ -1,5 +1,5 @@
/*
* Copyright 2016 Red Hat, Inc. and/or its affiliates
* Copyright 2024 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");
@ -14,15 +14,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class Globals {
package org.keycloak.client.cli.common;
public static boolean dumpTrace = false;
public interface CommandState {
public static boolean help = false;
String getCommand();
String getDefaultConfigFilePath();
}

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.keycloak.client.admin.cli;
package org.keycloak.client.cli.common;
import picocli.CommandLine;
import picocli.CommandLine.ParseResult;

View file

@ -0,0 +1,65 @@
/*
* Copyright 2016 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.client.cli.common;
import org.keycloak.client.cli.util.ClassLoaderUtil;
import org.keycloak.common.crypto.CryptoIntegration;
import java.io.PrintWriter;
import picocli.CommandLine;
import picocli.CommandLine.Model.CommandSpec;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class Globals {
public static boolean dumpTrace = false;
public static boolean help = false;
public static void main(String [] args, BaseGlobalOptionsCmd rootCommand, String command, String defaultConfigFile) {
String libDir = System.getProperty("kc.lib.dir");
if (libDir == null) {
throw new RuntimeException("System property kc.lib.dir needs to be set");
}
ClassLoader cl = ClassLoaderUtil.resolveClassLoader(libDir);
Thread.currentThread().setContextClassLoader(cl);
CryptoIntegration.init(cl);
System.setProperty(BaseAuthOptionsCmd.DEFAULT_CONFIG_PATH_STRING_KEY, defaultConfigFile);
CommandLine cli = createCommandLine(rootCommand, command);
int exitCode = cli.execute(args);
System.exit(exitCode);
}
public static CommandLine createCommandLine(BaseGlobalOptionsCmd rootCommand, String command) {
CommandSpec spec = CommandSpec.forAnnotatedObject(rootCommand).name(command);
CommandLine cmd = new CommandLine(spec);
cmd.setExecutionExceptionHandler(new ExecutionExceptionHandler());
cmd.setParameterExceptionHandler(new ShortErrorMessageHandler());
cmd.setErr(new PrintWriter(System.err, true));
return cmd;
}
}

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.keycloak.client.admin.cli;
package org.keycloak.client.cli.common;
import java.io.PrintWriter;

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.config;
package org.keycloak.client.cli.config;
import com.fasterxml.jackson.annotation.JsonIgnore;
import org.keycloak.util.JsonSerialization;

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.config;
package org.keycloak.client.cli.config;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.config;
package org.keycloak.client.cli.config;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>

View file

@ -14,9 +14,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.config;
package org.keycloak.client.cli.config;
import org.keycloak.client.admin.cli.util.IoUtil;
import org.keycloak.client.cli.util.IoUtil;
import org.keycloak.util.JsonSerialization;
import java.io.ByteArrayInputStream;
@ -30,7 +30,7 @@ import java.nio.channels.OverlappingFileLockException;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.keycloak.client.admin.cli.util.IoUtil.printErr;
import static org.keycloak.client.cli.util.IoUtil.printErr;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.config;
package org.keycloak.client.cli.config;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>

View file

@ -14,8 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.registration.cli.config;
package org.keycloak.client.cli.config;
import org.keycloak.util.JsonSerialization;
@ -23,6 +22,8 @@ import java.io.IOException;
import java.util.LinkedHashMap;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonInclude;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
@ -50,11 +51,12 @@ public class RealmConfigData {
private Long sigExpiresAt;
@JsonInclude(JsonInclude.Include.NON_NULL)
private String initialToken;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private Map<String, String> clients = new LinkedHashMap<String, String>();
public String serverUrl() {
return serverUrl;
}
@ -111,6 +113,14 @@ public class RealmConfigData {
this.secret = secret;
}
public String getGrantTypeForAuthentication() {
return grantTypeForAuthentication;
}
public void setGrantTypeForAuthentication(String grantTypeForAuthentication) {
this.grantTypeForAuthentication = grantTypeForAuthentication;
}
public Long getExpiresAt() {
return expiresAt;
}
@ -127,14 +137,6 @@ public class RealmConfigData {
this.refreshExpiresAt = refreshExpiresAt;
}
public String getGrantTypeForAuthentication() {
return grantTypeForAuthentication;
}
public void setGrantTypeForAuthentication(String grantTypeForAuthentication) {
this.grantTypeForAuthentication = grantTypeForAuthentication;
}
public Long getSigExpiresAt() {
return sigExpiresAt;
}
@ -194,13 +196,6 @@ public class RealmConfigData {
refreshToken = source.refreshToken;
expiresAt = source.expiresAt;
refreshExpiresAt = source.refreshExpiresAt;
mergeClients(source);
}
public void mergeRegistrationTokens(RealmConfigData source) {
initialToken = source.initialToken;
mergeClients(source);
}
@Override

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.cli.util;
import java.io.FilterOutputStream;
import java.io.IOException;

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.cli.util;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>

View file

@ -14,10 +14,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.cli.util;
import org.keycloak.client.admin.cli.config.ConfigData;
import org.keycloak.client.admin.cli.config.RealmConfigData;
import org.keycloak.client.cli.config.ConfigData;
import org.keycloak.client.cli.config.RealmConfigData;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.common.util.Time;
import org.keycloak.jose.jws.JWSBuilder;
@ -33,24 +33,24 @@ import java.security.KeyPair;
import java.util.UUID;
import static java.lang.System.currentTimeMillis;
import static org.keycloak.client.admin.cli.util.ConfigUtil.checkAuthInfo;
import static org.keycloak.client.admin.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.admin.cli.util.HttpUtil.APPLICATION_FORM_URL_ENCODED;
import static org.keycloak.client.admin.cli.util.HttpUtil.APPLICATION_JSON;
import static org.keycloak.client.admin.cli.util.HttpUtil.doPost;
import static org.keycloak.client.admin.cli.util.HttpUtil.urlencode;
import static org.keycloak.client.cli.util.ConfigUtil.checkServerInfo;
import static org.keycloak.client.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.cli.util.HttpUtil.APPLICATION_FORM_URL_ENCODED;
import static org.keycloak.client.cli.util.HttpUtil.APPLICATION_JSON;
import static org.keycloak.client.cli.util.HttpUtil.doPost;
import static org.keycloak.client.cli.util.HttpUtil.urlencode;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class AuthUtil {
public static String ensureToken(ConfigData config) {
public static String ensureToken(ConfigData config, String cmd) {
if (config.getExternalToken() != null) {
return config.getExternalToken();
}
checkAuthInfo(config);
checkServerInfo(config, cmd);
RealmConfigData realmConfig = config.sessionRealmConfigData();
@ -63,11 +63,11 @@ public class AuthUtil {
// check refresh_token against expiry time
// if it's less than 5s to expiry, fail with credentials expired
if (realmConfig.getRefreshExpiresAt() != null && realmConfig.getRefreshExpiresAt() - now < 5000) {
throw new RuntimeException("Session has expired. Login again with '" + OsUtil.CMD + " config credentials'");
throw new RuntimeException("Session has expired. Login again with '" + cmd + " config credentials'");
}
if (realmConfig.getSigExpiresAt() != null && realmConfig.getSigExpiresAt() - now < 5000) {
throw new RuntimeException("Session has expired. Login again with '" + OsUtil.CMD + " config credentials'");
throw new RuntimeException("Session has expired. Login again with '" + cmd + " config credentials'");
}
try {

View file

@ -17,7 +17,7 @@
*
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.cli.util;
import java.io.File;
import java.net.MalformedURLException;

View file

@ -14,14 +14,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.cli.util;
import org.keycloak.OAuth2Constants;
import org.keycloak.client.admin.cli.config.ConfigData;
import org.keycloak.client.admin.cli.config.ConfigHandler;
import org.keycloak.client.admin.cli.config.ConfigUpdateOperation;
import org.keycloak.client.admin.cli.config.InMemoryConfigHandler;
import org.keycloak.client.admin.cli.config.RealmConfigData;
import org.keycloak.client.cli.config.ConfigData;
import org.keycloak.client.cli.config.ConfigHandler;
import org.keycloak.client.cli.config.ConfigUpdateOperation;
import org.keycloak.client.cli.config.InMemoryConfigHandler;
import org.keycloak.client.cli.config.RealmConfigData;
import org.keycloak.representations.AccessTokenResponse;
/**
@ -31,10 +31,6 @@ public class ConfigUtil {
public static final String DEFAULT_CLIENT = "admin-cli";
public static final String DEFAULT_CONFIG_FILE_STRING = OsUtil.OS_ARCH.isWindows() ? "%HOMEDRIVE%%HOMEPATH%\\.keycloak\\kcadm.config" : "~/.keycloak/kcadm.config";
public static final String DEFAULT_CONFIG_FILE_PATH = System.getProperty("user.home") + "/.keycloak/kcadm.config";
private static ConfigHandler handler;
public static ConfigHandler getHandler() {
@ -45,6 +41,15 @@ public class ConfigUtil {
ConfigUtil.handler = handler;
}
public static String getRegistrationToken(RealmConfigData data, String clientId) {
String token = data.getClients().get(clientId);
return token == null || token.length() == 0 ? null : token;
}
public static void setRegistrationToken(RealmConfigData data, String clientId, String token) {
data.getClients().put(clientId, token == null ? "" : token);
}
public static void saveTokens(AccessTokenResponse tokens, String endpoint, String realm, String clientId, String signKey, Long sigExpiresAt, String secret,
String grantTypeForAuthentication) {
handler.saveMergeConfig(config -> {
@ -67,19 +72,15 @@ public class ConfigUtil {
});
}
public static void checkServerInfo(ConfigData config) {
public static void checkServerInfo(ConfigData config, String cmd) {
if (config.getServerUrl() == null) {
throw new RuntimeException("No server specified. Use --server, or '" + OsUtil.CMD + " config credentials'.");
throw new RuntimeException("No server specified. Use --server, or '" + cmd + " config credentials'.");
}
if (config.getRealm() == null && config.getExternalToken() == null) {
throw new RuntimeException("No realm or token specified. Use --realm, --token, or '" + OsUtil.CMD + " config credentials'.");
throw new RuntimeException("No realm or token specified. Use --realm, --token, or '" + cmd + " config credentials'.");
}
}
public static void checkAuthInfo(ConfigData config) {
checkServerInfo(config);
}
public static boolean credentialsAvailable(ConfigData config) {
// Just supporting "client_credentials" grant type for the case when refresh token is missing
boolean credsAvailable = config.getServerUrl() != null && (config.getExternalToken() != null || (config.getRealm() != null

View file

@ -14,18 +14,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.cli.util;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import static org.keycloak.client.cli.util.OutputUtil.MAPPER;
import static org.keycloak.client.cli.util.OutputUtil.convertToJsonNode;
import java.io.IOException;
import java.util.Iterator;
import static org.keycloak.client.admin.cli.util.OutputUtil.MAPPER;
import static org.keycloak.client.admin.cli.util.OutputUtil.convertToJsonNode;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.cli.util;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.cli.util;
import org.apache.http.entity.ContentType;

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.cli.util;
import org.apache.http.entity.ContentType;
@ -22,7 +22,7 @@ import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.charset.Charset;
import static org.keycloak.client.admin.cli.util.IoUtil.copyStream;
import static org.keycloak.client.cli.util.IoUtil.copyStream;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.cli.util;
import org.keycloak.util.JsonSerialization;

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.httpcomponents;
package org.keycloak.client.cli.util;
import org.apache.http.annotation.Contract;
import org.apache.http.annotation.ThreadingBehavior;

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.cli.util;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>

View file

@ -14,10 +14,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.cli.util;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.apache.http.HeaderIterator;
import org.apache.http.HttpHeaders;
import org.apache.http.HttpResponse;
@ -36,9 +34,6 @@ import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.keycloak.client.admin.cli.httpcomponents.HttpDelete;
import org.keycloak.client.admin.cli.operations.LocalSearch;
import org.keycloak.client.admin.cli.operations.RoleOperations;
import org.keycloak.util.JsonSerialization;
import javax.net.ssl.SSLContext;
@ -53,12 +48,8 @@ import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Supplier;
import static org.keycloak.common.util.ObjectUtil.capitalize;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -69,7 +60,6 @@ public class HttpUtil {
public static final String APPLICATION_JSON = "application/json";
public static final String APPLICATION_FORM_URL_ENCODED = "application/x-www-form-urlencoded";
public static final String UTF_8 = "utf-8";
private static final String[] DEFAULT_QUERY_PARAMS = { "first", "0", "max", "2" };
private static HttpClient httpClient;
private static SSLConnectionSocketFactory sslsf;
@ -436,57 +426,6 @@ public class HttpUtil {
checkSuccess(resourceUrl, response);
}
public static String getIdForType(String rootUrl, String realm, String auth, String resourceEndpoint, String attrName, String attrValue, String inputAttrName) {
return getAttrForType(rootUrl, realm, auth, resourceEndpoint, attrName, attrValue, inputAttrName, "id", null);
}
public static String getIdForType(String rootUrl, String realm, String auth, String resourceEndpoint, String attrName, String attrValue, String inputAttrName, Supplier<String[]> endpointParams) {
return getAttrForType(rootUrl, realm, auth, resourceEndpoint, attrName, attrValue, inputAttrName, "id", endpointParams);
}
public static String getAttrForType(String rootUrl, String realm, String auth, String resourceEndpoint, String attrName, String attrValue, String inputAttrName, String returnAttrName) {
return getAttrForType(rootUrl, realm, auth, resourceEndpoint, attrName, attrValue, inputAttrName, returnAttrName, null);
}
public static String getAttrForType(String rootUrl, String realm, String auth, String resourceEndpoint, String attrName, String attrValue, String inputAttrName, String returnAttrName, Supplier<String[]> endpointParams) {
String resourceUrl = composeResourceUrl(rootUrl, realm, resourceEndpoint);
String[] defaultParams;
if (endpointParams == null) {
defaultParams = DEFAULT_QUERY_PARAMS;
} else {
defaultParams = endpointParams.get();
}
resourceUrl = HttpUtil.addQueryParamsToUri(resourceUrl, attrName, attrValue);
resourceUrl = HttpUtil.addQueryParamsToUri(resourceUrl, defaultParams);
List<ObjectNode> results = doGetJSON(RoleOperations.LIST_OF_NODES.class, resourceUrl, auth);
ObjectNode match;
try {
match = new LocalSearch(results).exactMatchOne(attrValue, inputAttrName);
} catch (Exception e) {
throw new RuntimeException("Multiple " + resourceEndpoint + " found for " + inputAttrName + ": " + attrValue, e);
}
String typeName = singularize(resourceEndpoint);
if (match == null) {
if (results.size() > 1) {
throw new RuntimeException("Some matches, but not an exact match, found for " + capitalize(typeName) + " with " + inputAttrName + ": " + attrValue + ". Try using a more unique search, such as an id.");
}
throw new RuntimeException(capitalize(typeName) + " not found for " + inputAttrName + ": " + attrValue);
}
JsonNode attr = match.get(returnAttrName);
if (attr == null) {
throw new RuntimeException("Returned " + typeName + " info has no '" + returnAttrName + "' attribute");
}
return attr.asText();
}
public static String singularize(String value) {
return value.substring(0, value.length()-1);
}

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.cli.util;
import java.io.Console;
import java.io.FileInputStream;

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.cli.util;
/**
* @author <a href="mailto:marko.strukelj@gmail.com">Marko Strukelj</a>

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.cli.util;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -22,8 +22,6 @@ package org.keycloak.client.admin.cli.util;
public class OsUtil {
public static final OsArch OS_ARCH = determineOSAndArch();
// TODO: move CMD out of this class
public static final String CMD = OS_ARCH.isWindows() ? "kcadm.bat" : "kcadm.sh";
public static final String PROMPT = OS_ARCH.isWindows() ? "c:\\>" : "$";

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.cli.util;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.cli.util;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;

View file

@ -14,31 +14,24 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.common;
package org.keycloak.client.cli.util;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class CmdStdinContext<T> {
public class ParseUtil {
private T result;
private String content;
public static String[] parseKeyVal(String keyval) {
// we expect = as a separator
int pos = keyval.indexOf("=");
if (pos <= 0) {
throw new IllegalArgumentException("Invalid key=value parameter: [" + keyval + "]");
}
public CmdStdinContext() {}
String [] parsed = new String[2];
parsed[0] = keyval.substring(0, pos);
parsed[1] = keyval.substring(pos+1);
public T getResult() {
return result;
}
public void setResult(T result) {
this.result = result;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
return parsed;
}
}

View file

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.cli.util;
import java.util.Collections;
import java.util.HashMap;

View file

@ -15,7 +15,7 @@
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.admin.cli;
import org.junit.Test;

View file

@ -14,22 +14,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.cli.util;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.Test;
import org.keycloak.client.admin.cli.common.AttributeOperation;
import org.keycloak.client.admin.cli.common.CmdStdinContext;
import org.keycloak.client.admin.cli.CmdStdinContext;
import org.keycloak.client.admin.cli.ReflectionUtil;
import org.keycloak.client.cli.common.AttributeOperation;
import java.nio.charset.StandardCharsets;
import java.util.LinkedList;
import java.util.List;
import static org.keycloak.client.admin.cli.common.AttributeOperation.Type.DELETE;
import static org.keycloak.client.admin.cli.common.AttributeOperation.Type.SET;
import static org.keycloak.client.admin.cli.util.OutputUtil.MAPPER;
import static org.keycloak.client.admin.cli.util.ParseUtil.mergeAttributes;
import static org.keycloak.client.cli.common.AttributeOperation.Type.DELETE;
import static org.keycloak.client.cli.common.AttributeOperation.Type.SET;
import static org.keycloak.client.cli.util.OutputUtil.MAPPER;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -71,7 +71,7 @@ public class MergeAttributesTest {
CmdStdinContext<JsonNode> ctx = new CmdStdinContext<>();
ctx.setResult(localNode);
ctx = mergeAttributes(ctx, MAPPER.createObjectNode(), attrs);
ctx = CmdStdinContext.mergeAttributes(ctx, MAPPER.createObjectNode(), attrs);
System.out.println(ctx);
String remoteJSON = "{\n" +

View file

@ -15,9 +15,11 @@
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.cli.util;
import org.junit.Test;
import org.keycloak.client.cli.util.OutputUtil;
import org.keycloak.client.cli.util.ReturnFields;
import java.io.IOException;
import java.util.ArrayList;

View file

@ -14,10 +14,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.keycloak.client.admin.cli.util;
package org.keycloak.client.cli.util;
import org.junit.Assert;
import org.junit.Test;
import org.keycloak.client.cli.util.ReturnFields;
/**
* @author <a href="mailto:marko.strukelj@gmail.com">Marko Strukelj</a>

View file

@ -15,13 +15,10 @@
* limitations under the License.
*/
package org.keycloak.client.registration.cli.util;
package org.keycloak.client.registration.cli;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
import org.keycloak.client.registration.cli.common.AttributeOperation;
import org.keycloak.client.registration.cli.common.CmdStdinContext;
import org.keycloak.client.registration.cli.common.EndpointType;
import org.keycloak.client.cli.common.AttributeOperation;
import org.keycloak.client.cli.util.AttributeException;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
import org.keycloak.util.JsonSerialization;
@ -29,38 +26,65 @@ import org.keycloak.util.JsonSerialization;
import java.io.IOException;
import java.util.List;
import static java.lang.System.arraycopy;
import static org.keycloak.client.registration.cli.util.IoUtil.readFileOrStdin;
import static org.keycloak.client.registration.cli.util.ReflectionUtil.setAttributes;
import com.fasterxml.jackson.core.JsonParseException;
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
import static org.keycloak.client.cli.util.IoUtil.readFileOrStdin;
import static org.keycloak.client.registration.cli.ReflectionUtil.setAttributes;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class ParseUtil {
public class CmdStdinContext {
private EndpointType regType;
private ClientRepresentation client;
private OIDCClientRepresentation oidcClient;
private String content;
public static final String CLIENT_OPTION_WARN = "You're using what looks like an OPTION as CLIENT: %s";
public static final String TOKEN_OPTION_WARN = "You're using what looks like an OPTION as TOKEN: %s";
public static String[] shift(String[] args) {
if (args.length == 1)
return new String[0];
String [] nu = new String [args.length-1];
arraycopy(args, 1, nu, 0, args.length-1);
return nu;
public CmdStdinContext() {}
public EndpointType getEndpointType() {
return regType;
}
public static String[] parseKeyVal(String keyval) {
// we expect = as a separator
int pos = keyval.indexOf("=");
if (pos <= 0) {
throw new RuntimeException("Invalid key=value parameter: [" + keyval + "]");
public void setEndpointType(EndpointType regType) {
this.regType = regType;
}
public ClientRepresentation getClient() {
return client;
}
public void setClient(ClientRepresentation client) {
this.client = client;
}
public OIDCClientRepresentation getOidcClient() {
return oidcClient;
}
public void setOidcClient(OIDCClientRepresentation oidcClient) {
this.oidcClient = oidcClient;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getRegistrationAccessToken() {
if (client != null) {
return client.getRegistrationAccessToken();
} else if (oidcClient != null) {
return oidcClient.getRegistrationAccessToken();
}
String [] parsed = new String[2];
parsed[0] = keyval.substring(0, pos);
parsed[1] = keyval.substring(pos+1);
return parsed;
return null;
}
public static CmdStdinContext parseFileOrStdin(String file, EndpointType type) {

View file

@ -15,7 +15,9 @@
* limitations under the License.
*/
package org.keycloak.client.registration.cli.common;
package org.keycloak.client.registration.cli;
import org.keycloak.client.cli.util.HttpUtil;
import java.util.Arrays;
import java.util.HashSet;
@ -60,4 +62,16 @@ public enum EndpointType {
public String getName() {
return preferredName;
}
public static String getExpectedContentType(EndpointType type) {
switch (type) {
case DEFAULT:
case OIDC:
return HttpUtil.APPLICATION_JSON;
case SAML2:
return HttpUtil.APPLICATION_XML;
default:
throw new RuntimeException("Unsupported endpoint type: " + type);
}
}
}

View file

@ -1,7 +1,5 @@
package org.keycloak.client.registration.cli;
import org.keycloak.client.registration.cli.common.EndpointType;
import picocli.CommandLine.ITypeConverter;
/**

View file

@ -1,12 +0,0 @@
package org.keycloak.client.registration.cli;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class Globals {
public static boolean dumpTrace = false;
public static boolean help = false;
}

View file

@ -1,46 +1,36 @@
package org.keycloak.client.registration.cli;
import org.keycloak.client.admin.cli.ExecutionExceptionHandler;
import org.keycloak.client.admin.cli.ShortErrorMessageHandler;
import org.keycloak.client.cli.common.CommandState;
import org.keycloak.client.cli.common.Globals;
import org.keycloak.client.cli.util.OsUtil;
import org.keycloak.client.registration.cli.commands.KcRegCmd;
import org.keycloak.client.registration.cli.util.ClassLoaderUtil;
import org.keycloak.client.registration.cli.util.OsUtil;
import org.keycloak.common.crypto.CryptoIntegration;
import java.io.PrintWriter;
import picocli.CommandLine;
import picocli.CommandLine.Model.CommandSpec;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class KcRegMain {
public static void main(String [] args) {
String libDir = System.getProperty("kc.lib.dir");
if (libDir == null) {
throw new RuntimeException("System property kc.lib.dir needs to be set");
public static final String DEFAULT_CONFIG_FILE_PATH = System.getProperty("user.home") + "/.keycloak/kcreg.config";
public static final String DEFAULT_CONFIG_FILE_STRING = OsUtil.OS_ARCH.isWindows() ? "%HOMEDRIVE%%HOMEPATH%\\.keycloak\\kcreg.config" : "~/.keycloak/kcreg.config";
public static final String CMD = OsUtil.OS_ARCH.isWindows() ? "kcreg.bat" : "kcreg.sh";
public static final CommandState COMMAND_STATE = new CommandState() {
@Override
public String getCommand() {
return CMD;
}
ClassLoader cl = ClassLoaderUtil.resolveClassLoader(libDir);
Thread.currentThread().setContextClassLoader(cl);
CryptoIntegration.init(cl);
@Override
public String getDefaultConfigFilePath() {
return DEFAULT_CONFIG_FILE_PATH;
}
CommandLine cli = createCommandLine();
int exitCode = cli.execute(args);
System.exit(exitCode);
}
};
public static CommandLine createCommandLine() {
CommandSpec spec = CommandSpec.forAnnotatedObject(new KcRegCmd()).name(OsUtil.CMD);
CommandLine cmd = new CommandLine(spec);
cmd.setExecutionExceptionHandler(new ExecutionExceptionHandler());
cmd.setParameterExceptionHandler(new ShortErrorMessageHandler());
cmd.setErr(new PrintWriter(System.err, true));
return cmd;
public static void main(String [] args) {
Globals.main(args, new KcRegCmd(), CMD, DEFAULT_CONFIG_FILE_STRING);
}
}

View file

@ -15,11 +15,13 @@
* limitations under the License.
*/
package org.keycloak.client.registration.cli.util;
package org.keycloak.client.registration.cli;
import com.fasterxml.jackson.core.JsonParseException;
import org.keycloak.client.registration.cli.common.AttributeKey;
import org.keycloak.client.registration.cli.common.AttributeOperation;
import org.keycloak.client.cli.common.AttributeKey;
import org.keycloak.client.cli.common.AttributeOperation;
import org.keycloak.client.cli.util.AttributeException;
import org.keycloak.util.JsonSerialization;
import java.io.IOException;

View file

@ -1,255 +1,22 @@
package org.keycloak.client.registration.cli.commands;
import org.keycloak.OAuth2Constants;
import org.keycloak.client.registration.cli.config.ConfigData;
import org.keycloak.client.registration.cli.config.ConfigHandler;
import org.keycloak.client.registration.cli.config.FileConfigHandler;
import org.keycloak.client.registration.cli.config.InMemoryConfigHandler;
import org.keycloak.client.registration.cli.config.RealmConfigData;
import org.keycloak.client.registration.cli.util.ConfigUtil;
import org.keycloak.client.registration.cli.util.HttpUtil;
import org.keycloak.client.registration.cli.util.IoUtil;
import java.io.File;
import org.keycloak.client.cli.common.BaseAuthOptionsCmd;
import org.keycloak.client.registration.cli.KcRegMain;
import picocli.CommandLine.Option;
import static org.keycloak.client.registration.cli.config.FileConfigHandler.setConfigFile;
import static org.keycloak.client.registration.cli.util.ConfigUtil.checkAuthInfo;
import static org.keycloak.client.registration.cli.util.ConfigUtil.checkServerInfo;
import static org.keycloak.client.registration.cli.util.ConfigUtil.loadConfig;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
static final String DEFAULT_CLIENT = "admin-cli";
@Option(names = "--config", description = "Path to the config file (~/.keycloak/kcreg.config by default)")
protected String config;
@Option(names = "--no-config", description = "No configuration file should be used, no authentication info is loaded or saved")
protected boolean noconfig;
@Option(names = "--server", description = "Server endpoint url (e.g. 'http://localhost:8080')")
protected String server;
@Option(names = "--realm", description = "Realm name to authenticate against")
protected String realm;
@Option(names = "--client", description = "Realm name to authenticate against")
protected String clientId;
@Option(names = "--user", description = "Username to login with")
protected String user;
@Option(names = "--password", description = "Password to login with (prompted for if not specified and --user is used)")
protected String password;
@Option(names = "--secret", description = "Secret to authenticate the client (prompted for if no --user or --keystore is specified)")
protected String secret;
@Option(names = "--keystore", description = "Path to a keystore containing private key")
protected String keystore;
@Option(names = "--storepass", description = "Keystore password (prompted for if not specified and --keystore is used)")
protected String storePass;
@Option(names = "--keypass", description = "Key password (prompted for if not specified and --keystore is used without --storepass, \n otherwise defaults to keystore password)")
protected String keyPass;
@Option(names = "--alias", description = "Alias of the key inside a keystore (defaults to the value of ClientId)")
protected String alias;
@Option(names = "--truststore", description = "Path to a truststore")
protected String trustStore;
@Option(names = "--trustpass", description = "Truststore password (prompted for if not specified and --truststore is used)")
protected String trustPass;
@Option(names = "--insecure", description = "Turns off TLS validation")
protected boolean insecure;
public abstract class AbstractAuthOptionsCmd extends BaseAuthOptionsCmd {
@Option(names = {"-t", "--token"}, description = "Initial / Registration access token to use)")
protected String token;
protected void initFromParent(AbstractAuthOptionsCmd parent) {
noconfig = parent.noconfig;
config = parent.config;
server = parent.server;
realm = parent.realm;
clientId = parent.clientId;
user = parent.user;
password = parent.password;
secret = parent.secret;
keystore = parent.keystore;
storePass = parent.storePass;
keyPass = parent.keyPass;
alias = parent.alias;
trustStore = parent.trustStore;
trustPass = parent.trustPass;
token = parent.token;
insecure = parent.insecure;
public void setToken(String token) {
this.externalToken = token;
}
protected void applyDefaultOptionValues() {
if (clientId == null) {
clientId = DEFAULT_CLIENT;
}
public AbstractAuthOptionsCmd() {
super(KcRegMain.COMMAND_STATE);
}
protected boolean noOptions() {
return server == null && realm == null && clientId == null && secret == null &&
user == null && password == null &&
keystore == null && storePass == null && keyPass == null && alias == null &&
trustStore == null && trustPass == null &&
token == null && config == null;
}
@Override
protected void processOptions() {
if (config != null && noconfig) {
throw new IllegalArgumentException("Options --config and --no-config are mutually exclusive");
}
if (!noconfig) {
setConfigFile(config != null ? config : ConfigUtil.DEFAULT_CONFIG_FILE_PATH);
ConfigUtil.setHandler(new FileConfigHandler());
} else {
InMemoryConfigHandler handler = new InMemoryConfigHandler();
ConfigData data = new ConfigData();
initConfigData(data);
handler.setConfigData(data);
ConfigUtil.setHandler(handler);
}
}
protected void setupTruststore(ConfigData configData) {
if (!configData.getServerUrl().startsWith("https:")) {
return;
}
String truststore = trustStore;
if (truststore == null) {
truststore = configData.getTruststore();
}
if (truststore != null) {
String pass = trustPass;
if (pass == null) {
pass = configData.getTrustpass();
}
if (pass == null) {
pass = IoUtil.readSecret("Enter truststore password: ");
}
try {
HttpUtil.setTruststore(new File(truststore), pass);
} catch (Exception e) {
throw new RuntimeException("Failed to load truststore: " + truststore, e);
}
}
if (insecure) {
HttpUtil.setSkipCertificateValidation();
}
}
protected ConfigData ensureAuthInfo(ConfigData config) {
if (requiresLogin()) {
// make sure current handler is in-memory handler
// restore it at the end
ConfigHandler old = ConfigUtil.getHandler();
try {
// make sure all defaults are initialized after this point
applyDefaultOptionValues();
initConfigData(config);
ConfigUtil.setupInMemoryHandler(config);
ConfigCredentialsCmd login = new ConfigCredentialsCmd();
login.initFromParent(this);
login.init(config);
login.process();
// this must be executed before finally block which restores config handler
return loadConfig();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e);
} finally {
ConfigUtil.setHandler(old);
}
} else {
checkAuthInfo(config);
// make sure all defaults are initialized after this point
applyDefaultOptionValues();
return loadConfig();
}
}
protected boolean requiresLogin() {
return user != null || password != null || secret != null || keystore != null
|| keyPass != null || storePass != null || alias != null;
}
protected ConfigData copyWithServerInfo(ConfigData config) {
ConfigData result = config.deepcopy();
if (server != null) {
result.setServerUrl(server);
}
if (realm != null) {
result.setRealm(realm);
}
checkServerInfo(result);
return result;
}
private void initConfigData(ConfigData data) {
if (server != null)
data.setServerUrl(server);
if (realm != null)
data.setRealm(realm);
if (trustStore != null)
data.setTruststore(trustStore);
RealmConfigData rdata = data.sessionRealmConfigData();
if (clientId != null)
rdata.setClientId(clientId);
if (secret != null)
rdata.setSecret(secret);
String grantTypeForAuthentication = user == null ? OAuth2Constants.CLIENT_CREDENTIALS : OAuth2Constants.PASSWORD;
rdata.setGrantTypeForAuthentication(grantTypeForAuthentication);
}
@Override
protected void checkUnsupportedOptions(String ... options) {
if (options.length % 2 != 0) {
throw new IllegalArgumentException("Even number of argument required");
}
for (int i = 0; i < options.length; i++) {
String name = options[i];
String value = options[++i];
if (value != null) {
throw new IllegalArgumentException("Unsupported option: " + name);
}
}
}
protected static String booleanOptionForCheck(boolean value) {
return value ? "true" : null;
}
}

View file

@ -1,8 +1,9 @@
package org.keycloak.client.registration.cli.commands;
import org.keycloak.client.registration.cli.common.AttributeKey;
import org.keycloak.client.registration.cli.common.EndpointType;
import org.keycloak.client.registration.cli.util.ReflectionUtil;
import org.keycloak.client.cli.common.AttributeKey;
import org.keycloak.client.cli.common.BaseGlobalOptionsCmd;
import org.keycloak.client.registration.cli.EndpointType;
import org.keycloak.client.registration.cli.ReflectionUtil;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
@ -20,18 +21,18 @@ import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.registration.cli.util.ReflectionUtil.getAttributeListWithJSonTypes;
import static org.keycloak.client.registration.cli.util.ReflectionUtil.isBasicType;
import static org.keycloak.client.registration.cli.util.ReflectionUtil.isListType;
import static org.keycloak.client.registration.cli.util.ReflectionUtil.isMapType;
import static org.keycloak.client.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.registration.cli.KcRegMain.CMD;
import static org.keycloak.client.registration.cli.ReflectionUtil.getAttributeListWithJSonTypes;
import static org.keycloak.client.registration.cli.ReflectionUtil.isBasicType;
import static org.keycloak.client.registration.cli.ReflectionUtil.isListType;
import static org.keycloak.client.registration.cli.ReflectionUtil.isMapType;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
@Command(name = "attrs", description = "[ATTRIBUTE] [--endpoint TYPE]")
public class AttrsCmd extends AbstractGlobalOptionsCmd {
public class AttrsCmd extends BaseGlobalOptionsCmd {
CommandLine.Model.CommandSpec spec;

View file

@ -22,7 +22,7 @@ import java.io.StringWriter;
import picocli.CommandLine.Command;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.KcRegMain.CMD;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>

View file

@ -1,243 +1,18 @@
package org.keycloak.client.registration.cli.commands;
import org.keycloak.OAuth2Constants;
import org.keycloak.client.registration.cli.config.ConfigData;
import org.keycloak.client.registration.cli.config.RealmConfigData;
import org.keycloak.client.registration.cli.util.AuthUtil;
import org.keycloak.representations.AccessTokenResponse;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.URL;
import org.keycloak.client.cli.common.BaseConfigCredentialsCmd;
import org.keycloak.client.registration.cli.KcRegMain;
import picocli.CommandLine.Command;
import static org.keycloak.client.registration.cli.util.AuthUtil.getAuthTokens;
import static org.keycloak.client.registration.cli.util.AuthUtil.getAuthTokensByJWT;
import static org.keycloak.client.registration.cli.util.AuthUtil.getAuthTokensBySecret;
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.registration.cli.util.ConfigUtil.getHandler;
import static org.keycloak.client.registration.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.registration.cli.util.ConfigUtil.saveTokens;
import static org.keycloak.client.registration.cli.util.IoUtil.printErr;
import static org.keycloak.client.registration.cli.util.IoUtil.readSecret;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
@Command(name = "credentials", description = "--server SERVER_URL --realm REALM [ARGUMENTS]")
public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd {
public class ConfigCredentialsCmd extends BaseConfigCredentialsCmd {
private int sigLifetime = 600;
public void init(ConfigData configData) {
if (server == null) {
server = configData.getServerUrl();
}
if (realm == null) {
realm = configData.getRealm();
}
if (trustStore == null) {
trustStore = configData.getTruststore();
}
RealmConfigData rdata = configData.getRealmConfigData(server, realm);
if (rdata == null) {
return;
}
if (clientId == null) {
clientId = rdata.getClientId();
}
public ConfigCredentialsCmd() {
super(KcRegMain.COMMAND_STATE);
}
@Override
protected String[] getUnsupportedOptions() {
return new String[] {"--no-config", booleanOptionForCheck(noconfig)};
}
@Override
protected boolean nothingToDo() {
return noOptions();
}
@Override
protected void process() {
// check server
if (server == null) {
throw new IllegalArgumentException("Required option not specified: --server");
}
try {
new URL(server);
} catch (Exception e) {
throw new RuntimeException("Invalid server endpoint url: " + server, e);
}
if (realm == null)
throw new IllegalArgumentException("Required option not specified: --realm");
String signedRequestToken = null;
boolean clientSet = clientId != null;
applyDefaultOptionValues();
String grantTypeForAuthentication = null;
if (user != null) {
grantTypeForAuthentication = OAuth2Constants.PASSWORD;
printErr("Logging into " + server + " as user " + user + " of realm " + realm);
// if user was set there needs to be a password so we can authenticate
if (password == null) {
password = readSecret("Enter password: ");
}
// if secret was set to be read from stdin, then ask for it
if ("-".equals(secret) && keystore == null) {
secret = readSecret("Enter client secret: ");
}
} else if (keystore != null || secret != null || clientSet) {
grantTypeForAuthentication = OAuth2Constants.CLIENT_CREDENTIALS;
printErr("Logging into " + server + " as " + "service-account-" + clientId + " of realm " + realm);
if (keystore == null) {
if (secret == null) {
secret = readSecret("Enter client secret: ");
}
}
}
if (keystore != null) {
if (secret != null) {
throw new IllegalArgumentException("Can't use both --keystore and --secret");
}
if (!new File(keystore).isFile()) {
throw new RuntimeException("No such keystore file: " + keystore);
}
if (storePass == null) {
storePass = readSecret("Enter keystore password: ");
keyPass = readSecret("Enter key password: ");
}
if (keyPass == null) {
keyPass = storePass;
}
if (alias == null) {
alias = clientId;
}
String realmInfoUrl = server + "/realms/" + realm;
signedRequestToken = AuthUtil.getSignedRequestToken(keystore, storePass, keyPass,
alias, sigLifetime, clientId, realmInfoUrl);
}
// if only server and realm are set, just save config and be done
if (user == null && secret == null && keystore == null) {
getHandler().saveMergeConfig(config -> {
config.setServerUrl(server);
config.setRealm(realm);
});
return;
}
setupTruststore(copyWithServerInfo(loadConfig()));
// now use the token endpoint to retrieve access token, and refresh token
AccessTokenResponse tokens = signedRequestToken != null ?
getAuthTokensByJWT(server, realm, user, password, clientId, signedRequestToken) :
secret != null ?
getAuthTokensBySecret(server, realm, user, password, clientId, secret) :
getAuthTokens(server, realm, user, password, clientId);
Long sigExpiresAt = signedRequestToken == null ? null : System.currentTimeMillis() + sigLifetime * 1000;
// save tokens to config file
saveTokens(tokens, server, realm, clientId, signedRequestToken, sigExpiresAt, secret, grantTypeForAuthentication);
}
@Override
protected String help() {
return usage();
}
public static String usage() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
out.println("Usage: " + CMD + " config credentials --server SERVER_URL --realm REALM [ARGUMENTS]");
out.println(" " + CMD + " config credentials --server SERVER_URL --realm REALM --user USER [--password PASSWORD] [ARGUMENTS]");
out.println(" " + CMD + " config credentials --server SERVER_URL --realm REALM --client CLIENT_ID [--secret SECRET] [ARGUMENTS]");
out.println(" " + CMD + " config credentials --server SERVER_URL --realm REALM --client CLIENT_ID [--keystore KEYSTORE] [ARGUMENTS]");
out.println();
out.println("Command to establish an authenticated client session with the server. There are many authentication");
out.println("options available, and it depends on server side client authentication configuration how client can or should authenticate.");
out.println("The information always required includes --server, and --realm. That is enough to establish unauthenticated session.");
out.println("If --client is not provided it defaults to 'admin-cli'. The authantication options / requirements depend on how this client is configured.");
out.println();
out.println("If you have an account configured with the rights to manage clients then you can use username, and password to authenticate.");
out.println("If confidential client authentication is also configured, you may have to specify a client id, and client credentials in addition to");
out.println("user credentials. Client credentials are either a client secret, or a keystore information to use Signed JWT mechanism.");
out.println("If only client credentials are provided, and no user credentials, then the service account is used for login.");
out.println();
out.println("Arguments:");
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to a config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");
out.println();
out.println(" Command specific options:");
out.println(" --server SERVER_URL Server endpoint url (e.g. 'http://localhost:8080')");
out.println(" --realm REALM Realm name to use");
out.println(" --user USER Username to login with");
out.println(" --password PASSWORD Password to login with (prompted for if not specified and --user is used)");
out.println(" --client CLIENT_ID ClientId used by this client tool ('admin-cli' by default)");
out.println(" --secret SECRET Secret to authenticate the client (prompted for if --client is specified, and no --keystore is specified)");
out.println(" --keystore PATH Path to a keystore containing private key");
out.println(" --storepass PASSWORD Keystore password (prompted for if not specified and --keystore is used)");
out.println(" --keypass PASSWORD Key password (prompted for if not specified and --keystore is used without --storepass,");
out.println(" otherwise defaults to keystore password)");
out.println(" --alias ALIAS Alias of the key inside a keystore (defaults to the value of ClientId)");
out.println();
out.println();
out.println("Examples:");
out.println();
out.println("Login as 'admin' user of 'master' realm to a local Keycloak server running on default port.");
out.println("You will be prompted for a password:");
out.println(" " + PROMPT + " " + CMD + " config credentials --server http://localhost:8080 --realm master --user admin");
out.println();
out.println("Login to Keycloak server at non-default endpoint passing the password via standard input:");
if (OS_ARCH.isWindows()) {
out.println(" " + PROMPT + " echo mypassword | " + CMD + " config credentials --server http://localhost:9080 --realm master --user admin");
} else {
out.println(" " + PROMPT + " " + CMD + " config credentials --server http://localhost:9080 --realm master --user admin << EOF");
out.println(" mypassword");
out.println(" EOF");
}
out.println();
out.println("Login specifying a password through command line:");
out.println(" " + PROMPT + " " + CMD + " config credentials --server http://localhost:9080 --realm master --user admin --password " + OS_ARCH.envVar("PASSWORD"));
out.println();
out.println("Login using a client service account of a custom client. You will be prompted for a client secret:");
out.println(" " + PROMPT + " " + CMD + " config credentials --server http://localhost:9080 --realm master --client reg-cli");
out.println();
out.println("Login using a client service account of a custom client, authenticating with signed JWT.");
out.println("You will be prompted for a keystore password, and a key password:");
out.println(" " + PROMPT + " " + CMD + " config credentials --server http://localhost:9080 --realm master --client reg-cli --keystore " + OS_ARCH.path("~/.keycloak/keystore.jks"));
out.println();
out.println("Login as 'user' while also authenticating a custom client with signed JWT.");
out.println("You will be prompted for a user password, a keystore password, and a key password:");
out.println(" " + PROMPT + " " + CMD + " config credentials --server http://localhost:9080 --realm master --user user --client reg-cli --keystore " + OS_ARCH.path("~/.keycloak/keystore.jks"));
out.println();
out.println();
out.println("Use '" + CMD + " help' for general information and a list of commands");
return sb.toString();
}
}

View file

@ -1,8 +1,9 @@
package org.keycloak.client.registration.cli.commands;
import org.keycloak.client.registration.cli.config.RealmConfigData;
import org.keycloak.client.registration.cli.util.IoUtil;
import org.keycloak.client.registration.cli.util.ParseUtil;
import org.keycloak.client.cli.config.RealmConfigData;
import org.keycloak.client.cli.util.IoUtil;
import org.keycloak.client.registration.cli.CmdStdinContext;
import org.keycloak.client.registration.cli.KcRegMain;
import java.io.PrintWriter;
import java.io.StringWriter;
@ -11,12 +12,11 @@ import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.registration.cli.util.IoUtil.warnfOut;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.cli.util.IoUtil.warnfOut;
import static org.keycloak.client.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.registration.cli.KcRegMain.CMD;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -34,7 +34,7 @@ public class ConfigInitialTokenCmd extends AbstractAuthOptionsCmd {
@Override
protected boolean nothingToDo() {
return noOptions() && token == null && !delete && !keepDomain;
return super.nothingToDo() && token == null && !delete && !keepDomain;
}
@Override
@ -60,7 +60,7 @@ public class ConfigInitialTokenCmd extends AbstractAuthOptionsCmd {
}
if (token != null && token.startsWith("-")) {
warnfOut(ParseUtil.TOKEN_OPTION_WARN, token);
warnfOut(CmdStdinContext.TOKEN_OPTION_WARN, token);
}
if (!delete && token == null) {
@ -106,7 +106,7 @@ public class ConfigInitialTokenCmd extends AbstractAuthOptionsCmd {
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --config Path to the config file (" + KcRegMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println();
out.println(" Command specific options:");
out.println(" --server SERVER Server endpoint url (e.g. 'http://localhost:8080')");

View file

@ -1,7 +1,8 @@
package org.keycloak.client.registration.cli.commands;
import org.keycloak.client.registration.cli.config.RealmConfigData;
import org.keycloak.client.registration.cli.util.IoUtil;
import org.keycloak.client.registration.cli.KcRegMain;
import org.keycloak.client.cli.config.RealmConfigData;
import org.keycloak.client.cli.util.IoUtil;
import java.io.PrintWriter;
import java.io.StringWriter;
@ -10,11 +11,10 @@ import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.registration.cli.KcRegMain.CMD;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -30,7 +30,7 @@ public class ConfigRegistrationTokenCmd extends AbstractAuthOptionsCmd {
@Override
protected boolean nothingToDo() {
return noOptions() && token == null && !delete;
return super.nothingToDo() && token == null && !delete;
}
@Override
@ -97,7 +97,7 @@ public class ConfigRegistrationTokenCmd extends AbstractAuthOptionsCmd {
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --config Path to the config file (" + KcRegMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println();
out.println(" Command specific options:");
out.println(" --server SERVER Server endpoint url (e.g. 'http://localhost:8080')");

View file

@ -16,131 +16,19 @@
*/
package org.keycloak.client.registration.cli.commands;
import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import org.keycloak.client.cli.common.BaseConfigTruststoreCmd;
import org.keycloak.client.registration.cli.KcRegMain;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.registration.cli.util.IoUtil.readSecret;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
@Command(name = "truststore", description = "PATH [ARGUMENTS]")
public class ConfigTruststoreCmd extends AbstractAuthOptionsCmd {
public class ConfigTruststoreCmd extends BaseConfigTruststoreCmd {
@Option(names = {"-d", "--delete"}, description = "Indicates that initial access token should be removed")
private boolean delete;
@Parameters(arity = "0..1")
private String truststorePath;
@Override
protected boolean nothingToDo() {
return noOptions() && truststorePath == null && !delete;
public ConfigTruststoreCmd() {
super(KcRegMain.COMMAND_STATE);
}
@Override
protected String[] getUnsupportedOptions() {
return new String[] {"--server", server,
"--realm", realm,
"--client", clientId,
"--user", user,
"--password", password,
"--secret", secret,
"--truststore", trustStore,
"--keystore", keystore,
"--keypass", keyPass,
"--alias", alias,
"--no-config", booleanOptionForCheck(noconfig)};
}
@Override
protected void process() {
// now update the config
String store;
String pass;
if (!delete) {
if (truststorePath == null) {
throw new IllegalArgumentException("No truststore specified");
}
if (!new File(truststorePath).isFile()) {
throw new RuntimeException("Truststore file not found: " + truststorePath);
}
if ("-".equals(trustPass)) {
trustPass = readSecret("Enter truststore password: ");
}
store = truststorePath;
pass = trustPass;
} else {
if (truststorePath != null) {
throw new IllegalArgumentException("Option --delete is mutually exclusive with specifying a TRUSTSTORE");
}
if (trustPass != null) {
throw new IllegalArgumentException("Options --trustpass and --delete are mutually exclusive");
}
store = null;
pass = null;
}
saveMergeConfig(config -> {
config.setTruststore(store);
config.setTrustpass(pass);
});
}
@Override
protected String help() {
return usage();
}
public static String usage() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
out.println("Usage: " + CMD + " config truststore [TRUSTSTORE | --delete] [--trustpass PASSWORD] [ARGUMENTS]");
out.println();
out.println("Command to configure a global truststore to use when using https to connect to Keycloak server.");
out.println();
out.println("Arguments:");
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println();
out.println(" Command specific options:");
out.println(" TRUSTSTORE Path to truststore file");
out.println(" --trustpass PASSWORD Truststore password to unlock truststore (prompted for if set to '-')");
out.println(" -d, --delete Remove truststore configuration");
out.println();
out.println();
out.println("Examples:");
out.println();
out.println("Specify a truststore - you will be prompted for truststore password every time it is used:");
out.println(" " + PROMPT + " " + CMD + " config truststore " + OS_ARCH.path("~/.keycloak/truststore.jks"));
out.println();
out.println("Specify a truststore, and password - truststore will automatically be used without prompting for password:");
out.println(" " + PROMPT + " " + CMD + " config truststore --storepass " + OS_ARCH.envVar("PASSWORD") + " " + OS_ARCH.path("~/.keycloak/truststore.jks"));
out.println();
out.println("Remove truststore configuration:");
out.println(" " + PROMPT + " " + CMD + " config truststore --delete");
out.println();
out.println();
out.println("Use '" + CMD + " help' for general information and a list of commands");
return sb.toString();
}
}

View file

@ -17,12 +17,13 @@
package org.keycloak.client.registration.cli.commands;
import org.keycloak.client.registration.cli.CmdStdinContext;
import org.keycloak.client.registration.cli.EndpointType;
import org.keycloak.client.registration.cli.EndpointTypeConverter;
import org.keycloak.client.registration.cli.common.AttributeOperation;
import org.keycloak.client.registration.cli.common.CmdStdinContext;
import org.keycloak.client.registration.cli.common.EndpointType;
import org.keycloak.client.registration.cli.config.ConfigData;
import org.keycloak.client.registration.cli.util.HttpUtil;
import org.keycloak.client.registration.cli.KcRegMain;
import org.keycloak.client.cli.common.AttributeOperation;
import org.keycloak.client.cli.config.ConfigData;
import org.keycloak.client.cli.util.HttpUtil;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
import org.keycloak.util.JsonSerialization;
@ -39,28 +40,23 @@ import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import static org.keycloak.client.registration.cli.common.AttributeOperation.Type.SET;
import static org.keycloak.client.registration.cli.common.EndpointType.DEFAULT;
import static org.keycloak.client.registration.cli.common.EndpointType.OIDC;
import static org.keycloak.client.registration.cli.common.EndpointType.SAML2;
import static org.keycloak.client.registration.cli.util.AuthUtil.ensureToken;
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.registration.cli.util.ConfigUtil.credentialsAvailable;
import static org.keycloak.client.registration.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.registration.cli.util.ConfigUtil.setRegistrationToken;
import static org.keycloak.client.registration.cli.util.HttpUtil.doPost;
import static org.keycloak.client.registration.cli.util.HttpUtil.getExpectedContentType;
import static org.keycloak.client.registration.cli.util.IoUtil.printErr;
import static org.keycloak.client.registration.cli.util.IoUtil.printOut;
import static org.keycloak.client.registration.cli.util.IoUtil.readFully;
import static org.keycloak.client.registration.cli.util.IoUtil.readSecret;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.registration.cli.util.ParseUtil.mergeAttributes;
import static org.keycloak.client.registration.cli.util.ParseUtil.parseFileOrStdin;
import static org.keycloak.client.registration.cli.util.ParseUtil.parseKeyVal;
import static org.keycloak.client.cli.common.AttributeOperation.Type.SET;
import static org.keycloak.client.cli.util.ConfigUtil.credentialsAvailable;
import static org.keycloak.client.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.cli.util.ConfigUtil.setRegistrationToken;
import static org.keycloak.client.cli.util.HttpUtil.doPost;
import static org.keycloak.client.cli.util.IoUtil.printErr;
import static org.keycloak.client.cli.util.IoUtil.printOut;
import static org.keycloak.client.cli.util.IoUtil.readFully;
import static org.keycloak.client.cli.util.IoUtil.readSecret;
import static org.keycloak.client.cli.util.OsUtil.OS_ARCH;
import static org.keycloak.client.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.cli.util.ParseUtil.parseKeyVal;
import static org.keycloak.client.registration.cli.EndpointType.DEFAULT;
import static org.keycloak.client.registration.cli.EndpointType.OIDC;
import static org.keycloak.client.registration.cli.EndpointType.SAML2;
import static org.keycloak.client.registration.cli.KcRegMain.CMD;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -109,13 +105,13 @@ public class CreateCmd extends AbstractAuthOptionsCmd {
}
// if --token is specified read it
if ("-".equals(token)) {
token = readSecret("Enter Initial Access Token: ");
if ("-".equals(externalToken)) {
externalToken = readSecret("Enter Initial Access Token: ");
}
CmdStdinContext ctx = new CmdStdinContext();
if (file != null) {
ctx = parseFileOrStdin(file, regType);
ctx = CmdStdinContext.parseFileOrStdin(file, regType);
}
if (ctx.getEndpointType() == null) {
@ -126,22 +122,22 @@ public class CreateCmd extends AbstractAuthOptionsCmd {
}
if (attrs.size() > 0) {
ctx = mergeAttributes(ctx, attrs);
ctx = CmdStdinContext.mergeAttributes(ctx, attrs);
}
String contentType = getExpectedContentType(ctx.getEndpointType());
String contentType = EndpointType.getExpectedContentType(ctx.getEndpointType());
ConfigData config = loadConfig();
config = copyWithServerInfo(config);
if (token == null) {
if (externalToken == null) {
// if initial token is not set, try use the one from configuration
token = config.sessionRealmConfigData().getInitialToken();
externalToken = config.sessionRealmConfigData().getInitialToken();
}
setupTruststore(config);
String auth = token;
String auth = externalToken;
if (auth == null) {
config = ensureAuthInfo(config);
config = copyWithServerInfo(config);
@ -199,7 +195,7 @@ public class CreateCmd extends AbstractAuthOptionsCmd {
@Override
protected boolean nothingToDo() {
return noOptions() && regType == null && file == null && rawSets.isEmpty();
return super.nothingToDo() && regType == null && file == null && rawSets.isEmpty();
}
@Override
@ -220,7 +216,7 @@ public class CreateCmd extends AbstractAuthOptionsCmd {
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --config Path to the config file (" + KcRegMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");

View file

@ -17,8 +17,9 @@
package org.keycloak.client.registration.cli.commands;
import org.keycloak.client.registration.cli.config.ConfigData;
import org.keycloak.client.registration.cli.util.ParseUtil;
import org.keycloak.client.registration.cli.KcRegMain;
import org.keycloak.client.cli.config.ConfigData;
import org.keycloak.client.registration.cli.CmdStdinContext;
import java.io.PrintWriter;
import java.io.StringWriter;
@ -26,17 +27,15 @@ import java.io.StringWriter;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
import static org.keycloak.client.registration.cli.util.AuthUtil.ensureToken;
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.registration.cli.util.ConfigUtil.credentialsAvailable;
import static org.keycloak.client.registration.cli.util.ConfigUtil.getRegistrationToken;
import static org.keycloak.client.registration.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.registration.cli.util.HttpUtil.doDelete;
import static org.keycloak.client.registration.cli.util.HttpUtil.urlencode;
import static org.keycloak.client.registration.cli.util.IoUtil.warnfErr;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.cli.util.ConfigUtil.credentialsAvailable;
import static org.keycloak.client.cli.util.ConfigUtil.getRegistrationToken;
import static org.keycloak.client.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.cli.util.HttpUtil.doDelete;
import static org.keycloak.client.cli.util.HttpUtil.urlencode;
import static org.keycloak.client.cli.util.IoUtil.warnfErr;
import static org.keycloak.client.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.registration.cli.KcRegMain.CMD;
/**
@ -55,7 +54,7 @@ public class DeleteCmd extends AbstractAuthOptionsCmd {
}
if (clientId.startsWith("-")) {
warnfErr(ParseUtil.CLIENT_OPTION_WARN, clientId);
warnfErr(CmdStdinContext.CLIENT_OPTION_WARN, clientId);
}
String regType = "default";
@ -63,14 +62,14 @@ public class DeleteCmd extends AbstractAuthOptionsCmd {
ConfigData config = loadConfig();
config = copyWithServerInfo(config);
if (token == null) {
if (externalToken == null) {
// if registration access token is not set via -t, try use the one from configuration
token = getRegistrationToken(config.sessionRealmConfigData(), clientId);
externalToken = getRegistrationToken(config.sessionRealmConfigData(), clientId);
}
setupTruststore(config);
String auth = token;
String auth = externalToken;
if (auth == null) {
config = ensureAuthInfo(config);
config = copyWithServerInfo(config);
@ -94,7 +93,7 @@ public class DeleteCmd extends AbstractAuthOptionsCmd {
@Override
protected boolean nothingToDo() {
return noOptions() && clientId == null;
return super.nothingToDo() && clientId == null;
}
@Override
@ -114,7 +113,7 @@ public class DeleteCmd extends AbstractAuthOptionsCmd {
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --config Path to the config file (" + KcRegMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");

View file

@ -17,9 +17,10 @@
package org.keycloak.client.registration.cli.commands;
import org.keycloak.client.registration.cli.config.ConfigData;
import org.keycloak.client.registration.cli.common.EndpointType;
import org.keycloak.client.registration.cli.util.ParseUtil;
import org.keycloak.client.registration.cli.KcRegMain;
import org.keycloak.client.cli.config.ConfigData;
import org.keycloak.client.registration.cli.CmdStdinContext;
import org.keycloak.client.registration.cli.EndpointType;
import org.keycloak.representations.adapters.config.AdapterConfig;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
@ -34,21 +35,19 @@ import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import static org.keycloak.client.registration.cli.util.AuthUtil.ensureToken;
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.registration.cli.util.ConfigUtil.credentialsAvailable;
import static org.keycloak.client.registration.cli.util.ConfigUtil.getRegistrationToken;
import static org.keycloak.client.registration.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.registration.cli.util.ConfigUtil.setRegistrationToken;
import static org.keycloak.client.registration.cli.util.HttpUtil.APPLICATION_JSON;
import static org.keycloak.client.registration.cli.util.HttpUtil.doGet;
import static org.keycloak.client.registration.cli.util.HttpUtil.urlencode;
import static org.keycloak.client.registration.cli.util.IoUtil.warnfErr;
import static org.keycloak.client.registration.cli.util.IoUtil.printOut;
import static org.keycloak.client.registration.cli.util.IoUtil.readFully;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.cli.util.ConfigUtil.credentialsAvailable;
import static org.keycloak.client.cli.util.ConfigUtil.getRegistrationToken;
import static org.keycloak.client.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.cli.util.ConfigUtil.setRegistrationToken;
import static org.keycloak.client.cli.util.HttpUtil.APPLICATION_JSON;
import static org.keycloak.client.cli.util.HttpUtil.doGet;
import static org.keycloak.client.cli.util.HttpUtil.urlencode;
import static org.keycloak.client.cli.util.IoUtil.printOut;
import static org.keycloak.client.cli.util.IoUtil.readFully;
import static org.keycloak.client.cli.util.IoUtil.warnfErr;
import static org.keycloak.client.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.registration.cli.KcRegMain.CMD;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -75,20 +74,20 @@ public class GetCmd extends AbstractAuthOptionsCmd {
if (clientId.startsWith("-")) {
warnfErr(ParseUtil.CLIENT_OPTION_WARN, clientId);
warnfErr(CmdStdinContext.CLIENT_OPTION_WARN, clientId);
}
ConfigData config = loadConfig();
config = copyWithServerInfo(config);
if (token == null) {
if (externalToken == null) {
// if registration access token is not set via -t, try use the one from configuration
token = getRegistrationToken(config.sessionRealmConfigData(), clientId);
externalToken = getRegistrationToken(config.sessionRealmConfigData(), clientId);
}
setupTruststore(config);
String auth = token;
String auth = externalToken;
if (auth == null) {
config = ensureAuthInfo(config);
config = copyWithServerInfo(config);
@ -156,7 +155,7 @@ public class GetCmd extends AbstractAuthOptionsCmd {
@Override
protected boolean nothingToDo() {
return noOptions() && endpoint == null && clientId == null;
return super.nothingToDo() && endpoint == null && clientId == null;
}
@Override
@ -176,7 +175,7 @@ public class GetCmd extends AbstractAuthOptionsCmd {
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --config Path to the config file (" + KcRegMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");

View file

@ -5,7 +5,7 @@ import java.util.List;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
import static org.keycloak.client.registration.cli.util.IoUtil.printOut;
import static org.keycloak.client.cli.util.IoUtil.printOut;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -27,7 +27,7 @@ public class HelpCmd implements Runnable {
if (args.size() > 1) {
switch (args.get(1)) {
case "credentials": {
printOut(ConfigCredentialsCmd.usage());
printOut(new ConfigCredentialsCmd().help());
break outer;
}
case "initial-token": {
@ -39,7 +39,7 @@ public class HelpCmd implements Runnable {
break outer;
}
case "truststore": {
printOut(ConfigTruststoreCmd.usage());
printOut(new ConfigTruststoreCmd().help());
break outer;
}
}

View file

@ -16,15 +16,16 @@
*/
package org.keycloak.client.registration.cli.commands;
import org.keycloak.client.cli.common.BaseGlobalOptionsCmd;
import org.keycloak.client.registration.cli.KcRegMain;
import java.io.PrintWriter;
import java.io.StringWriter;
import picocli.CommandLine.Command;
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.registration.cli.util.IoUtil.printOut;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.registration.cli.KcRegMain.CMD;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -49,13 +50,18 @@ subcommands = {
AttrsCmd.class,
UpdateTokenCmd.class
})
public class KcRegCmd extends AbstractGlobalOptionsCmd {
public class KcRegCmd extends BaseGlobalOptionsCmd {
@Override
protected boolean nothingToDo() {
return true;
}
@Override
protected String help() {
return usage();
}
public static String usage() {
StringWriter sb = new StringWriter();
PrintWriter out = new PrintWriter(sb);
@ -93,7 +99,7 @@ public class KcRegCmd extends AbstractGlobalOptionsCmd {
out.println("Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --help Print help for specific command");
out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --config Path to the config file (" + KcRegMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println();
out.println("Commands: ");

View file

@ -24,13 +24,13 @@ import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
import org.keycloak.client.registration.cli.CmdStdinContext;
import org.keycloak.client.registration.cli.EndpointType;
import org.keycloak.client.registration.cli.EndpointTypeConverter;
import org.keycloak.client.registration.cli.common.AttributeOperation;
import org.keycloak.client.registration.cli.config.ConfigData;
import org.keycloak.client.registration.cli.common.CmdStdinContext;
import org.keycloak.client.registration.cli.common.EndpointType;
import org.keycloak.client.registration.cli.util.ParseUtil;
import org.keycloak.client.registration.cli.util.ReflectionUtil;
import org.keycloak.client.registration.cli.ReflectionUtil;
import org.keycloak.client.registration.cli.KcRegMain;
import org.keycloak.client.cli.common.AttributeOperation;
import org.keycloak.client.cli.config.ConfigData;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
import org.keycloak.util.JsonSerialization;
@ -43,29 +43,25 @@ import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import static org.keycloak.client.registration.cli.common.AttributeOperation.Type.DELETE;
import static org.keycloak.client.registration.cli.common.AttributeOperation.Type.SET;
import static org.keycloak.client.registration.cli.util.AuthUtil.ensureToken;
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.registration.cli.util.ConfigUtil.credentialsAvailable;
import static org.keycloak.client.registration.cli.util.ConfigUtil.getRegistrationToken;
import static org.keycloak.client.registration.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.registration.cli.util.ConfigUtil.setRegistrationToken;
import static org.keycloak.client.registration.cli.common.EndpointType.DEFAULT;
import static org.keycloak.client.registration.cli.common.EndpointType.OIDC;
import static org.keycloak.client.registration.cli.util.HttpUtil.doGet;
import static org.keycloak.client.registration.cli.util.HttpUtil.doPut;
import static org.keycloak.client.registration.cli.util.HttpUtil.urlencode;
import static org.keycloak.client.registration.cli.util.IoUtil.printOut;
import static org.keycloak.client.registration.cli.util.IoUtil.warnfErr;
import static org.keycloak.client.registration.cli.util.IoUtil.readFully;
import static org.keycloak.client.registration.cli.util.HttpUtil.APPLICATION_JSON;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.registration.cli.util.ParseUtil.mergeAttributes;
import static org.keycloak.client.registration.cli.util.ParseUtil.parseFileOrStdin;
import static org.keycloak.client.registration.cli.util.ParseUtil.parseKeyVal;
import static org.keycloak.client.cli.common.AttributeOperation.Type.DELETE;
import static org.keycloak.client.cli.common.AttributeOperation.Type.SET;
import static org.keycloak.client.cli.util.ConfigUtil.credentialsAvailable;
import static org.keycloak.client.cli.util.ConfigUtil.getRegistrationToken;
import static org.keycloak.client.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.cli.util.ConfigUtil.setRegistrationToken;
import static org.keycloak.client.cli.util.HttpUtil.APPLICATION_JSON;
import static org.keycloak.client.cli.util.HttpUtil.doGet;
import static org.keycloak.client.cli.util.HttpUtil.doPut;
import static org.keycloak.client.cli.util.HttpUtil.urlencode;
import static org.keycloak.client.cli.util.IoUtil.printOut;
import static org.keycloak.client.cli.util.IoUtil.readFully;
import static org.keycloak.client.cli.util.IoUtil.warnfErr;
import static org.keycloak.client.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.cli.util.ParseUtil.parseKeyVal;
import static org.keycloak.client.registration.cli.EndpointType.DEFAULT;
import static org.keycloak.client.registration.cli.EndpointType.OIDC;
import static org.keycloak.client.registration.cli.KcRegMain.CMD;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -123,7 +119,7 @@ public class UpdateCmd extends AbstractAuthOptionsCmd {
}
if (clientId.startsWith("-")) {
warnfErr(ParseUtil.CLIENT_OPTION_WARN, clientId);
warnfErr(CmdStdinContext.CLIENT_OPTION_WARN, clientId);
}
if (file == null && attrs.size() == 0) {
@ -165,7 +161,7 @@ public class UpdateCmd extends AbstractAuthOptionsCmd {
CmdStdinContext ctx = new CmdStdinContext();
if (file != null) {
ctx = parseFileOrStdin(file, regType);
ctx = CmdStdinContext.parseFileOrStdin(file, regType);
regType = ctx.getEndpointType();
}
@ -185,7 +181,7 @@ public class UpdateCmd extends AbstractAuthOptionsCmd {
final String server = config.getServerUrl();
final String realm = config.getRealm();
if (token == null) {
if (externalToken == null) {
// if registration access token is not set via --token, see if it's in the body of any input file
// but first see if it's overridden by --set, or maybe deliberately muted via -d registrationAccessToken
boolean processed = false;
@ -193,25 +189,25 @@ public class UpdateCmd extends AbstractAuthOptionsCmd {
if ("registrationAccessToken".equals(op.getKey().toString())) {
processed = true;
if (op.getType() == AttributeOperation.Type.SET) {
token = op.getValue();
externalToken = op.getValue();
}
// otherwise it's delete - meaning it should stay null
break;
}
}
if (!processed) {
token = ctx.getRegistrationAccessToken();
externalToken = ctx.getRegistrationAccessToken();
}
}
if (token == null) {
if (externalToken == null) {
// if registration access token is not set, try use the one from configuration
token = getRegistrationToken(config.sessionRealmConfigData(), clientId);
externalToken = getRegistrationToken(config.sessionRealmConfigData(), clientId);
}
setupTruststore(config);
String auth = token;
String auth = externalToken;
if (auth == null) {
config = ensureAuthInfo(config);
config = copyWithServerInfo(config);
@ -236,10 +232,10 @@ public class UpdateCmd extends AbstractAuthOptionsCmd {
if (regType == DEFAULT) {
ctxremote.setClient(JsonSerialization.readValue(json, ClientRepresentation.class));
token = ctxremote.getClient().getRegistrationAccessToken();
externalToken = ctxremote.getClient().getRegistrationAccessToken();
} else if (regType == OIDC) {
ctxremote.setOidcClient(JsonSerialization.readValue(json, OIDCClientRepresentation.class));
token = ctxremote.getOidcClient().getRegistrationAccessToken();
externalToken = ctxremote.getOidcClient().getRegistrationAccessToken();
}
} catch (JsonParseException e) {
throw new RuntimeException("Not a valid JSON document. " + e.getMessage(), e);
@ -249,11 +245,11 @@ public class UpdateCmd extends AbstractAuthOptionsCmd {
// we have to use registration access token retrieved from previous operation
// that ensures optimistic locking semantics
if (token != null) {
if (externalToken != null) {
// we use auth with doPost later
auth = "Bearer " + token;
auth = "Bearer " + externalToken;
String newToken = token;
String newToken = externalToken;
String clientToUpdate = clientId;
saveMergeConfig(cfg -> {
setRegistrationToken(cfg.ensureRealmConfigData(server, realm), clientToUpdate, newToken);
@ -270,7 +266,7 @@ public class UpdateCmd extends AbstractAuthOptionsCmd {
}
if (attrs.size() > 0) {
ctx = mergeAttributes(ctx, attrs);
ctx = CmdStdinContext.mergeAttributes(ctx, attrs);
}
// now update
@ -280,14 +276,14 @@ public class UpdateCmd extends AbstractAuthOptionsCmd {
if (regType == DEFAULT) {
ClientRepresentation clirep = JsonSerialization.readValue(response, ClientRepresentation.class);
outputResult(clirep);
token = clirep.getRegistrationAccessToken();
externalToken = clirep.getRegistrationAccessToken();
} else if (regType == OIDC) {
OIDCClientRepresentation clirep = JsonSerialization.readValue(response, OIDCClientRepresentation.class);
outputResult(clirep);
token = clirep.getRegistrationAccessToken();
externalToken = clirep.getRegistrationAccessToken();
}
String newToken = token;
String newToken = externalToken;
String clientToUpdate = clientId;
saveMergeConfig(cfg -> {
setRegistrationToken(cfg.ensureRealmConfigData(server, realm), clientToUpdate, newToken);
@ -310,7 +306,7 @@ public class UpdateCmd extends AbstractAuthOptionsCmd {
@Override
protected boolean nothingToDo() {
return noOptions() && regType == null && file == null && rawAttributeOperations.isEmpty() && clientId == null;
return super.nothingToDo() && regType == null && file == null && rawAttributeOperations.isEmpty() && clientId == null;
}
@Override
@ -332,7 +328,7 @@ public class UpdateCmd extends AbstractAuthOptionsCmd {
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --config Path to the config file (" + KcRegMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");

View file

@ -22,8 +22,9 @@ import com.fasterxml.jackson.core.type.TypeReference;
import picocli.CommandLine.Command;
import picocli.CommandLine.Parameters;
import org.keycloak.client.registration.cli.config.ConfigData;
import org.keycloak.client.registration.cli.util.ParseUtil;
import org.keycloak.client.registration.cli.KcRegMain;
import org.keycloak.client.cli.config.ConfigData;
import org.keycloak.client.registration.cli.CmdStdinContext;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.util.JsonSerialization;
@ -33,18 +34,16 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;
import static org.keycloak.client.registration.cli.util.AuthUtil.ensureToken;
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
import static org.keycloak.client.registration.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.registration.cli.util.ConfigUtil.setRegistrationToken;
import static org.keycloak.client.registration.cli.util.HttpUtil.APPLICATION_JSON;
import static org.keycloak.client.registration.cli.util.HttpUtil.doGet;
import static org.keycloak.client.registration.cli.util.HttpUtil.doPost;
import static org.keycloak.client.registration.cli.util.IoUtil.printOut;
import static org.keycloak.client.registration.cli.util.IoUtil.warnfOut;
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.cli.util.ConfigUtil.loadConfig;
import static org.keycloak.client.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.cli.util.ConfigUtil.setRegistrationToken;
import static org.keycloak.client.cli.util.HttpUtil.APPLICATION_JSON;
import static org.keycloak.client.cli.util.HttpUtil.doGet;
import static org.keycloak.client.cli.util.HttpUtil.doPost;
import static org.keycloak.client.cli.util.IoUtil.printOut;
import static org.keycloak.client.cli.util.IoUtil.warnfOut;
import static org.keycloak.client.cli.util.OsUtil.PROMPT;
import static org.keycloak.client.registration.cli.KcRegMain.CMD;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
@ -62,7 +61,7 @@ public class UpdateTokenCmd extends AbstractAuthOptionsCmd {
}
if (clientId.startsWith("-")) {
warnfOut(ParseUtil.CLIENT_OPTION_WARN, clientId);
warnfOut(CmdStdinContext.CLIENT_OPTION_WARN, clientId);
}
ConfigData config = loadConfig();
@ -116,7 +115,7 @@ public class UpdateTokenCmd extends AbstractAuthOptionsCmd {
@Override
protected boolean nothingToDo() {
return noOptions() && clientId == null;
return super.nothingToDo() && clientId == null;
}
@Override
@ -136,7 +135,7 @@ public class UpdateTokenCmd extends AbstractAuthOptionsCmd {
out.println();
out.println(" Global options:");
out.println(" -x Print full stack trace when exiting with error");
out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --config Path to the config file (" + KcRegMain.DEFAULT_CONFIG_FILE_STRING + " by default)");
out.println(" --no-config Don't use config file - no authentication info is loaded or saved");
out.println(" --truststore PATH Path to a truststore containing trusted certificates");
out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)");

View file

@ -1,154 +0,0 @@
package org.keycloak.client.registration.cli.common;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class AttributeKey {
private static final int START = 0;
private static final int QUOTED = 1;
private static final int UNQUOTED = 2;
private static final int END = 3;
private List<Component> components;
private boolean append;
public AttributeKey() {
components = Collections.emptyList();
}
public AttributeKey(String key) {
if (key.endsWith("+")) {
append = true;
key = key.substring(0, key.length() - 1);
}
components = parse(key);
}
static List<Component> parse(String key) {
if (key == null || "".equals(key)) {
return Collections.emptyList();
}
List<Component> cs = new LinkedList<>();
StringBuilder sb = new StringBuilder();
int state = START;
char[] buf = key.toCharArray();
for (int pos = 0; pos < buf.length; pos++) {
char c = buf[pos];
if (state == START) {
if ('\"' == c) {
state = QUOTED;
} else if ('.' == c) {
throw new RuntimeException("Invalid attribute key: " + key + " (at position " + (pos + 1) + ")");
} else {
state = UNQUOTED;
sb.append(c);
}
} else if (state == QUOTED) {
if ('\"' == c) {
state = END;
} else {
sb.append(c);
}
} else if (state == UNQUOTED || state == END) {
if ('.' == c) {
state = START;
cs.add(new Component(sb.toString()));
sb.setLength(0);
} else if (state == END || '\"' == c) {
throw new RuntimeException("Invalid attribute key: " + key + " (at position " + (pos + 1) + ")");
} else {
sb.append(c);
}
}
}
boolean ok = false;
if (sb.length() > 0) {
if (state == UNQUOTED || state == END) {
cs.add(new Component(sb.toString()));
ok = true;
}
} else if (state == END) {
ok = true;
}
if (!ok) {
throw new RuntimeException("Invalid attribute key: " + key + " (at position " + (buf.length) + ")");
}
return Collections.unmodifiableList(cs);
}
public List<Component> getComponents() {
return components;
}
public boolean isAppend() {
return append;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder();
for (Component c: components) {
if (sb.length() > 0) {
sb.append(".");
}
sb.append(c.toString());
}
return sb.toString();
}
public static class Component {
private int index = -1;
private String name;
Component(String name) {
if (name.endsWith("]")) {
int pos = name.lastIndexOf("[", name.length() - 1);
if (pos == -1) {
throw new RuntimeException("Invalid attribute key: " + name + " (']' not allowed here)");
}
String idx = name.substring(pos + 1, name.length() - 1);
try {
index = Integer.parseInt(idx);
} catch (Exception e) {
throw new RuntimeException("Invalid attribute key: " + name + " (Invalid array index: '[" + idx + "]')");
}
this.name = name.substring(0, pos);
} else {
this.name = name;
}
}
public boolean isArray() {
return index >= 0;
}
public int getIndex() {
return index;
}
public String getName() {
return name;
}
@Override
public String toString() {
return name + (index != -1 ? "[" + index + "]" : "");
}
}
}

View file

@ -1,42 +0,0 @@
package org.keycloak.client.registration.cli.common;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class AttributeOperation {
private Type type;
private AttributeKey key;
private String value;
public AttributeOperation(Type type, String key) {
this(type, key, null);
}
public AttributeOperation(Type type, String key, String value) {
if (type == Type.DELETE && value != null) {
throw new IllegalArgumentException("When type is DELETE, value has to be null");
}
this.type = type;
this.key = new AttributeKey(key);
this.value = value;
}
public Type getType() {
return type;
}
public AttributeKey getKey() {
return key;
}
public String getValue() {
return value;
}
public enum Type {
SET,
DELETE
}
}

View file

@ -1,75 +0,0 @@
/*
* Copyright 2016 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.client.registration.cli.common;
import org.keycloak.representations.idm.ClientRepresentation;
import org.keycloak.representations.oidc.OIDCClientRepresentation;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class CmdStdinContext {
private EndpointType regType;
private ClientRepresentation client;
private OIDCClientRepresentation oidcClient;
private String content;
public CmdStdinContext() {}
public EndpointType getEndpointType() {
return regType;
}
public void setEndpointType(EndpointType regType) {
this.regType = regType;
}
public ClientRepresentation getClient() {
return client;
}
public void setClient(ClientRepresentation client) {
this.client = client;
}
public OIDCClientRepresentation getOidcClient() {
return oidcClient;
}
public void setOidcClient(OIDCClientRepresentation oidcClient) {
this.oidcClient = oidcClient;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public String getRegistrationAccessToken() {
if (client != null) {
return client.getRegistrationAccessToken();
} else if (oidcClient != null) {
return oidcClient.getRegistrationAccessToken();
}
return null;
}
}

View file

@ -1,113 +0,0 @@
package org.keycloak.client.registration.cli.common;
/**
* An iterator wrapping command line
*
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class ParsingContext {
private int offset;
private int pos = -1;
private String [] args;
public ParsingContext(String [] args) {
this(args, 0, -1);
}
public ParsingContext(String [] args, int offset) {
this(args, offset, -1);
}
public ParsingContext(String [] args, int offset, int pos) {
this.args = args.clone();
this.offset = offset;
this.pos = pos;
}
public boolean hasNext() {
return pos < args.length-1;
}
public boolean hasNext(int count) {
return pos < args.length - count;
}
public boolean hasPrevious() {
return pos > 0;
}
/**
* Get next argument
*
* @return Next argument or null if beyond the end of arguments
*/
public String next() {
if (hasNext()) {
return args[++pos];
} else {
pos = args.length;
return null;
}
}
/**
* Check that a next argument is available
*
* @return Next argument or RuntimeException if next argument is not available
*/
public String nextRequired() {
if (!hasNext()) {
throw new RuntimeException("Option " + current() + " requires a value");
}
return next();
}
/**
* Get next n-th argument
*
* @return Next n-th argument or null if beyond the end of arguments
*/
public String next(int n) {
if (hasNext(n)) {
pos += n;
return args[pos];
} else {
pos = args.length;
return null;
}
}
/**
* Get previous argument
*
* @return Previous argument or null if previous call was at the beginning of the arguments (pos == 0)
*/
public String previous() {
if (hasPrevious()) {
return args[--pos];
} else {
pos = -1;
return null;
}
}
/**
* Get current argument
*
* @return Current argument or null if current parsing position is beyond end, or before start
*/
public String current() {
if (pos < 0 || pos >= args.length) {
return null;
} else {
return args[pos];
}
}
public String [] getArgs() {
return args;
}
}

View file

@ -1,177 +0,0 @@
/*
* Copyright 2016 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.client.registration.cli.config;
import org.keycloak.util.JsonSerialization;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class ConfigData {
private String serverUrl;
private String realm;
private String truststore;
private String trustpass;
private Map<String, Map<String, RealmConfigData>> endpoints = new HashMap<>();
public String getServerUrl() {
return serverUrl;
}
public void setServerUrl(String serverUrl) {
this.serverUrl = serverUrl;
}
public String getRealm() {
return realm;
}
public void setRealm(String realm) {
this.realm = realm;
}
public String getTruststore() {
return truststore;
}
public void setTruststore(String truststore) {
this.truststore = truststore;
}
public String getTrustpass() {
return trustpass;
}
public void setTrustpass(String trustpass) {
this.trustpass = trustpass;
}
public Map<String, Map<String, RealmConfigData>> getEndpoints() {
return endpoints;
}
public void setEndpoints(Map<String, Map<String, RealmConfigData>> endpoints) {
for (Map.Entry<String, Map<String, RealmConfigData>> entry: endpoints.entrySet()) {
String endpoint = entry.getKey();
for (Map.Entry<String, RealmConfigData> sub: entry.getValue().entrySet()) {
RealmConfigData rdata = sub.getValue();
rdata.serverUrl(endpoint);
rdata.realm(sub.getKey());
}
}
this.endpoints = endpoints;
}
public RealmConfigData sessionRealmConfigData() {
if (serverUrl == null)
throw new RuntimeException("Illegal state - no current endpoint in config data");
if (realm == null)
throw new RuntimeException("Illegal state - no current realm in config data");
return ensureRealmConfigData(serverUrl, realm);
}
public RealmConfigData getRealmConfigData(String endpoint, String realm) {
Map<String, RealmConfigData> realmData = endpoints.get(endpoint);
if (realmData == null) {
return null;
}
return realmData.get(realm);
}
public RealmConfigData ensureRealmConfigData(String endpoint, String realm) {
RealmConfigData result = getRealmConfigData(endpoint, realm);
if (result == null) {
result = new RealmConfigData();
result.serverUrl(endpoint);
result.realm(realm);
setRealmConfigData(result);
}
return result;
}
public void setRealmConfigData(RealmConfigData data) {
Map<String, RealmConfigData> realm = endpoints.get(data.serverUrl());
if (realm == null) {
realm = new HashMap<>();
endpoints.put(data.serverUrl(), realm);
}
realm.put(data.realm(), data);
}
public void merge(ConfigData source) {
serverUrl = source.serverUrl;
realm = source.realm;
truststore = source.truststore;
trustpass = source.trustpass;
RealmConfigData current = getRealmConfigData(serverUrl, realm);
RealmConfigData sourceRealm = source.getRealmConfigData(serverUrl, realm);
if (current == null) {
setRealmConfigData(sourceRealm);
} else {
current.merge(sourceRealm);
}
}
public ConfigData deepcopy() {
ConfigData data = new ConfigData();
data.serverUrl = serverUrl;
data.realm = realm;
data.truststore = truststore;
data.trustpass = trustpass;
data.endpoints = new HashMap<>();
for (Map.Entry<String, Map<String, RealmConfigData>> item: endpoints.entrySet()) {
Map<String, RealmConfigData> nuitems = new HashMap<>();
Map<String, RealmConfigData> curitems = item.getValue();
if (curitems != null) {
for (Map.Entry<String, RealmConfigData> ditem : curitems.entrySet()) {
RealmConfigData nudata = ditem.getValue();
if (nudata != null) {
nuitems.put(ditem.getKey(), nudata.deepcopy());
}
}
data.endpoints.put(item.getKey(), nuitems);
}
}
return data;
}
@Override
public String toString() {
try {
return JsonSerialization.writeValueAsPrettyString(this);
} catch (IOException e) {
return super.toString() + " - Error: " + e.toString();
}
}
}

View file

@ -1,12 +0,0 @@
package org.keycloak.client.registration.cli.config;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public interface ConfigHandler {
void saveMergeConfig(ConfigUpdateOperation op);
ConfigData loadConfig();
}

View file

@ -1,10 +0,0 @@
package org.keycloak.client.registration.cli.config;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public interface ConfigUpdateOperation {
void update(ConfigData data);
}

View file

@ -1,119 +0,0 @@
package org.keycloak.client.registration.cli.config;
import org.keycloak.client.registration.cli.util.IoUtil;
import org.keycloak.util.JsonSerialization;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.FileLock;
import java.nio.channels.OverlappingFileLockException;
import java.nio.file.Path;
import java.nio.file.Paths;
import static org.keycloak.client.registration.cli.util.IoUtil.printErr;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class FileConfigHandler implements ConfigHandler {
private static final long MAX_SIZE = 10 * 1024 * 1024;
private static String configFile;
public static void setConfigFile(String filename) {
configFile = filename;
}
public static String getConfigFile() {
return configFile;
}
public ConfigData loadConfig() {
// for now just dumb impl ignoring file locks for read
File file = new File(configFile);
if (!file.isFile() || file.length() == 0) {
return new ConfigData();
}
try {
try (FileInputStream is = new FileInputStream(configFile)) {
return JsonSerialization.readValue(is, ConfigData.class);
}
} catch (IOException e) {
throw new RuntimeException("Failed to load " + configFile, e);
}
}
public static void ensureFile() {
Path path = null;
try {
path = Paths.get(new File(configFile).getAbsolutePath());
IoUtil.ensureFile(path);
} catch (Exception e) {
throw new RuntimeException("Failed to create config file: " + path, e);
}
}
public void saveMergeConfig(ConfigUpdateOperation op) {
try {
ensureFile();
try (RandomAccessFile file = new RandomAccessFile(new File(configFile), "rw")) {
FileChannel fileChannel = file.getChannel();
FileLock fileLock = null;
// lock file for write
int tryCount = 0;
do try {
fileLock = fileChannel.tryLock();
break;
} catch (OverlappingFileLockException e) {
// sleep a little, and try again
try {
Thread.sleep(100);
continue;
} catch (InterruptedException e1) {
throw new RuntimeException("Interrupted");
}
} while (tryCount++ < 10);
if (fileLock != null) {
try {
// load config from file
ConfigData config = new ConfigData();
long size = file.length();
if (size > MAX_SIZE) {
printErr("Config file " + configFile + " is too big. It will be overwritten.");
file.setLength(0);
} else if (size > 0){
byte[] buf = new byte[(int) size];
file.readFully(buf);
config = JsonSerialization.readValue(new ByteArrayInputStream(buf), ConfigData.class);
}
// update loaded config
op.update(config);
// save config to file
byte [] content = JsonSerialization.writeValueAsPrettyString(config).getBytes("utf-8");
file.seek(0);
file.write(content);
file.setLength(content.length);
} finally {
fileLock.release();
}
} else {
throw new RuntimeException("Failed to get lock on " + configFile);
}
}
} catch (IOException e) {
throw new RuntimeException("Failed to save " + configFile, e);
}
}
}

View file

@ -1,24 +0,0 @@
package org.keycloak.client.registration.cli.config;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class InMemoryConfigHandler implements ConfigHandler {
private ConfigData cached;
@Override
public void saveMergeConfig(ConfigUpdateOperation config) {
config.update(cached);
}
@Override
public ConfigData loadConfig() {
return cached;
}
public void setConfigData(ConfigData data) {
this.cached = data;
}
}

View file

@ -1,23 +0,0 @@
package org.keycloak.client.registration.cli.util;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class AttributeException extends RuntimeException {
private final String attrName;
public AttributeException(String attrName, String message) {
super(message);
this.attrName = attrName;
}
public AttributeException(String attrName, String message, Throwable th) {
super(message, th);
this.attrName = attrName;
}
public String getAttributeName() {
return attrName;
}
}

View file

@ -1,214 +0,0 @@
/*
* Copyright 2016 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.client.registration.cli.util;
import org.keycloak.client.registration.cli.config.ConfigData;
import org.keycloak.client.registration.cli.config.RealmConfigData;
import org.keycloak.common.util.KeystoreUtil;
import org.keycloak.common.util.Time;
import org.keycloak.jose.jws.JWSBuilder;
import org.keycloak.representations.AccessTokenResponse;
import org.keycloak.representations.JsonWebToken;
import org.keycloak.util.BasicAuthHelper;
import org.keycloak.util.JsonSerialization;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.security.KeyPair;
import java.util.UUID;
import static java.lang.System.currentTimeMillis;
import static org.keycloak.client.registration.cli.util.ConfigUtil.checkAuthInfo;
import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig;
import static org.keycloak.client.registration.cli.util.HttpUtil.APPLICATION_FORM_URL_ENCODED;
import static org.keycloak.client.registration.cli.util.HttpUtil.APPLICATION_JSON;
import static org.keycloak.client.registration.cli.util.HttpUtil.doPost;
import static org.keycloak.client.registration.cli.util.HttpUtil.urlencode;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class AuthUtil {
public static String ensureToken(ConfigData config) {
checkAuthInfo(config);
RealmConfigData realmConfig = config.sessionRealmConfigData();
long now = currentTimeMillis();
// check expires of access_token against time
// if it's less than 5s to expiry, renew it
if (realmConfig.getExpiresAt() - now < 5000) {
// check refresh_token against expiry time
// if it's less than 5s to expiry, fail with credentials expired
if (realmConfig.getRefreshExpiresAt() != null && realmConfig.getRefreshExpiresAt() - now < 5000) {
throw new RuntimeException("Session has expired. Login again with '" + OsUtil.CMD + " config credentials'");
}
if (realmConfig.getSigExpiresAt() != null && realmConfig.getSigExpiresAt() - now < 5000) {
throw new RuntimeException("Session has expired. Login again with '" + OsUtil.CMD + " config credentials'");
}
try {
String authorization = null;
StringBuilder body = new StringBuilder();
if (realmConfig.getRefreshToken() != null) {
body.append("grant_type=refresh_token")
.append("&refresh_token=").append(realmConfig.getRefreshToken());
} else {
body.append("grant_type=").append(realmConfig.getGrantTypeForAuthentication());
}
body.append("&client_id=").append(urlencode(realmConfig.getClientId()));
if (realmConfig.getSigningToken() != null) {
body.append("&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
.append("&client_assertion=").append(realmConfig.getSigningToken());
} else if (realmConfig.getSecret() != null) {
authorization = BasicAuthHelper.createHeader(realmConfig.getClientId(), realmConfig.getSecret());
}
InputStream result = doPost(realmConfig.serverUrl() + "/realms/" + realmConfig.realm() + "/protocol/openid-connect/token",
APPLICATION_FORM_URL_ENCODED, APPLICATION_JSON, body.toString(), authorization);
AccessTokenResponse token = JsonSerialization.readValue(result, AccessTokenResponse.class);
saveMergeConfig(cfg -> {
RealmConfigData realmData = cfg.sessionRealmConfigData();
realmData.setToken(token.getToken());
realmData.setRefreshToken(token.getRefreshToken());
realmData.setExpiresAt(currentTimeMillis() + token.getExpiresIn() * 1000);
if (token.getRefreshToken() != null) {
realmData.setRefreshExpiresAt(currentTimeMillis() + token.getRefreshExpiresIn() * 1000);
}
});
return token.getToken();
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Unexpected error", e);
} catch (IOException e) {
throw new RuntimeException("Failed to read Refresh Token response", e);
}
}
return realmConfig.getToken();
}
public static AccessTokenResponse getAuthTokens(String server, String realm, String user, String password, String clientId) {
StringBuilder body = new StringBuilder();
try {
body.append("grant_type=password")
.append("&username=").append(urlencode(user))
.append("&password=").append(urlencode(password))
.append("&client_id=").append(urlencode(clientId));
InputStream result = doPost(server + "/realms/" + realm + "/protocol/openid-connect/token",
APPLICATION_FORM_URL_ENCODED, APPLICATION_JSON, body.toString(), null);
return JsonSerialization.readValue(result, AccessTokenResponse.class);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Unexpected error: ", e);
} catch (IOException e) {
throw new RuntimeException("Error receiving response: ", e);
}
}
public static AccessTokenResponse getAuthTokensByJWT(String server, String realm, String user, String password, String clientId, String signedRequestToken) {
StringBuilder body = new StringBuilder();
try {
body.append("client_id=").append(urlencode(clientId))
.append("&client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
.append("&client_assertion=").append(signedRequestToken);
if (user != null) {
if (password == null) {
throw new RuntimeException("No password specified");
}
body.append("&grant_type=password")
.append("&username=").append(urlencode(user))
.append("&password=").append(urlencode(password));
} else {
body.append("&grant_type=client_credentials");
}
InputStream result = doPost(server + "/realms/" + realm + "/protocol/openid-connect/token",
APPLICATION_FORM_URL_ENCODED, APPLICATION_JSON, body.toString(), null);
return JsonSerialization.readValue(result, AccessTokenResponse.class);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Unexpected error: ", e);
} catch (IOException e) {
throw new RuntimeException("Error receiving response: ", e);
}
}
public static AccessTokenResponse getAuthTokensBySecret(String server, String realm, String user, String password, String clientId, String secret) {
StringBuilder body = new StringBuilder();
try {
if (user != null) {
if (password == null) {
throw new RuntimeException("No password specified");
}
body.append("client_id=").append(urlencode(clientId))
.append("&grant_type=password")
.append("&username=").append(urlencode(user))
.append("&password=").append(urlencode(password));
} else {
body.append("grant_type=client_credentials");
}
InputStream result = doPost(server + "/realms/" + realm + "/protocol/openid-connect/token",
APPLICATION_FORM_URL_ENCODED, APPLICATION_JSON, body.toString(), BasicAuthHelper.createHeader(clientId, secret));
return JsonSerialization.readValue(result, AccessTokenResponse.class);
} catch (UnsupportedEncodingException e) {
throw new RuntimeException("Unexpected error: ", e);
} catch (IOException e) {
throw new RuntimeException("Error receiving response: ", e);
}
}
public static String getSignedRequestToken(String keystore, String storePass, String keyPass, String alias, int sigLifetime, String clientId, String realmInfoUrl) {
KeystoreUtil.KeystoreFormat keystoreType = Enum.valueOf(KeystoreUtil.KeystoreFormat.class, KeystoreUtil.getKeystoreType(null, keystore, KeystoreUtil.KeystoreFormat.JKS.toString()));
KeyPair keypair = KeystoreUtil.loadKeyPairFromKeystore(keystore, storePass, keyPass, alias, keystoreType);
JsonWebToken reqToken = new JsonWebToken();
reqToken.id(UUID.randomUUID().toString());
reqToken.issuer(clientId);
reqToken.subject(clientId);
reqToken.audience(realmInfoUrl);
int now = Time.currentTime();
reqToken.issuedAt(now);
reqToken.expiration(now + sigLifetime);
reqToken.notBefore(now);
String signedRequestToken = new JWSBuilder()
.jsonContent(reqToken)
.rsa256(keypair.getPrivate());
return signedRequestToken;
}
}

View file

@ -1,60 +0,0 @@
/*
* Copyright 2022 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.client.registration.cli.util;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.stream.Stream;
/**
* @author <a href="mailto:mposolda@redhat.com">Marek Posolda</a>
*/
public class ClassLoaderUtil {
/**
* Detect if BC FIPS jars are present in the given directory. Return classloader with appropriate JARS based on that
*/
public static ClassLoader resolveClassLoader(String libDir) {
File[] jarsInDir = new File(libDir).listFiles(file -> file.getName().endsWith(".jar"));
// Detect if BC FIPS jars are present in the "client/lib" directory
boolean bcFipsJarPresent = Stream.of(jarsInDir).anyMatch(file -> file.getName().startsWith("bc-fips"));
String[] validJarPrefixes = bcFipsJarPresent ? new String[] {"keycloak-crypto-fips1402", "bc-fips", "bctls-fips"} : new String[] {"keycloak-crypto-default", "bcprov-jdk18on"};
URL[] usedJars = Stream.of(jarsInDir)
.filter(file -> {
for (String prefix : validJarPrefixes) {
if (file.getName().startsWith(prefix + "-")) return true;
}
return false;
})
.map(file -> {
try {
return file.toURI().toURL();
} catch (MalformedURLException ex) {
throw new IllegalStateException("Error when converting file into URL. Please check the files in the directory " + jarsInDir, ex);
}
}).toArray(URL[]::new);
return new URLClassLoader(usedJars, ClassLoaderUtil.class.getClassLoader());
}
}

View file

@ -1,122 +0,0 @@
/*
* Copyright 2016 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.client.registration.cli.util;
import org.keycloak.OAuth2Constants;
import org.keycloak.client.registration.cli.config.ConfigData;
import org.keycloak.client.registration.cli.config.ConfigHandler;
import org.keycloak.client.registration.cli.config.ConfigUpdateOperation;
import org.keycloak.client.registration.cli.config.InMemoryConfigHandler;
import org.keycloak.client.registration.cli.config.RealmConfigData;
import org.keycloak.representations.AccessTokenResponse;
/**
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
*/
public class ConfigUtil {
public static final String DEFAULT_CONFIG_FILE_STRING = OsUtil.OS_ARCH.isWindows() ? "%HOMEDRIVE%%HOMEPATH%\\.keycloak\\kcreg.config" : "~/.keycloak/kcreg.config";
public static final String DEFAULT_CONFIG_FILE_PATH = System.getProperty("user.home") + "/.keycloak/kcreg.config";
private static ConfigHandler handler;
public static ConfigHandler getHandler() {
return handler;
}
public static void setHandler(ConfigHandler handler) {
ConfigUtil.handler = handler;
}
public static String getRegistrationToken(RealmConfigData data, String clientId) {
String token = data.getClients().get(clientId);
return token == null || token.length() == 0 ? null : token;
}
public static void setRegistrationToken(RealmConfigData data, String clientId, String token) {
data.getClients().put(clientId, token == null ? "" : token);
}
public static void saveTokens(AccessTokenResponse tokens, String endpoint, String realm, String clientId, String signKey, Long sigExpiresAt, String secret,
String grantTypeForAuthentication) {
handler.saveMergeConfig(config -> {
config.setServerUrl(endpoint);
config.setRealm(realm);
RealmConfigData realmConfig = config.ensureRealmConfigData(endpoint, realm);
realmConfig.setToken(tokens.getToken());
realmConfig.setRefreshToken(tokens.getRefreshToken());
realmConfig.setSigningToken(signKey);
realmConfig.setSecret(secret);
realmConfig.setExpiresAt(System.currentTimeMillis() + tokens.getExpiresIn() * 1000);
if (realmConfig.getRefreshToken() != null) {
realmConfig.setRefreshExpiresAt(tokens.getRefreshExpiresIn() == 0 ?
Long.MAX_VALUE : System.currentTimeMillis() + tokens.getRefreshExpiresIn() * 1000);
}
realmConfig.setSigExpiresAt(sigExpiresAt);
realmConfig.setClientId(clientId);
realmConfig.setGrantTypeForAuthentication(grantTypeForAuthentication);
});
}
public static void checkServerInfo(ConfigData config) {
if (config.getServerUrl() == null || config.getRealm() == null) {
throw new RuntimeException("No server or realm specified. Use --server, --realm, or '" + OsUtil.CMD + " config credentials'.");
}
}
public static void checkAuthInfo(ConfigData config) {
checkServerInfo(config);
}
public static boolean credentialsAvailable(ConfigData config) {
// Just supporting "client_credentials" grant type for the case when refresh token is missing
boolean credsAvailable = config.getServerUrl() != null && config.getRealm() != null
&& config.sessionRealmConfigData() != null &&
(config.sessionRealmConfigData().getRefreshToken() != null || (config.sessionRealmConfigData().getToken() != null && OAuth2Constants.CLIENT_CREDENTIALS.equals(config.sessionRealmConfigData().getGrantTypeForAuthentication())));
return credsAvailable;
}
public static ConfigData loadConfig() {
if (handler == null) {
throw new RuntimeException("No ConfigHandler set");
}
return handler.loadConfig();
}
public static void saveMergeConfig(ConfigUpdateOperation op) {
if (handler == null) {
throw new RuntimeException("No ConfigHandler set");
}
handler.saveMergeConfig(op);
}
public static void setupInMemoryHandler(ConfigData config) {
InMemoryConfigHandler memhandler = null;
if (handler instanceof InMemoryConfigHandler) {
memhandler = (InMemoryConfigHandler) handler;
} else {
memhandler = new InMemoryConfigHandler();
handler = memhandler;
}
memhandler.setConfigData(config);
}
}

Some files were not shown because too many files have changed in this diff Show more