fix: replaces aesh with picocli (#28276)
* fix: replaces aesh with picocli closes: #28275 Signed-off-by: Steve Hawkins <shawkins@redhat.com> * fix: replaces aesh with picocli closes: #28275 Signed-off-by: Steve Hawkins <shawkins@redhat.com> --------- Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
parent
b428e7dc81
commit
58398d1f69
31 changed files with 933 additions and 1591 deletions
|
@ -69,9 +69,9 @@ Options `cache`, `cache-stack`, and `cache-config-file` are no longer build opti
|
||||||
This eliminates the need to execute the build phase and rebuild your image due to them.
|
This eliminates the need to execute the build phase and rebuild your image due to them.
|
||||||
Be aware that they will not be recognized during the `build` phase, so you need to remove them.
|
Be aware that they will not be recognized during the `build` phase, so you need to remove them.
|
||||||
|
|
||||||
= kcadm Changes
|
= kcadm and kcreg changes
|
||||||
|
|
||||||
How kcadm parses and handles options and parameters has changed. Error messages from usage errors, the wrong option or parameter, may be slightly different than previous versions. Also usage errors will have an exit code of 2 instead of 1.
|
How kcadm and kcreg parse and handle options and parameters has changed. Error messages from usage errors, the wrong option or parameter, may be slightly different than previous versions. Also usage errors will have an exit code of 2 instead of 1.
|
||||||
|
|
||||||
= Removing custom user attribute indexes
|
= Removing custom user attribute indexes
|
||||||
|
|
||||||
|
|
|
@ -31,8 +31,12 @@
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.aesh</groupId>
|
<groupId>info.picocli</groupId>
|
||||||
<artifactId>aesh</artifactId>
|
<artifactId>picocli</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.keycloak</groupId>
|
||||||
|
<artifactId>keycloak-admin-cli</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<groupId>org.keycloak</groupId>
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
package org.keycloak.client.registration.cli;
|
||||||
|
|
||||||
|
import org.keycloak.client.registration.cli.common.EndpointType;
|
||||||
|
|
||||||
|
import picocli.CommandLine.ITypeConverter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
|
*/
|
||||||
|
public class EndpointTypeConverter implements ITypeConverter<EndpointType> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EndpointType convert(String value) throws Exception {
|
||||||
|
return EndpointType.of(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
package org.keycloak.client.registration.cli;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
|
*/
|
||||||
|
public class Globals {
|
||||||
|
|
||||||
|
public static boolean dumpTrace = false;
|
||||||
|
|
||||||
|
public static boolean help = false;
|
||||||
|
|
||||||
|
}
|
|
@ -1,21 +1,16 @@
|
||||||
package org.keycloak.client.registration.cli;
|
package org.keycloak.client.registration.cli;
|
||||||
|
|
||||||
import org.jboss.aesh.console.AeshConsoleBuilder;
|
import org.keycloak.client.admin.cli.ExecutionExceptionHandler;
|
||||||
import org.jboss.aesh.console.AeshConsoleImpl;
|
import org.keycloak.client.admin.cli.ShortErrorMessageHandler;
|
||||||
import org.jboss.aesh.console.Prompt;
|
|
||||||
import org.jboss.aesh.console.command.registry.AeshCommandRegistryBuilder;
|
|
||||||
import org.jboss.aesh.console.command.registry.CommandRegistry;
|
|
||||||
import org.jboss.aesh.console.settings.Settings;
|
|
||||||
import org.jboss.aesh.console.settings.SettingsBuilder;
|
|
||||||
import org.keycloak.client.registration.cli.aesh.AeshEnhancer;
|
|
||||||
import org.keycloak.client.registration.cli.aesh.ValveInputStream;
|
|
||||||
import org.keycloak.client.registration.cli.aesh.Globals;
|
|
||||||
import org.keycloak.client.registration.cli.commands.KcRegCmd;
|
import org.keycloak.client.registration.cli.commands.KcRegCmd;
|
||||||
import org.keycloak.client.registration.cli.util.ClassLoaderUtil;
|
import org.keycloak.client.registration.cli.util.ClassLoaderUtil;
|
||||||
|
import org.keycloak.client.registration.cli.util.OsUtil;
|
||||||
import org.keycloak.common.crypto.CryptoIntegration;
|
import org.keycloak.common.crypto.CryptoIntegration;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.io.PrintWriter;
|
||||||
import java.util.Arrays;
|
|
||||||
|
import picocli.CommandLine;
|
||||||
|
import picocli.CommandLine.Model.CommandSpec;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
|
@ -32,54 +27,20 @@ public class KcRegMain {
|
||||||
|
|
||||||
CryptoIntegration.init(cl);
|
CryptoIntegration.init(cl);
|
||||||
|
|
||||||
Globals.stdin = new ValveInputStream();
|
CommandLine cli = createCommandLine();
|
||||||
|
int exitCode = cli.execute(args);
|
||||||
|
System.exit(exitCode);
|
||||||
|
}
|
||||||
|
|
||||||
Settings settings = new SettingsBuilder()
|
public static CommandLine createCommandLine() {
|
||||||
.logging(false)
|
CommandSpec spec = CommandSpec.forAnnotatedObject(new KcRegCmd()).name(OsUtil.CMD);
|
||||||
.readInputrc(false)
|
|
||||||
.disableCompletion(true)
|
|
||||||
.disableHistory(true)
|
|
||||||
.enableAlias(false)
|
|
||||||
.enableExport(false)
|
|
||||||
.inputStream(Globals.stdin)
|
|
||||||
.create();
|
|
||||||
|
|
||||||
CommandRegistry registry = new AeshCommandRegistryBuilder()
|
CommandLine cmd = new CommandLine(spec);
|
||||||
.command(KcRegCmd.class)
|
|
||||||
.create();
|
|
||||||
|
|
||||||
AeshConsoleImpl console = (AeshConsoleImpl) new AeshConsoleBuilder()
|
cmd.setExecutionExceptionHandler(new ExecutionExceptionHandler());
|
||||||
.settings(settings)
|
cmd.setParameterExceptionHandler(new ShortErrorMessageHandler());
|
||||||
.commandRegistry(registry)
|
cmd.setErr(new PrintWriter(System.err, true));
|
||||||
.prompt(new Prompt(""))
|
|
||||||
.create();
|
|
||||||
|
|
||||||
AeshEnhancer.enhance(console);
|
return cmd;
|
||||||
|
|
||||||
// work around parser issues with quotes and brackets
|
|
||||||
ArrayList<String> arguments = new ArrayList<>();
|
|
||||||
arguments.add("kcreg");
|
|
||||||
arguments.addAll(Arrays.asList(args));
|
|
||||||
Globals.args = arguments;
|
|
||||||
|
|
||||||
StringBuilder b = new StringBuilder();
|
|
||||||
for (String s : args) {
|
|
||||||
// quote if necessary
|
|
||||||
boolean needQuote = false;
|
|
||||||
needQuote = s.indexOf(' ') != -1 || s.indexOf('\"') != -1 || s.indexOf('\'') != -1;
|
|
||||||
b.append(' ');
|
|
||||||
if (needQuote) {
|
|
||||||
b.append('\'');
|
|
||||||
}
|
|
||||||
b.append(s);
|
|
||||||
if (needQuote) {
|
|
||||||
b.append('\'');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.setEcho(false);
|
|
||||||
|
|
||||||
console.execute("kcreg" + b.toString());
|
|
||||||
|
|
||||||
console.start();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,101 +0,0 @@
|
||||||
package org.keycloak.client.registration.cli.aesh;
|
|
||||||
|
|
||||||
import org.jboss.aesh.cl.parser.OptionParserException;
|
|
||||||
import org.jboss.aesh.cl.result.ResultHandler;
|
|
||||||
import org.jboss.aesh.console.AeshConsoleCallback;
|
|
||||||
import org.jboss.aesh.console.AeshConsoleImpl;
|
|
||||||
import org.jboss.aesh.console.ConsoleOperation;
|
|
||||||
import org.jboss.aesh.console.command.CommandNotFoundException;
|
|
||||||
import org.jboss.aesh.console.command.CommandResult;
|
|
||||||
import org.jboss.aesh.console.command.container.CommandContainer;
|
|
||||||
import org.jboss.aesh.console.command.container.CommandContainerResult;
|
|
||||||
import org.jboss.aesh.console.command.invocation.AeshCommandInvocation;
|
|
||||||
import org.jboss.aesh.console.command.invocation.AeshCommandInvocationProvider;
|
|
||||||
import org.jboss.aesh.parser.AeshLine;
|
|
||||||
import org.jboss.aesh.parser.ParserStatus;
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
class AeshConsoleCallbackImpl extends AeshConsoleCallback {
|
|
||||||
|
|
||||||
private final AeshConsoleImpl console;
|
|
||||||
private CommandResult result;
|
|
||||||
|
|
||||||
AeshConsoleCallbackImpl(AeshConsoleImpl aeshConsole) {
|
|
||||||
this.console = aeshConsole;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public int execute(ConsoleOperation output) throws InterruptedException {
|
|
||||||
if (output != null && output.getBuffer().trim().length() > 0) {
|
|
||||||
ResultHandler resultHandler = null;
|
|
||||||
//AeshLine aeshLine = Parser.findAllWords(output.getBuffer());
|
|
||||||
AeshLine aeshLine = new AeshLine(output.getBuffer(), Globals.args, ParserStatus.OK, "");
|
|
||||||
try (CommandContainer commandContainer = getCommand(output, aeshLine)) {
|
|
||||||
resultHandler = commandContainer.getParser().getProcessedCommand().getResultHandler();
|
|
||||||
CommandContainerResult ccResult =
|
|
||||||
commandContainer.executeCommand(aeshLine, console.getInvocationProviders(), console.getAeshContext(),
|
|
||||||
new AeshCommandInvocationProvider().enhanceCommandInvocation(
|
|
||||||
new AeshCommandInvocation(console,
|
|
||||||
output.getControlOperator(), output.getPid(), this)));
|
|
||||||
|
|
||||||
result = ccResult.getCommandResult();
|
|
||||||
|
|
||||||
if(result == CommandResult.SUCCESS && resultHandler != null)
|
|
||||||
resultHandler.onSuccess();
|
|
||||||
else if(resultHandler != null)
|
|
||||||
resultHandler.onFailure(result);
|
|
||||||
|
|
||||||
if (result == CommandResult.FAILURE) {
|
|
||||||
// we assume the command has already output any error messages
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
console.stop();
|
|
||||||
|
|
||||||
if (e instanceof OptionParserException) {
|
|
||||||
System.err.println("Unknown command: " + aeshLine.getWords().get(0));
|
|
||||||
} else {
|
|
||||||
System.err.println(e.getMessage());
|
|
||||||
}
|
|
||||||
if (Globals.dumpTrace) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
System.exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// empty line
|
|
||||||
else if (output != null) {
|
|
||||||
result = CommandResult.FAILURE;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
//stop();
|
|
||||||
result = CommandResult.FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result == CommandResult.SUCCESS) {
|
|
||||||
return 0;
|
|
||||||
} else {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private CommandContainer getCommand(ConsoleOperation output, AeshLine aeshLine) throws CommandNotFoundException {
|
|
||||||
Method m;
|
|
||||||
try {
|
|
||||||
m = console.getClass().getDeclaredMethod("getCommand", AeshLine.class, String.class);
|
|
||||||
} catch (NoSuchMethodException e) {
|
|
||||||
throw new RuntimeException("Unexpected error: ", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
m.setAccessible(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
return (CommandContainer) m.invoke(console, aeshLine, output.getBuffer());
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Unexpected error: ", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,25 +0,0 @@
|
||||||
package org.keycloak.client.registration.cli.aesh;
|
|
||||||
|
|
||||||
import org.jboss.aesh.console.AeshConsoleImpl;
|
|
||||||
import org.jboss.aesh.console.Console;
|
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
|
||||||
*/
|
|
||||||
public class AeshEnhancer {
|
|
||||||
|
|
||||||
public static void enhance(AeshConsoleImpl console) {
|
|
||||||
try {
|
|
||||||
Globals.stdin.setConsole(console);
|
|
||||||
|
|
||||||
Field field = AeshConsoleImpl.class.getDeclaredField("console");
|
|
||||||
field.setAccessible(true);
|
|
||||||
Console internalConsole = (Console) field.get(console);
|
|
||||||
internalConsole.setConsoleCallback(new AeshConsoleCallbackImpl(console));
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("Failed to install Aesh enhancement", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package org.keycloak.client.registration.cli.aesh;
|
|
||||||
|
|
||||||
import org.jboss.aesh.cl.converter.Converter;
|
|
||||||
import org.jboss.aesh.cl.validator.OptionValidatorException;
|
|
||||||
import org.jboss.aesh.console.command.converter.ConverterInvocation;
|
|
||||||
import org.keycloak.client.registration.cli.common.EndpointType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
|
||||||
*/
|
|
||||||
public class EndpointTypeConverter implements Converter<EndpointType, ConverterInvocation> {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public EndpointType convert(ConverterInvocation converterInvocation) throws OptionValidatorException {
|
|
||||||
return EndpointType.of(converterInvocation.getInput());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
package org.keycloak.client.registration.cli.aesh;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
|
||||||
*/
|
|
||||||
public class Globals {
|
|
||||||
|
|
||||||
public static boolean dumpTrace = false;
|
|
||||||
|
|
||||||
public static ValveInputStream stdin;
|
|
||||||
|
|
||||||
public static List<String> args;
|
|
||||||
}
|
|
|
@ -1,71 +0,0 @@
|
||||||
package org.keycloak.client.registration.cli.aesh;
|
|
||||||
|
|
||||||
import org.jboss.aesh.console.AeshConsoleImpl;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InterruptedIOException;
|
|
||||||
import java.util.concurrent.BlockingQueue;
|
|
||||||
import java.util.concurrent.LinkedBlockingQueue;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* This stream blocks and waits, until there is some stream in the queue.
|
|
||||||
* It reads all streams from the queue, and then blocks until it receives more.
|
|
||||||
*/
|
|
||||||
public class ValveInputStream extends InputStream {
|
|
||||||
|
|
||||||
private BlockingQueue<InputStream> queue = new LinkedBlockingQueue<>(10);
|
|
||||||
|
|
||||||
private InputStream current;
|
|
||||||
|
|
||||||
private AeshConsoleImpl console;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int read() throws IOException {
|
|
||||||
if (current == null) {
|
|
||||||
try {
|
|
||||||
current = queue.take();
|
|
||||||
} catch (InterruptedException e) {
|
|
||||||
throw new InterruptedIOException("Signalled to exit");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int c = current.read();
|
|
||||||
if (c == -1) {
|
|
||||||
//current = null;
|
|
||||||
if (console != null) {
|
|
||||||
console.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* For some reason AeshInputStream wants to do blocking read of whole buffers, which for stdin
|
|
||||||
* results in blocked input.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public int read(byte b[], int off, int len) throws IOException {
|
|
||||||
int c = read();
|
|
||||||
if (c == -1) {
|
|
||||||
return c;
|
|
||||||
}
|
|
||||||
b[off] = (byte) c;
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setInputStream(InputStream is) {
|
|
||||||
if (queue.contains(is)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
queue.add(is);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setConsole(AeshConsoleImpl console) {
|
|
||||||
this.console = console;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isStdinAvailable() {
|
|
||||||
return console.isRunning();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +1,5 @@
|
||||||
package org.keycloak.client.registration.cli.commands;
|
package org.keycloak.client.registration.cli.commands;
|
||||||
|
|
||||||
import org.jboss.aesh.cl.Option;
|
|
||||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.client.registration.cli.config.ConfigData;
|
import org.keycloak.client.registration.cli.config.ConfigData;
|
||||||
import org.keycloak.client.registration.cli.config.ConfigHandler;
|
import org.keycloak.client.registration.cli.config.ConfigHandler;
|
||||||
|
@ -14,6 +12,8 @@ import org.keycloak.client.registration.cli.util.IoUtil;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
|
import picocli.CommandLine.Option;
|
||||||
|
|
||||||
import static org.keycloak.client.registration.cli.config.FileConfigHandler.setConfigFile;
|
import static org.keycloak.client.registration.cli.config.FileConfigHandler.setConfigFile;
|
||||||
import static org.keycloak.client.registration.cli.util.ConfigUtil.checkAuthInfo;
|
import static org.keycloak.client.registration.cli.util.ConfigUtil.checkAuthInfo;
|
||||||
import static org.keycloak.client.registration.cli.util.ConfigUtil.checkServerInfo;
|
import static org.keycloak.client.registration.cli.util.ConfigUtil.checkServerInfo;
|
||||||
|
@ -26,58 +26,55 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
|
||||||
|
|
||||||
static final String DEFAULT_CLIENT = "admin-cli";
|
static final String DEFAULT_CLIENT = "admin-cli";
|
||||||
|
|
||||||
@Option(name = "config", description = "Path to the config file (~/.keycloak/kcreg.config by default)", hasValue = true)
|
@Option(names = "--config", description = "Path to the config file (~/.keycloak/kcreg.config by default)")
|
||||||
protected String config;
|
protected String config;
|
||||||
|
|
||||||
@Option(name = "no-config", description = "No configuration file should be used, no authentication info is loaded or saved", hasValue = false)
|
@Option(names = "--no-config", description = "No configuration file should be used, no authentication info is loaded or saved")
|
||||||
protected boolean noconfig;
|
protected boolean noconfig;
|
||||||
|
|
||||||
@Option(name = "server", description = "Server endpoint url (e.g. 'http://localhost:8080')", hasValue = true)
|
@Option(names = "--server", description = "Server endpoint url (e.g. 'http://localhost:8080')")
|
||||||
protected String server;
|
protected String server;
|
||||||
|
|
||||||
@Option(name = "realm", description = "Realm name to authenticate against", hasValue = true)
|
@Option(names = "--realm", description = "Realm name to authenticate against")
|
||||||
protected String realm;
|
protected String realm;
|
||||||
|
|
||||||
@Option(name = "client", description = "Realm name to authenticate against", hasValue = true)
|
@Option(names = "--client", description = "Realm name to authenticate against")
|
||||||
protected String clientId;
|
protected String clientId;
|
||||||
|
|
||||||
@Option(name = "user", description = "Username to login with", hasValue = true)
|
@Option(names = "--user", description = "Username to login with")
|
||||||
protected String user;
|
protected String user;
|
||||||
|
|
||||||
@Option(name = "password", description = "Password to login with (prompted for if not specified and --user is used)", hasValue = true)
|
@Option(names = "--password", description = "Password to login with (prompted for if not specified and --user is used)")
|
||||||
protected String password;
|
protected String password;
|
||||||
|
|
||||||
@Option(name = "secret", description = "Secret to authenticate the client (prompted for if no --user or --keystore is specified)", hasValue = true)
|
@Option(names = "--secret", description = "Secret to authenticate the client (prompted for if no --user or --keystore is specified)")
|
||||||
protected String secret;
|
protected String secret;
|
||||||
|
|
||||||
@Option(name = "keystore", description = "Path to a keystore containing private key", hasValue = true)
|
@Option(names = "--keystore", description = "Path to a keystore containing private key")
|
||||||
protected String keystore;
|
protected String keystore;
|
||||||
|
|
||||||
@Option(name = "storepass", description = "Keystore password (prompted for if not specified and --keystore is used)", hasValue = true)
|
@Option(names = "--storepass", description = "Keystore password (prompted for if not specified and --keystore is used)")
|
||||||
protected String storePass;
|
protected String storePass;
|
||||||
|
|
||||||
@Option(name = "keypass", description = "Key password (prompted for if not specified and --keystore is used without --storepass, \n otherwise defaults to keystore password)", hasValue = true)
|
@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;
|
protected String keyPass;
|
||||||
|
|
||||||
@Option(name = "alias", description = "Alias of the key inside a keystore (defaults to the value of ClientId)", hasValue = true)
|
@Option(names = "--alias", description = "Alias of the key inside a keystore (defaults to the value of ClientId)")
|
||||||
protected String alias;
|
protected String alias;
|
||||||
|
|
||||||
@Option(name = "truststore", description = "Path to a truststore", hasValue = true)
|
@Option(names = "--truststore", description = "Path to a truststore")
|
||||||
protected String trustStore;
|
protected String trustStore;
|
||||||
|
|
||||||
@Option(name = "trustpass", description = "Truststore password (prompted for if not specified and --truststore is used)", hasValue = true)
|
@Option(names = "--trustpass", description = "Truststore password (prompted for if not specified and --truststore is used)")
|
||||||
protected String trustPass;
|
protected String trustPass;
|
||||||
|
|
||||||
@Option(name = "insecure", description = "Turns off TLS validation", hasValue = false)
|
@Option(names = "--insecure", description = "Turns off TLS validation")
|
||||||
protected boolean insecure;
|
protected boolean insecure;
|
||||||
|
|
||||||
@Option(shortName = 't', name = "token", description = "Initial / Registration access token to use)", hasValue = true)
|
@Option(names = {"-t", "--token"}, description = "Initial / Registration access token to use)")
|
||||||
protected String token;
|
protected String token;
|
||||||
|
|
||||||
protected void initFromParent(AbstractAuthOptionsCmd parent) {
|
protected void initFromParent(AbstractAuthOptionsCmd parent) {
|
||||||
|
|
||||||
super.initFromParent(parent);
|
|
||||||
|
|
||||||
noconfig = parent.noconfig;
|
noconfig = parent.noconfig;
|
||||||
config = parent.config;
|
config = parent.config;
|
||||||
server = parent.server;
|
server = parent.server;
|
||||||
|
@ -110,12 +107,11 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
|
||||||
token == null && config == null;
|
token == null && config == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void processGlobalOptions() {
|
@Override
|
||||||
|
protected void processOptions() {
|
||||||
super.processGlobalOptions();
|
|
||||||
|
|
||||||
if (config != null && noconfig) {
|
if (config != null && noconfig) {
|
||||||
throw new RuntimeException("Options --config and --no-config are mutually exclusive");
|
throw new IllegalArgumentException("Options --config and --no-config are mutually exclusive");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!noconfig) {
|
if (!noconfig) {
|
||||||
|
@ -130,7 +126,7 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setupTruststore(ConfigData configData, CommandInvocation invocation ) {
|
protected void setupTruststore(ConfigData configData) {
|
||||||
|
|
||||||
if (!configData.getServerUrl().startsWith("https:")) {
|
if (!configData.getServerUrl().startsWith("https:")) {
|
||||||
return;
|
return;
|
||||||
|
@ -147,7 +143,7 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
|
||||||
pass = configData.getTrustpass();
|
pass = configData.getTrustpass();
|
||||||
}
|
}
|
||||||
if (pass == null) {
|
if (pass == null) {
|
||||||
pass = IoUtil.readSecret("Enter truststore password: ", invocation);
|
pass = IoUtil.readSecret("Enter truststore password: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -162,7 +158,7 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ConfigData ensureAuthInfo(ConfigData config, CommandInvocation commandInvocation) {
|
protected ConfigData ensureAuthInfo(ConfigData config) {
|
||||||
|
|
||||||
if (requiresLogin()) {
|
if (requiresLogin()) {
|
||||||
// make sure current handler is in-memory handler
|
// make sure current handler is in-memory handler
|
||||||
|
@ -178,7 +174,7 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
|
||||||
ConfigCredentialsCmd login = new ConfigCredentialsCmd();
|
ConfigCredentialsCmd login = new ConfigCredentialsCmd();
|
||||||
login.initFromParent(this);
|
login.initFromParent(this);
|
||||||
login.init(config);
|
login.init(config);
|
||||||
login.process(commandInvocation);
|
login.process();
|
||||||
|
|
||||||
// this must be executed before finally block which restores config handler
|
// this must be executed before finally block which restores config handler
|
||||||
return loadConfig();
|
return loadConfig();
|
||||||
|
@ -237,6 +233,7 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
|
||||||
rdata.setGrantTypeForAuthentication(grantTypeForAuthentication);
|
rdata.setGrantTypeForAuthentication(grantTypeForAuthentication);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected void checkUnsupportedOptions(String ... options) {
|
protected void checkUnsupportedOptions(String ... options) {
|
||||||
if (options.length % 2 != 0) {
|
if (options.length % 2 != 0) {
|
||||||
throw new IllegalArgumentException("Even number of argument required");
|
throw new IllegalArgumentException("Even number of argument required");
|
||||||
|
|
|
@ -1,38 +1,37 @@
|
||||||
package org.keycloak.client.registration.cli.commands;
|
package org.keycloak.client.registration.cli.commands;
|
||||||
|
|
||||||
import org.jboss.aesh.cl.Option;
|
import org.keycloak.client.registration.cli.Globals;
|
||||||
import org.jboss.aesh.console.command.Command;
|
|
||||||
import org.keycloak.client.registration.cli.aesh.Globals;
|
import picocli.CommandLine;
|
||||||
|
import picocli.CommandLine.Option;
|
||||||
|
|
||||||
import static org.keycloak.client.registration.cli.util.IoUtil.printOut;
|
import static org.keycloak.client.registration.cli.util.IoUtil.printOut;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
public abstract class AbstractGlobalOptionsCmd implements Command {
|
public abstract class AbstractGlobalOptionsCmd implements Runnable {
|
||||||
|
|
||||||
@Option(shortName = 'x', description = "Print full stack trace when exiting with error", hasValue = false)
|
@Option(names = "--help",
|
||||||
protected boolean dumpTrace;
|
description = "Print command specific help")
|
||||||
|
public void setHelp(boolean help) {
|
||||||
@Option(name = "help", description = "Print command specific help", hasValue = false)
|
Globals.help = help;
|
||||||
protected boolean help;
|
|
||||||
|
|
||||||
protected void initFromParent(AbstractGlobalOptionsCmd parent) {
|
|
||||||
dumpTrace = parent.dumpTrace;
|
|
||||||
help = parent.help;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void processGlobalOptions() {
|
@Option(names = "-x",
|
||||||
|
description = "Print full stack trace when exiting with error")
|
||||||
|
public void setDumpTrace(boolean dumpTrace) {
|
||||||
Globals.dumpTrace = dumpTrace;
|
Globals.dumpTrace = dumpTrace;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean printHelp() {
|
protected void printHelpIfNeeded() {
|
||||||
if (help || nothingToDo()) {
|
if (Globals.help) {
|
||||||
printOut(help());
|
printOut(help());
|
||||||
return true;
|
System.exit(CommandLine.ExitCode.OK);
|
||||||
|
} else if (nothingToDo()) {
|
||||||
|
printOut(help());
|
||||||
|
System.exit(CommandLine.ExitCode.USAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean nothingToDo() {
|
protected boolean nothingToDo() {
|
||||||
|
@ -42,4 +41,47 @@ public abstract class AbstractGlobalOptionsCmd implements Command {
|
||||||
protected String help() {
|
protected String help() {
|
||||||
return KcRegCmd.usage();
|
return KcRegCmd.usage();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
printHelpIfNeeded();
|
||||||
|
|
||||||
|
checkUnsupportedOptions(getUnsupportedOptions());
|
||||||
|
|
||||||
|
processOptions();
|
||||||
|
|
||||||
|
process();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String[] getUnsupportedOptions() {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void processOptions() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void process() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkUnsupportedOptions(String ... options) {
|
||||||
|
if (options.length % 2 != 0) {
|
||||||
|
throw new IllegalArgumentException("Even number of argument required");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < options.length; i++) {
|
||||||
|
String name = options[i];
|
||||||
|
String value = options[++i];
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
throw new IllegalArgumentException("Unsupported option: " + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String booleanOptionForCheck(boolean value) {
|
||||||
|
return value ? "true" : null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
package org.keycloak.client.registration.cli.commands;
|
package org.keycloak.client.registration.cli.commands;
|
||||||
|
|
||||||
import org.jboss.aesh.cl.Arguments;
|
|
||||||
import org.jboss.aesh.cl.CommandDefinition;
|
|
||||||
import org.jboss.aesh.cl.Option;
|
|
||||||
import org.jboss.aesh.console.command.CommandException;
|
|
||||||
import org.jboss.aesh.console.command.CommandResult;
|
|
||||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|
||||||
import org.keycloak.client.registration.cli.common.AttributeKey;
|
import org.keycloak.client.registration.cli.common.AttributeKey;
|
||||||
import org.keycloak.client.registration.cli.common.EndpointType;
|
import org.keycloak.client.registration.cli.common.EndpointType;
|
||||||
import org.keycloak.client.registration.cli.util.ReflectionUtil;
|
import org.keycloak.client.registration.cli.util.ReflectionUtil;
|
||||||
|
@ -19,9 +13,13 @@ import java.lang.reflect.Field;
|
||||||
import java.lang.reflect.ParameterizedType;
|
import java.lang.reflect.ParameterizedType;
|
||||||
import java.lang.reflect.Type;
|
import java.lang.reflect.Type;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import picocli.CommandLine;
|
||||||
|
import picocli.CommandLine.Command;
|
||||||
|
import picocli.CommandLine.Option;
|
||||||
|
import picocli.CommandLine.Parameters;
|
||||||
|
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
|
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
|
||||||
import static org.keycloak.client.registration.cli.util.ReflectionUtil.getAttributeListWithJSonTypes;
|
import static org.keycloak.client.registration.cli.util.ReflectionUtil.getAttributeListWithJSonTypes;
|
||||||
|
@ -32,97 +30,78 @@ import static org.keycloak.client.registration.cli.util.ReflectionUtil.isMapType
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
@CommandDefinition(name = "attrs", description = "[ATTRIBUTE] [--endpoint TYPE]")
|
@Command(name = "attrs", description = "[ATTRIBUTE] [--endpoint TYPE]")
|
||||||
public class AttrsCmd extends AbstractGlobalOptionsCmd {
|
public class AttrsCmd extends AbstractGlobalOptionsCmd {
|
||||||
|
|
||||||
@Option(shortName = 'e', name = "endpoint", description = "Endpoint type to use", hasValue = true)
|
CommandLine.Model.CommandSpec spec;
|
||||||
|
|
||||||
|
@Option(names = {"-e", "--endpoint"}, description = "Endpoint type to use")
|
||||||
protected String endpoint;
|
protected String endpoint;
|
||||||
|
|
||||||
@Arguments
|
@Parameters(arity = "0..1")
|
||||||
protected List<String> args;
|
|
||||||
|
|
||||||
protected String attr;
|
protected String attr;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
protected void process() {
|
||||||
try {
|
EndpointType regType = EndpointType.DEFAULT;
|
||||||
processGlobalOptions();
|
PrintStream out = System.out;
|
||||||
|
|
||||||
if (printHelp()) {
|
if (endpoint != null) {
|
||||||
return CommandResult.SUCCESS;
|
regType = EndpointType.of(endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
Class type = regType == EndpointType.DEFAULT ? ClientRepresentation.class : (regType == EndpointType.OIDC ? OIDCClientRepresentation.class : null);
|
||||||
|
if (type == null) {
|
||||||
|
throw new IllegalArgumentException("Endpoint not supported: " + regType);
|
||||||
|
}
|
||||||
|
AttributeKey key = attr == null ? new AttributeKey() : new AttributeKey(attr);
|
||||||
|
|
||||||
|
Field f = ReflectionUtil.resolveField(type, key);
|
||||||
|
String ts = f != null ? ReflectionUtil.getTypeString(null, f) : null;
|
||||||
|
|
||||||
|
if (f == null) {
|
||||||
|
out.printf("Attributes for %s format:\n", regType.getEndpoint());
|
||||||
|
|
||||||
|
LinkedHashMap<String, String> items = getAttributeListWithJSonTypes(type, key);
|
||||||
|
for (Map.Entry<String, String> item : items.entrySet()) {
|
||||||
|
out.printf(" %-40s %s\n", item.getKey(), item.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
EndpointType regType = EndpointType.DEFAULT;
|
} else {
|
||||||
PrintStream out = commandInvocation.getShell().out();
|
out.printf("%-40s %s", attr, ts);
|
||||||
|
boolean eol = false;
|
||||||
|
|
||||||
if (endpoint != null) {
|
Type t = f.getGenericType();
|
||||||
regType = EndpointType.of(endpoint);
|
if (isListType(f.getType()) && t instanceof ParameterizedType) {
|
||||||
}
|
t = ((ParameterizedType) t).getActualTypeArguments()[0];
|
||||||
|
if (!isBasicType(t) && t instanceof Class) {
|
||||||
if (args != null) {
|
eol = true;
|
||||||
if (args.size() > 1) {
|
out.printf(", where value is:\n", ts);
|
||||||
throw new IllegalArgumentException("Invalid option: " + args.get(1));
|
LinkedHashMap<String, String> items = ReflectionUtil.getAttributeListWithJSonTypes((Class) t, null);
|
||||||
}
|
for (Map.Entry<String, String> item : items.entrySet()) {
|
||||||
attr = args.get(0);
|
out.printf(" %-36s %s\n", item.getKey(), item.getValue());
|
||||||
}
|
|
||||||
|
|
||||||
Class type = regType == EndpointType.DEFAULT ? ClientRepresentation.class : (regType == EndpointType.OIDC ? OIDCClientRepresentation.class : null);
|
|
||||||
if (type == null) {
|
|
||||||
throw new IllegalArgumentException("Endpoint not supported: " + regType);
|
|
||||||
}
|
|
||||||
AttributeKey key = attr == null ? new AttributeKey() : new AttributeKey(attr);
|
|
||||||
|
|
||||||
Field f = ReflectionUtil.resolveField(type, key);
|
|
||||||
String ts = f != null ? ReflectionUtil.getTypeString(null, f) : null;
|
|
||||||
|
|
||||||
if (f == null) {
|
|
||||||
out.printf("Attributes for %s format:\n", regType.getEndpoint());
|
|
||||||
|
|
||||||
LinkedHashMap<String, String> items = getAttributeListWithJSonTypes(type, key);
|
|
||||||
for (Map.Entry<String, String> item : items.entrySet()) {
|
|
||||||
out.printf(" %-40s %s\n", item.getKey(), item.getValue());
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
out.printf("%-40s %s", attr, ts);
|
|
||||||
boolean eol = false;
|
|
||||||
|
|
||||||
Type t = f.getGenericType();
|
|
||||||
if (isListType(f.getType()) && t instanceof ParameterizedType) {
|
|
||||||
t = ((ParameterizedType) t).getActualTypeArguments()[0];
|
|
||||||
if (!isBasicType(t) && t instanceof Class) {
|
|
||||||
eol = true;
|
|
||||||
System.out.printf(", where value is:\n", ts);
|
|
||||||
LinkedHashMap<String, String> items = ReflectionUtil.getAttributeListWithJSonTypes((Class) t, null);
|
|
||||||
for (Map.Entry<String, String> item : items.entrySet()) {
|
|
||||||
out.printf(" %-36s %s\n", item.getKey(), item.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else if (isMapType(f.getType()) && t instanceof ParameterizedType) {
|
|
||||||
t = ((ParameterizedType) t).getActualTypeArguments()[1];
|
|
||||||
if (!isBasicType(t) && t instanceof Class) {
|
|
||||||
eol = true;
|
|
||||||
out.printf(", where value is:\n", ts);
|
|
||||||
LinkedHashMap<String, String> items = ReflectionUtil.getAttributeListWithJSonTypes((Class) t, null);
|
|
||||||
for (Map.Entry<String, String> item : items.entrySet()) {
|
|
||||||
out.printf(" %-36s %s\n", item.getKey(), item.getValue());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} else if (isMapType(f.getType()) && t instanceof ParameterizedType) {
|
||||||
if (!eol) {
|
t = ((ParameterizedType) t).getActualTypeArguments()[1];
|
||||||
// add end of line
|
if (!isBasicType(t) && t instanceof Class) {
|
||||||
out.println();
|
eol = true;
|
||||||
|
out.printf(", where value is:\n", ts);
|
||||||
|
LinkedHashMap<String, String> items = ReflectionUtil.getAttributeListWithJSonTypes((Class) t, null);
|
||||||
|
for (Map.Entry<String, String> item : items.entrySet()) {
|
||||||
|
out.printf(" %-36s %s\n", item.getKey(), item.getValue());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return CommandResult.SUCCESS;
|
if (!eol) {
|
||||||
|
// add end of line
|
||||||
} finally {
|
out.println();
|
||||||
commandInvocation.stop();
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected String help() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,80 +17,36 @@
|
||||||
|
|
||||||
package org.keycloak.client.registration.cli.commands;
|
package org.keycloak.client.registration.cli.commands;
|
||||||
|
|
||||||
import org.jboss.aesh.cl.Arguments;
|
|
||||||
import org.jboss.aesh.cl.GroupCommandDefinition;
|
|
||||||
import org.jboss.aesh.console.command.CommandException;
|
|
||||||
import org.jboss.aesh.console.command.Command;
|
|
||||||
import org.jboss.aesh.console.command.CommandResult;
|
|
||||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.util.List;
|
|
||||||
|
import picocli.CommandLine.Command;
|
||||||
|
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@GroupCommandDefinition(name = "config", description = "COMMAND [ARGUMENTS]", groupCommands = {ConfigCredentialsCmd.class} )
|
@Command(name = "config", description = "COMMAND [ARGUMENTS]", subcommands = {
|
||||||
public class ConfigCmd extends AbstractAuthOptionsCmd implements Command {
|
ConfigCredentialsCmd.class,
|
||||||
|
ConfigInitialTokenCmd.class,
|
||||||
|
ConfigRegistrationTokenCmd.class,
|
||||||
|
ConfigTruststoreCmd.class
|
||||||
|
})
|
||||||
|
public class ConfigCmd extends AbstractAuthOptionsCmd {
|
||||||
|
|
||||||
@Arguments
|
@Override
|
||||||
protected List<String> args;
|
protected void process() {
|
||||||
|
|
||||||
|
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
|
||||||
try {
|
|
||||||
if (args != null && args.size() > 0) {
|
|
||||||
String cmd = args.get(0);
|
|
||||||
switch (cmd) {
|
|
||||||
case "credentials": {
|
|
||||||
ConfigCredentialsCmd command = new ConfigCredentialsCmd();
|
|
||||||
command.initFromParent(this);
|
|
||||||
return command.execute(commandInvocation);
|
|
||||||
}
|
|
||||||
case "truststore": {
|
|
||||||
ConfigTruststoreCmd command = new ConfigTruststoreCmd();
|
|
||||||
command.initFromParent(this);
|
|
||||||
return command.execute(commandInvocation);
|
|
||||||
}
|
|
||||||
case "initial-token": {
|
|
||||||
ConfigInitialTokenCmd command = new ConfigInitialTokenCmd();
|
|
||||||
command.initFromParent(this);
|
|
||||||
return command.execute(commandInvocation);
|
|
||||||
}
|
|
||||||
case "registration-token": {
|
|
||||||
ConfigRegistrationTokenCmd command = new ConfigRegistrationTokenCmd();
|
|
||||||
command.initFromParent(this);
|
|
||||||
return command.execute(commandInvocation);
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
if (printHelp()) {
|
|
||||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
|
||||||
}
|
|
||||||
throw new IllegalArgumentException("Unknown sub-command: " + cmd + suggestHelp());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (printHelp()) {
|
|
||||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new IllegalArgumentException("Sub-command required by '" + CMD + " config' - one of: 'credentials', 'truststore', 'initial-token', 'registration-token'");
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
commandInvocation.stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String suggestHelp() {
|
@Override
|
||||||
return EOL + "Try '" + CMD + " help config' for more information";
|
protected boolean nothingToDo() {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected String help() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
package org.keycloak.client.registration.cli.commands;
|
package org.keycloak.client.registration.cli.commands;
|
||||||
|
|
||||||
import org.jboss.aesh.cl.CommandDefinition;
|
|
||||||
import org.jboss.aesh.console.command.Command;
|
|
||||||
import org.jboss.aesh.console.command.CommandException;
|
|
||||||
import org.jboss.aesh.console.command.CommandResult;
|
|
||||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
import org.keycloak.client.registration.cli.config.ConfigData;
|
import org.keycloak.client.registration.cli.config.ConfigData;
|
||||||
import org.keycloak.client.registration.cli.config.RealmConfigData;
|
import org.keycloak.client.registration.cli.config.RealmConfigData;
|
||||||
|
@ -16,6 +11,8 @@ import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
|
import picocli.CommandLine.Command;
|
||||||
|
|
||||||
import static org.keycloak.client.registration.cli.util.AuthUtil.getAuthTokens;
|
import static org.keycloak.client.registration.cli.util.AuthUtil.getAuthTokens;
|
||||||
import static org.keycloak.client.registration.cli.util.AuthUtil.getAuthTokensByJWT;
|
import static org.keycloak.client.registration.cli.util.AuthUtil.getAuthTokensByJWT;
|
||||||
import static org.keycloak.client.registration.cli.util.AuthUtil.getAuthTokensBySecret;
|
import static org.keycloak.client.registration.cli.util.AuthUtil.getAuthTokensBySecret;
|
||||||
|
@ -26,15 +23,14 @@ import static org.keycloak.client.registration.cli.util.ConfigUtil.saveTokens;
|
||||||
import static org.keycloak.client.registration.cli.util.IoUtil.printErr;
|
import static org.keycloak.client.registration.cli.util.IoUtil.printErr;
|
||||||
import static org.keycloak.client.registration.cli.util.IoUtil.readSecret;
|
import static org.keycloak.client.registration.cli.util.IoUtil.readSecret;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
|
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH;
|
import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
|
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
@CommandDefinition(name = "credentials", description = "--server SERVER_URL --realm REALM [ARGUMENTS]")
|
@Command(name = "credentials", description = "--server SERVER_URL --realm REALM [ARGUMENTS]")
|
||||||
public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd implements Command {
|
public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd {
|
||||||
|
|
||||||
private int sigLifetime = 600;
|
private int sigLifetime = 600;
|
||||||
|
|
||||||
|
@ -60,24 +56,9 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd implements Comm
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
protected String[] getUnsupportedOptions() {
|
||||||
try {
|
return new String[] {"--no-config", booleanOptionForCheck(noconfig)};
|
||||||
if (printHelp()) {
|
|
||||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkUnsupportedOptions("--no-config", booleanOptionForCheck(noconfig));
|
|
||||||
|
|
||||||
processGlobalOptions();
|
|
||||||
|
|
||||||
return process(commandInvocation);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
|
|
||||||
} finally {
|
|
||||||
commandInvocation.stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -85,8 +66,8 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd implements Comm
|
||||||
return noOptions();
|
return noOptions();
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandResult process(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
@Override
|
||||||
|
protected void process() {
|
||||||
// check server
|
// check server
|
||||||
if (server == null) {
|
if (server == null) {
|
||||||
throw new IllegalArgumentException("Required option not specified: --server");
|
throw new IllegalArgumentException("Required option not specified: --server");
|
||||||
|
@ -113,18 +94,18 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd implements Comm
|
||||||
|
|
||||||
// if user was set there needs to be a password so we can authenticate
|
// if user was set there needs to be a password so we can authenticate
|
||||||
if (password == null) {
|
if (password == null) {
|
||||||
password = readSecret("Enter password: ", commandInvocation);
|
password = readSecret("Enter password: ");
|
||||||
}
|
}
|
||||||
// if secret was set to be read from stdin, then ask for it
|
// if secret was set to be read from stdin, then ask for it
|
||||||
if ("-".equals(secret) && keystore == null) {
|
if ("-".equals(secret) && keystore == null) {
|
||||||
secret = readSecret("Enter client secret: ", commandInvocation);
|
secret = readSecret("Enter client secret: ");
|
||||||
}
|
}
|
||||||
} else if (keystore != null || secret != null || clientSet) {
|
} else if (keystore != null || secret != null || clientSet) {
|
||||||
grantTypeForAuthentication = OAuth2Constants.CLIENT_CREDENTIALS;
|
grantTypeForAuthentication = OAuth2Constants.CLIENT_CREDENTIALS;
|
||||||
printErr("Logging into " + server + " as " + "service-account-" + clientId + " of realm " + realm);
|
printErr("Logging into " + server + " as " + "service-account-" + clientId + " of realm " + realm);
|
||||||
if (keystore == null) {
|
if (keystore == null) {
|
||||||
if (secret == null) {
|
if (secret == null) {
|
||||||
secret = readSecret("Enter client secret: ", commandInvocation);
|
secret = readSecret("Enter client secret: ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -139,8 +120,8 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd implements Comm
|
||||||
}
|
}
|
||||||
|
|
||||||
if (storePass == null) {
|
if (storePass == null) {
|
||||||
storePass = readSecret("Enter keystore password: ", commandInvocation);
|
storePass = readSecret("Enter keystore password: ");
|
||||||
keyPass = readSecret("Enter key password: ", commandInvocation);
|
keyPass = readSecret("Enter key password: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyPass == null) {
|
if (keyPass == null) {
|
||||||
|
@ -163,10 +144,10 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd implements Comm
|
||||||
config.setServerUrl(server);
|
config.setServerUrl(server);
|
||||||
config.setRealm(realm);
|
config.setRealm(realm);
|
||||||
});
|
});
|
||||||
return CommandResult.SUCCESS;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setupTruststore(copyWithServerInfo(loadConfig()), commandInvocation);
|
setupTruststore(copyWithServerInfo(loadConfig()));
|
||||||
|
|
||||||
// now use the token endpoint to retrieve access token, and refresh token
|
// now use the token endpoint to retrieve access token, and refresh token
|
||||||
AccessTokenResponse tokens = signedRequestToken != null ?
|
AccessTokenResponse tokens = signedRequestToken != null ?
|
||||||
|
@ -179,14 +160,9 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd implements Comm
|
||||||
|
|
||||||
// save tokens to config file
|
// save tokens to config file
|
||||||
saveTokens(tokens, server, realm, clientId, signedRequestToken, sigExpiresAt, secret, grantTypeForAuthentication);
|
saveTokens(tokens, server, realm, clientId, signedRequestToken, sigExpiresAt, secret, grantTypeForAuthentication);
|
||||||
|
|
||||||
return CommandResult.SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String suggestHelp() {
|
|
||||||
return EOL + "Try '" + CMD + " help config credentials' for more information";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected String help() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,107 +1,45 @@
|
||||||
package org.keycloak.client.registration.cli.commands;
|
package org.keycloak.client.registration.cli.commands;
|
||||||
|
|
||||||
import org.jboss.aesh.cl.CommandDefinition;
|
|
||||||
import org.jboss.aesh.console.command.Command;
|
|
||||||
import org.jboss.aesh.console.command.CommandException;
|
|
||||||
import org.jboss.aesh.console.command.CommandResult;
|
|
||||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|
||||||
import org.keycloak.client.registration.cli.config.RealmConfigData;
|
import org.keycloak.client.registration.cli.config.RealmConfigData;
|
||||||
import org.keycloak.client.registration.cli.util.IoUtil;
|
import org.keycloak.client.registration.cli.util.IoUtil;
|
||||||
import org.keycloak.client.registration.cli.util.ParseUtil;
|
import org.keycloak.client.registration.cli.util.ParseUtil;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
import picocli.CommandLine.Command;
|
||||||
import java.util.List;
|
import picocli.CommandLine.Option;
|
||||||
|
import picocli.CommandLine.Parameters;
|
||||||
|
|
||||||
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
||||||
import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig;
|
import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig;
|
||||||
import static org.keycloak.client.registration.cli.util.IoUtil.warnfOut;
|
import static org.keycloak.client.registration.cli.util.IoUtil.warnfOut;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
|
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH;
|
import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
|
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
@CommandDefinition(name = "initial-token", description = "[--server SERVER] --realm REALM [--delete | TOKEN] [ARGUMENTS]")
|
@Command(name = "initial-token", description = "[--server SERVER] --realm REALM [--delete | TOKEN] [ARGUMENTS]")
|
||||||
public class ConfigInitialTokenCmd extends AbstractAuthOptionsCmd implements Command {
|
public class ConfigInitialTokenCmd extends AbstractAuthOptionsCmd {
|
||||||
|
|
||||||
private ConfigCmd parent;
|
|
||||||
|
|
||||||
|
@Option(names = {"-d", "--delete"}, description = "Indicates that initial access token should be removed")
|
||||||
private boolean delete;
|
private boolean delete;
|
||||||
|
@Option(names = {"-k", "--keep-domain"}, description = "Don't overwrite default server and realm")
|
||||||
private boolean keepDomain;
|
private boolean keepDomain;
|
||||||
|
|
||||||
|
@Parameters(arity = "0..1")
|
||||||
protected void initFromParent(ConfigCmd parent) {
|
private String token;
|
||||||
this.parent = parent;
|
|
||||||
super.initFromParent(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
|
||||||
try {
|
|
||||||
if (printHelp()) {
|
|
||||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return process(commandInvocation);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
|
|
||||||
} finally {
|
|
||||||
commandInvocation.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean nothingToDo() {
|
protected boolean nothingToDo() {
|
||||||
return noOptions() && parent.args.size() == 1;
|
return noOptions() && token == null && !delete && !keepDomain;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandResult process(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
@Override
|
||||||
|
protected String[] getUnsupportedOptions() {
|
||||||
List<String> args = new ArrayList<>();
|
return new String[] {
|
||||||
|
|
||||||
Iterator<String> it = parent.args.iterator();
|
|
||||||
// skip the first argument 'initial-token'
|
|
||||||
it.next();
|
|
||||||
|
|
||||||
while (it.hasNext()) {
|
|
||||||
String arg = it.next();
|
|
||||||
switch (arg) {
|
|
||||||
case "-d":
|
|
||||||
case "--delete": {
|
|
||||||
delete = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "-k":
|
|
||||||
case "--keep-domain": {
|
|
||||||
keepDomain = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
args.add(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.size() > 1) {
|
|
||||||
throw new IllegalArgumentException("Invalid option: " + args.get(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
String token = args.size() == 1 ? args.get(0) : null;
|
|
||||||
|
|
||||||
if (realm == null) {
|
|
||||||
throw new IllegalArgumentException("Realm not specified");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (token != null && token.startsWith("-")) {
|
|
||||||
warnfOut(ParseUtil.TOKEN_OPTION_WARN, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkUnsupportedOptions(
|
|
||||||
"--client", clientId,
|
"--client", clientId,
|
||||||
"--user", user,
|
"--user", user,
|
||||||
"--password", password,
|
"--password", password,
|
||||||
|
@ -112,15 +50,24 @@ public class ConfigInitialTokenCmd extends AbstractAuthOptionsCmd implements Com
|
||||||
"--alias", alias,
|
"--alias", alias,
|
||||||
"--truststore", trustStore,
|
"--truststore", trustStore,
|
||||||
"--trustpass", keyPass,
|
"--trustpass", keyPass,
|
||||||
"--no-config", booleanOptionForCheck(noconfig));
|
"--no-config", booleanOptionForCheck(noconfig)};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void process() {
|
||||||
|
if (realm == null) {
|
||||||
|
throw new IllegalArgumentException("Realm not specified");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (token != null && token.startsWith("-")) {
|
||||||
|
warnfOut(ParseUtil.TOKEN_OPTION_WARN, token);
|
||||||
|
}
|
||||||
|
|
||||||
if (!delete && token == null) {
|
if (!delete && token == null) {
|
||||||
token = IoUtil.readSecret("Enter Initial Access Token: ", commandInvocation);
|
token = IoUtil.readSecret("Enter Initial Access Token: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
// now update the config
|
// now update the config
|
||||||
processGlobalOptions();
|
|
||||||
|
|
||||||
String initialToken = token;
|
String initialToken = token;
|
||||||
saveMergeConfig(config -> {
|
saveMergeConfig(config -> {
|
||||||
|
@ -138,14 +85,9 @@ public class ConfigInitialTokenCmd extends AbstractAuthOptionsCmd implements Com
|
||||||
rdata.setInitialToken(initialToken);
|
rdata.setInitialToken(initialToken);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return CommandResult.SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String suggestHelp() {
|
|
||||||
return EOL + "Try '" + CMD + " help config initial-token' for more information";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected String help() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,91 +1,40 @@
|
||||||
package org.keycloak.client.registration.cli.commands;
|
package org.keycloak.client.registration.cli.commands;
|
||||||
|
|
||||||
import org.jboss.aesh.cl.CommandDefinition;
|
|
||||||
import org.jboss.aesh.console.command.Command;
|
|
||||||
import org.jboss.aesh.console.command.CommandException;
|
|
||||||
import org.jboss.aesh.console.command.CommandResult;
|
|
||||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|
||||||
import org.keycloak.client.registration.cli.config.RealmConfigData;
|
import org.keycloak.client.registration.cli.config.RealmConfigData;
|
||||||
import org.keycloak.client.registration.cli.util.IoUtil;
|
import org.keycloak.client.registration.cli.util.IoUtil;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
import picocli.CommandLine.Command;
|
||||||
import java.util.List;
|
import picocli.CommandLine.Option;
|
||||||
|
import picocli.CommandLine.Parameters;
|
||||||
|
|
||||||
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
||||||
import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig;
|
import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
|
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH;
|
import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
|
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
@CommandDefinition(name = "registration-token", description = "[--server SERVER] --realm REALM --client CLIENT [--delete | TOKEN] [ARGUMENTS]")
|
@Command(name = "registration-token", description = "[--server SERVER] --realm REALM --client CLIENT [--delete | TOKEN] [ARGUMENTS]")
|
||||||
public class ConfigRegistrationTokenCmd extends AbstractAuthOptionsCmd implements Command {
|
public class ConfigRegistrationTokenCmd extends AbstractAuthOptionsCmd {
|
||||||
|
|
||||||
private ConfigCmd parent;
|
|
||||||
|
|
||||||
|
@Option(names = {"-d", "--delete"}, description = "Indicates that initial access token should be removed")
|
||||||
private boolean delete;
|
private boolean delete;
|
||||||
|
|
||||||
|
@Parameters(arity = "0..1")
|
||||||
protected void initFromParent(ConfigCmd parent) {
|
private String token;
|
||||||
this.parent = parent;
|
|
||||||
super.initFromParent(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
|
||||||
try {
|
|
||||||
if (printHelp()) {
|
|
||||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return process(commandInvocation);
|
|
||||||
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
|
|
||||||
} finally {
|
|
||||||
commandInvocation.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean nothingToDo() {
|
protected boolean nothingToDo() {
|
||||||
return noOptions() && parent.args.size() == 1;
|
return noOptions() && token == null && !delete;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandResult process(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
@Override
|
||||||
|
protected void process() {
|
||||||
List<String> args = new ArrayList<>();
|
|
||||||
|
|
||||||
Iterator<String> it = parent.args.iterator();
|
|
||||||
// skip the first argument 'registration-token'
|
|
||||||
it.next();
|
|
||||||
|
|
||||||
while (it.hasNext()) {
|
|
||||||
String arg = it.next();
|
|
||||||
switch (arg) {
|
|
||||||
case "-d":
|
|
||||||
case "--delete": {
|
|
||||||
delete = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
args.add(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.size() > 1) {
|
|
||||||
throw new IllegalArgumentException("Invalid option: " + args.get(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
String token = args.size() == 1 ? args.get(0) : null;
|
|
||||||
|
|
||||||
if (server == null) {
|
if (server == null) {
|
||||||
throw new IllegalArgumentException("Required option not specified: --server");
|
throw new IllegalArgumentException("Required option not specified: --server");
|
||||||
}
|
}
|
||||||
|
@ -112,11 +61,10 @@ public class ConfigRegistrationTokenCmd extends AbstractAuthOptionsCmd implement
|
||||||
|
|
||||||
|
|
||||||
if (!delete && token == null) {
|
if (!delete && token == null) {
|
||||||
token = IoUtil.readSecret("Enter Registration Access Token: ", commandInvocation);
|
token = IoUtil.readSecret("Enter Registration Access Token: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
// now update the config
|
// now update the config
|
||||||
processGlobalOptions();
|
|
||||||
|
|
||||||
String registrationToken = token;
|
String registrationToken = token;
|
||||||
saveMergeConfig(config -> {
|
saveMergeConfig(config -> {
|
||||||
|
@ -129,14 +77,9 @@ public class ConfigRegistrationTokenCmd extends AbstractAuthOptionsCmd implement
|
||||||
config.ensureRealmConfigData(server, realm).getClients().put(clientId, registrationToken);
|
config.ensureRealmConfigData(server, realm).getClients().put(clientId, registrationToken);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return CommandResult.SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String suggestHelp() {
|
|
||||||
return EOL + "Try '" + CMD + " help config registration-token' for more information";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected String help() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,96 +16,41 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.client.registration.cli.commands;
|
package org.keycloak.client.registration.cli.commands;
|
||||||
|
|
||||||
import org.jboss.aesh.cl.CommandDefinition;
|
|
||||||
import org.jboss.aesh.console.command.Command;
|
|
||||||
import org.jboss.aesh.console.command.CommandException;
|
|
||||||
import org.jboss.aesh.console.command.CommandResult;
|
|
||||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
import picocli.CommandLine.Command;
|
||||||
import java.util.List;
|
import picocli.CommandLine.Option;
|
||||||
|
import picocli.CommandLine.Parameters;
|
||||||
|
|
||||||
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
||||||
import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig;
|
import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig;
|
||||||
import static org.keycloak.client.registration.cli.util.IoUtil.readSecret;
|
import static org.keycloak.client.registration.cli.util.IoUtil.readSecret;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
|
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH;
|
import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
|
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
@CommandDefinition(name = "truststore", description = "PATH [ARGUMENTS]")
|
@Command(name = "truststore", description = "PATH [ARGUMENTS]")
|
||||||
public class ConfigTruststoreCmd extends AbstractAuthOptionsCmd implements Command {
|
public class ConfigTruststoreCmd extends AbstractAuthOptionsCmd {
|
||||||
|
|
||||||
private ConfigCmd parent;
|
|
||||||
|
|
||||||
|
@Option(names = {"-d", "--delete"}, description = "Indicates that initial access token should be removed")
|
||||||
private boolean delete;
|
private boolean delete;
|
||||||
|
|
||||||
|
@Parameters(arity = "0..1")
|
||||||
protected void initFromParent(ConfigCmd parent) {
|
private String truststorePath;
|
||||||
this.parent = parent;
|
|
||||||
super.initFromParent(parent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
|
||||||
try {
|
|
||||||
if (printHelp()) {
|
|
||||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return process(commandInvocation);
|
|
||||||
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
|
|
||||||
} finally {
|
|
||||||
commandInvocation.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean nothingToDo() {
|
protected boolean nothingToDo() {
|
||||||
return noOptions() && parent.args.size() == 1;
|
return noOptions() && truststorePath == null && !delete;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandResult process(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
@Override
|
||||||
|
protected String[] getUnsupportedOptions() {
|
||||||
List<String> args = new ArrayList<>();
|
return new String[] {"--server", server,
|
||||||
|
|
||||||
Iterator<String> it = parent.args.iterator();
|
|
||||||
// skip the first argument 'truststore'
|
|
||||||
it.next();
|
|
||||||
|
|
||||||
while (it.hasNext()) {
|
|
||||||
String arg = it.next();
|
|
||||||
switch (arg) {
|
|
||||||
case "-d":
|
|
||||||
case "--delete": {
|
|
||||||
delete = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
args.add(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.size() > 1) {
|
|
||||||
throw new IllegalArgumentException("Invalid option: " + args.get(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
String truststore = null;
|
|
||||||
if (args.size() > 0) {
|
|
||||||
truststore = args.get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkUnsupportedOptions("--server", server,
|
|
||||||
"--realm", realm,
|
"--realm", realm,
|
||||||
"--client", clientId,
|
"--client", clientId,
|
||||||
"--user", user,
|
"--user", user,
|
||||||
|
@ -115,33 +60,35 @@ public class ConfigTruststoreCmd extends AbstractAuthOptionsCmd implements Comma
|
||||||
"--keystore", keystore,
|
"--keystore", keystore,
|
||||||
"--keypass", keyPass,
|
"--keypass", keyPass,
|
||||||
"--alias", alias,
|
"--alias", alias,
|
||||||
"--no-config", booleanOptionForCheck(noconfig));
|
"--no-config", booleanOptionForCheck(noconfig)};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void process() {
|
||||||
// now update the config
|
// now update the config
|
||||||
processGlobalOptions();
|
|
||||||
|
|
||||||
String store;
|
String store;
|
||||||
String pass;
|
String pass;
|
||||||
|
|
||||||
if (!delete) {
|
if (!delete) {
|
||||||
|
|
||||||
if (truststore == null) {
|
if (truststorePath == null) {
|
||||||
throw new IllegalArgumentException("No truststore specified");
|
throw new IllegalArgumentException("No truststore specified");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!new File(truststore).isFile()) {
|
if (!new File(truststorePath).isFile()) {
|
||||||
throw new RuntimeException("Truststore file not found: " + truststore);
|
throw new RuntimeException("Truststore file not found: " + truststorePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("-".equals(trustPass)) {
|
if ("-".equals(trustPass)) {
|
||||||
trustPass = readSecret("Enter truststore password: ", commandInvocation);
|
trustPass = readSecret("Enter truststore password: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
store = truststore;
|
store = truststorePath;
|
||||||
pass = trustPass;
|
pass = trustPass;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (truststore != null) {
|
if (truststorePath != null) {
|
||||||
throw new IllegalArgumentException("Option --delete is mutually exclusive with specifying a TRUSTSTORE");
|
throw new IllegalArgumentException("Option --delete is mutually exclusive with specifying a TRUSTSTORE");
|
||||||
}
|
}
|
||||||
if (trustPass != null) {
|
if (trustPass != null) {
|
||||||
|
@ -155,14 +102,9 @@ public class ConfigTruststoreCmd extends AbstractAuthOptionsCmd implements Comma
|
||||||
config.setTruststore(store);
|
config.setTruststore(store);
|
||||||
config.setTrustpass(pass);
|
config.setTrustpass(pass);
|
||||||
});
|
});
|
||||||
|
|
||||||
return CommandResult.SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String suggestHelp() {
|
|
||||||
return EOL + "Try '" + CMD + " help config truststore' for more information";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected String help() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,19 +17,11 @@
|
||||||
|
|
||||||
package org.keycloak.client.registration.cli.commands;
|
package org.keycloak.client.registration.cli.commands;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
|
import org.keycloak.client.registration.cli.EndpointTypeConverter;
|
||||||
import org.jboss.aesh.cl.Arguments;
|
|
||||||
import org.jboss.aesh.cl.CommandDefinition;
|
|
||||||
import org.jboss.aesh.cl.Option;
|
|
||||||
import org.jboss.aesh.console.command.Command;
|
|
||||||
import org.jboss.aesh.console.command.CommandException;
|
|
||||||
import org.jboss.aesh.console.command.CommandResult;
|
|
||||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|
||||||
import org.keycloak.client.registration.cli.aesh.EndpointTypeConverter;
|
|
||||||
import org.keycloak.client.registration.cli.common.AttributeOperation;
|
import org.keycloak.client.registration.cli.common.AttributeOperation;
|
||||||
import org.keycloak.client.registration.cli.config.ConfigData;
|
|
||||||
import org.keycloak.client.registration.cli.common.CmdStdinContext;
|
import org.keycloak.client.registration.cli.common.CmdStdinContext;
|
||||||
import org.keycloak.client.registration.cli.common.EndpointType;
|
import org.keycloak.client.registration.cli.common.EndpointType;
|
||||||
|
import org.keycloak.client.registration.cli.config.ConfigData;
|
||||||
import org.keycloak.client.registration.cli.util.HttpUtil;
|
import org.keycloak.client.registration.cli.util.HttpUtil;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
import org.keycloak.representations.oidc.OIDCClientRepresentation;
|
||||||
|
@ -39,183 +31,155 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.util.Iterator;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.exc.UnrecognizedPropertyException;
|
||||||
|
|
||||||
|
import picocli.CommandLine.Command;
|
||||||
|
import picocli.CommandLine.Option;
|
||||||
|
|
||||||
import static org.keycloak.client.registration.cli.common.AttributeOperation.Type.SET;
|
import static org.keycloak.client.registration.cli.common.AttributeOperation.Type.SET;
|
||||||
import static org.keycloak.client.registration.cli.common.EndpointType.DEFAULT;
|
import static org.keycloak.client.registration.cli.common.EndpointType.DEFAULT;
|
||||||
import static org.keycloak.client.registration.cli.common.EndpointType.OIDC;
|
import static org.keycloak.client.registration.cli.common.EndpointType.OIDC;
|
||||||
import static org.keycloak.client.registration.cli.common.EndpointType.SAML2;
|
import static org.keycloak.client.registration.cli.common.EndpointType.SAML2;
|
||||||
|
import static org.keycloak.client.registration.cli.util.AuthUtil.ensureToken;
|
||||||
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
||||||
import static org.keycloak.client.registration.cli.util.ConfigUtil.credentialsAvailable;
|
import static org.keycloak.client.registration.cli.util.ConfigUtil.credentialsAvailable;
|
||||||
import static org.keycloak.client.registration.cli.util.ConfigUtil.loadConfig;
|
import static org.keycloak.client.registration.cli.util.ConfigUtil.loadConfig;
|
||||||
import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig;
|
import static org.keycloak.client.registration.cli.util.ConfigUtil.saveMergeConfig;
|
||||||
|
import static org.keycloak.client.registration.cli.util.ConfigUtil.setRegistrationToken;
|
||||||
|
import static org.keycloak.client.registration.cli.util.HttpUtil.doPost;
|
||||||
import static org.keycloak.client.registration.cli.util.HttpUtil.getExpectedContentType;
|
import static org.keycloak.client.registration.cli.util.HttpUtil.getExpectedContentType;
|
||||||
import static org.keycloak.client.registration.cli.util.IoUtil.printErr;
|
import static org.keycloak.client.registration.cli.util.IoUtil.printErr;
|
||||||
|
import static org.keycloak.client.registration.cli.util.IoUtil.printOut;
|
||||||
import static org.keycloak.client.registration.cli.util.IoUtil.readFully;
|
import static org.keycloak.client.registration.cli.util.IoUtil.readFully;
|
||||||
import static org.keycloak.client.registration.cli.util.IoUtil.readSecret;
|
import static org.keycloak.client.registration.cli.util.IoUtil.readSecret;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
|
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH;
|
import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
|
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
|
||||||
import static org.keycloak.client.registration.cli.util.ParseUtil.mergeAttributes;
|
import static org.keycloak.client.registration.cli.util.ParseUtil.mergeAttributes;
|
||||||
import static org.keycloak.client.registration.cli.util.ParseUtil.parseFileOrStdin;
|
import static org.keycloak.client.registration.cli.util.ParseUtil.parseFileOrStdin;
|
||||||
import static org.keycloak.client.registration.cli.util.AuthUtil.ensureToken;
|
|
||||||
import static org.keycloak.client.registration.cli.util.ConfigUtil.setRegistrationToken;
|
|
||||||
import static org.keycloak.client.registration.cli.util.HttpUtil.doPost;
|
|
||||||
import static org.keycloak.client.registration.cli.util.IoUtil.printOut;
|
|
||||||
import static org.keycloak.client.registration.cli.util.ParseUtil.parseKeyVal;
|
import static org.keycloak.client.registration.cli.util.ParseUtil.parseKeyVal;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
@CommandDefinition(name = "create", description = "[ARGUMENTS]")
|
@Command(name = "create", description = "[ARGUMENTS]")
|
||||||
public class CreateCmd extends AbstractAuthOptionsCmd implements Command {
|
public class CreateCmd extends AbstractAuthOptionsCmd {
|
||||||
|
|
||||||
@Option(shortName = 'i', name = "clientId", description = "After creation only print clientId to standard output", hasValue = false)
|
@Option(names = {"-i", "--clientId"}, description = "After creation only print clientId to standard output")
|
||||||
protected boolean returnClientId = false;
|
protected boolean returnClientId = false;
|
||||||
|
|
||||||
@Option(shortName = 'e', name = "endpoint", description = "Endpoint type / document format to use - one of: 'default', 'oidc', 'saml2'",
|
@Option(names = {"-e", "--endpoint"}, description = "Endpoint type / document format to use - one of: 'default', 'oidc', 'saml2'", converter = EndpointTypeConverter.class)
|
||||||
hasValue = true, converter = EndpointTypeConverter.class)
|
|
||||||
protected EndpointType regType;
|
protected EndpointType regType;
|
||||||
|
|
||||||
@Option(shortName = 'f', name = "file", description = "Read object from file or standard input if FILENAME is set to '-'", hasValue = true)
|
@Option(names = {"-f", "--file"}, description = "Read object from file or standard input if FILENAME is set to '-'")
|
||||||
protected String file;
|
protected String file;
|
||||||
|
|
||||||
@Option(shortName = 'o', name = "output", description = "After creation output the new client configuration to standard output", hasValue = false)
|
@Option(names = {"-o", "--output"}, description = "After creation output the new client configuration to standard output")
|
||||||
protected boolean outputClient = false;
|
protected boolean outputClient = false;
|
||||||
|
|
||||||
@Option(shortName = 'c', name = "compressed", description = "Don't pretty print the output", hasValue = false)
|
@Option(names = {"-c", "--compressed"}, description = "Don't pretty print the output")
|
||||||
protected boolean compressed = false;
|
protected boolean compressed = false;
|
||||||
|
|
||||||
//@OptionGroup(shortName = 's', name = "set", description = "Set attribute to the specified value")
|
@Option(names = {"-s", "--set"}, description = "Set a specific attribute NAME to a specified value VALUE")
|
||||||
//Map<String, String> attributes = new LinkedHashMap<>();
|
List<String> rawSets = new ArrayList<>();
|
||||||
|
|
||||||
@Arguments
|
List<AttributeOperation> attrs = new ArrayList<>();
|
||||||
protected List<String> args;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
protected void processOptions() {
|
||||||
|
super.processOptions();
|
||||||
|
|
||||||
List<AttributeOperation> attrs = new LinkedList<>();
|
for (String set : rawSets) {
|
||||||
|
String[] keyVal = parseKeyVal(set);
|
||||||
|
attrs.add(new AttributeOperation(SET, keyVal[0], keyVal[1]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void process() {
|
||||||
|
if (file == null && attrs.size() == 0) {
|
||||||
|
throw new IllegalArgumentException("No file nor attribute values specified");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (outputClient && returnClientId) {
|
||||||
|
throw new IllegalArgumentException("Options -o and -i are mutually exclusive");
|
||||||
|
}
|
||||||
|
|
||||||
|
// if --token is specified read it
|
||||||
|
if ("-".equals(token)) {
|
||||||
|
token = readSecret("Enter Initial Access Token: ");
|
||||||
|
}
|
||||||
|
|
||||||
|
CmdStdinContext ctx = new CmdStdinContext();
|
||||||
|
if (file != null) {
|
||||||
|
ctx = parseFileOrStdin(file, regType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.getEndpointType() == null) {
|
||||||
|
regType = regType != null ? regType : DEFAULT;
|
||||||
|
ctx.setEndpointType(regType);
|
||||||
|
} else if (regType != null && ctx.getEndpointType() != regType) {
|
||||||
|
throw new RuntimeException("Requested endpoint type not compatible with detected configuration format: " + ctx.getEndpointType());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attrs.size() > 0) {
|
||||||
|
ctx = mergeAttributes(ctx, attrs);
|
||||||
|
}
|
||||||
|
|
||||||
|
String contentType = getExpectedContentType(ctx.getEndpointType());
|
||||||
|
|
||||||
|
ConfigData config = loadConfig();
|
||||||
|
config = copyWithServerInfo(config);
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
// if initial token is not set, try use the one from configuration
|
||||||
|
token = config.sessionRealmConfigData().getInitialToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupTruststore(config);
|
||||||
|
|
||||||
|
String auth = token;
|
||||||
|
if (auth == null) {
|
||||||
|
config = ensureAuthInfo(config);
|
||||||
|
config = copyWithServerInfo(config);
|
||||||
|
if (credentialsAvailable(config)) {
|
||||||
|
auth = ensureToken(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auth = auth != null ? "Bearer " + auth : null;
|
||||||
|
|
||||||
|
final String server = config.getServerUrl();
|
||||||
|
final String realm = config.getRealm();
|
||||||
|
|
||||||
|
InputStream response = doPost(server + "/realms/" + realm + "/clients-registrations/" + ctx.getEndpointType().getEndpoint(),
|
||||||
|
contentType, HttpUtil.APPLICATION_JSON, ctx.getContent(), auth);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (printHelp()) {
|
if (ctx.getEndpointType() == DEFAULT || ctx.getEndpointType() == SAML2) {
|
||||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
ClientRepresentation client = JsonSerialization.readValue(response, ClientRepresentation.class);
|
||||||
|
outputResult(client.getClientId(), client);
|
||||||
|
|
||||||
|
saveMergeConfig(cfg -> {
|
||||||
|
setRegistrationToken(cfg.ensureRealmConfigData(server, realm), client.getClientId(), client.getRegistrationAccessToken());
|
||||||
|
});
|
||||||
|
} else if (ctx.getEndpointType() == OIDC) {
|
||||||
|
OIDCClientRepresentation client = JsonSerialization.readValue(response, OIDCClientRepresentation.class);
|
||||||
|
outputResult(client.getClientId(), client);
|
||||||
|
|
||||||
|
saveMergeConfig(cfg -> {
|
||||||
|
setRegistrationToken(cfg.ensureRealmConfigData(server, realm), client.getClientId(), client.getRegistrationAccessToken());
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
printOut("Response from server: " + readFully(response));
|
||||||
}
|
}
|
||||||
|
} catch (UnrecognizedPropertyException e) {
|
||||||
processGlobalOptions();
|
throw new RuntimeException("Failed to process HTTP reponse - " + e.getMessage(), e);
|
||||||
|
} catch (IOException e) {
|
||||||
if (args != null) {
|
throw new RuntimeException("Failed to process HTTP response", e);
|
||||||
Iterator<String> it = args.iterator();
|
|
||||||
while (it.hasNext()) {
|
|
||||||
String option = it.next();
|
|
||||||
switch (option) {
|
|
||||||
case "-s":
|
|
||||||
case "--set": {
|
|
||||||
if (!it.hasNext()) {
|
|
||||||
throw new IllegalArgumentException("Option " + option + " requires a value");
|
|
||||||
}
|
|
||||||
String[] keyVal = parseKeyVal(it.next());
|
|
||||||
attrs.add(new AttributeOperation(SET, keyVal[0], keyVal[1]));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
throw new IllegalArgumentException("Unsupported option: " + option);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (file == null && attrs.size() == 0) {
|
|
||||||
throw new IllegalArgumentException("No file nor attribute values specified");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (outputClient && returnClientId) {
|
|
||||||
throw new IllegalArgumentException("Options -o and -i are mutually exclusive");
|
|
||||||
}
|
|
||||||
|
|
||||||
// if --token is specified read it
|
|
||||||
if ("-".equals(token)) {
|
|
||||||
token = readSecret("Enter Initial Access Token: ", commandInvocation);
|
|
||||||
}
|
|
||||||
|
|
||||||
CmdStdinContext ctx = new CmdStdinContext();
|
|
||||||
if (file != null) {
|
|
||||||
ctx = parseFileOrStdin(file, regType);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ctx.getEndpointType() == null) {
|
|
||||||
regType = regType != null ? regType : DEFAULT;
|
|
||||||
ctx.setEndpointType(regType);
|
|
||||||
} else if (regType != null && ctx.getEndpointType() != regType) {
|
|
||||||
throw new RuntimeException("Requested endpoint type not compatible with detected configuration format: " + ctx.getEndpointType());
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attrs.size() > 0) {
|
|
||||||
ctx = mergeAttributes(ctx, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
String contentType = getExpectedContentType(ctx.getEndpointType());
|
|
||||||
|
|
||||||
ConfigData config = loadConfig();
|
|
||||||
config = copyWithServerInfo(config);
|
|
||||||
|
|
||||||
if (token == null) {
|
|
||||||
// if initial token is not set, try use the one from configuration
|
|
||||||
token = config.sessionRealmConfigData().getInitialToken();
|
|
||||||
}
|
|
||||||
|
|
||||||
setupTruststore(config, commandInvocation);
|
|
||||||
|
|
||||||
String auth = token;
|
|
||||||
if (auth == null) {
|
|
||||||
config = ensureAuthInfo(config, commandInvocation);
|
|
||||||
config = copyWithServerInfo(config);
|
|
||||||
if (credentialsAvailable(config)) {
|
|
||||||
auth = ensureToken(config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auth = auth != null ? "Bearer " + auth : null;
|
|
||||||
|
|
||||||
final String server = config.getServerUrl();
|
|
||||||
final String realm = config.getRealm();
|
|
||||||
|
|
||||||
InputStream response = doPost(server + "/realms/" + realm + "/clients-registrations/" + ctx.getEndpointType().getEndpoint(),
|
|
||||||
contentType, HttpUtil.APPLICATION_JSON, ctx.getContent(), auth);
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (ctx.getEndpointType() == DEFAULT || ctx.getEndpointType() == SAML2) {
|
|
||||||
ClientRepresentation client = JsonSerialization.readValue(response, ClientRepresentation.class);
|
|
||||||
outputResult(client.getClientId(), client);
|
|
||||||
|
|
||||||
saveMergeConfig(cfg -> {
|
|
||||||
setRegistrationToken(cfg.ensureRealmConfigData(server, realm), client.getClientId(), client.getRegistrationAccessToken());
|
|
||||||
});
|
|
||||||
} else if (ctx.getEndpointType() == OIDC) {
|
|
||||||
OIDCClientRepresentation client = JsonSerialization.readValue(response, OIDCClientRepresentation.class);
|
|
||||||
outputResult(client.getClientId(), client);
|
|
||||||
|
|
||||||
saveMergeConfig(cfg -> {
|
|
||||||
setRegistrationToken(cfg.ensureRealmConfigData(server, realm), client.getClientId(), client.getRegistrationAccessToken());
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
printOut("Response from server: " + readFully(response));
|
|
||||||
}
|
|
||||||
} catch (UnrecognizedPropertyException e) {
|
|
||||||
throw new RuntimeException("Failed to process HTTP reponse - " + e.getMessage(), e);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Failed to process HTTP response", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return CommandResult.SUCCESS;
|
|
||||||
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
|
|
||||||
} finally {
|
|
||||||
commandInvocation.stop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -235,13 +199,10 @@ public class CreateCmd extends AbstractAuthOptionsCmd implements Command {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean nothingToDo() {
|
protected boolean nothingToDo() {
|
||||||
return noOptions() && regType == null && file == null && (args == null || args.size() == 0);
|
return noOptions() && regType == null && file == null && rawSets.isEmpty();
|
||||||
}
|
|
||||||
|
|
||||||
protected String suggestHelp() {
|
|
||||||
return EOL + "Try '" + CMD + " help create' for more information";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected String help() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,17 +17,14 @@
|
||||||
|
|
||||||
package org.keycloak.client.registration.cli.commands;
|
package org.keycloak.client.registration.cli.commands;
|
||||||
|
|
||||||
import org.jboss.aesh.cl.Arguments;
|
|
||||||
import org.jboss.aesh.cl.CommandDefinition;
|
|
||||||
import org.jboss.aesh.console.command.CommandException;
|
|
||||||
import org.jboss.aesh.console.command.CommandResult;
|
|
||||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|
||||||
import org.keycloak.client.registration.cli.config.ConfigData;
|
import org.keycloak.client.registration.cli.config.ConfigData;
|
||||||
import org.keycloak.client.registration.cli.util.ParseUtil;
|
import org.keycloak.client.registration.cli.util.ParseUtil;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.util.List;
|
|
||||||
|
import picocli.CommandLine.Command;
|
||||||
|
import picocli.CommandLine.Parameters;
|
||||||
|
|
||||||
import static org.keycloak.client.registration.cli.util.AuthUtil.ensureToken;
|
import static org.keycloak.client.registration.cli.util.AuthUtil.ensureToken;
|
||||||
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
||||||
|
@ -39,92 +36,68 @@ import static org.keycloak.client.registration.cli.util.HttpUtil.doDelete;
|
||||||
import static org.keycloak.client.registration.cli.util.HttpUtil.urlencode;
|
import static org.keycloak.client.registration.cli.util.HttpUtil.urlencode;
|
||||||
import static org.keycloak.client.registration.cli.util.IoUtil.warnfErr;
|
import static org.keycloak.client.registration.cli.util.IoUtil.warnfErr;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
|
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
|
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
@CommandDefinition(name = "delete", description = "CLIENT [GLOBAL_OPTIONS]")
|
@Command(name = "delete", description = "CLIENT [GLOBAL_OPTIONS]")
|
||||||
public class DeleteCmd extends AbstractAuthOptionsCmd {
|
public class DeleteCmd extends AbstractAuthOptionsCmd {
|
||||||
|
|
||||||
@Arguments
|
@Parameters(arity = "0..1")
|
||||||
private List<String> args;
|
String clientId;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
protected void process() {
|
||||||
try {
|
if (clientId == null) {
|
||||||
if (printHelp()) {
|
throw new IllegalArgumentException("CLIENT not specified");
|
||||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
processGlobalOptions();
|
|
||||||
|
|
||||||
if (args == null || args.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("CLIENT not specified");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.size() > 1) {
|
|
||||||
throw new IllegalArgumentException("Invalid option: " + args.get(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
String clientId = args.get(0);
|
|
||||||
|
|
||||||
if (clientId.startsWith("-")) {
|
|
||||||
warnfErr(ParseUtil.CLIENT_OPTION_WARN, clientId);
|
|
||||||
}
|
|
||||||
|
|
||||||
String regType = "default";
|
|
||||||
|
|
||||||
ConfigData config = loadConfig();
|
|
||||||
config = copyWithServerInfo(config);
|
|
||||||
|
|
||||||
if (token == null) {
|
|
||||||
// if registration access token is not set via -t, try use the one from configuration
|
|
||||||
token = getRegistrationToken(config.sessionRealmConfigData(), clientId);
|
|
||||||
}
|
|
||||||
|
|
||||||
setupTruststore(config, commandInvocation);
|
|
||||||
|
|
||||||
String auth = token;
|
|
||||||
if (auth == null) {
|
|
||||||
config = ensureAuthInfo(config, commandInvocation);
|
|
||||||
config = copyWithServerInfo(config);
|
|
||||||
if (credentialsAvailable(config)) {
|
|
||||||
auth = ensureToken(config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auth = auth != null ? "Bearer " + auth : null;
|
|
||||||
|
|
||||||
|
|
||||||
final String server = config.getServerUrl();
|
|
||||||
final String realm = config.getRealm();
|
|
||||||
|
|
||||||
doDelete(server + "/realms/" + realm + "/clients-registrations/" + regType + "/" + urlencode(clientId), auth);
|
|
||||||
|
|
||||||
saveMergeConfig(cfg -> {
|
|
||||||
cfg.ensureRealmConfigData(server, realm).getClients().remove(clientId);
|
|
||||||
});
|
|
||||||
return CommandResult.SUCCESS;
|
|
||||||
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
|
|
||||||
} finally {
|
|
||||||
commandInvocation.stop();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (clientId.startsWith("-")) {
|
||||||
|
warnfErr(ParseUtil.CLIENT_OPTION_WARN, clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
String regType = "default";
|
||||||
|
|
||||||
|
ConfigData config = loadConfig();
|
||||||
|
config = copyWithServerInfo(config);
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
// if registration access token is not set via -t, try use the one from configuration
|
||||||
|
token = getRegistrationToken(config.sessionRealmConfigData(), clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupTruststore(config);
|
||||||
|
|
||||||
|
String auth = token;
|
||||||
|
if (auth == null) {
|
||||||
|
config = ensureAuthInfo(config);
|
||||||
|
config = copyWithServerInfo(config);
|
||||||
|
if (credentialsAvailable(config)) {
|
||||||
|
auth = ensureToken(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auth = auth != null ? "Bearer " + auth : null;
|
||||||
|
|
||||||
|
|
||||||
|
final String server = config.getServerUrl();
|
||||||
|
final String realm = config.getRealm();
|
||||||
|
|
||||||
|
doDelete(server + "/realms/" + realm + "/clients-registrations/" + regType + "/" + urlencode(clientId), auth);
|
||||||
|
|
||||||
|
saveMergeConfig(cfg -> {
|
||||||
|
cfg.ensureRealmConfigData(server, realm).getClients().remove(clientId);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean nothingToDo() {
|
protected boolean nothingToDo() {
|
||||||
return noOptions() && (args == null || args.size() == 0);
|
return noOptions() && clientId == null;
|
||||||
}
|
|
||||||
|
|
||||||
protected String suggestHelp() {
|
|
||||||
return EOL + "Try '" + CMD + " help delete' for more information";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected String help() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,12 +17,6 @@
|
||||||
|
|
||||||
package org.keycloak.client.registration.cli.commands;
|
package org.keycloak.client.registration.cli.commands;
|
||||||
|
|
||||||
import org.jboss.aesh.cl.Arguments;
|
|
||||||
import org.jboss.aesh.cl.CommandDefinition;
|
|
||||||
import org.jboss.aesh.cl.Option;
|
|
||||||
import org.jboss.aesh.console.command.CommandException;
|
|
||||||
import org.jboss.aesh.console.command.CommandResult;
|
|
||||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|
||||||
import org.keycloak.client.registration.cli.config.ConfigData;
|
import org.keycloak.client.registration.cli.config.ConfigData;
|
||||||
import org.keycloak.client.registration.cli.common.EndpointType;
|
import org.keycloak.client.registration.cli.common.EndpointType;
|
||||||
import org.keycloak.client.registration.cli.util.ParseUtil;
|
import org.keycloak.client.registration.cli.util.ParseUtil;
|
||||||
|
@ -35,7 +29,10 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.util.List;
|
|
||||||
|
import picocli.CommandLine.Command;
|
||||||
|
import picocli.CommandLine.Option;
|
||||||
|
import picocli.CommandLine.Parameters;
|
||||||
|
|
||||||
import static org.keycloak.client.registration.cli.util.AuthUtil.ensureToken;
|
import static org.keycloak.client.registration.cli.util.AuthUtil.ensureToken;
|
||||||
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
||||||
|
@ -51,142 +48,118 @@ import static org.keycloak.client.registration.cli.util.IoUtil.warnfErr;
|
||||||
import static org.keycloak.client.registration.cli.util.IoUtil.printOut;
|
import static org.keycloak.client.registration.cli.util.IoUtil.printOut;
|
||||||
import static org.keycloak.client.registration.cli.util.IoUtil.readFully;
|
import static org.keycloak.client.registration.cli.util.IoUtil.readFully;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
|
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
|
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
@CommandDefinition(name = "get", description = "[ARGUMENTS]")
|
@Command(name = "get", description = "[ARGUMENTS]")
|
||||||
public class GetCmd extends AbstractAuthOptionsCmd {
|
public class GetCmd extends AbstractAuthOptionsCmd {
|
||||||
|
|
||||||
@Option(shortName = 'c', name = "compressed", description = "Print full stack trace when exiting with error", hasValue = false)
|
@Option(names = {"-c", "--compressed"}, description = "Print full stack trace when exiting with error")
|
||||||
private boolean compressed = false;
|
private boolean compressed = false;
|
||||||
|
|
||||||
@Option(shortName = 'e', name = "endpoint", description = "Endpoint type to use", hasValue = true)
|
@Option(names = {"-e", "--endpoint"}, description = "Endpoint type to use")
|
||||||
private String endpoint;
|
private String endpoint;
|
||||||
|
|
||||||
@Arguments
|
@Parameters(arity = "0..1")
|
||||||
private List<String> args;
|
String clientId;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
protected void process() {
|
||||||
|
if (clientId == null) {
|
||||||
|
throw new IllegalArgumentException("CLIENT not specified");
|
||||||
|
}
|
||||||
|
|
||||||
|
EndpointType regType = endpoint != null ? EndpointType.of(endpoint) : EndpointType.DEFAULT;
|
||||||
|
|
||||||
|
|
||||||
|
if (clientId.startsWith("-")) {
|
||||||
|
warnfErr(ParseUtil.CLIENT_OPTION_WARN, clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigData config = loadConfig();
|
||||||
|
config = copyWithServerInfo(config);
|
||||||
|
|
||||||
|
if (token == null) {
|
||||||
|
// if registration access token is not set via -t, try use the one from configuration
|
||||||
|
token = getRegistrationToken(config.sessionRealmConfigData(), clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
setupTruststore(config);
|
||||||
|
|
||||||
|
String auth = token;
|
||||||
|
if (auth == null) {
|
||||||
|
config = ensureAuthInfo(config);
|
||||||
|
config = copyWithServerInfo(config);
|
||||||
|
if (credentialsAvailable(config)) {
|
||||||
|
auth = ensureToken(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auth = auth != null ? "Bearer " + auth : null;
|
||||||
|
|
||||||
|
|
||||||
|
final String server = config.getServerUrl();
|
||||||
|
final String realm = config.getRealm();
|
||||||
|
|
||||||
|
InputStream response = doGet(server + "/realms/" + realm + "/clients-registrations/" + regType.getEndpoint() + "/" + urlencode(clientId),
|
||||||
|
APPLICATION_JSON, auth);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (printHelp()) {
|
String json = readFully(response);
|
||||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
Object result = null;
|
||||||
}
|
|
||||||
|
|
||||||
processGlobalOptions();
|
switch (regType) {
|
||||||
|
case DEFAULT: {
|
||||||
|
ClientRepresentation client = JsonSerialization.readValue(json, ClientRepresentation.class);
|
||||||
|
result = client;
|
||||||
|
|
||||||
if (args == null || args.isEmpty()) {
|
saveMergeConfig(cfg -> {
|
||||||
throw new IllegalArgumentException("CLIENT not specified");
|
setRegistrationToken(cfg.ensureRealmConfigData(server, realm), client.getClientId(), client.getRegistrationAccessToken());
|
||||||
}
|
});
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case OIDC: {
|
||||||
|
OIDCClientRepresentation client = JsonSerialization.readValue(json, OIDCClientRepresentation.class);
|
||||||
|
result = client;
|
||||||
|
|
||||||
if (args.size() > 1) {
|
saveMergeConfig(cfg -> {
|
||||||
throw new IllegalArgumentException("Invalid option: " + args.get(1));
|
setRegistrationToken(cfg.ensureRealmConfigData(server, realm), client.getClientId(), client.getRegistrationAccessToken());
|
||||||
}
|
});
|
||||||
|
break;
|
||||||
String clientId = args.get(0);
|
}
|
||||||
EndpointType regType = endpoint != null ? EndpointType.of(endpoint) : EndpointType.DEFAULT;
|
case INSTALL: {
|
||||||
|
result = JsonSerialization.readValue(json, AdapterConfig.class);
|
||||||
|
break;
|
||||||
if (clientId.startsWith("-")) {
|
}
|
||||||
warnfErr(ParseUtil.CLIENT_OPTION_WARN, clientId);
|
case SAML2: {
|
||||||
}
|
break;
|
||||||
|
}
|
||||||
ConfigData config = loadConfig();
|
default: {
|
||||||
config = copyWithServerInfo(config);
|
throw new RuntimeException("Unexpected type: " + regType);
|
||||||
|
|
||||||
if (token == null) {
|
|
||||||
// if registration access token is not set via -t, try use the one from configuration
|
|
||||||
token = getRegistrationToken(config.sessionRealmConfigData(), clientId);
|
|
||||||
}
|
|
||||||
|
|
||||||
setupTruststore(config, commandInvocation);
|
|
||||||
|
|
||||||
String auth = token;
|
|
||||||
if (auth == null) {
|
|
||||||
config = ensureAuthInfo(config, commandInvocation);
|
|
||||||
config = copyWithServerInfo(config);
|
|
||||||
if (credentialsAvailable(config)) {
|
|
||||||
auth = ensureToken(config);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auth = auth != null ? "Bearer " + auth : null;
|
if (!compressed && result != null) {
|
||||||
|
json = JsonSerialization.writeValueAsPrettyString(result);
|
||||||
|
|
||||||
final String server = config.getServerUrl();
|
|
||||||
final String realm = config.getRealm();
|
|
||||||
|
|
||||||
InputStream response = doGet(server + "/realms/" + realm + "/clients-registrations/" + regType.getEndpoint() + "/" + urlencode(clientId),
|
|
||||||
APPLICATION_JSON, auth);
|
|
||||||
|
|
||||||
try {
|
|
||||||
String json = readFully(response);
|
|
||||||
Object result = null;
|
|
||||||
|
|
||||||
switch (regType) {
|
|
||||||
case DEFAULT: {
|
|
||||||
ClientRepresentation client = JsonSerialization.readValue(json, ClientRepresentation.class);
|
|
||||||
result = client;
|
|
||||||
|
|
||||||
saveMergeConfig(cfg -> {
|
|
||||||
setRegistrationToken(cfg.ensureRealmConfigData(server, realm), client.getClientId(), client.getRegistrationAccessToken());
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case OIDC: {
|
|
||||||
OIDCClientRepresentation client = JsonSerialization.readValue(json, OIDCClientRepresentation.class);
|
|
||||||
result = client;
|
|
||||||
|
|
||||||
saveMergeConfig(cfg -> {
|
|
||||||
setRegistrationToken(cfg.ensureRealmConfigData(server, realm), client.getClientId(), client.getRegistrationAccessToken());
|
|
||||||
});
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case INSTALL: {
|
|
||||||
result = JsonSerialization.readValue(json, AdapterConfig.class);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case SAML2: {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
throw new RuntimeException("Unexpected type: " + regType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!compressed && result != null) {
|
|
||||||
json = JsonSerialization.writeValueAsPrettyString(result);
|
|
||||||
}
|
|
||||||
|
|
||||||
printOut(json);
|
|
||||||
|
|
||||||
//} catch (UnrecognizedPropertyException e) {
|
|
||||||
// throw new RuntimeException("Failed to parse returned JSON - " + e.getMessage(), e);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Failed to process HTTP response", e);
|
|
||||||
}
|
}
|
||||||
return CommandResult.SUCCESS;
|
|
||||||
|
|
||||||
} catch (IllegalArgumentException e) {
|
printOut(json);
|
||||||
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
|
|
||||||
} finally {
|
//} catch (UnrecognizedPropertyException e) {
|
||||||
commandInvocation.stop();
|
// throw new RuntimeException("Failed to parse returned JSON - " + e.getMessage(), e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Failed to process HTTP response", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean nothingToDo() {
|
protected boolean nothingToDo() {
|
||||||
return noOptions() && endpoint == null && (args == null || args.size() == 0);
|
return noOptions() && endpoint == null && clientId == null;
|
||||||
}
|
|
||||||
|
|
||||||
protected String suggestHelp() {
|
|
||||||
return EOL + "Try '" + CMD + " help get' for more information";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected String help() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,90 +1,80 @@
|
||||||
package org.keycloak.client.registration.cli.commands;
|
package org.keycloak.client.registration.cli.commands;
|
||||||
|
|
||||||
import org.jboss.aesh.cl.Arguments;
|
|
||||||
import org.jboss.aesh.cl.CommandDefinition;
|
|
||||||
import org.jboss.aesh.console.command.Command;
|
|
||||||
import org.jboss.aesh.console.command.CommandException;
|
|
||||||
import org.jboss.aesh.console.command.CommandResult;
|
|
||||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import picocli.CommandLine.Command;
|
||||||
|
import picocli.CommandLine.Parameters;
|
||||||
|
|
||||||
import static org.keycloak.client.registration.cli.util.IoUtil.printOut;
|
import static org.keycloak.client.registration.cli.util.IoUtil.printOut;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
@CommandDefinition(name = "help", description = "This help")
|
@Command(name = "help", description = "This help")
|
||||||
public class HelpCmd implements Command {
|
public class HelpCmd implements Runnable {
|
||||||
|
|
||||||
@Arguments
|
@Parameters
|
||||||
private List<String> args;
|
private List<String> args;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
public void run() {
|
||||||
try {
|
if (args == null || args.size() == 0) {
|
||||||
if (args == null || args.size() == 0) {
|
printOut(KcRegCmd.usage());
|
||||||
printOut(KcRegCmd.usage());
|
} else {
|
||||||
} else {
|
outer:
|
||||||
outer:
|
switch (args.get(0)) {
|
||||||
switch (args.get(0)) {
|
case "config": {
|
||||||
case "config": {
|
if (args.size() > 1) {
|
||||||
if (args.size() > 1) {
|
switch (args.get(1)) {
|
||||||
switch (args.get(1)) {
|
case "credentials": {
|
||||||
case "credentials": {
|
printOut(ConfigCredentialsCmd.usage());
|
||||||
printOut(ConfigCredentialsCmd.usage());
|
break outer;
|
||||||
break outer;
|
}
|
||||||
}
|
case "initial-token": {
|
||||||
case "initial-token": {
|
printOut(ConfigInitialTokenCmd.usage());
|
||||||
printOut(ConfigInitialTokenCmd.usage());
|
break outer;
|
||||||
break outer;
|
}
|
||||||
}
|
case "registration-token": {
|
||||||
case "registration-token": {
|
printOut(ConfigRegistrationTokenCmd.usage());
|
||||||
printOut(ConfigRegistrationTokenCmd.usage());
|
break outer;
|
||||||
break outer;
|
}
|
||||||
}
|
case "truststore": {
|
||||||
case "truststore": {
|
printOut(ConfigTruststoreCmd.usage());
|
||||||
printOut(ConfigTruststoreCmd.usage());
|
break outer;
|
||||||
break outer;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
printOut(ConfigCmd.usage());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "create": {
|
|
||||||
printOut(CreateCmd.usage());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "get": {
|
|
||||||
printOut(GetCmd.usage());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "update": {
|
|
||||||
printOut(UpdateCmd.usage());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "delete": {
|
|
||||||
printOut(DeleteCmd.usage());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "attrs": {
|
|
||||||
printOut(AttrsCmd.usage());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case "update-token": {
|
|
||||||
printOut(UpdateTokenCmd.usage());
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
throw new RuntimeException("Unknown command: " + args.get(0));
|
|
||||||
}
|
}
|
||||||
|
printOut(ConfigCmd.usage());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "create": {
|
||||||
|
printOut(CreateCmd.usage());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "get": {
|
||||||
|
printOut(GetCmd.usage());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "update": {
|
||||||
|
printOut(UpdateCmd.usage());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "delete": {
|
||||||
|
printOut(DeleteCmd.usage());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "attrs": {
|
||||||
|
printOut(AttrsCmd.usage());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case "update-token": {
|
||||||
|
printOut(UpdateTokenCmd.usage());
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
throw new IllegalArgumentException("Unknown command: " + args.get(0));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return CommandResult.SUCCESS;
|
|
||||||
} finally {
|
|
||||||
commandInvocation.stop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,14 +16,11 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.client.registration.cli.commands;
|
package org.keycloak.client.registration.cli.commands;
|
||||||
|
|
||||||
import org.jboss.aesh.cl.GroupCommandDefinition;
|
|
||||||
import org.jboss.aesh.console.command.CommandException;
|
|
||||||
import org.jboss.aesh.console.command.CommandResult;
|
|
||||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
|
|
||||||
|
import picocli.CommandLine.Command;
|
||||||
|
|
||||||
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
import static org.keycloak.client.registration.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
||||||
import static org.keycloak.client.registration.cli.util.IoUtil.printOut;
|
import static org.keycloak.client.registration.cli.util.IoUtil.printOut;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
||||||
|
@ -33,27 +30,30 @@ import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@GroupCommandDefinition(name = "kcreg", description = "COMMAND [ARGUMENTS]", groupCommands = {
|
@Command(name = "kcreg",
|
||||||
HelpCmd.class, ConfigCmd.class, CreateCmd.class, UpdateCmd.class, GetCmd.class, DeleteCmd.class, AttrsCmd.class, UpdateTokenCmd.class} )
|
header = {
|
||||||
|
"Keycloak - Open Source Identity and Access Management",
|
||||||
|
"",
|
||||||
|
"Find more information at: https://www.keycloak.org/docs/latest"
|
||||||
|
},
|
||||||
|
description = {
|
||||||
|
"%nCOMMAND [ARGUMENTS]"
|
||||||
|
},
|
||||||
|
subcommands = {
|
||||||
|
HelpCmd.class,
|
||||||
|
ConfigCmd.class,
|
||||||
|
CreateCmd.class,
|
||||||
|
GetCmd.class,
|
||||||
|
UpdateCmd.class,
|
||||||
|
DeleteCmd.class,
|
||||||
|
AttrsCmd.class,
|
||||||
|
UpdateTokenCmd.class
|
||||||
|
})
|
||||||
public class KcRegCmd extends AbstractGlobalOptionsCmd {
|
public class KcRegCmd extends AbstractGlobalOptionsCmd {
|
||||||
|
|
||||||
//@Arguments
|
|
||||||
//private List<String> args;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
protected boolean nothingToDo() {
|
||||||
try {
|
return true;
|
||||||
// if --help was requested then status is SUCCESS
|
|
||||||
// if not we print help anyway, but status is FAILURE
|
|
||||||
if (printHelp()) {
|
|
||||||
return CommandResult.SUCCESS;
|
|
||||||
} else {
|
|
||||||
printOut(usage());
|
|
||||||
return CommandResult.FAILURE;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
commandInvocation.stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String usage() {
|
public static String usage() {
|
||||||
|
|
|
@ -18,13 +18,13 @@
|
||||||
package org.keycloak.client.registration.cli.commands;
|
package org.keycloak.client.registration.cli.commands;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonParseException;
|
import com.fasterxml.jackson.core.JsonParseException;
|
||||||
import org.jboss.aesh.cl.Arguments;
|
|
||||||
import org.jboss.aesh.cl.CommandDefinition;
|
import picocli.CommandLine.ArgGroup;
|
||||||
import org.jboss.aesh.cl.Option;
|
import picocli.CommandLine.Command;
|
||||||
import org.jboss.aesh.console.command.CommandException;
|
import picocli.CommandLine.Option;
|
||||||
import org.jboss.aesh.console.command.CommandResult;
|
import picocli.CommandLine.Parameters;
|
||||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|
||||||
import org.keycloak.client.registration.cli.aesh.EndpointTypeConverter;
|
import org.keycloak.client.registration.cli.EndpointTypeConverter;
|
||||||
import org.keycloak.client.registration.cli.common.AttributeOperation;
|
import org.keycloak.client.registration.cli.common.AttributeOperation;
|
||||||
import org.keycloak.client.registration.cli.config.ConfigData;
|
import org.keycloak.client.registration.cli.config.ConfigData;
|
||||||
import org.keycloak.client.registration.cli.common.CmdStdinContext;
|
import org.keycloak.client.registration.cli.common.CmdStdinContext;
|
||||||
|
@ -39,7 +39,7 @@ import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.util.Iterator;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
@ -62,7 +62,6 @@ import static org.keycloak.client.registration.cli.util.IoUtil.warnfErr;
|
||||||
import static org.keycloak.client.registration.cli.util.IoUtil.readFully;
|
import static org.keycloak.client.registration.cli.util.IoUtil.readFully;
|
||||||
import static org.keycloak.client.registration.cli.util.HttpUtil.APPLICATION_JSON;
|
import static org.keycloak.client.registration.cli.util.HttpUtil.APPLICATION_JSON;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
|
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
|
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
|
||||||
import static org.keycloak.client.registration.cli.util.ParseUtil.mergeAttributes;
|
import static org.keycloak.client.registration.cli.util.ParseUtil.mergeAttributes;
|
||||||
import static org.keycloak.client.registration.cli.util.ParseUtil.parseFileOrStdin;
|
import static org.keycloak.client.registration.cli.util.ParseUtil.parseFileOrStdin;
|
||||||
|
@ -71,258 +70,231 @@ import static org.keycloak.client.registration.cli.util.ParseUtil.parseKeyVal;
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
@CommandDefinition(name = "update", description = "CLIENT_ID [ARGUMENTS]")
|
@Command(name = "update", description = "CLIENT_ID [ARGUMENTS]")
|
||||||
public class UpdateCmd extends AbstractAuthOptionsCmd {
|
public class UpdateCmd extends AbstractAuthOptionsCmd {
|
||||||
|
|
||||||
@Option(shortName = 'e', name = "endpoint", description = "Endpoint type to use - one of: 'default', 'oidc'", hasValue = true, converter = EndpointTypeConverter.class)
|
@Option(names = {"-e", "--endpoint"}, description = "Endpoint type to use - one of: 'default', 'oidc'", converter = EndpointTypeConverter.class)
|
||||||
private EndpointType regType = null;
|
private EndpointType regType = null;
|
||||||
|
|
||||||
//@GroupOption(shortName = 's', name = "set", description = "Set specific attribute to a specified value", hasValue = true)
|
@Option(names = {"-f", "--file"}, description = "Use the file or standard input if '-' is specified")
|
||||||
//private List<String> attributes = new ArrayList<>();
|
|
||||||
|
|
||||||
@Option(shortName = 'f', name = "file", description = "Use the file or standard input if '-' is specified", hasValue = true)
|
|
||||||
private String file = null;
|
private String file = null;
|
||||||
|
|
||||||
@Option(shortName = 'm', name = "merge", description = "Merge new values with existing configuration on the server", hasValue = false)
|
@Option(names = {"-m", "--merge"}, description = "Merge new values with existing configuration on the server")
|
||||||
private boolean mergeMode = true;
|
private boolean mergeMode = false;
|
||||||
|
|
||||||
@Option(shortName = 'o', name = "output", description = "After update output the new client configuration", hasValue = false)
|
@Option(names = {"-o", "--output"}, description = "After update output the new client configuration")
|
||||||
private boolean outputClient = false;
|
private boolean outputClient = false;
|
||||||
|
|
||||||
@Option(shortName = 'c', name = "compressed", description = "Don't pretty print the output", hasValue = false)
|
@Option(names = {"-c", "--compressed"}, description = "Don't pretty print the output")
|
||||||
private boolean compressed = false;
|
private boolean compressed = false;
|
||||||
|
|
||||||
@Arguments
|
@Parameters(arity = "0..1")
|
||||||
private List<String> args;
|
String clientId;
|
||||||
|
|
||||||
|
// to maintain relative positions of set and delete operations
|
||||||
|
static class AttributeOperations {
|
||||||
|
@Option(names = {"-s", "--set"}, required = true) String set;
|
||||||
|
@Option(names = {"-d", "--delete"}, required = true) String delete;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ArgGroup(exclusive = true, multiplicity = "0..*")
|
||||||
|
List<AttributeOperations> rawAttributeOperations = new ArrayList<>();
|
||||||
|
|
||||||
|
List<AttributeOperation> attrs = new LinkedList<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
protected void processOptions() {
|
||||||
|
super.processOptions();
|
||||||
|
|
||||||
List<AttributeOperation> attrs = new LinkedList<>();
|
for (AttributeOperations entry : rawAttributeOperations) {
|
||||||
|
if (entry.delete != null) {
|
||||||
try {
|
attrs.add(new AttributeOperation(DELETE, entry.delete));
|
||||||
if (printHelp()) {
|
} else {
|
||||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
String[] keyVal = parseKeyVal(entry.set);
|
||||||
|
attrs.add(new AttributeOperation(SET, keyVal[0], keyVal[1]));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
processGlobalOptions();
|
@Override
|
||||||
|
protected void process() {
|
||||||
|
if (clientId == null) {
|
||||||
|
throw new IllegalArgumentException("CLIENT_ID not specified");
|
||||||
|
}
|
||||||
|
|
||||||
String clientId = null;
|
if (clientId.startsWith("-")) {
|
||||||
|
warnfErr(ParseUtil.CLIENT_OPTION_WARN, clientId);
|
||||||
|
}
|
||||||
|
|
||||||
if (args != null) {
|
if (file == null && attrs.size() == 0) {
|
||||||
Iterator<String> it = args.iterator();
|
throw new IllegalArgumentException("No file nor attribute values specified");
|
||||||
if (!it.hasNext()) {
|
}
|
||||||
throw new IllegalArgumentException("CLIENT_ID not specified");
|
|
||||||
}
|
|
||||||
|
|
||||||
clientId = it.next();
|
// We have several options for update:
|
||||||
|
//
|
||||||
|
// A) if a file is specified, then we can overwrite server state with that file
|
||||||
|
// (that's the normal flow - get and save locally, edit, post to server)
|
||||||
|
//
|
||||||
|
// update my_client -f new_client.json
|
||||||
|
//
|
||||||
|
// B) if a file is specified, and overrides are specified, then we override the file values with those from command line
|
||||||
|
// (that allows us to have a local file as a template, it's also batch job friendly)
|
||||||
|
//
|
||||||
|
// update my_client -s public=true -s enableDirectGrants=false -f new_client.json
|
||||||
|
//
|
||||||
|
// C) if no file is specified, then we can fetch the client definition from server, apply changes to it, and post it back
|
||||||
|
// (again a batch job friendly mode)
|
||||||
|
//
|
||||||
|
// update my_client -s public=true -s enableDirectGrants=false
|
||||||
|
//
|
||||||
|
// This is merge mode by default - if --merge is additionally specified, it is ignored
|
||||||
|
//
|
||||||
|
// D) if a file is specified, then we can merge the file with current state on the server
|
||||||
|
// (that is similar to previous mode except that the overrides are also taken from a file)
|
||||||
|
//
|
||||||
|
// update my_client --merge -f new_client.json
|
||||||
|
// update my_client --merge -s public=true -s enableDirectGrants=false -f new_client.json
|
||||||
|
//
|
||||||
|
// We could also support environment variables in input file, and apply them before parsing it.
|
||||||
|
//
|
||||||
|
// One problem - what if it is SAML XML? No problem as we don't support update for SAML - only create.
|
||||||
|
//
|
||||||
|
if (file == null && attrs.size() > 0) {
|
||||||
|
mergeMode = true;
|
||||||
|
}
|
||||||
|
|
||||||
if (clientId.startsWith("-")) {
|
CmdStdinContext ctx = new CmdStdinContext();
|
||||||
warnfErr(ParseUtil.CLIENT_OPTION_WARN, clientId);
|
if (file != null) {
|
||||||
}
|
ctx = parseFileOrStdin(file, regType);
|
||||||
|
regType = ctx.getEndpointType();
|
||||||
|
}
|
||||||
|
|
||||||
while (it.hasNext()) {
|
if (regType == null) {
|
||||||
String option = it.next();
|
regType = DEFAULT;
|
||||||
switch (option) {
|
ctx.setEndpointType(regType);
|
||||||
case "-s":
|
} else if (regType != DEFAULT && regType != OIDC) {
|
||||||
case "--set": {
|
throw new RuntimeException("Update not supported for endpoint type: " + regType.getEndpoint());
|
||||||
if (!it.hasNext()) {
|
}
|
||||||
throw new IllegalArgumentException("Option " + option + " requires a value");
|
|
||||||
}
|
// initialize config only after reading from stdin,
|
||||||
String[] keyVal = parseKeyVal(it.next());
|
// to allow proper operation when piping 'get' - which consumes the old
|
||||||
attrs.add(new AttributeOperation(SET, keyVal[0], keyVal[1]));
|
// registration access token, and saves the new one to the config
|
||||||
break;
|
ConfigData config = loadConfig();
|
||||||
}
|
config = copyWithServerInfo(config);
|
||||||
case "-d":
|
|
||||||
case "--delete": {
|
final String server = config.getServerUrl();
|
||||||
attrs.add(new AttributeOperation(DELETE, it.next()));
|
final String realm = config.getRealm();
|
||||||
break;
|
|
||||||
}
|
if (token == null) {
|
||||||
default: {
|
// if registration access token is not set via --token, see if it's in the body of any input file
|
||||||
throw new IllegalArgumentException("Unsupported option: " + option);
|
// but first see if it's overridden by --set, or maybe deliberately muted via -d registrationAccessToken
|
||||||
}
|
boolean processed = false;
|
||||||
|
for (AttributeOperation op: attrs) {
|
||||||
|
if ("registrationAccessToken".equals(op.getKey().toString())) {
|
||||||
|
processed = true;
|
||||||
|
if (op.getType() == AttributeOperation.Type.SET) {
|
||||||
|
token = op.getValue();
|
||||||
}
|
}
|
||||||
|
// otherwise it's delete - meaning it should stay null
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!processed) {
|
||||||
if (file == null && attrs.size() == 0) {
|
token = ctx.getRegistrationAccessToken();
|
||||||
throw new IllegalArgumentException("No file nor attribute values specified");
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// We have several options for update:
|
if (token == null) {
|
||||||
//
|
// if registration access token is not set, try use the one from configuration
|
||||||
// A) if a file is specified, then we can overwrite server state with that file
|
token = getRegistrationToken(config.sessionRealmConfigData(), clientId);
|
||||||
// (that's the normal flow - get and save locally, edit, post to server)
|
}
|
||||||
//
|
|
||||||
// update my_client -f new_client.json
|
|
||||||
//
|
|
||||||
// B) if a file is specified, and overrides are specified, then we override the file values with those from command line
|
|
||||||
// (that allows us to have a local file as a template, it's also batch job friendly)
|
|
||||||
//
|
|
||||||
// update my_client -s public=true -s enableDirectGrants=false -f new_client.json
|
|
||||||
//
|
|
||||||
// C) if no file is specified, then we can fetch the client definition from server, apply changes to it, and post it back
|
|
||||||
// (again a batch job friendly mode)
|
|
||||||
//
|
|
||||||
// update my_client -s public=true -s enableDirectGrants=false
|
|
||||||
//
|
|
||||||
// This is merge mode by default - if --merge is additionally specified, it is ignored
|
|
||||||
//
|
|
||||||
// D) if a file is specified, then we can merge the file with current state on the server
|
|
||||||
// (that is similar to previous mode except that the overrides are also taken from a file)
|
|
||||||
//
|
|
||||||
// update my_client --merge -f new_client.json
|
|
||||||
// update my_client --merge -s public=true -s enableDirectGrants=false -f new_client.json
|
|
||||||
//
|
|
||||||
// We could also support environment variables in input file, and apply them before parsing it.
|
|
||||||
//
|
|
||||||
// One problem - what if it is SAML XML? No problem as we don't support update for SAML - only create.
|
|
||||||
//
|
|
||||||
if (file == null && attrs.size() > 0) {
|
|
||||||
mergeMode = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
CmdStdinContext ctx = new CmdStdinContext();
|
setupTruststore(config);
|
||||||
if (file != null) {
|
|
||||||
ctx = parseFileOrStdin(file, regType);
|
|
||||||
regType = ctx.getEndpointType();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (regType == null) {
|
String auth = token;
|
||||||
regType = DEFAULT;
|
if (auth == null) {
|
||||||
ctx.setEndpointType(regType);
|
config = ensureAuthInfo(config);
|
||||||
} else if (regType != DEFAULT && regType != OIDC) {
|
|
||||||
throw new RuntimeException("Update not supported for endpoint type: " + regType.getEndpoint());
|
|
||||||
}
|
|
||||||
|
|
||||||
// initialize config only after reading from stdin,
|
|
||||||
// to allow proper operation when piping 'get' - which consumes the old
|
|
||||||
// registration access token, and saves the new one to the config
|
|
||||||
ConfigData config = loadConfig();
|
|
||||||
config = copyWithServerInfo(config);
|
config = copyWithServerInfo(config);
|
||||||
|
if (credentialsAvailable(config)) {
|
||||||
final String server = config.getServerUrl();
|
auth = ensureToken(config);
|
||||||
final String realm = config.getRealm();
|
|
||||||
|
|
||||||
if (token == null) {
|
|
||||||
// if registration access token is not set via --token, see if it's in the body of any input file
|
|
||||||
// but first see if it's overridden by --set, or maybe deliberately muted via -d registrationAccessToken
|
|
||||||
boolean processed = false;
|
|
||||||
for (AttributeOperation op: attrs) {
|
|
||||||
if ("registrationAccessToken".equals(op.getKey().toString())) {
|
|
||||||
processed = true;
|
|
||||||
if (op.getType() == AttributeOperation.Type.SET) {
|
|
||||||
token = op.getValue();
|
|
||||||
}
|
|
||||||
// otherwise it's delete - meaning it should stay null
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!processed) {
|
|
||||||
token = ctx.getRegistrationAccessToken();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (token == null) {
|
auth = auth != null ? "Bearer " + auth : null;
|
||||||
// if registration access token is not set, try use the one from configuration
|
|
||||||
token = getRegistrationToken(config.sessionRealmConfigData(), clientId);
|
|
||||||
}
|
|
||||||
|
|
||||||
setupTruststore(config, commandInvocation);
|
|
||||||
|
|
||||||
String auth = token;
|
|
||||||
if (auth == null) {
|
|
||||||
config = ensureAuthInfo(config, commandInvocation);
|
|
||||||
config = copyWithServerInfo(config);
|
|
||||||
if (credentialsAvailable(config)) {
|
|
||||||
auth = ensureToken(config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auth = auth != null ? "Bearer " + auth : null;
|
|
||||||
|
|
||||||
|
|
||||||
if (mergeMode) {
|
if (mergeMode) {
|
||||||
InputStream response = doGet(server + "/realms/" + realm + "/clients-registrations/" + regType.getEndpoint() + "/" + urlencode(clientId),
|
InputStream response = doGet(server + "/realms/" + realm + "/clients-registrations/" + regType.getEndpoint() + "/" + urlencode(clientId),
|
||||||
APPLICATION_JSON, auth);
|
APPLICATION_JSON, auth);
|
||||||
|
|
||||||
String json = readFully(response);
|
String json = readFully(response);
|
||||||
|
|
||||||
CmdStdinContext ctxremote = new CmdStdinContext();
|
CmdStdinContext ctxremote = new CmdStdinContext();
|
||||||
ctxremote.setContent(json);
|
ctxremote.setContent(json);
|
||||||
ctxremote.setEndpointType(regType);
|
ctxremote.setEndpointType(regType);
|
||||||
try {
|
|
||||||
|
|
||||||
if (regType == DEFAULT) {
|
|
||||||
ctxremote.setClient(JsonSerialization.readValue(json, ClientRepresentation.class));
|
|
||||||
token = ctxremote.getClient().getRegistrationAccessToken();
|
|
||||||
} else if (regType == OIDC) {
|
|
||||||
ctxremote.setOidcClient(JsonSerialization.readValue(json, OIDCClientRepresentation.class));
|
|
||||||
token = ctxremote.getOidcClient().getRegistrationAccessToken();
|
|
||||||
}
|
|
||||||
} catch (JsonParseException e) {
|
|
||||||
throw new RuntimeException("Not a valid JSON document. " + e.getMessage(), e);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Not a valid JSON document", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
// we have to use registration access token retrieved from previous operation
|
|
||||||
// that ensures optimistic locking semantics
|
|
||||||
if (token != null) {
|
|
||||||
// we use auth with doPost later
|
|
||||||
auth = "Bearer " + token;
|
|
||||||
|
|
||||||
String newToken = token;
|
|
||||||
String clientToUpdate = clientId;
|
|
||||||
saveMergeConfig(cfg -> {
|
|
||||||
setRegistrationToken(cfg.ensureRealmConfigData(server, realm), clientToUpdate, newToken);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// merge local representation over remote one
|
|
||||||
if (ctx.getClient() != null) {
|
|
||||||
ReflectionUtil.merge(ctx.getClient(), ctxremote.getClient());
|
|
||||||
} else if (ctx.getOidcClient() != null) {
|
|
||||||
ReflectionUtil.merge(ctx.getOidcClient(), ctxremote.getOidcClient());
|
|
||||||
}
|
|
||||||
ctx = ctxremote;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (attrs.size() > 0) {
|
|
||||||
ctx = mergeAttributes(ctx, attrs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// now update
|
|
||||||
InputStream response = doPut(server + "/realms/" + realm + "/clients-registrations/" + regType.getEndpoint() + "/" + urlencode(clientId),
|
|
||||||
APPLICATION_JSON, APPLICATION_JSON, ctx.getContent(), auth);
|
|
||||||
try {
|
try {
|
||||||
|
|
||||||
if (regType == DEFAULT) {
|
if (regType == DEFAULT) {
|
||||||
ClientRepresentation clirep = JsonSerialization.readValue(response, ClientRepresentation.class);
|
ctxremote.setClient(JsonSerialization.readValue(json, ClientRepresentation.class));
|
||||||
outputResult(clirep);
|
token = ctxremote.getClient().getRegistrationAccessToken();
|
||||||
token = clirep.getRegistrationAccessToken();
|
|
||||||
} else if (regType == OIDC) {
|
} else if (regType == OIDC) {
|
||||||
OIDCClientRepresentation clirep = JsonSerialization.readValue(response, OIDCClientRepresentation.class);
|
ctxremote.setOidcClient(JsonSerialization.readValue(json, OIDCClientRepresentation.class));
|
||||||
outputResult(clirep);
|
token = ctxremote.getOidcClient().getRegistrationAccessToken();
|
||||||
token = clirep.getRegistrationAccessToken();
|
|
||||||
}
|
}
|
||||||
|
} catch (JsonParseException e) {
|
||||||
|
throw new RuntimeException("Not a valid JSON document. " + e.getMessage(), e);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Not a valid JSON document", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we have to use registration access token retrieved from previous operation
|
||||||
|
// that ensures optimistic locking semantics
|
||||||
|
if (token != null) {
|
||||||
|
// we use auth with doPost later
|
||||||
|
auth = "Bearer " + token;
|
||||||
|
|
||||||
String newToken = token;
|
String newToken = token;
|
||||||
String clientToUpdate = clientId;
|
String clientToUpdate = clientId;
|
||||||
saveMergeConfig(cfg -> {
|
saveMergeConfig(cfg -> {
|
||||||
setRegistrationToken(cfg.ensureRealmConfigData(server, realm), clientToUpdate, newToken);
|
setRegistrationToken(cfg.ensureRealmConfigData(server, realm), clientToUpdate, newToken);
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Failed to process HTTP response", e);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return CommandResult.SUCCESS;
|
// merge local representation over remote one
|
||||||
|
if (ctx.getClient() != null) {
|
||||||
|
ReflectionUtil.merge(ctx.getClient(), ctxremote.getClient());
|
||||||
|
} else if (ctx.getOidcClient() != null) {
|
||||||
|
ReflectionUtil.merge(ctx.getOidcClient(), ctxremote.getOidcClient());
|
||||||
|
}
|
||||||
|
ctx = ctxremote;
|
||||||
|
}
|
||||||
|
|
||||||
} catch (IllegalArgumentException e) {
|
if (attrs.size() > 0) {
|
||||||
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
|
ctx = mergeAttributes(ctx, attrs);
|
||||||
} finally {
|
}
|
||||||
commandInvocation.stop();
|
|
||||||
|
// now update
|
||||||
|
InputStream response = doPut(server + "/realms/" + realm + "/clients-registrations/" + regType.getEndpoint() + "/" + urlencode(clientId),
|
||||||
|
APPLICATION_JSON, APPLICATION_JSON, ctx.getContent(), auth);
|
||||||
|
try {
|
||||||
|
if (regType == DEFAULT) {
|
||||||
|
ClientRepresentation clirep = JsonSerialization.readValue(response, ClientRepresentation.class);
|
||||||
|
outputResult(clirep);
|
||||||
|
token = clirep.getRegistrationAccessToken();
|
||||||
|
} else if (regType == OIDC) {
|
||||||
|
OIDCClientRepresentation clirep = JsonSerialization.readValue(response, OIDCClientRepresentation.class);
|
||||||
|
outputResult(clirep);
|
||||||
|
token = clirep.getRegistrationAccessToken();
|
||||||
|
}
|
||||||
|
|
||||||
|
String newToken = token;
|
||||||
|
String clientToUpdate = clientId;
|
||||||
|
saveMergeConfig(cfg -> {
|
||||||
|
setRegistrationToken(cfg.ensureRealmConfigData(server, realm), clientToUpdate, newToken);
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Failed to process HTTP response", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -338,13 +310,10 @@ public class UpdateCmd extends AbstractAuthOptionsCmd {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean nothingToDo() {
|
protected boolean nothingToDo() {
|
||||||
return noOptions() && regType == null && file == null && (args == null || args.size() == 0);
|
return noOptions() && regType == null && file == null && rawAttributeOperations.isEmpty() && clientId == null;
|
||||||
}
|
|
||||||
|
|
||||||
protected String suggestHelp() {
|
|
||||||
return EOL + "Try '" + CMD + " help update' for more information";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected String help() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,10 @@
|
||||||
package org.keycloak.client.registration.cli.commands;
|
package org.keycloak.client.registration.cli.commands;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import org.jboss.aesh.cl.Arguments;
|
|
||||||
import org.jboss.aesh.cl.CommandDefinition;
|
import picocli.CommandLine.Command;
|
||||||
import org.jboss.aesh.console.command.CommandException;
|
import picocli.CommandLine.Parameters;
|
||||||
import org.jboss.aesh.console.command.CommandResult;
|
|
||||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|
||||||
import org.keycloak.client.registration.cli.config.ConfigData;
|
import org.keycloak.client.registration.cli.config.ConfigData;
|
||||||
import org.keycloak.client.registration.cli.util.ParseUtil;
|
import org.keycloak.client.registration.cli.util.ParseUtil;
|
||||||
import org.keycloak.representations.idm.ClientRepresentation;
|
import org.keycloak.representations.idm.ClientRepresentation;
|
||||||
|
@ -45,105 +44,82 @@ import static org.keycloak.client.registration.cli.util.HttpUtil.doPost;
|
||||||
import static org.keycloak.client.registration.cli.util.IoUtil.printOut;
|
import static org.keycloak.client.registration.cli.util.IoUtil.printOut;
|
||||||
import static org.keycloak.client.registration.cli.util.IoUtil.warnfOut;
|
import static org.keycloak.client.registration.cli.util.IoUtil.warnfOut;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
import static org.keycloak.client.registration.cli.util.OsUtil.CMD;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.EOL;
|
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
|
import static org.keycloak.client.registration.cli.util.OsUtil.PROMPT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
@CommandDefinition(name = "update-token", description = "CLIENT [ARGUMENTS]")
|
@Command(name = "update-token", description = "CLIENT [ARGUMENTS]")
|
||||||
public class UpdateTokenCmd extends AbstractAuthOptionsCmd {
|
public class UpdateTokenCmd extends AbstractAuthOptionsCmd {
|
||||||
|
|
||||||
@Arguments
|
@Parameters(arity = "0..1")
|
||||||
private List<String> args;
|
String clientId;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
protected void process() {
|
||||||
|
if (clientId == null) {
|
||||||
|
throw new IllegalArgumentException("CLIENT not specified");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientId.startsWith("-")) {
|
||||||
|
warnfOut(ParseUtil.CLIENT_OPTION_WARN, clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
ConfigData config = loadConfig();
|
||||||
|
config = copyWithServerInfo(config);
|
||||||
|
setupTruststore(config);
|
||||||
|
|
||||||
|
config = ensureAuthInfo(config);
|
||||||
|
String auth = ensureToken(config);
|
||||||
|
|
||||||
|
String cid = null;
|
||||||
|
|
||||||
|
final String server = config.getServerUrl();
|
||||||
|
final String realm = config.getRealm();
|
||||||
|
|
||||||
|
// first we need to get id of the client with client_id == clientId
|
||||||
|
InputStream response = doGet(server + "/admin/realms/" + realm + "/clients", APPLICATION_JSON, "Bearer " + auth);
|
||||||
|
try {
|
||||||
|
List<ClientRepresentation> clients = JsonSerialization.readValue(response, new TypeReference<List<ClientRepresentation>>() {});
|
||||||
|
for (ClientRepresentation client: clients) {
|
||||||
|
if (clientId.equals(client.getClientId())) {
|
||||||
|
cid = client.getId();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException("Failed to process response from server", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cid == null) {
|
||||||
|
throw new RuntimeException("No client found for: " + clientId);
|
||||||
|
}
|
||||||
|
|
||||||
|
response = doPost(server + "/admin/realms/" + realm + "/clients/" + cid + "/registration-access-token",
|
||||||
|
APPLICATION_JSON, APPLICATION_JSON, null, "Bearer " + auth);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (printHelp()) {
|
ClientRepresentation client = JsonSerialization.readValue(response, ClientRepresentation.class);
|
||||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
|
||||||
|
if (noconfig) {
|
||||||
|
// output to stdout
|
||||||
|
printOut(client.getRegistrationAccessToken());
|
||||||
|
} else {
|
||||||
|
saveMergeConfig(cfg -> {
|
||||||
|
setRegistrationToken(cfg.ensureRealmConfigData(server, realm), client.getClientId(), client.getRegistrationAccessToken());
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
processGlobalOptions();
|
throw new RuntimeException("Failed to process response from server", e);
|
||||||
|
|
||||||
if (args == null || args.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("CLIENT not specified");
|
|
||||||
}
|
|
||||||
|
|
||||||
String clientId = args.get(0);
|
|
||||||
|
|
||||||
if (clientId.startsWith("-")) {
|
|
||||||
warnfOut(ParseUtil.CLIENT_OPTION_WARN, clientId);
|
|
||||||
}
|
|
||||||
|
|
||||||
ConfigData config = loadConfig();
|
|
||||||
config = copyWithServerInfo(config);
|
|
||||||
setupTruststore(config, commandInvocation);
|
|
||||||
|
|
||||||
config = ensureAuthInfo(config, commandInvocation);
|
|
||||||
String auth = ensureToken(config);
|
|
||||||
|
|
||||||
String cid = null;
|
|
||||||
|
|
||||||
final String server = config.getServerUrl();
|
|
||||||
final String realm = config.getRealm();
|
|
||||||
|
|
||||||
// first we need to get id of the client with client_id == clientId
|
|
||||||
InputStream response = doGet(server + "/admin/realms/" + realm + "/clients", APPLICATION_JSON, "Bearer " + auth);
|
|
||||||
try {
|
|
||||||
List<ClientRepresentation> clients = JsonSerialization.readValue(response, new TypeReference<List<ClientRepresentation>>() {});
|
|
||||||
for (ClientRepresentation client: clients) {
|
|
||||||
if (clientId.equals(client.getClientId())) {
|
|
||||||
cid = client.getId();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Failed to process response from server", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cid == null) {
|
|
||||||
throw new RuntimeException("No client found for: " + clientId);
|
|
||||||
}
|
|
||||||
|
|
||||||
response = doPost(server + "/admin/realms/" + realm + "/clients/" + cid + "/registration-access-token",
|
|
||||||
APPLICATION_JSON, APPLICATION_JSON, null, "Bearer " + auth);
|
|
||||||
|
|
||||||
try {
|
|
||||||
ClientRepresentation client = JsonSerialization.readValue(response, ClientRepresentation.class);
|
|
||||||
|
|
||||||
if (noconfig) {
|
|
||||||
// output to stdout
|
|
||||||
printOut(client.getRegistrationAccessToken());
|
|
||||||
} else {
|
|
||||||
saveMergeConfig(cfg -> {
|
|
||||||
setRegistrationToken(cfg.ensureRealmConfigData(server, realm), client.getClientId(), client.getRegistrationAccessToken());
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Failed to process response from server", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
//System.out.println("Token updated for client " + clientId);
|
|
||||||
return CommandResult.SUCCESS;
|
|
||||||
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
|
|
||||||
} finally {
|
|
||||||
commandInvocation.stop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean nothingToDo() {
|
protected boolean nothingToDo() {
|
||||||
return noOptions() && (args == null || args.size() == 0);
|
return noOptions() && clientId == null;
|
||||||
}
|
|
||||||
|
|
||||||
protected String suggestHelp() {
|
|
||||||
return EOL + "Try '" + CMD + " help update-token' for more information";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected String help() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,14 +17,7 @@
|
||||||
|
|
||||||
package org.keycloak.client.registration.cli.util;
|
package org.keycloak.client.registration.cli.util;
|
||||||
|
|
||||||
import org.jboss.aesh.console.AeshConsoleBufferBuilder;
|
import java.io.Console;
|
||||||
import org.jboss.aesh.console.AeshInputProcessorBuilder;
|
|
||||||
import org.jboss.aesh.console.ConsoleBuffer;
|
|
||||||
import org.jboss.aesh.console.InputProcessor;
|
|
||||||
import org.jboss.aesh.console.Prompt;
|
|
||||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|
||||||
import org.keycloak.client.registration.cli.aesh.Globals;
|
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -50,7 +43,6 @@ import static java.nio.file.Files.createDirectories;
|
||||||
import static java.nio.file.Files.createFile;
|
import static java.nio.file.Files.createFile;
|
||||||
import static java.nio.file.Files.isDirectory;
|
import static java.nio.file.Files.isDirectory;
|
||||||
import static java.nio.file.Files.isRegularFile;
|
import static java.nio.file.Files.isRegularFile;
|
||||||
import static org.keycloak.client.registration.cli.util.OsUtil.OS_ARCH;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
|
@ -81,43 +73,14 @@ public class IoUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String readSecret(String prompt, CommandInvocation invocation) {
|
public static String readSecret(String prompt) {
|
||||||
|
Console cons;
|
||||||
// TODO Windows hack - masking not working on Windows
|
char[] passwd;
|
||||||
char maskChar = OS_ARCH.isWindows() ? 0 : '*';
|
if ((cons = System.console()) != null &&
|
||||||
ConsoleBuffer consoleBuffer = new AeshConsoleBufferBuilder()
|
(passwd = cons.readPassword("%s", prompt)) != null) {
|
||||||
.shell(invocation.getShell())
|
return new String(passwd);
|
||||||
.prompt(new Prompt(prompt, maskChar))
|
|
||||||
.create();
|
|
||||||
InputProcessor inputProcessor = new AeshInputProcessorBuilder()
|
|
||||||
.consoleBuffer(consoleBuffer)
|
|
||||||
.create();
|
|
||||||
|
|
||||||
consoleBuffer.displayPrompt();
|
|
||||||
|
|
||||||
// activate stdin
|
|
||||||
Globals.stdin.setInputStream(System.in);
|
|
||||||
|
|
||||||
String result;
|
|
||||||
try {
|
|
||||||
do {
|
|
||||||
result = inputProcessor.parseOperation(invocation.getInput());
|
|
||||||
} while (result == null);
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException("^C", e);
|
|
||||||
}
|
}
|
||||||
/*
|
throw new RuntimeException("Console is not active, or no password provided");
|
||||||
if (!Globals.stdin.isStdinAvailable()) {
|
|
||||||
try {
|
|
||||||
return readLine(new InputStreamReader(System.in));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Standard input not available");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
// Windows hack - get rid of any \n
|
|
||||||
result = result.replaceAll("\\n", "");
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String readFully(InputStream is) {
|
public static String readFully(InputStream is) {
|
||||||
|
|
6
pom.xml
6
pom.xml
|
@ -80,7 +80,6 @@
|
||||||
<jboss.as.subsystem.test.version>7.5.22.Final-redhat-1</jboss.as.subsystem.test.version>
|
<jboss.as.subsystem.test.version>7.5.22.Final-redhat-1</jboss.as.subsystem.test.version>
|
||||||
|
|
||||||
<!-- Versions used mostly for Undertow server, aligned with WildFly -->
|
<!-- Versions used mostly for Undertow server, aligned with WildFly -->
|
||||||
<jboss.aesh.version>0.66.19</jboss.aesh.version>
|
|
||||||
<apache.httpcomponents.version>4.5.14</apache.httpcomponents.version>
|
<apache.httpcomponents.version>4.5.14</apache.httpcomponents.version>
|
||||||
<jboss.dmr.version>1.5.1.Final</jboss.dmr.version>
|
<jboss.dmr.version>1.5.1.Final</jboss.dmr.version>
|
||||||
|
|
||||||
|
@ -948,11 +947,6 @@
|
||||||
<artifactId>pax-web-spi</artifactId>
|
<artifactId>pax-web-spi</artifactId>
|
||||||
<version>${pax.web.version}</version>
|
<version>${pax.web.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
|
||||||
<groupId>org.jboss.aesh</groupId>
|
|
||||||
<artifactId>aesh</artifactId>
|
|
||||||
<version>${jboss.aesh.version}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<!-- keycloak -->
|
<!-- keycloak -->
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -20,27 +20,27 @@ public class KcRegConfigTest extends AbstractRegCliTest {
|
||||||
@Test
|
@Test
|
||||||
public void testRegistrationToken() throws IOException {
|
public void testRegistrationToken() throws IOException {
|
||||||
|
|
||||||
FileConfigHandler handler = initCustomConfigFile();
|
initCustomConfigFile();
|
||||||
|
|
||||||
try (TempFileResource configFile = new TempFileResource(handler.getConfigFile())) {
|
try (TempFileResource configFile = new TempFileResource(FileConfigHandler.getConfigFile())) {
|
||||||
|
|
||||||
// without --server
|
// without --server
|
||||||
KcRegExec exe = execute("config registration-token --config '" + configFile.getName() + "' ");
|
KcRegExec exe = execute("config registration-token --config '" + configFile.getName() + "' ");
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||||
Assert.assertEquals("error message", "Required option not specified: --server", exe.stderrLines().get(0));
|
Assert.assertEquals("error message", "Required option not specified: --server", exe.stderrLines().get(0));
|
||||||
Assert.assertEquals("try help", "Try '" + CMD + " help config registration-token' for more information", exe.stderrLines().get(1));
|
Assert.assertEquals("try help", "Try '" + CMD + " config registration-token --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||||
|
|
||||||
// without --realm
|
// without --realm
|
||||||
exe = execute("config registration-token --config '" + configFile.getName() + "' --server http://localhost:8080/auth");
|
exe = execute("config registration-token --config '" + configFile.getName() + "' --server http://localhost:8080/auth");
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||||
Assert.assertEquals("error message", "Required option not specified: --realm", exe.stderrLines().get(0));
|
Assert.assertEquals("error message", "Required option not specified: --realm", exe.stderrLines().get(0));
|
||||||
Assert.assertEquals("try help", "Try '" + CMD + " help config registration-token' for more information", exe.stderrLines().get(1));
|
Assert.assertEquals("try help", "Try '" + CMD + " config registration-token --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||||
|
|
||||||
// without --client
|
// without --client
|
||||||
exe = execute("config registration-token --config '" + configFile.getName() + "' --server http://localhost:8080/auth --realm test");
|
exe = execute("config registration-token --config '" + configFile.getName() + "' --server http://localhost:8080/auth --realm test");
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||||
Assert.assertEquals("error message", "Required option not specified: --client", exe.stderrLines().get(0));
|
Assert.assertEquals("error message", "Required option not specified: --client", exe.stderrLines().get(0));
|
||||||
Assert.assertEquals("try help", "Try '" + CMD + " help config registration-token' for more information", exe.stderrLines().get(1));
|
Assert.assertEquals("try help", "Try '" + CMD + " config registration-token --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||||
|
|
||||||
// specify token on cmdline
|
// specify token on cmdline
|
||||||
exe = execute("config registration-token --config '" + configFile.getName() + "' --server http://localhost:8080/auth --realm test --client my_client NEWTOKEN");
|
exe = execute("config registration-token --config '" + configFile.getName() + "' --server http://localhost:8080/auth --realm test --client my_client NEWTOKEN");
|
||||||
|
@ -75,14 +75,14 @@ public class KcRegConfigTest extends AbstractRegCliTest {
|
||||||
public void testNoConfigOption() throws IOException {
|
public void testNoConfigOption() throws IOException {
|
||||||
|
|
||||||
KcRegExec exe = execute("config registration-token --no-config --server http://localhost:8080/auth --realm test --client my_client --delete");
|
KcRegExec exe = execute("config registration-token --no-config --server http://localhost:8080/auth --realm test --client my_client --delete");
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||||
Assert.assertEquals("stderr first line", "Unsupported option: --no-config", exe.stderrLines().get(0));
|
Assert.assertEquals("stderr first line", "Unsupported option: --no-config", exe.stderrLines().get(0));
|
||||||
Assert.assertEquals("try help", "Try '" + CMD + " help config registration-token' for more information", exe.stderrLines().get(1));
|
Assert.assertEquals("try help", "Try '" + CMD + " config registration-token --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||||
|
|
||||||
exe = execute("config initial-token --no-config --server http://localhost:8080/auth --realm test --delete");
|
exe = execute("config initial-token --no-config --server http://localhost:8080/auth --realm test --delete");
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||||
Assert.assertEquals("stderr first line", "Unsupported option: --no-config", exe.stderrLines().get(0));
|
Assert.assertEquals("stderr first line", "Unsupported option: --no-config", exe.stderrLines().get(0));
|
||||||
Assert.assertEquals("try help", "Try '" + CMD + " help config initial-token' for more information", exe.stderrLines().get(1));
|
Assert.assertEquals("try help", "Try '" + CMD + " config initial-token --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -36,7 +36,7 @@ public class KcRegTest extends AbstractRegCliTest {
|
||||||
*/
|
*/
|
||||||
KcRegExec exe = execute("");
|
KcRegExec exe = execute("");
|
||||||
|
|
||||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||||
|
|
||||||
List<String> lines = exe.stdoutLines();
|
List<String> lines = exe.stdoutLines();
|
||||||
Assert.assertTrue("stdout output not empty", lines.size() > 0);
|
Assert.assertTrue("stdout output not empty", lines.size() > 0);
|
||||||
|
@ -49,51 +49,51 @@ public class KcRegTest extends AbstractRegCliTest {
|
||||||
* Test commands without arguments
|
* Test commands without arguments
|
||||||
*/
|
*/
|
||||||
exe = execute("config");
|
exe = execute("config");
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 1);
|
assertExitCodeAndStreamSizes(exe, 2, 8, 0);
|
||||||
Assert.assertEquals("error message",
|
Assert.assertEquals("error message",
|
||||||
"Sub-command required by '" + CMD + " config' - one of: 'credentials', 'truststore', 'initial-token', 'registration-token'",
|
"Usage: kcreg.sh config SUB_COMMAND [ARGUMENTS]",
|
||||||
exe.stderrLines().get(0));
|
exe.stdoutLines().get(0));
|
||||||
|
|
||||||
exe = execute("config credentials");
|
exe = execute("config credentials");
|
||||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||||
Assert.assertEquals("help message", "Usage: " + CMD + " config credentials --server SERVER_URL --realm REALM [ARGUMENTS]", exe.stdoutLines().get(0));
|
Assert.assertEquals("help message", "Usage: " + CMD + " config credentials --server SERVER_URL --realm REALM [ARGUMENTS]", exe.stdoutLines().get(0));
|
||||||
|
|
||||||
exe = execute("config initial-token");
|
exe = execute("config initial-token");
|
||||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||||
Assert.assertEquals("help message", "Usage: " + CMD + " config initial-token --server SERVER --realm REALM [--delete | TOKEN] [ARGUMENTS]", exe.stdoutLines().get(0));
|
Assert.assertEquals("help message", "Usage: " + CMD + " config initial-token --server SERVER --realm REALM [--delete | TOKEN] [ARGUMENTS]", exe.stdoutLines().get(0));
|
||||||
|
|
||||||
exe = execute("config registration-token");
|
exe = execute("config registration-token");
|
||||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||||
Assert.assertEquals("help message", "Usage: " + CMD + " config registration-token --server SERVER --realm REALM --client CLIENT [--delete | TOKEN] [ARGUMENTS]", exe.stdoutLines().get(0));
|
Assert.assertEquals("help message", "Usage: " + CMD + " config registration-token --server SERVER --realm REALM --client CLIENT [--delete | TOKEN] [ARGUMENTS]", exe.stdoutLines().get(0));
|
||||||
|
|
||||||
exe = execute("config truststore");
|
exe = execute("config truststore");
|
||||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||||
Assert.assertEquals("help message", "Usage: " + CMD + " config truststore [TRUSTSTORE | --delete] [--trustpass PASSWORD] [ARGUMENTS]", exe.stdoutLines().get(0));
|
Assert.assertEquals("help message", "Usage: " + CMD + " config truststore [TRUSTSTORE | --delete] [--trustpass PASSWORD] [ARGUMENTS]", exe.stdoutLines().get(0));
|
||||||
|
|
||||||
exe = execute("create");
|
exe = execute("create");
|
||||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||||
Assert.assertEquals("help message", "Usage: " + CMD + " create [ARGUMENTS]", exe.stdoutLines().get(0));
|
Assert.assertEquals("help message", "Usage: " + CMD + " create [ARGUMENTS]", exe.stdoutLines().get(0));
|
||||||
//Assert.assertEquals("error message", "No file nor attribute values specified", exe.stderrLines().get(0));
|
//Assert.assertEquals("error message", "No file nor attribute values specified", exe.stderrLines().get(0));
|
||||||
|
|
||||||
exe = execute("get");
|
exe = execute("get");
|
||||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||||
Assert.assertEquals("help message", "Usage: " + CMD + " get CLIENT [ARGUMENTS]", exe.stdoutLines().get(0));
|
Assert.assertEquals("help message", "Usage: " + CMD + " get CLIENT [ARGUMENTS]", exe.stdoutLines().get(0));
|
||||||
//Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0));
|
//Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0));
|
||||||
|
|
||||||
exe = execute("update");
|
exe = execute("update");
|
||||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||||
Assert.assertEquals("help message", "Usage: " + CMD + " update CLIENT [ARGUMENTS]", exe.stdoutLines().get(0));
|
Assert.assertEquals("help message", "Usage: " + CMD + " update CLIENT [ARGUMENTS]", exe.stdoutLines().get(0));
|
||||||
//Assert.assertEquals("error message", "No file nor attribute values specified", exe.stderrLines().get(0));
|
//Assert.assertEquals("error message", "No file nor attribute values specified", exe.stderrLines().get(0));
|
||||||
|
|
||||||
exe = execute("delete");
|
exe = execute("delete");
|
||||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||||
Assert.assertEquals("help message", "Usage: " + CMD + " delete CLIENT [ARGUMENTS]", exe.stdoutLines().get(0));
|
Assert.assertEquals("help message", "Usage: " + CMD + " delete CLIENT [ARGUMENTS]", exe.stdoutLines().get(0));
|
||||||
//Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0));
|
//Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0));
|
||||||
|
@ -104,7 +104,7 @@ public class KcRegTest extends AbstractRegCliTest {
|
||||||
Assert.assertEquals("first line", "Attributes for default format:", exe.stdoutLines().get(0));
|
Assert.assertEquals("first line", "Attributes for default format:", exe.stdoutLines().get(0));
|
||||||
|
|
||||||
exe = execute("update-token");
|
exe = execute("update-token");
|
||||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||||
Assert.assertEquals("help message", "Usage: " + CMD + " update-token CLIENT [ARGUMENTS]", exe.stdoutLines().get(0));
|
Assert.assertEquals("help message", "Usage: " + CMD + " update-token CLIENT [ARGUMENTS]", exe.stdoutLines().get(0));
|
||||||
//Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0));
|
//Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0));
|
||||||
|
@ -188,8 +188,8 @@ public class KcRegTest extends AbstractRegCliTest {
|
||||||
*/
|
*/
|
||||||
KcRegExec exe = execute("nonexistent");
|
KcRegExec exe = execute("nonexistent");
|
||||||
|
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 1);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 3);
|
||||||
Assert.assertEquals("stderr first line", "Unknown command: nonexistent", exe.stderrLines().get(0));
|
Assert.assertEquals("stderr first line", "Unmatched argument at index 0: 'nonexistent'", exe.stderrLines().get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -199,8 +199,8 @@ public class KcRegTest extends AbstractRegCliTest {
|
||||||
*/
|
*/
|
||||||
KcRegExec exe = execute("--nonexistent");
|
KcRegExec exe = execute("--nonexistent");
|
||||||
|
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 1);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||||
Assert.assertEquals("stderr first line", "Unknown command: --nonexistent", exe.stderrLines().get(0));
|
Assert.assertEquals("stderr first line", "Unknown option: '--nonexistent'", exe.stderrLines().get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -211,9 +211,9 @@ public class KcRegTest extends AbstractRegCliTest {
|
||||||
|
|
||||||
KcRegExec exe = execute("get my_client --nonexistent");
|
KcRegExec exe = execute("get my_client --nonexistent");
|
||||||
|
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 3);
|
||||||
Assert.assertEquals("stderr first line", "Invalid option: --nonexistent", exe.stderrLines().get(0));
|
Assert.assertEquals("stderr first line", "Unknown option: '--nonexistent'", exe.stderrLines().get(0));
|
||||||
Assert.assertEquals("try help", "Try '" + CMD + " help get' for more information", exe.stderrLines().get(1));
|
Assert.assertEquals("try help", "Try '" + CMD + " get --help' for more information on the available options.", exe.stderrLines().get(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -233,9 +233,9 @@ public class KcRegTest extends AbstractRegCliTest {
|
||||||
*/
|
*/
|
||||||
KcRegExec exe = execute("config credentials --realm master --user admin --password admin");
|
KcRegExec exe = execute("config credentials --realm master --user admin --password admin");
|
||||||
|
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||||
Assert.assertEquals("stderr first line", "Required option not specified: --server", exe.stderrLines().get(0));
|
Assert.assertEquals("stderr first line", "Required option not specified: --server", exe.stderrLines().get(0));
|
||||||
Assert.assertEquals("try help", "Try '" + CMD + " help config credentials' for more information", exe.stderrLines().get(1));
|
Assert.assertEquals("try help", "Try '" + CMD + " config credentials --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -245,9 +245,9 @@ public class KcRegTest extends AbstractRegCliTest {
|
||||||
*/
|
*/
|
||||||
KcRegExec exe = execute("config credentials --server " + serverUrl + " --user admin --password admin");
|
KcRegExec exe = execute("config credentials --server " + serverUrl + " --user admin --password admin");
|
||||||
|
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||||
Assert.assertEquals("stderr first line", "Required option not specified: --realm", exe.stderrLines().get(0));
|
Assert.assertEquals("stderr first line", "Required option not specified: --realm", exe.stderrLines().get(0));
|
||||||
Assert.assertEquals("try help", "Try '" + CMD + " help config credentials' for more information", exe.stderrLines().get(1));
|
Assert.assertEquals("try help", "Try '" + CMD + " config credentials --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -257,9 +257,9 @@ public class KcRegTest extends AbstractRegCliTest {
|
||||||
*/
|
*/
|
||||||
KcRegExec exe = KcRegExec.execute("config credentials --no-config --server " + serverUrl + " --realm master --user admin --password admin");
|
KcRegExec exe = KcRegExec.execute("config credentials --no-config --server " + serverUrl + " --realm master --user admin --password admin");
|
||||||
|
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||||
Assert.assertEquals("stderr first line", "Unsupported option: --no-config", exe.stderrLines().get(0));
|
Assert.assertEquals("stderr first line", "Unsupported option: --no-config", exe.stderrLines().get(0));
|
||||||
Assert.assertEquals("try help", "Try '" + CMD + " help config credentials' for more information", exe.stderrLines().get(1));
|
Assert.assertEquals("try help", "Try '" + CMD + " config credentials --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -6,6 +6,7 @@ import org.keycloak.client.registration.cli.config.ConfigData;
|
||||||
import org.keycloak.client.registration.cli.config.FileConfigHandler;
|
import org.keycloak.client.registration.cli.config.FileConfigHandler;
|
||||||
import org.keycloak.client.registration.cli.util.OsUtil;
|
import org.keycloak.client.registration.cli.util.OsUtil;
|
||||||
import org.keycloak.testsuite.cli.KcRegExec;
|
import org.keycloak.testsuite.cli.KcRegExec;
|
||||||
|
import org.keycloak.testsuite.util.OAuthClient;
|
||||||
import org.keycloak.testsuite.util.TempFileResource;
|
import org.keycloak.testsuite.util.TempFileResource;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -29,9 +30,9 @@ public class KcRegTruststoreTest extends AbstractRegCliTest {
|
||||||
|
|
||||||
KcRegExec exe = execute("config truststore --no-config '" + truststore.getAbsolutePath() + "'");
|
KcRegExec exe = execute("config truststore --no-config '" + truststore.getAbsolutePath() + "'");
|
||||||
|
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||||
Assert.assertEquals("stderr first line", "Unsupported option: --no-config", exe.stderrLines().get(0));
|
Assert.assertEquals("stderr first line", "Unsupported option: --no-config", exe.stderrLines().get(0));
|
||||||
Assert.assertEquals("try help", "Try '" + OsUtil.CMD + " help config truststore' for more information", exe.stderrLines().get(1));
|
Assert.assertEquals("try help", "Try '" + OsUtil.CMD + " config truststore --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||||
|
|
||||||
// only run the rest of this test if ssl protected keycloak server is available
|
// only run the rest of this test if ssl protected keycloak server is available
|
||||||
if (!AUTH_SERVER_SSL_REQUIRED) {
|
if (!AUTH_SERVER_SSL_REQUIRED) {
|
||||||
|
@ -39,9 +40,9 @@ public class KcRegTruststoreTest extends AbstractRegCliTest {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileConfigHandler handler = initCustomConfigFile();
|
initCustomConfigFile();
|
||||||
|
|
||||||
try (TempFileResource configFile = new TempFileResource(handler.getConfigFile())) {
|
try (TempFileResource configFile = new TempFileResource(FileConfigHandler.getConfigFile())) {
|
||||||
|
|
||||||
if (runIntermittentlyFailingTests()) {
|
if (runIntermittentlyFailingTests()) {
|
||||||
// configure truststore
|
// configure truststore
|
||||||
|
@ -52,7 +53,7 @@ public class KcRegTruststoreTest extends AbstractRegCliTest {
|
||||||
|
|
||||||
// perform authentication against server - asks for password, then for truststore password
|
// perform authentication against server - asks for password, then for truststore password
|
||||||
exe = KcRegExec.newBuilder()
|
exe = KcRegExec.newBuilder()
|
||||||
.argsLine("config credentials --server " + oauth.AUTH_SERVER_ROOT + " --realm test --user user1" +
|
.argsLine("config credentials --server " + OAuthClient.AUTH_SERVER_ROOT + " --realm test --user user1" +
|
||||||
" --config '" + configFile.getName() + "'")
|
" --config '" + configFile.getName() + "'")
|
||||||
.executeAsync();
|
.executeAsync();
|
||||||
|
|
||||||
|
@ -72,7 +73,7 @@ public class KcRegTruststoreTest extends AbstractRegCliTest {
|
||||||
|
|
||||||
// perform authentication against server - asks for password, then for truststore password
|
// perform authentication against server - asks for password, then for truststore password
|
||||||
exe = KcRegExec.newBuilder()
|
exe = KcRegExec.newBuilder()
|
||||||
.argsLine("config credentials --server " + oauth.AUTH_SERVER_ROOT + " --realm test --user user1" +
|
.argsLine("config credentials --server " + OAuthClient.AUTH_SERVER_ROOT + " --realm test --user user1" +
|
||||||
" --config '" + configFile.getName() + "'")
|
" --config '" + configFile.getName() + "'")
|
||||||
.executeAsync();
|
.executeAsync();
|
||||||
|
|
||||||
|
@ -99,17 +100,17 @@ public class KcRegTruststoreTest extends AbstractRegCliTest {
|
||||||
assertExitCodeAndStreamSizes(exe, 0, 0, 0);
|
assertExitCodeAndStreamSizes(exe, 0, 0, 0);
|
||||||
|
|
||||||
exe = execute("config truststore --delete '" + truststore.getAbsolutePath() + "'");
|
exe = execute("config truststore --delete '" + truststore.getAbsolutePath() + "'");
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||||
Assert.assertEquals("incompatible", "Option --delete is mutually exclusive with specifying a TRUSTSTORE", exe.stderrLines().get(0));
|
Assert.assertEquals("incompatible", "Option --delete is mutually exclusive with specifying a TRUSTSTORE", exe.stderrLines().get(0));
|
||||||
Assert.assertEquals("try help", "Try '" + CMD + " help config truststore' for more information", exe.stderrLines().get(1));
|
Assert.assertEquals("try help", "Try '" + CMD + " config truststore --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||||
|
|
||||||
exe = execute("config truststore --delete --trustpass secret");
|
exe = execute("config truststore --delete --trustpass secret");
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||||
Assert.assertEquals("no truststore error", "Options --trustpass and --delete are mutually exclusive", exe.stderrLines().get(0));
|
Assert.assertEquals("no truststore error", "Options --trustpass and --delete are mutually exclusive", exe.stderrLines().get(0));
|
||||||
Assert.assertEquals("try help", "Try '" + CMD + " help config truststore' for more information", exe.stderrLines().get(1));
|
Assert.assertEquals("try help", "Try '" + CMD + " config truststore --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||||
|
|
||||||
FileConfigHandler cfghandler = new FileConfigHandler();
|
FileConfigHandler cfghandler = new FileConfigHandler();
|
||||||
cfghandler.setConfigFile(DEFAULT_CONFIG_FILE_PATH);
|
FileConfigHandler.setConfigFile(DEFAULT_CONFIG_FILE_PATH);
|
||||||
ConfigData config = cfghandler.loadConfig();
|
ConfigData config = cfghandler.loadConfig();
|
||||||
Assert.assertNull("truststore null", config.getTruststore());
|
Assert.assertNull("truststore null", config.getTruststore());
|
||||||
Assert.assertNull("trustpass null", config.getTrustpass());
|
Assert.assertNull("trustpass null", config.getTrustpass());
|
||||||
|
|
|
@ -26,7 +26,7 @@ public class KcRegUpdateTest extends AbstractRegCliTest {
|
||||||
|
|
||||||
FileConfigHandler handler = initCustomConfigFile();
|
FileConfigHandler handler = initCustomConfigFile();
|
||||||
|
|
||||||
try (TempFileResource configFile = new TempFileResource(handler.getConfigFile())) {
|
try (TempFileResource configFile = new TempFileResource(FileConfigHandler.getConfigFile())) {
|
||||||
|
|
||||||
final String realm = "test";
|
final String realm = "test";
|
||||||
|
|
||||||
|
@ -91,9 +91,9 @@ public class KcRegUpdateTest extends AbstractRegCliTest {
|
||||||
// check that using an invalid attribute key is not ignored
|
// check that using an invalid attribute key is not ignored
|
||||||
exe = execute("update my_client --nonexisting --config '" + configFile.getName() + "'");
|
exe = execute("update my_client --nonexisting --config '" + configFile.getName() + "'");
|
||||||
|
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 3);
|
||||||
Assert.assertEquals("error message", "Unsupported option: --nonexisting", exe.stderrLines().get(0));
|
Assert.assertEquals("error message", "Unknown option: '--nonexisting'", exe.stderrLines().get(0));
|
||||||
Assert.assertEquals("try help", "Try '" + CMD + " help update' for more information", exe.stderrLines().get(1));
|
Assert.assertEquals("try help", "Try '" + CMD + " update --help' for more information on the available options.", exe.stderrLines().get(2));
|
||||||
|
|
||||||
|
|
||||||
// try use incompatible endpoint
|
// try use incompatible endpoint
|
||||||
|
|
Loading…
Reference in a new issue