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:
parent
808926b63e
commit
0be34d64e7
121 changed files with 1569 additions and 3927 deletions
|
@ -14,49 +14,57 @@
|
|||
* 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) {
|
||||
|
||||
|
||||
String content = readFileOrStdin(file).trim();
|
||||
JsonNode result = null;
|
||||
|
||||
|
||||
if (content.length() == 0) {
|
||||
throw new RuntimeException("Document provided by --file option is empty");
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
result = JsonSerialization.readValue(content, JsonNode.class);
|
||||
} catch (JsonParseException e) {
|
||||
|
@ -66,7 +74,7 @@ public class ParseUtil {
|
|||
} catch (Exception e) {
|
||||
throw new RuntimeException("Not a valid JSON document", e);
|
||||
}
|
||||
|
||||
|
||||
CmdStdinContext<JsonNode> ctx = new CmdStdinContext<>();
|
||||
ctx.setContent(content);
|
||||
ctx.setResult(result);
|
||||
|
@ -74,33 +82,33 @@ public class ParseUtil {
|
|||
}
|
||||
|
||||
public static <T> CmdStdinContext<JsonNode> mergeAttributes(CmdStdinContext<JsonNode> ctx, ObjectNode newObject, List<AttributeOperation> attrs) {
|
||||
|
||||
|
||||
JsonNode node = ctx.getResult();
|
||||
if (node != null && !node.isObject()) {
|
||||
throw new RuntimeException("Not a JSON object: " + node);
|
||||
}
|
||||
ObjectNode result = (ObjectNode) node;
|
||||
try {
|
||||
|
||||
|
||||
if (result == null) {
|
||||
result = newObject;
|
||||
}
|
||||
|
||||
|
||||
if (result == null) {
|
||||
throw new RuntimeException("Failed to set attribute(s) - no target object");
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
setAttributes(result, attrs);
|
||||
} catch (AttributeException e) {
|
||||
throw new RuntimeException("Failed to set attribute '" + e.getAttributeName() + "' on document type '" + result.getClass().getName() + "'", e);
|
||||
}
|
||||
ctx.setContent(JsonSerialization.writeValueAsString(result));
|
||||
|
||||
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to merge attributes with configuration from file", e);
|
||||
}
|
||||
|
||||
|
||||
ctx.setResult(result);
|
||||
return ctx;
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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"});
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -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>
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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() {
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -15,7 +15,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.client.admin.cli;
|
||||
package org.keycloak.client.cli.common;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
|
@ -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;
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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
|
|
@ -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;
|
|
@ -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>
|
|
@ -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 {
|
|
@ -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;
|
|
@ -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
|
|
@ -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>
|
||||
*/
|
|
@ -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>
|
|
@ -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;
|
||||
|
|
@ -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>
|
|
@ -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;
|
||||
|
|
@ -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;
|
|
@ -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>
|
|
@ -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);
|
||||
}
|
|
@ -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;
|
|
@ -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>
|
|
@ -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:\\>" : "$";
|
||||
|
|
@ -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>
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
|
@ -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;
|
||||
|
|
@ -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" +
|
|
@ -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;
|
|
@ -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>
|
|
@ -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,46 +26,73 @@ 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) {
|
||||
|
||||
|
||||
String content = readFileOrStdin(file).trim();
|
||||
ClientRepresentation client = null;
|
||||
OIDCClientRepresentation oidcClient = null;
|
||||
|
||||
|
||||
if (type == null) {
|
||||
// guess the correct endpoint from content of the file
|
||||
if (content.startsWith("<")) {
|
||||
|
@ -80,13 +104,13 @@ public class ParseUtil {
|
|||
try {
|
||||
client = JsonSerialization.readValue(content, ClientRepresentation.class);
|
||||
type = EndpointType.DEFAULT;
|
||||
|
||||
|
||||
} catch (JsonParseException e) {
|
||||
throw new RuntimeException("Failed to read the input document as JSON: " + e.getMessage(), e);
|
||||
} catch (Exception ignored) {
|
||||
// deliberately not logged
|
||||
}
|
||||
|
||||
|
||||
if (client == null) {
|
||||
// try parse as OIDCClientRepresentation
|
||||
try {
|
||||
|
@ -98,14 +122,14 @@ public class ParseUtil {
|
|||
throw new RuntimeException("Failed to read the input document as JSON", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
} else if (content.length() == 0) {
|
||||
throw new RuntimeException("Document provided by --file option is empty");
|
||||
} else {
|
||||
throw new RuntimeException("Unable to determine input document type. Use -e TYPE to specify the registration endpoint to use");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// check content type, making sure it can be parsed into .json if it's not saml xml
|
||||
if (content != null) {
|
||||
try {
|
||||
|
@ -122,7 +146,7 @@ public class ParseUtil {
|
|||
throw new RuntimeException("Not a valid JSON document", e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
CmdStdinContext ctx = new CmdStdinContext();
|
||||
ctx.setEndpointType(type);
|
||||
ctx.setContent(content);
|
||||
|
@ -158,7 +182,7 @@ public class ParseUtil {
|
|||
} catch (IOException e) {
|
||||
throw new RuntimeException("Failed to merge set attributes with configuration from file", e);
|
||||
}
|
||||
|
||||
|
||||
ctx.setContent(content);
|
||||
ctx.setClient(client);
|
||||
ctx.setOidcClient(oidcClient);
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,7 +1,5 @@
|
|||
package org.keycloak.client.registration.cli;
|
||||
|
||||
import org.keycloak.client.registration.cli.common.EndpointType;
|
||||
|
||||
import picocli.CommandLine.ITypeConverter;
|
||||
|
||||
/**
|
||||
|
|
|
@ -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;
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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')");
|
||||
|
|
|
@ -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')");
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)");
|
||||
|
|
|
@ -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)");
|
||||
|
|
|
@ -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)");
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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: ");
|
||||
|
|
|
@ -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)");
|
||||
|
|
|
@ -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)");
|
||||
|
|
|
@ -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 + "]" : "");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
|
@ -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
Loading…
Reference in a new issue