mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-25 16:42:34 +00:00
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:
committed by
Pedro Igor
parent
808926b63e
commit
0be34d64e7
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -14,24 +14,14 @@
|
||||
* 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;
|
||||
package org.keycloak.client.cli.common;
|
||||
|
||||
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;
|
||||
import static org.keycloak.client.cli.util.IoUtil.printOut;
|
||||
|
||||
public abstract class AbstractGlobalOptionsCmd implements Runnable {
|
||||
public abstract class BaseGlobalOptionsCmd implements Runnable {
|
||||
|
||||
@Option(names = "--help",
|
||||
description = "Print command specific help")
|
||||
@@ -59,37 +49,7 @@ public abstract class AbstractGlobalOptionsCmd implements Runnable {
|
||||
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);
|
||||
}
|
||||
}
|
||||
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,11 +14,15 @@
|
||||
* 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.util.JsonSerialization;
|
||||
|
||||
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>
|
||||
@@ -47,6 +51,11 @@ 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;
|
||||
@@ -136,6 +145,18 @@ public class RealmConfigData {
|
||||
this.sigExpiresAt = sigExpiresAt;
|
||||
}
|
||||
|
||||
public String getInitialToken() {
|
||||
return initialToken;
|
||||
}
|
||||
|
||||
public void setInitialToken(String initialToken) {
|
||||
this.initialToken = initialToken;
|
||||
}
|
||||
|
||||
public Map<String, String> getClients() {
|
||||
return clients;
|
||||
}
|
||||
|
||||
public void merge(RealmConfigData source) {
|
||||
serverUrl = source.serverUrl;
|
||||
realm = source.realm;
|
||||
@@ -148,6 +169,26 @@ public class RealmConfigData {
|
||||
expiresAt = source.expiresAt;
|
||||
refreshExpiresAt = source.refreshExpiresAt;
|
||||
sigExpiresAt = source.sigExpiresAt;
|
||||
initialToken = source.initialToken;
|
||||
|
||||
mergeClients(source);
|
||||
}
|
||||
|
||||
private void mergeClients(RealmConfigData source) {
|
||||
if (source.clients != null) {
|
||||
if (clients == null) {
|
||||
clients = source.clients;
|
||||
} else {
|
||||
for (String key: source.clients.keySet()) {
|
||||
String val = source.clients.get(key);
|
||||
if (!"".equals(val)) {
|
||||
clients.put(key, val);
|
||||
} else {
|
||||
clients.remove(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void mergeRefreshTokens(RealmConfigData source) {
|
||||
@@ -179,6 +220,8 @@ public class RealmConfigData {
|
||||
data.expiresAt = expiresAt;
|
||||
data.refreshExpiresAt = refreshExpiresAt;
|
||||
data.sigExpiresAt = sigExpiresAt;
|
||||
data.initialToken = initialToken;
|
||||
data.clients = new LinkedHashMap<>(clients);
|
||||
return data;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user