mirror of
https://github.com/keycloak/keycloak.git
synced 2026-01-25 16:42:34 +00:00
fix: replace aesh with picocli (#27458)
* fix: replace aesh with picocli closes: #27388 Signed-off-by: Steve Hawkins <shawkins@redhat.com> * Update integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/AbstractRequestCmd.java Co-authored-by: Martin Bartoš <mabartos@redhat.com> * splitting the error handling for password input Signed-off-by: Steve Hawkins <shawkins@redhat.com> * adding a change note about kcadm Signed-off-by: Steve Hawkins <shawkins@redhat.com> * Update docs/documentation/upgrading/topics/changes/changes-25_0_0.adoc Co-authored-by: Martin Bartoš <mabartos@redhat.com> --------- Signed-off-by: Steve Hawkins <shawkins@redhat.com> Co-authored-by: Martin Bartoš <mabartos@redhat.com>
This commit is contained in:
@@ -29,20 +29,10 @@
|
||||
<name>Keycloak Admin CLI</name>
|
||||
<description/>
|
||||
|
||||
<properties>
|
||||
<jansi.version>1.18</jansi.version>
|
||||
</properties>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.jboss.aesh</groupId>
|
||||
<artifactId>aesh</artifactId>
|
||||
</dependency>
|
||||
<!-- Jansi library version needs to be overridden due to the backwards compatibility - see #21851 -->
|
||||
<dependency>
|
||||
<groupId>org.fusesource.jansi</groupId>
|
||||
<artifactId>jansi</artifactId>
|
||||
<version>${jansi.version}</version>
|
||||
<groupId>info.picocli</groupId>
|
||||
<artifactId>picocli</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.keycloak</groupId>
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.client.admin.cli;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.ParseResult;
|
||||
|
||||
public final class ExecutionExceptionHandler implements CommandLine.IExecutionExceptionHandler {
|
||||
|
||||
@Override
|
||||
public int handleExecutionException(Exception cause, CommandLine cmd, ParseResult parseResult) {
|
||||
int exitCode = ShortErrorMessageHandler.shortErrorMessage(cause, cmd);
|
||||
if (Globals.dumpTrace) {
|
||||
cause.printStackTrace();
|
||||
}
|
||||
return exitCode;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -14,9 +14,7 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.client.admin.cli.aesh;
|
||||
|
||||
import java.util.List;
|
||||
package org.keycloak.client.admin.cli;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
@@ -25,7 +23,6 @@ public class Globals {
|
||||
|
||||
public static boolean dumpTrace = false;
|
||||
|
||||
public static ValveInputStream stdin;
|
||||
public static boolean help = false;
|
||||
|
||||
public static List<String> args;
|
||||
}
|
||||
@@ -16,22 +16,15 @@
|
||||
*/
|
||||
package org.keycloak.client.admin.cli;
|
||||
|
||||
import org.jboss.aesh.console.AeshConsoleBuilder;
|
||||
import org.jboss.aesh.console.AeshConsoleImpl;
|
||||
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.admin.cli.aesh.AeshEnhancer;
|
||||
import org.keycloak.client.admin.cli.aesh.Globals;
|
||||
import org.keycloak.client.admin.cli.aesh.ValveInputStream;
|
||||
import org.keycloak.client.admin.cli.commands.KcAdmCmd;
|
||||
import org.keycloak.client.admin.cli.util.ClassLoaderUtil;
|
||||
import org.keycloak.client.admin.cli.util.OsUtil;
|
||||
import org.keycloak.common.crypto.CryptoIntegration;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Model.CommandSpec;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
@@ -47,53 +40,22 @@ public class KcAdmMain {
|
||||
Thread.currentThread().setContextClassLoader(cl);
|
||||
|
||||
CryptoIntegration.init(cl);
|
||||
|
||||
Globals.stdin = new ValveInputStream();
|
||||
|
||||
Settings settings = new SettingsBuilder()
|
||||
.logging(false)
|
||||
.readInputrc(false)
|
||||
.disableCompletion(true)
|
||||
.disableHistory(true)
|
||||
.enableAlias(false)
|
||||
.enableExport(false)
|
||||
.inputStream(Globals.stdin)
|
||||
.create();
|
||||
|
||||
CommandRegistry registry = new AeshCommandRegistryBuilder()
|
||||
.command(KcAdmCmd.class)
|
||||
.create();
|
||||
|
||||
AeshConsoleImpl console = (AeshConsoleImpl) new AeshConsoleBuilder()
|
||||
.settings(settings)
|
||||
.commandRegistry(registry)
|
||||
.prompt(new Prompt(""))
|
||||
// .commandInvocationProvider(new CommandInvocationServices() {
|
||||
//
|
||||
// })
|
||||
.create();
|
||||
|
||||
AeshEnhancer.enhance(console);
|
||||
|
||||
// work around parser issues with quotes and brackets
|
||||
ArrayList<String> arguments = new ArrayList<>();
|
||||
arguments.add("kcadm");
|
||||
arguments.addAll(Arrays.asList(args));
|
||||
Globals.args = arguments;
|
||||
|
||||
StringBuilder b = new StringBuilder();
|
||||
for (String s : args) {
|
||||
// quote if necessary
|
||||
b.append(' ');
|
||||
s = s.replace("'", "\\'");
|
||||
b.append('\'');
|
||||
b.append(s);
|
||||
b.append('\'');
|
||||
}
|
||||
console.setEcho(false);
|
||||
|
||||
console.execute("kcadm" + b.toString());
|
||||
|
||||
console.start();
|
||||
}
|
||||
CommandLine cli = createCommandLine();
|
||||
int exitCode = cli.execute(args);
|
||||
System.exit(exitCode);
|
||||
}
|
||||
|
||||
public static CommandLine createCommandLine() {
|
||||
CommandSpec spec = CommandSpec.forAnnotatedObject(new KcAdmCmd()).name(OsUtil.CMD);
|
||||
|
||||
CommandLine cmd = new CommandLine(spec);
|
||||
|
||||
cmd.setExecutionExceptionHandler(new ExecutionExceptionHandler());
|
||||
cmd.setParameterExceptionHandler(new ShortErrorMessageHandler());
|
||||
cmd.setErr(new PrintWriter(System.err, true));
|
||||
|
||||
return cmd;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright 2024 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.keycloak.client.admin.cli;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.IParameterExceptionHandler;
|
||||
import picocli.CommandLine.Model.CommandSpec;
|
||||
import picocli.CommandLine.ParameterException;
|
||||
import picocli.CommandLine.UnmatchedArgumentException;
|
||||
|
||||
public class ShortErrorMessageHandler implements IParameterExceptionHandler {
|
||||
|
||||
@Override
|
||||
public int handleParseException(ParameterException ex, String[] args) {
|
||||
CommandLine cmd = ex.getCommandLine();
|
||||
return shortErrorMessage(ex, cmd);
|
||||
}
|
||||
|
||||
static int shortErrorMessage(Exception ex, CommandLine cmd) {
|
||||
PrintWriter writer = cmd.getErr();
|
||||
String errorMessage = ex.getMessage();
|
||||
|
||||
writer.println(cmd.getColorScheme().errorText(errorMessage));
|
||||
if (ex instanceof ParameterException) {
|
||||
UnmatchedArgumentException.printSuggestions((ParameterException)ex, writer);
|
||||
}
|
||||
|
||||
if (ex instanceof ParameterException || ex instanceof IllegalArgumentException) {
|
||||
CommandSpec spec = cmd.getCommandSpec();
|
||||
writer.printf("Try '%s%s' for more information on the available options.%n", spec.qualifiedName(), "help".equals(spec.name())?"":" --help");
|
||||
return cmd.getCommandSpec().exitCodeOnInvalidInput();
|
||||
}
|
||||
return cmd.getCommandSpec().exitCodeOnExecutionException();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.client.admin.cli.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 && "Option: - must be followed by a valid operator".equals(e.getMessage())) {
|
||||
System.err.println("Please double check your command options, one or more of them are not specified correctly. "
|
||||
+ "It is possible to have unintentional overlap with other options. e.g. using --clientid will get mistaken for --client, however --cclientid is needed.");
|
||||
} 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,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.client.admin.cli.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,89 +0,0 @@
|
||||
/*
|
||||
* Copyright 2016 Red Hat, Inc. and/or its affiliates
|
||||
* and other contributors as indicated by the @author tags.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.keycloak.client.admin.cli.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 a stream in the queue.
|
||||
* It reads the stream to the end, then stops Aesh console.
|
||||
*
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
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();
|
||||
}
|
||||
}
|
||||
@@ -16,8 +16,6 @@
|
||||
*/
|
||||
package org.keycloak.client.admin.cli.commands;
|
||||
|
||||
import org.jboss.aesh.cl.Option;
|
||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
||||
import org.keycloak.OAuth2Constants;
|
||||
import org.keycloak.client.admin.cli.config.ConfigData;
|
||||
import org.keycloak.client.admin.cli.config.ConfigHandler;
|
||||
@@ -30,6 +28,8 @@ import org.keycloak.client.admin.cli.util.IoUtil;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import picocli.CommandLine.Option;
|
||||
|
||||
import static org.keycloak.client.admin.cli.config.FileConfigHandler.setConfigFile;
|
||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CLIENT;
|
||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.checkAuthInfo;
|
||||
@@ -42,65 +42,61 @@ import static org.keycloak.client.admin.cli.util.ConfigUtil.loadConfig;
|
||||
*/
|
||||
public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
|
||||
|
||||
@Option(shortName = 'a', name = "admin-root", description = "URL of Admin REST endpoint root if not default - e.g. http://localhost:8080/admin")
|
||||
@Option(names = {"-a", "--admin-root"}, description = "URL of Admin REST endpoint root if not default - e.g. http://localhost:8080/admin")
|
||||
String adminRestRoot;
|
||||
|
||||
@Option(name = "config", description = "Path to the config file (~/.keycloak/kcadm.config by default)")
|
||||
@Option(names = "--config", description = "Path to the config file (~/.keycloak/kcadm.config by default)")
|
||||
String config;
|
||||
|
||||
@Option(name = "no-config", description = "Don't use config file - no authentication info is loaded or saved", hasValue = false)
|
||||
@Option(names = "--no-config", description = "Don't use config file - no authentication info is loaded or saved")
|
||||
boolean noconfig;
|
||||
|
||||
@Option(name = "server", description = "Server endpoint url (e.g. 'http://localhost:8080')")
|
||||
@Option(names = "--server", description = "Server endpoint url (e.g. 'http://localhost:8080')")
|
||||
String server;
|
||||
|
||||
@Option(shortName = 'r', name = "target-realm", description = "Realm to target - when it's different than the realm we authenticate against")
|
||||
@Option(names = {"-r", "--target-realm"}, description = "Realm to target - when it's different than the realm we authenticate against")
|
||||
String targetRealm;
|
||||
|
||||
@Option(name = "realm", description = "Realm name to authenticate against")
|
||||
@Option(names = "--realm", description = "Realm name to authenticate against")
|
||||
String realm;
|
||||
|
||||
@Option(name = "client", description = "Realm name to authenticate against")
|
||||
@Option(names = "--client", description = "Realm name to authenticate against")
|
||||
String clientId;
|
||||
|
||||
@Option(name = "user", description = "Username to login with")
|
||||
@Option(names = "--user", description = "Username to login with")
|
||||
String user;
|
||||
|
||||
@Option(name = "password", description = "Password to login with (prompted for if not specified and --user is used)")
|
||||
@Option(names = "--password", description = "Password to login with (prompted for if not specified and --user is used)")
|
||||
String password;
|
||||
|
||||
@Option(name = "secret", description = "Secret to authenticate the client (prompted for if no --user or --keystore is specified)")
|
||||
@Option(names = "--secret", description = "Secret to authenticate the client (prompted for if no --user or --keystore is specified)")
|
||||
String secret;
|
||||
|
||||
@Option(name = "keystore", description = "Path to a keystore containing private key")
|
||||
@Option(names = "--keystore", description = "Path to a keystore containing private key")
|
||||
String keystore;
|
||||
|
||||
@Option(name = "storepass", description = "Keystore password (prompted for if not specified and --keystore is used)")
|
||||
@Option(names = "--storepass", description = "Keystore password (prompted for if not specified and --keystore is used)")
|
||||
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)")
|
||||
@Option(names = "--keypass", description = "Key password (prompted for if not specified and --keystore is used without --storepass, \n otherwise defaults to keystore password)")
|
||||
String keyPass;
|
||||
|
||||
@Option(name = "alias", description = "Alias of the key inside a keystore (defaults to the value of ClientId)")
|
||||
@Option(names = "--alias", description = "Alias of the key inside a keystore (defaults to the value of ClientId)")
|
||||
String alias;
|
||||
|
||||
@Option(name = "truststore", description = "Path to a truststore")
|
||||
@Option(names = "--truststore", description = "Path to a truststore")
|
||||
String trustStore;
|
||||
|
||||
@Option(name = "trustpass", description = "Truststore password (prompted for if not specified and --truststore is used)")
|
||||
@Option(names = "--trustpass", description = "Truststore password (prompted for if not specified and --truststore is used)")
|
||||
String trustPass;
|
||||
|
||||
@Option(name = "insecure", description = "Turns off TLS validation", hasValue = false)
|
||||
@Option(names = "--insecure", description = "Turns off TLS validation")
|
||||
boolean insecure;
|
||||
|
||||
@Option(name = "token", description = "Token to use for invocations. With this option set, every other authentication option is ignored")
|
||||
@Option(names = "--token", description = "Token to use for invocations. With this option set, every other authentication option is ignored")
|
||||
String externalToken;
|
||||
|
||||
|
||||
protected void initFromParent(AbstractAuthOptionsCmd parent) {
|
||||
|
||||
super.initFromParent(parent);
|
||||
|
||||
noconfig = parent.noconfig;
|
||||
config = parent.config;
|
||||
server = parent.server;
|
||||
@@ -124,11 +120,12 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean noOptions() {
|
||||
@Override
|
||||
protected boolean nothingToDo() {
|
||||
return externalToken == null && server == null && realm == null && clientId == null && secret == null &&
|
||||
user == null && password == null &&
|
||||
keystore == null && storePass == null && keyPass == null && alias == null &&
|
||||
trustStore == null && trustPass == null && config == null && (args == null || args.size() == 0);
|
||||
trustStore == null && trustPass == null && config == null;
|
||||
}
|
||||
|
||||
|
||||
@@ -136,12 +133,10 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
|
||||
return targetRealm != null ? targetRealm : config.getRealm();
|
||||
}
|
||||
|
||||
protected void processGlobalOptions() {
|
||||
|
||||
super.processGlobalOptions();
|
||||
|
||||
@Override
|
||||
protected void processOptions() {
|
||||
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) {
|
||||
@@ -156,7 +151,7 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
|
||||
}
|
||||
}
|
||||
|
||||
protected void setupTruststore(ConfigData configData, CommandInvocation invocation ) {
|
||||
protected void setupTruststore(ConfigData configData) {
|
||||
|
||||
if (!configData.getServerUrl().startsWith("https:")) {
|
||||
return;
|
||||
@@ -173,7 +168,7 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
|
||||
pass = configData.getTrustpass();
|
||||
}
|
||||
if (pass == null) {
|
||||
pass = IoUtil.readSecret("Enter truststore password: ", invocation);
|
||||
pass = IoUtil.readSecret("Enter truststore password: ");
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -188,7 +183,7 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
|
||||
}
|
||||
}
|
||||
|
||||
protected ConfigData ensureAuthInfo(ConfigData config, CommandInvocation commandInvocation) {
|
||||
protected ConfigData ensureAuthInfo(ConfigData config) {
|
||||
|
||||
if (requiresLogin()) {
|
||||
// make sure current handler is in-memory handler
|
||||
@@ -204,7 +199,7 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
|
||||
ConfigCredentialsCmd login = new ConfigCredentialsCmd();
|
||||
login.initFromParent(this);
|
||||
login.init(config);
|
||||
login.process(commandInvocation);
|
||||
login.process();
|
||||
|
||||
// this must be executed before finally block which restores config handler
|
||||
return loadConfig();
|
||||
@@ -269,22 +264,4 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
|
||||
rdata.setGrantTypeForAuthentication(grantTypeForAuthentication);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,56 +16,43 @@
|
||||
*/
|
||||
package org.keycloak.client.admin.cli.commands;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.jboss.aesh.cl.Arguments;
|
||||
import org.jboss.aesh.cl.Option;
|
||||
import org.jboss.aesh.console.command.Command;
|
||||
import org.keycloak.client.admin.cli.aesh.Globals;
|
||||
import org.keycloak.client.admin.cli.Globals;
|
||||
import org.keycloak.client.admin.cli.util.FilterUtil;
|
||||
import org.keycloak.client.admin.cli.util.ReturnFields;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import picocli.CommandLine;
|
||||
import picocli.CommandLine.Option;
|
||||
|
||||
import static org.keycloak.client.admin.cli.util.HttpUtil.normalize;
|
||||
import static org.keycloak.client.admin.cli.util.IoUtil.printOut;
|
||||
|
||||
/**
|
||||
* @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)
|
||||
boolean dumpTrace;
|
||||
|
||||
@Option(name = "help", description = "Print command specific help", hasValue = false)
|
||||
boolean help;
|
||||
|
||||
|
||||
// we don't want Aesh to handle illegal options
|
||||
@Arguments
|
||||
List<String> args;
|
||||
|
||||
|
||||
protected void initFromParent(AbstractGlobalOptionsCmd parent) {
|
||||
dumpTrace = parent.dumpTrace;
|
||||
help = parent.help;
|
||||
args = parent.args;
|
||||
@Option(names = "--help",
|
||||
description = "Print command specific help")
|
||||
public void setHelp(boolean help) {
|
||||
Globals.help = help;
|
||||
}
|
||||
|
||||
protected void processGlobalOptions() {
|
||||
@Option(names = "-x",
|
||||
description = "Print full stack trace when exiting with error")
|
||||
public void setDumpTrace(boolean dumpTrace) {
|
||||
Globals.dumpTrace = dumpTrace;
|
||||
}
|
||||
|
||||
protected boolean printHelp() {
|
||||
if (help || nothingToDo()) {
|
||||
protected void printHelpIfNeeded() {
|
||||
if (Globals.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() {
|
||||
@@ -80,13 +67,6 @@ public abstract class AbstractGlobalOptionsCmd implements Command {
|
||||
return normalize(server) + "admin";
|
||||
}
|
||||
|
||||
|
||||
protected void requireValue(Iterator<String> it, String option) {
|
||||
if (!it.hasNext()) {
|
||||
throw new IllegalArgumentException("Option " + option + " requires a value");
|
||||
}
|
||||
}
|
||||
|
||||
protected String extractTypeNameFromUri(String resourceUrl) {
|
||||
String type = extractLastComponentOfUri(resourceUrl);
|
||||
if (type.endsWith("s")) {
|
||||
@@ -110,4 +90,47 @@ public abstract class AbstractGlobalOptionsCmd implements Command {
|
||||
throw new RuntimeException("Failed to apply fields filter", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
printHelpIfNeeded();
|
||||
|
||||
checkUnsupportedOptions(getUnsupportedOptions());
|
||||
|
||||
processOptions();
|
||||
|
||||
process();
|
||||
}
|
||||
|
||||
protected String[] getUnsupportedOptions() {
|
||||
return new String[0];
|
||||
}
|
||||
|
||||
protected void processOptions() {
|
||||
|
||||
}
|
||||
|
||||
protected void process() {
|
||||
|
||||
}
|
||||
|
||||
protected void checkUnsupportedOptions(String ... options) {
|
||||
if (options.length % 2 != 0) {
|
||||
throw new IllegalArgumentException("Even number of argument required");
|
||||
}
|
||||
|
||||
for (int i = 0; i < options.length; i++) {
|
||||
String name = options[i];
|
||||
String value = options[++i];
|
||||
|
||||
if (value != null) {
|
||||
throw new IllegalArgumentException("Unsupported option: " + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static String booleanOptionForCheck(boolean value) {
|
||||
return value ? "true" : null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -16,13 +16,7 @@
|
||||
*/
|
||||
package org.keycloak.client.admin.cli.commands;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import org.apache.http.entity.ContentType;
|
||||
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.admin.cli.common.AttributeOperation;
|
||||
import org.keycloak.client.admin.cli.common.CmdStdinContext;
|
||||
import org.keycloak.client.admin.cli.config.ConfigData;
|
||||
@@ -44,13 +38,20 @@ import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import picocli.CommandLine.ArgGroup;
|
||||
import picocli.CommandLine.Option;
|
||||
import picocli.CommandLine.Parameters;
|
||||
|
||||
import static org.keycloak.client.admin.cli.common.AttributeOperation.Type.DELETE;
|
||||
import static org.keycloak.client.admin.cli.common.AttributeOperation.Type.SET;
|
||||
import static org.keycloak.client.admin.cli.util.AuthUtil.ensureToken;
|
||||
@@ -103,100 +104,57 @@ public abstract class AbstractRequestCmd extends AbstractAuthOptionsCmd {
|
||||
|
||||
String httpVerb;
|
||||
|
||||
Headers headers = new Headers();
|
||||
@Option(names = {"-h", "--header"}, description = "Set request header NAME to VALUE")
|
||||
List<String> rawHeaders = new LinkedList<>();
|
||||
|
||||
List<AttributeOperation> attrs = new LinkedList<>();
|
||||
|
||||
Map<String, String> filter = new HashMap<>();
|
||||
|
||||
String url = null;
|
||||
|
||||
|
||||
@Override
|
||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
||||
try {
|
||||
initOptions();
|
||||
|
||||
if (printHelp()) {
|
||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
||||
}
|
||||
|
||||
processGlobalOptions();
|
||||
|
||||
processOptions(commandInvocation);
|
||||
|
||||
return process(commandInvocation);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
|
||||
} finally {
|
||||
commandInvocation.stop();
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
abstract void initOptions();
|
||||
@ArgGroup(exclusive = true, multiplicity = "0..*")
|
||||
List<AttributeOperations> rawAttributeOperations = new ArrayList<>();
|
||||
|
||||
abstract String suggestHelp();
|
||||
@Option(names = {"-q", "--query"}, description = "Add to request URI a NAME query parameter with value VALUE")
|
||||
List<String> rawFilters = new LinkedList<>();
|
||||
|
||||
@Parameters(arity = "0..1")
|
||||
String uri;
|
||||
|
||||
void processOptions(CommandInvocation commandInvocation) {
|
||||
List<AttributeOperation> attrs = new LinkedList<>();
|
||||
Headers headers = new Headers();
|
||||
Map<String, String> filter = new HashMap<>();
|
||||
|
||||
if (args == null || args.isEmpty()) {
|
||||
throw new IllegalArgumentException("URI not specified");
|
||||
}
|
||||
@Override
|
||||
protected void processOptions() {
|
||||
super.processOptions();
|
||||
|
||||
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;
|
||||
}
|
||||
case "-d":
|
||||
case "--delete": {
|
||||
attrs.add(new AttributeOperation(DELETE, it.next()));
|
||||
break;
|
||||
}
|
||||
case "-h":
|
||||
case "--header": {
|
||||
requireValue(it, option);
|
||||
String[] keyVal = parseKeyVal(it.next());
|
||||
headers.add(keyVal[0], keyVal[1]);
|
||||
break;
|
||||
}
|
||||
case "-q":
|
||||
case "--query": {
|
||||
if (!it.hasNext()) {
|
||||
throw new IllegalArgumentException("Option " + option + " requires a value");
|
||||
}
|
||||
String arg = it.next();
|
||||
String[] keyVal;
|
||||
if (arg.indexOf("=") == -1) {
|
||||
keyVal = new String[] {"", arg};
|
||||
} else {
|
||||
keyVal = parseKeyVal(arg);
|
||||
}
|
||||
filter.put(keyVal[0], keyVal[1]);
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
if (url == null) {
|
||||
url = option;
|
||||
} else {
|
||||
throw new IllegalArgumentException("Invalid option: " + option);
|
||||
}
|
||||
}
|
||||
for (AttributeOperations entry : rawAttributeOperations) {
|
||||
if (entry.delete != null) {
|
||||
attrs.add(new AttributeOperation(DELETE, entry.delete));
|
||||
} else {
|
||||
String[] keyVal = parseKeyVal(entry.set);
|
||||
attrs.add(new AttributeOperation(SET, keyVal[0], keyVal[1]));
|
||||
}
|
||||
}
|
||||
|
||||
for (String header : rawHeaders) {
|
||||
String[] keyVal = parseKeyVal(header);
|
||||
headers.add(keyVal[0], keyVal[1]);
|
||||
}
|
||||
|
||||
if (url == null) {
|
||||
for (String arg : rawFilters) {
|
||||
String[] keyVal;
|
||||
if (arg.indexOf("=") == -1) {
|
||||
keyVal = new String[] {"", arg};
|
||||
} else {
|
||||
keyVal = parseKeyVal(arg);
|
||||
}
|
||||
filter.put(keyVal[0], keyVal[1]);
|
||||
}
|
||||
|
||||
if (uri == null) {
|
||||
throw new IllegalArgumentException("Resource URI not specified");
|
||||
}
|
||||
|
||||
@@ -207,7 +165,7 @@ public abstract class AbstractRequestCmd extends AbstractAuthOptionsCmd {
|
||||
try {
|
||||
outputFormat = OutputFormat.valueOf(format.toUpperCase());
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unsupported output format: " + format);
|
||||
throw new IllegalArgumentException("Unsupported output format: " + format);
|
||||
}
|
||||
|
||||
if (mergeMode && noMerge) {
|
||||
@@ -223,10 +181,14 @@ public abstract class AbstractRequestCmd extends AbstractAuthOptionsCmd {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean nothingToDo() {
|
||||
return super.nothingToDo() && file == null && body == null && uri == null && fields == null
|
||||
&& rawAttributeOperations.isEmpty() && rawFilters.isEmpty() && rawHeaders.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
public CommandResult process(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
||||
|
||||
@Override
|
||||
protected void process() {
|
||||
// see if Content-Type header is explicitly set to non-json value
|
||||
Header ctype = headers.get("content-type");
|
||||
|
||||
@@ -255,11 +217,11 @@ public abstract class AbstractRequestCmd extends AbstractAuthOptionsCmd {
|
||||
ConfigData config = loadConfig();
|
||||
config = copyWithServerInfo(config);
|
||||
|
||||
setupTruststore(config, commandInvocation);
|
||||
setupTruststore(config);
|
||||
|
||||
String auth = null;
|
||||
|
||||
config = ensureAuthInfo(config, commandInvocation);
|
||||
config = ensureAuthInfo(config);
|
||||
config = copyWithServerInfo(config);
|
||||
if (credentialsAvailable(config)) {
|
||||
auth = ensureToken(config);
|
||||
@@ -277,7 +239,7 @@ public abstract class AbstractRequestCmd extends AbstractAuthOptionsCmd {
|
||||
final String adminRoot = adminRestRoot != null ? adminRestRoot : composeAdminRoot(server);
|
||||
|
||||
|
||||
String resourceUrl = composeResourceUrl(adminRoot, realm, url);
|
||||
String resourceUrl = composeResourceUrl(adminRoot, realm, uri);
|
||||
String typeName = extractTypeNameFromUri(resourceUrl);
|
||||
|
||||
|
||||
@@ -385,7 +347,7 @@ public abstract class AbstractRequestCmd extends AbstractAuthOptionsCmd {
|
||||
}
|
||||
|
||||
if (outputResult) {
|
||||
if (isCreateOrUpdate() && (response.getStatusCode() == 204 || id != null) && isGetByID(url)) {
|
||||
if (isCreateOrUpdate() && (response.getStatusCode() == 204 || id != null) && isGetByID(uri)) {
|
||||
// get object for id
|
||||
headers = new Headers();
|
||||
if (auth != null) {
|
||||
@@ -423,7 +385,7 @@ public abstract class AbstractRequestCmd extends AbstractAuthOptionsCmd {
|
||||
} else {
|
||||
if (outputFormat != OutputFormat.JSON || returnFields != null) {
|
||||
printErr("Cannot create CSV nor filter returned fields because the response is " + (compressed ? "compressed":"not json"));
|
||||
return CommandResult.SUCCESS;
|
||||
return;
|
||||
}
|
||||
// in theory the user could explicitly request json, but this could be a non-json response
|
||||
// since there's no option for raw and we don't differentiate the default, there's no error about this
|
||||
@@ -435,8 +397,6 @@ public abstract class AbstractRequestCmd extends AbstractAuthOptionsCmd {
|
||||
if (lastByte != -1 && lastByte != 13 && lastByte != 10) {
|
||||
printErr("");
|
||||
}
|
||||
|
||||
return CommandResult.SUCCESS;
|
||||
}
|
||||
|
||||
private boolean isUpdate() {
|
||||
|
||||
@@ -17,11 +17,10 @@
|
||||
package org.keycloak.client.admin.cli.commands;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
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 picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Option;
|
||||
|
||||
import org.keycloak.client.admin.cli.config.ConfigData;
|
||||
import org.keycloak.client.admin.cli.operations.ClientOperations;
|
||||
import org.keycloak.client.admin.cli.operations.GroupOperations;
|
||||
@@ -33,8 +32,6 @@ import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -43,219 +40,184 @@ import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_
|
||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.credentialsAvailable;
|
||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.loadConfig;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.EOL;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
@CommandDefinition(name = "add-roles", description = "[ARGUMENTS]")
|
||||
@Command(name = "add-roles", description = "[ARGUMENTS]")
|
||||
public class AddRolesCmd extends AbstractAuthOptionsCmd {
|
||||
|
||||
@Option(name = "uusername", description = "Target user's 'username'")
|
||||
@Option(names = "--uusername", description = "Target user's 'username'")
|
||||
String uusername;
|
||||
|
||||
@Option(name = "uid", description = "Target user's 'id'")
|
||||
@Option(names = "--uid", description = "Target user's 'id'")
|
||||
String uid;
|
||||
|
||||
@Option(name = "gname", description = "Target group's 'name'")
|
||||
@Option(names = "--gname", description = "Target group's 'name'")
|
||||
String gname;
|
||||
|
||||
@Option(name = "gpath", description = "Target group's 'path'")
|
||||
@Option(names = "--gpath", description = "Target group's 'path'")
|
||||
String gpath;
|
||||
|
||||
@Option(name = "gid", description = "Target group's 'id'")
|
||||
@Option(names = "--gid", description = "Target group's 'id'")
|
||||
String gid;
|
||||
|
||||
@Option(name = "rname", description = "Composite role's 'name'")
|
||||
@Option(names = "--rname", description = "Composite role's 'name'")
|
||||
String rname;
|
||||
|
||||
@Option(name = "rid", description = "Composite role's 'id'")
|
||||
@Option(names = "--rid", description = "Composite role's 'id'")
|
||||
String rid;
|
||||
|
||||
@Option(name = "cclientid", description = "Target client's 'clientId'")
|
||||
@Option(names = "--cclientid", description = "Target client's 'clientId'")
|
||||
String cclientid;
|
||||
|
||||
@Option(name = "cid", description = "Target client's 'id'")
|
||||
@Option(names = "--cid", description = "Target client's 'id'")
|
||||
String cid;
|
||||
|
||||
@Option(names = "--rolename", description = "Role's 'name' attribute")
|
||||
List<String> roleNames = new ArrayList<>();
|
||||
|
||||
@Option(names = "--roleid", description = "Role's 'id' attribute")
|
||||
List<String> roleIds = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
||||
protected void process() {
|
||||
if (uid != null && uusername != null) {
|
||||
throw new IllegalArgumentException("Incompatible options: --uid and --uusername are mutually exclusive");
|
||||
}
|
||||
|
||||
List<String> roleNames = new LinkedList<>();
|
||||
List<String> roleIds = new LinkedList<>();
|
||||
if ((gid != null && gname != null) || (gid != null && gpath != null) || (gname != null && gpath != null)) {
|
||||
throw new IllegalArgumentException("Incompatible options: --gid, --gname and --gpath are mutually exclusive");
|
||||
}
|
||||
|
||||
try {
|
||||
if (printHelp()) {
|
||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
||||
if (roleNames.isEmpty() && roleIds.isEmpty()) {
|
||||
throw new IllegalArgumentException("No role to add specified. Use --rolename or --roleid to specify roles to add");
|
||||
}
|
||||
|
||||
if (cid != null && cclientid != null) {
|
||||
throw new IllegalArgumentException("Incompatible options: --cid and --cclientid are mutually exclusive");
|
||||
}
|
||||
|
||||
if (rid != null && rname != null) {
|
||||
throw new IllegalArgumentException("Incompatible options: --rid and --rname are mutually exclusive");
|
||||
}
|
||||
|
||||
if (isUserSpecified() && isGroupSpecified()) {
|
||||
throw new IllegalArgumentException("Incompatible options: --uusername / --uid can't be used at the same time as --gname / --gid / --gpath");
|
||||
}
|
||||
|
||||
if (isUserSpecified() && isCompositeRoleSpecified()) {
|
||||
throw new IllegalArgumentException("Incompatible options: --uusername / --uid can't be used at the same time as --rname / --rid");
|
||||
}
|
||||
|
||||
if (isGroupSpecified() && isCompositeRoleSpecified()) {
|
||||
throw new IllegalArgumentException("Incompatible options: --rname / --rid can't be used at the same time as --gname / --gid / --gpath");
|
||||
}
|
||||
|
||||
if (!isUserSpecified() && !isGroupSpecified() && !isCompositeRoleSpecified()) {
|
||||
throw new IllegalArgumentException("No user nor group nor composite role specified. Use --uusername / --uid to specify user or --gname / --gid / --gpath to specify group or --rname / --rid to specify a composite role");
|
||||
}
|
||||
|
||||
|
||||
ConfigData config = loadConfig();
|
||||
config = copyWithServerInfo(config);
|
||||
|
||||
setupTruststore(config);
|
||||
|
||||
String 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 = getTargetRealm(config);
|
||||
final String adminRoot = adminRestRoot != null ? adminRestRoot : composeAdminRoot(server);
|
||||
|
||||
|
||||
if (isUserSpecified()) {
|
||||
if (uid == null) {
|
||||
uid = UserOperations.getIdFromUsername(adminRoot, realm, auth, uusername);
|
||||
}
|
||||
|
||||
processGlobalOptions();
|
||||
|
||||
Iterator<String> it = args.iterator();
|
||||
|
||||
while (it.hasNext()) {
|
||||
String option = it.next();
|
||||
switch (option) {
|
||||
case "--rolename": {
|
||||
optionRequiresValueCheck(it, option);
|
||||
roleNames.add(it.next());
|
||||
break;
|
||||
}
|
||||
case "--roleid": {
|
||||
optionRequiresValueCheck(it, option);
|
||||
roleIds.add(it.next());
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new IllegalArgumentException("Invalid option: " + option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (uid != null && uusername != null) {
|
||||
throw new IllegalArgumentException("Incompatible options: --uid and --uusername are mutually exclusive");
|
||||
}
|
||||
|
||||
if ((gid != null && gname != null) || (gid != null && gpath != null) || (gname != null && gpath != null)) {
|
||||
throw new IllegalArgumentException("Incompatible options: --gid, --gname and --gpath are mutually exclusive");
|
||||
}
|
||||
|
||||
if (roleNames.isEmpty() && roleIds.isEmpty()) {
|
||||
throw new IllegalArgumentException("No role to add specified. Use --rolename or --roleid to specify roles to add");
|
||||
}
|
||||
|
||||
if (cid != null && cclientid != null) {
|
||||
throw new IllegalArgumentException("Incompatible options: --cid and --cclientid are mutually exclusive");
|
||||
}
|
||||
|
||||
if (rid != null && rname != null) {
|
||||
throw new IllegalArgumentException("Incompatible options: --rid and --rname are mutually exclusive");
|
||||
}
|
||||
|
||||
if (isUserSpecified() && isGroupSpecified()) {
|
||||
throw new IllegalArgumentException("Incompatible options: --uusername / --uid can't be used at the same time as --gname / --gid / --gpath");
|
||||
}
|
||||
|
||||
if (isUserSpecified() && isCompositeRoleSpecified()) {
|
||||
throw new IllegalArgumentException("Incompatible options: --uusername / --uid can't be used at the same time as --rname / --rid");
|
||||
}
|
||||
|
||||
if (isGroupSpecified() && isCompositeRoleSpecified()) {
|
||||
throw new IllegalArgumentException("Incompatible options: --rname / --rid can't be used at the same time as --gname / --gid / --gpath");
|
||||
}
|
||||
|
||||
if (!isUserSpecified() && !isGroupSpecified() && !isCompositeRoleSpecified()) {
|
||||
throw new IllegalArgumentException("No user nor group nor composite role specified. Use --uusername / --uid to specify user or --gname / --gid / --gpath to specify group or --rname / --rid to specify a composite role");
|
||||
}
|
||||
|
||||
|
||||
ConfigData config = loadConfig();
|
||||
config = copyWithServerInfo(config);
|
||||
|
||||
setupTruststore(config, commandInvocation);
|
||||
|
||||
String 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 = getTargetRealm(config);
|
||||
final String adminRoot = adminRestRoot != null ? adminRestRoot : composeAdminRoot(server);
|
||||
|
||||
|
||||
if (isUserSpecified()) {
|
||||
if (uid == null) {
|
||||
uid = UserOperations.getIdFromUsername(adminRoot, realm, auth, uusername);
|
||||
}
|
||||
if (isClientSpecified()) {
|
||||
// list client roles for a user
|
||||
if (cid == null) {
|
||||
cid = ClientOperations.getIdFromClientId(adminRoot, realm, auth, cclientid);
|
||||
}
|
||||
|
||||
List<ObjectNode> roles = RoleOperations.getClientRoles(adminRoot, realm, cid, auth);
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds, new LocalSearch(roles));
|
||||
|
||||
// now add all the roles
|
||||
UserOperations.addClientRoles(adminRoot, realm, auth, uid, cid, new ArrayList<>(rolesToAdd));
|
||||
|
||||
} else {
|
||||
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds,
|
||||
new LocalSearch(RoleOperations.getRealmRolesAsNodes(adminRoot, realm, auth)));
|
||||
|
||||
// now add all the roles
|
||||
UserOperations.addRealmRoles(adminRoot, realm, auth, uid, new ArrayList<>(rolesToAdd));
|
||||
if (isClientSpecified()) {
|
||||
// list client roles for a user
|
||||
if (cid == null) {
|
||||
cid = ClientOperations.getIdFromClientId(adminRoot, realm, auth, cclientid);
|
||||
}
|
||||
|
||||
} else if (isGroupSpecified()) {
|
||||
if (gname != null) {
|
||||
gid = GroupOperations.getIdFromName(adminRoot, realm, auth, gname);
|
||||
} else if (gpath != null) {
|
||||
gid = GroupOperations.getIdFromPath(adminRoot, realm, auth, gpath);
|
||||
}
|
||||
if (isClientSpecified()) {
|
||||
// list client roles for a group
|
||||
if (cid == null) {
|
||||
cid = ClientOperations.getIdFromClientId(adminRoot, realm, auth, cclientid);
|
||||
}
|
||||
List<ObjectNode> roles = RoleOperations.getClientRoles(adminRoot, realm, cid, auth);
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds, new LocalSearch(roles));
|
||||
|
||||
List<ObjectNode> roles = RoleOperations.getClientRoles(adminRoot, realm, cid, auth);
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds, new LocalSearch(roles));
|
||||
|
||||
// now add all the roles
|
||||
GroupOperations.addClientRoles(adminRoot, realm, auth, gid, cid, new ArrayList<>(rolesToAdd));
|
||||
|
||||
} else {
|
||||
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds,
|
||||
new LocalSearch(RoleOperations.getRealmRolesAsNodes(adminRoot, realm, auth)));
|
||||
|
||||
// now add all the roles
|
||||
GroupOperations.addRealmRoles(adminRoot, realm, auth, gid, new ArrayList<>(rolesToAdd));
|
||||
}
|
||||
|
||||
} else if (isCompositeRoleSpecified()) {
|
||||
if (rid == null) {
|
||||
rid = RoleOperations.getIdFromRoleName(adminRoot, realm, auth, rname);
|
||||
}
|
||||
if (isClientSpecified()) {
|
||||
// list client roles for a composite role
|
||||
if (cid == null) {
|
||||
cid = ClientOperations.getIdFromClientId(adminRoot, realm, auth, cclientid);
|
||||
}
|
||||
|
||||
List<ObjectNode> roles = RoleOperations.getClientRoles(adminRoot, realm, cid, auth);
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds, new LocalSearch(roles));
|
||||
|
||||
// now add all the roles
|
||||
RoleOperations.addClientRoles(adminRoot, realm, auth, rid, new ArrayList<>(rolesToAdd));
|
||||
|
||||
} else {
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds,
|
||||
new LocalSearch(RoleOperations.getRealmRolesAsNodes(adminRoot, realm, auth)));
|
||||
|
||||
// now add all the roles
|
||||
RoleOperations.addRealmRoles(adminRoot, realm, auth, rid, new ArrayList<>(rolesToAdd));
|
||||
}
|
||||
// now add all the roles
|
||||
UserOperations.addClientRoles(adminRoot, realm, auth, uid, cid, new ArrayList<>(rolesToAdd));
|
||||
|
||||
} else {
|
||||
throw new IllegalArgumentException("No user nor group, nor composite role specified. Use --uusername / --uid to specify user or --gname / --gid / --gpath to specify group or --rname / --rid to specify a composite role");
|
||||
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds,
|
||||
new LocalSearch(RoleOperations.getRealmRolesAsNodes(adminRoot, realm, auth)));
|
||||
|
||||
// now add all the roles
|
||||
UserOperations.addRealmRoles(adminRoot, realm, auth, uid, new ArrayList<>(rolesToAdd));
|
||||
}
|
||||
|
||||
return CommandResult.SUCCESS;
|
||||
} else if (isGroupSpecified()) {
|
||||
if (gname != null) {
|
||||
gid = GroupOperations.getIdFromName(adminRoot, realm, auth, gname);
|
||||
} else if (gpath != null) {
|
||||
gid = GroupOperations.getIdFromPath(adminRoot, realm, auth, gpath);
|
||||
}
|
||||
if (isClientSpecified()) {
|
||||
// list client roles for a group
|
||||
if (cid == null) {
|
||||
cid = ClientOperations.getIdFromClientId(adminRoot, realm, auth, cclientid);
|
||||
}
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
|
||||
} finally {
|
||||
commandInvocation.stop();
|
||||
List<ObjectNode> roles = RoleOperations.getClientRoles(adminRoot, realm, cid, auth);
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds, new LocalSearch(roles));
|
||||
|
||||
// now add all the roles
|
||||
GroupOperations.addClientRoles(adminRoot, realm, auth, gid, cid, new ArrayList<>(rolesToAdd));
|
||||
|
||||
} else {
|
||||
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds,
|
||||
new LocalSearch(RoleOperations.getRealmRolesAsNodes(adminRoot, realm, auth)));
|
||||
|
||||
// now add all the roles
|
||||
GroupOperations.addRealmRoles(adminRoot, realm, auth, gid, new ArrayList<>(rolesToAdd));
|
||||
}
|
||||
|
||||
} else if (isCompositeRoleSpecified()) {
|
||||
if (rid == null) {
|
||||
rid = RoleOperations.getIdFromRoleName(adminRoot, realm, auth, rname);
|
||||
}
|
||||
if (isClientSpecified()) {
|
||||
// list client roles for a composite role
|
||||
if (cid == null) {
|
||||
cid = ClientOperations.getIdFromClientId(adminRoot, realm, auth, cclientid);
|
||||
}
|
||||
|
||||
List<ObjectNode> roles = RoleOperations.getClientRoles(adminRoot, realm, cid, auth);
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds, new LocalSearch(roles));
|
||||
|
||||
// now add all the roles
|
||||
RoleOperations.addClientRoles(adminRoot, realm, auth, rid, new ArrayList<>(rolesToAdd));
|
||||
|
||||
} else {
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds,
|
||||
new LocalSearch(RoleOperations.getRealmRolesAsNodes(adminRoot, realm, auth)));
|
||||
|
||||
// now add all the roles
|
||||
RoleOperations.addRealmRoles(adminRoot, realm, auth, rid, new ArrayList<>(rolesToAdd));
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new IllegalArgumentException("No user nor group, nor composite role specified. Use --uusername / --uid to specify user or --gname / --gid / --gpath to specify group or --rname / --rid to specify a composite role");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,12 +242,6 @@ public class AddRolesCmd extends AbstractAuthOptionsCmd {
|
||||
return rolesToAdd;
|
||||
}
|
||||
|
||||
private void optionRequiresValueCheck(Iterator<String> it, String option) {
|
||||
if (!it.hasNext()) {
|
||||
throw new IllegalArgumentException("Option " + option + " requires a value");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isClientSpecified() {
|
||||
return cid != null || cclientid != null;
|
||||
}
|
||||
@@ -304,13 +260,10 @@ public class AddRolesCmd extends AbstractAuthOptionsCmd {
|
||||
|
||||
@Override
|
||||
protected boolean nothingToDo() {
|
||||
return noOptions() && uusername == null && uid == null && cclientid == null && (args == null || args.size() == 0);
|
||||
}
|
||||
|
||||
protected String suggestHelp() {
|
||||
return EOL + "Try '" + CMD + " help add-roles' for more information";
|
||||
return super.nothingToDo() && uusername == null && uid == null && cclientid == null && roleIds.isEmpty() && roleNames.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String help() {
|
||||
return usage();
|
||||
}
|
||||
|
||||
@@ -16,66 +16,35 @@
|
||||
*/
|
||||
package org.keycloak.client.admin.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.StringWriter;
|
||||
|
||||
import picocli.CommandLine.Command;
|
||||
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.EOL;
|
||||
|
||||
|
||||
/**
|
||||
* @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 = {
|
||||
ConfigCredentialsCmd.class,
|
||||
ConfigTruststoreCmd.class
|
||||
} )
|
||||
public class ConfigCmd extends AbstractAuthOptionsCmd {
|
||||
|
||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
||||
try {
|
||||
if (args != null && args.size() > 0) {
|
||||
String cmd = args.get(0);
|
||||
switch (cmd) {
|
||||
case "credentials": {
|
||||
args.remove(0);
|
||||
ConfigCredentialsCmd command = new ConfigCredentialsCmd();
|
||||
command.initFromParent(this);
|
||||
return command.execute(commandInvocation);
|
||||
}
|
||||
case "truststore": {
|
||||
args.remove(0);
|
||||
ConfigTruststoreCmd command = new ConfigTruststoreCmd();
|
||||
command.initFromParent(this);
|
||||
return command.execute(commandInvocation);
|
||||
}
|
||||
default: {
|
||||
if (printHelp()) {
|
||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
||||
}
|
||||
throw new IllegalArgumentException("Unknown sub-command: " + cmd + suggestHelp());
|
||||
}
|
||||
}
|
||||
}
|
||||
@Override
|
||||
protected void process() {
|
||||
|
||||
if (printHelp()) {
|
||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException("Sub-command required by '" + CMD + " config' - one of: 'credentials', 'truststore'");
|
||||
|
||||
} finally {
|
||||
commandInvocation.stop();
|
||||
}
|
||||
}
|
||||
|
||||
protected String suggestHelp() {
|
||||
return EOL + "Try '" + CMD + " help config' for more information";
|
||||
@Override
|
||||
protected boolean nothingToDo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String help() {
|
||||
return usage();
|
||||
}
|
||||
|
||||
@@ -16,10 +16,6 @@
|
||||
*/
|
||||
package org.keycloak.client.admin.cli.commands;
|
||||
|
||||
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.OAuth2Constants;
|
||||
import org.keycloak.client.admin.cli.config.ConfigData;
|
||||
import org.keycloak.client.admin.cli.config.RealmConfigData;
|
||||
@@ -31,6 +27,8 @@ import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.net.URL;
|
||||
|
||||
import picocli.CommandLine.Command;
|
||||
|
||||
import static org.keycloak.client.admin.cli.util.AuthUtil.getAuthTokens;
|
||||
import static org.keycloak.client.admin.cli.util.AuthUtil.getAuthTokensByJWT;
|
||||
import static org.keycloak.client.admin.cli.util.AuthUtil.getAuthTokensBySecret;
|
||||
@@ -41,7 +39,6 @@ import static org.keycloak.client.admin.cli.util.ConfigUtil.saveTokens;
|
||||
import static org.keycloak.client.admin.cli.util.IoUtil.printErr;
|
||||
import static org.keycloak.client.admin.cli.util.IoUtil.readSecret;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.EOL;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.OS_ARCH;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||
|
||||
@@ -49,12 +46,11 @@ import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||
/**
|
||||
* @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 {
|
||||
|
||||
private int sigLifetime = 600;
|
||||
|
||||
|
||||
public void init(ConfigData configData) {
|
||||
if (server == null) {
|
||||
server = configData.getServerUrl();
|
||||
@@ -76,33 +72,13 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
||||
try {
|
||||
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();
|
||||
}
|
||||
protected String[] getUnsupportedOptions() {
|
||||
return new String[] {"--no-config", booleanOptionForCheck(noconfig)};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean nothingToDo() {
|
||||
return noOptions();
|
||||
}
|
||||
|
||||
public CommandResult process(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
||||
|
||||
public void process() {
|
||||
// check server
|
||||
if (server == null) {
|
||||
throw new IllegalArgumentException("Required option not specified: --server");
|
||||
@@ -129,18 +105,18 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd {
|
||||
|
||||
// if user was set there needs to be a password so we can authenticate
|
||||
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 ("-".equals(secret) && keystore == null) {
|
||||
secret = readSecret("Enter client secret: ", commandInvocation);
|
||||
secret = readSecret("Enter client secret: ");
|
||||
}
|
||||
} else if (keystore != null || secret != null || clientSet) {
|
||||
grantTypeForAuthentication = OAuth2Constants.CLIENT_CREDENTIALS;
|
||||
printErr("Logging into " + server + " as " + "service-account-" + clientId + " of realm " + realm);
|
||||
if (keystore == null) {
|
||||
if (secret == null) {
|
||||
secret = readSecret("Enter client secret: ", commandInvocation);
|
||||
secret = readSecret("Enter client secret: ");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -155,8 +131,8 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd {
|
||||
}
|
||||
|
||||
if (storePass == null) {
|
||||
storePass = readSecret("Enter keystore password: ", commandInvocation);
|
||||
keyPass = readSecret("Enter key password: ", commandInvocation);
|
||||
storePass = readSecret("Enter keystore password: ");
|
||||
keyPass = readSecret("Enter key password: ");
|
||||
}
|
||||
|
||||
if (keyPass == null) {
|
||||
@@ -179,10 +155,10 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd {
|
||||
config.setServerUrl(server);
|
||||
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
|
||||
AccessTokenResponse tokens = signedRequestToken != null ?
|
||||
@@ -195,14 +171,9 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd {
|
||||
|
||||
// save tokens to config file
|
||||
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() {
|
||||
return usage();
|
||||
}
|
||||
|
||||
@@ -16,93 +16,41 @@
|
||||
*/
|
||||
package org.keycloak.client.admin.cli.commands;
|
||||
|
||||
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 java.io.File;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Option;
|
||||
import picocli.CommandLine.Parameters;
|
||||
|
||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.saveMergeConfig;
|
||||
import static org.keycloak.client.admin.cli.util.IoUtil.readSecret;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.EOL;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.OS_ARCH;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
@CommandDefinition(name = "truststore", description = "PATH [ARGUMENTS]")
|
||||
@Command(name = "truststore", description = "PATH [ARGUMENTS]")
|
||||
public class ConfigTruststoreCmd extends AbstractAuthOptionsCmd {
|
||||
|
||||
private ConfigCmd parent;
|
||||
@Parameters(arity = "0..1")
|
||||
private String store;
|
||||
|
||||
@Option(names = {"-d", "--delete"}, description = "Remove truststore configuration")
|
||||
private boolean delete;
|
||||
|
||||
|
||||
protected void initFromParent(ConfigCmd parent) {
|
||||
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
|
||||
protected boolean nothingToDo() {
|
||||
return noOptions();
|
||||
return super.nothingToDo() && store == null && !delete;
|
||||
}
|
||||
|
||||
public CommandResult process(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
||||
|
||||
List<String> args = new ArrayList<>();
|
||||
|
||||
Iterator<String> it = parent.args.iterator();
|
||||
|
||||
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,
|
||||
@Override
|
||||
protected String[] getUnsupportedOptions() {
|
||||
return new String[] {"--server", server,
|
||||
"--realm", realm,
|
||||
"--client", clientId,
|
||||
"--user", user,
|
||||
@@ -112,39 +60,36 @@ public class ConfigTruststoreCmd extends AbstractAuthOptionsCmd {
|
||||
"--keystore", keystore,
|
||||
"--keypass", keyPass,
|
||||
"--alias", alias,
|
||||
"--no-config", booleanOptionForCheck(noconfig));
|
||||
"--no-config", booleanOptionForCheck(noconfig)};
|
||||
}
|
||||
|
||||
// now update the config
|
||||
processGlobalOptions();
|
||||
|
||||
String store;
|
||||
@Override
|
||||
protected void process() {
|
||||
String pass;
|
||||
|
||||
if (!delete) {
|
||||
|
||||
if (truststore == null) {
|
||||
if (store == null) {
|
||||
throw new IllegalArgumentException("No truststore specified");
|
||||
}
|
||||
|
||||
if (!new File(truststore).isFile()) {
|
||||
throw new RuntimeException("Truststore file not found: " + truststore);
|
||||
if (!new File(store).isFile()) {
|
||||
throw new RuntimeException("Truststore file not found: " + store);
|
||||
}
|
||||
|
||||
if ("-".equals(trustPass)) {
|
||||
trustPass = readSecret("Enter truststore password: ", commandInvocation);
|
||||
trustPass = readSecret("Enter truststore password: ");
|
||||
}
|
||||
|
||||
store = truststore;
|
||||
pass = trustPass;
|
||||
|
||||
} else {
|
||||
if (truststore != null) {
|
||||
if (store != null) {
|
||||
throw new IllegalArgumentException("Option --delete is mutually exclusive with specifying a TRUSTSTORE");
|
||||
}
|
||||
if (trustPass != null) {
|
||||
throw new IllegalArgumentException("Options --trustpass and --delete are mutually exclusive");
|
||||
}
|
||||
store = null;
|
||||
pass = null;
|
||||
}
|
||||
|
||||
@@ -152,14 +97,9 @@ public class ConfigTruststoreCmd extends AbstractAuthOptionsCmd {
|
||||
config.setTruststore(store);
|
||||
config.setTrustpass(pass);
|
||||
});
|
||||
|
||||
return CommandResult.SUCCESS;
|
||||
}
|
||||
|
||||
protected String suggestHelp() {
|
||||
return EOL + "Try '" + CMD + " help config truststore' for more information";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String help() {
|
||||
return usage();
|
||||
}
|
||||
|
||||
@@ -16,70 +16,63 @@
|
||||
*/
|
||||
package org.keycloak.client.admin.cli.commands;
|
||||
|
||||
import org.jboss.aesh.cl.CommandDefinition;
|
||||
import org.jboss.aesh.cl.Option;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Option;
|
||||
|
||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.EOL;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.OS_ARCH;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
@CommandDefinition(name = "create", description = "Command to create new resources")
|
||||
@Command(name = "create", description = "Command to create new resources")
|
||||
public class CreateCmd extends AbstractRequestCmd {
|
||||
|
||||
@Option(shortName = 'f', name = "file", description = "Read object from file or standard input if FILENAME is set to '-'")
|
||||
String file;
|
||||
public CreateCmd() {
|
||||
this.httpVerb = "post";
|
||||
}
|
||||
|
||||
@Option(shortName = 'b', name = "body", description = "JSON object to be sent as-is or used as a template")
|
||||
String body;
|
||||
@Option(names = {"-f", "--file"}, description = "Read object from file or standard input if FILENAME is set to '-'")
|
||||
public void setFile(String file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Option(shortName = 'F', name = "fields", description = "A pattern specifying which attributes of JSON response body to actually display as result - causes mismatch with Content-Length header", hasValue = true)
|
||||
String fields;
|
||||
@Option(names = {"-b", "--body"}, description = "JSON object to be sent as-is or used as a template")
|
||||
public void setBody(String body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@Option(shortName = 'H', name = "print-headers", description = "Print response headers", hasValue = false)
|
||||
boolean printHeaders;
|
||||
@Option(names = {"-F", "--fields"}, description = "A pattern specifying which attributes of JSON response body to actually display as result - causes mismatch with Content-Length header")
|
||||
public void setFields(String fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
@Option(shortName = 'i', name = "id", description = "After creation only print id of created resource to standard output", hasValue = false)
|
||||
boolean returnId = false;
|
||||
@Option(names = {"-H", "--print-headers"}, description = "Print response headers")
|
||||
public void setPrintHeaders(boolean printHeaders) {
|
||||
this.printHeaders = printHeaders;
|
||||
}
|
||||
|
||||
@Option(shortName = 'o', name = "output", description = "After creation output the new resource to standard output", hasValue = false)
|
||||
boolean outputResult = false;
|
||||
@Option(names = {"-i", "--id"}, description = "After creation only print id of created resource to standard output")
|
||||
public void setReturnId(boolean returnId) {
|
||||
this.returnId = returnId;
|
||||
}
|
||||
|
||||
@Option(shortName = 'c', name = "compressed", description = "Don't pretty print the output", hasValue = false)
|
||||
boolean compressed = false;
|
||||
@Option(names = {"-o", "--output"}, description = "After creation output the new resource to standard output")
|
||||
public void setOutputResult(boolean outputResult) {
|
||||
this.outputResult = outputResult;
|
||||
}
|
||||
|
||||
//@OptionGroup(shortName = 's', name = "set", description = "Set attribute to the specified value")
|
||||
//Map<String, String> attributes = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
void initOptions() {
|
||||
// set options on parent
|
||||
super.file = file;
|
||||
super.body = body;
|
||||
super.fields = fields;
|
||||
super.printHeaders = printHeaders;
|
||||
super.returnId = returnId;
|
||||
super.outputResult = outputResult;
|
||||
super.compressed = compressed;
|
||||
super.httpVerb = "post";
|
||||
@Option(names = {"-c", "--compressed"}, description = "Don't pretty print the output")
|
||||
public void setCompressed(boolean compressed) {
|
||||
this.compressed = compressed;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean nothingToDo() {
|
||||
return noOptions() && file == null && body == null && (args == null || args.size() == 0);
|
||||
}
|
||||
|
||||
protected String suggestHelp() {
|
||||
return EOL + "Try '" + CMD + " help create' for more information";
|
||||
}
|
||||
|
||||
protected String help() {
|
||||
return usage();
|
||||
}
|
||||
|
||||
@@ -16,37 +16,27 @@
|
||||
*/
|
||||
package org.keycloak.client.admin.cli.commands;
|
||||
|
||||
import org.jboss.aesh.cl.CommandDefinition;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import picocli.CommandLine.Command;
|
||||
|
||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.EOL;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||
|
||||
|
||||
/**
|
||||
* @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 CreateCmd {
|
||||
|
||||
void initOptions() {
|
||||
super.initOptions();
|
||||
httpVerb = "delete";
|
||||
public DeleteCmd() {
|
||||
this.httpVerb = "delete";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean nothingToDo() {
|
||||
return noOptions() && (args == null || args.size() == 0);
|
||||
}
|
||||
|
||||
protected String suggestHelp() {
|
||||
return EOL + "Try '" + CMD + " help delete' for more information";
|
||||
}
|
||||
|
||||
protected String help() {
|
||||
return usage();
|
||||
}
|
||||
|
||||
@@ -16,69 +16,63 @@
|
||||
*/
|
||||
package org.keycloak.client.admin.cli.commands;
|
||||
|
||||
import org.jboss.aesh.cl.CommandDefinition;
|
||||
import org.jboss.aesh.cl.Option;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Option;
|
||||
|
||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.EOL;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
@CommandDefinition(name = "get", description = "[ARGUMENTS]")
|
||||
public class GetCmd extends AbstractRequestCmd {
|
||||
@Command(name = "get", description = "[ARGUMENTS]")
|
||||
public class GetCmd extends AbstractRequestCmd {
|
||||
|
||||
@Option(name = "noquotes", description = "", hasValue = false)
|
||||
boolean unquoted;
|
||||
public GetCmd() {
|
||||
this.httpVerb = "get";
|
||||
this.outputResult = true;
|
||||
}
|
||||
|
||||
@Option(shortName = 'F', name = "fields", description = "A pattern specifying which attributes of JSON response body to actually display as result - causes mismatch with Content-Length header")
|
||||
String fields;
|
||||
@Option(names = "--noquotes", description = "")
|
||||
public void setUnquoted(boolean unquoted) {
|
||||
this.unquoted = unquoted;
|
||||
}
|
||||
|
||||
@Option(shortName = 'H', name = "print-headers", description = "Print response headers", hasValue = false)
|
||||
boolean printHeaders;
|
||||
@Option(names = {"-F", "--fields"}, description = "A pattern specifying which attributes of JSON response body to actually display as result - causes mismatch with Content-Length header")
|
||||
public void setFields(String fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
@Option(shortName = 'c', name = "compressed", description = "Don't pretty print the output", hasValue = false)
|
||||
boolean compressed;
|
||||
@Option(names = {"-H", "--print-headers"}, description = "Print response headers")
|
||||
public void setPrintHeaders(boolean printHeaders) {
|
||||
this.printHeaders = printHeaders;
|
||||
}
|
||||
|
||||
@Option(shortName = 'o', name = "offset", description = "Number of results from beginning of resultset to skip")
|
||||
Integer offset;
|
||||
@Option(names = {"-c", "--compressed"}, description = "Don't pretty print the output")
|
||||
public void setCompressed(boolean compressed) {
|
||||
this.compressed = compressed;
|
||||
}
|
||||
|
||||
@Option(shortName = 'l', name = "limit", description = "Maksimum number of results to return")
|
||||
Integer limit;
|
||||
@Option(names = {"-o", "--offset"}, description = "Number of results from beginning of resultset to skip")
|
||||
public void setOffset(Integer offset) {
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Option(name = "format", description = "Output format - one of: json, csv", defaultValue = "json")
|
||||
String format;
|
||||
@Option(names = {"-l", "--limit"}, description = "Maksimum number of results to return")
|
||||
public void setLimit(Integer limit) {
|
||||
this.limit = limit;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
void initOptions() {
|
||||
// set options on parent
|
||||
super.fields = fields;
|
||||
super.printHeaders = printHeaders;
|
||||
super.returnId = false;
|
||||
super.outputResult = true;
|
||||
super.compressed = compressed;
|
||||
super.offset = offset;
|
||||
super.limit = limit;
|
||||
super.format = format;
|
||||
super.unquoted = unquoted;
|
||||
super.httpVerb = "get";
|
||||
@Option(names = "--format", description = "Output format - one of: json, csv", defaultValue = "json")
|
||||
public void setFormat(String format) {
|
||||
this.format = format;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean nothingToDo() {
|
||||
return noOptions() && (args == null || args.size() == 0);
|
||||
}
|
||||
|
||||
protected String suggestHelp() {
|
||||
return EOL + "Try '" + CMD + " help get' for more information";
|
||||
}
|
||||
|
||||
protected String help() {
|
||||
return usage();
|
||||
}
|
||||
|
||||
@@ -16,11 +16,6 @@
|
||||
*/
|
||||
package org.keycloak.client.admin.cli.commands;
|
||||
|
||||
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.admin.cli.config.ConfigData;
|
||||
import org.keycloak.client.admin.cli.operations.ClientOperations;
|
||||
import org.keycloak.client.admin.cli.operations.GroupOperations;
|
||||
@@ -29,7 +24,9 @@ import org.keycloak.client.admin.cli.operations.UserOperations;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Option;
|
||||
|
||||
import static org.keycloak.client.admin.cli.util.AuthUtil.ensureToken;
|
||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
||||
@@ -42,69 +39,58 @@ import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
@CommandDefinition(name = "get-roles", description = "[ARGUMENTS]")
|
||||
@Command(name = "get-roles", description = "[ARGUMENTS]")
|
||||
public class GetRolesCmd extends GetCmd {
|
||||
|
||||
@Option(name = "uusername", description = "Target user's 'username'")
|
||||
@Option(names = "--uusername", description = "Target user's 'username'")
|
||||
String uusername;
|
||||
|
||||
@Option(name = "uid", description = "Target user's 'id'")
|
||||
@Option(names = "--uid", description = "Target user's 'id'")
|
||||
String uid;
|
||||
|
||||
@Option(name = "cclientid", description = "Target client's 'clientId'")
|
||||
@Option(names = "--cclientid", description = "Target client's 'clientId'")
|
||||
String cclientid;
|
||||
|
||||
@Option(name = "cid", description = "Target client's 'id'")
|
||||
@Option(names = "--cid", description = "Target client's 'id'")
|
||||
String cid;
|
||||
|
||||
@Option(name = "rname", description = "Composite role's 'name'")
|
||||
@Option(names = "--rname", description = "Composite role's 'name'")
|
||||
String rname;
|
||||
|
||||
@Option(name = "rid", description = "Composite role's 'id'")
|
||||
@Option(names = "--rid", description = "Composite role's 'id'")
|
||||
String rid;
|
||||
|
||||
@Option(name = "gname", description = "Target group's 'name'")
|
||||
@Option(names = "--gname", description = "Target group's 'name'")
|
||||
String gname;
|
||||
|
||||
@Option(name = "gpath", description = "Target group's 'path'")
|
||||
@Option(names = "--gpath", description = "Target group's 'path'")
|
||||
String gpath;
|
||||
|
||||
@Option(name = "gid", description = "Target group's 'id'")
|
||||
@Option(names = "--gid", description = "Target group's 'id'")
|
||||
String gid;
|
||||
|
||||
@Option(name = "rolename", description = "Target role's 'name'")
|
||||
@Option(names = "--rolename", description = "Target role's 'name'")
|
||||
String rolename;
|
||||
|
||||
@Option(name = "roleid", description = "Target role's 'id'")
|
||||
@Option(names = "--roleid", description = "Target role's 'id'")
|
||||
String roleid;
|
||||
|
||||
@Option(name = "available", description = "List only available roles", hasValue = false)
|
||||
@Option(names = "--available", description = "List only available roles")
|
||||
boolean available;
|
||||
|
||||
@Option(name = "effective", description = "List assigned roles including transitively included roles", hasValue = false)
|
||||
@Option(names = "--effective", description = "List assigned roles including transitively included roles")
|
||||
boolean effective;
|
||||
|
||||
@Option(name = "all", description = "List roles for all clients in addition to realm roles", hasValue = false)
|
||||
@Option(names = "--all", description = "List roles for all clients in addition to realm roles")
|
||||
boolean all;
|
||||
|
||||
|
||||
void initOptions() {
|
||||
|
||||
super.initOptions();
|
||||
|
||||
@Override
|
||||
protected void processOptions() {
|
||||
// hack args so that GetCmd option check doesn't fail
|
||||
// set a placeholder
|
||||
if (args == null) {
|
||||
args = new ArrayList();
|
||||
if (uri == null) {
|
||||
uri = "uri";
|
||||
}
|
||||
if (args.size() == 0) {
|
||||
args.add("uri");
|
||||
} else {
|
||||
args.add(0, "uri");
|
||||
}
|
||||
}
|
||||
|
||||
void processOptions(CommandInvocation commandInvocation) {
|
||||
|
||||
if (uid != null && uusername != null) {
|
||||
throw new IllegalArgumentException("Incompatible options: --uid and --uusername are mutually exclusive");
|
||||
@@ -146,19 +132,19 @@ public class GetRolesCmd extends GetCmd {
|
||||
throw new IllegalArgumentException("Incompatible options: --all can't be used at the same time as --available");
|
||||
}
|
||||
|
||||
super.processOptions(commandInvocation);
|
||||
super.processOptions();
|
||||
}
|
||||
|
||||
public CommandResult process(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
||||
|
||||
@Override
|
||||
protected void process() {
|
||||
ConfigData config = loadConfig();
|
||||
config = copyWithServerInfo(config);
|
||||
|
||||
setupTruststore(config, commandInvocation);
|
||||
setupTruststore(config);
|
||||
|
||||
String auth = null;
|
||||
|
||||
config = ensureAuthInfo(config, commandInvocation);
|
||||
config = ensureAuthInfo(config);
|
||||
config = copyWithServerInfo(config);
|
||||
if (credentialsAvailable(config)) {
|
||||
auth = ensureToken(config);
|
||||
@@ -180,20 +166,20 @@ public class GetRolesCmd extends GetCmd {
|
||||
cid = ClientOperations.getIdFromClientId(adminRoot, realm, auth, cclientid);
|
||||
}
|
||||
if (available) {
|
||||
super.url = composeResourceUrl(adminRoot, realm, "users/" + uid + "/role-mappings/clients/" + cid + "/available");
|
||||
super.uri = composeResourceUrl(adminRoot, realm, "users/" + uid + "/role-mappings/clients/" + cid + "/available");
|
||||
} else if (effective) {
|
||||
super.url = composeResourceUrl(adminRoot, realm, "users/" + uid + "/role-mappings/clients/" + cid + "/composite");
|
||||
super.uri = composeResourceUrl(adminRoot, realm, "users/" + uid + "/role-mappings/clients/" + cid + "/composite");
|
||||
} else {
|
||||
super.url = composeResourceUrl(adminRoot, realm, "users/" + uid + "/role-mappings/clients/" + cid);
|
||||
super.uri = composeResourceUrl(adminRoot, realm, "users/" + uid + "/role-mappings/clients/" + cid);
|
||||
}
|
||||
} else {
|
||||
// list realm roles for a user
|
||||
if (available) {
|
||||
super.url = composeResourceUrl(adminRoot, realm, "users/" + uid + "/role-mappings/realm/available");
|
||||
super.uri = composeResourceUrl(adminRoot, realm, "users/" + uid + "/role-mappings/realm/available");
|
||||
} else if (effective) {
|
||||
super.url = composeResourceUrl(adminRoot, realm, "users/" + uid + "/role-mappings/realm/composite");
|
||||
super.uri = composeResourceUrl(adminRoot, realm, "users/" + uid + "/role-mappings/realm/composite");
|
||||
} else {
|
||||
super.url = composeResourceUrl(adminRoot, realm, "users/" + uid + (all ? "/role-mappings" : "/role-mappings/realm"));
|
||||
super.uri = composeResourceUrl(adminRoot, realm, "users/" + uid + (all ? "/role-mappings" : "/role-mappings/realm"));
|
||||
}
|
||||
}
|
||||
} else if (isGroupSpecified()) {
|
||||
@@ -208,20 +194,20 @@ public class GetRolesCmd extends GetCmd {
|
||||
cid = ClientOperations.getIdFromClientId(adminRoot, realm, auth, cclientid);
|
||||
}
|
||||
if (available) {
|
||||
super.url = composeResourceUrl(adminRoot, realm, "groups/" + gid + "/role-mappings/clients/" + cid + "/available");
|
||||
super.uri = composeResourceUrl(adminRoot, realm, "groups/" + gid + "/role-mappings/clients/" + cid + "/available");
|
||||
} else if (effective) {
|
||||
super.url = composeResourceUrl(adminRoot, realm, "groups/" + gid + "/role-mappings/clients/" + cid + "/composite");
|
||||
super.uri = composeResourceUrl(adminRoot, realm, "groups/" + gid + "/role-mappings/clients/" + cid + "/composite");
|
||||
} else {
|
||||
super.url = composeResourceUrl(adminRoot, realm, "groups/" + gid + "/role-mappings/clients/" + cid);
|
||||
super.uri = composeResourceUrl(adminRoot, realm, "groups/" + gid + "/role-mappings/clients/" + cid);
|
||||
}
|
||||
} else {
|
||||
// list realm roles for a group
|
||||
if (available) {
|
||||
super.url = composeResourceUrl(adminRoot, realm, "groups/" + gid + "/role-mappings/realm/available");
|
||||
super.uri = composeResourceUrl(adminRoot, realm, "groups/" + gid + "/role-mappings/realm/available");
|
||||
} else if (effective) {
|
||||
super.url = composeResourceUrl(adminRoot, realm, "groups/" + gid + "/role-mappings/realm/composite");
|
||||
super.uri = composeResourceUrl(adminRoot, realm, "groups/" + gid + "/role-mappings/realm/composite");
|
||||
} else {
|
||||
super.url = composeResourceUrl(adminRoot, realm, "groups/" + gid + (all ? "/role-mappings" : "/role-mappings/realm"));
|
||||
super.uri = composeResourceUrl(adminRoot, realm, "groups/" + gid + (all ? "/role-mappings" : "/role-mappings/realm"));
|
||||
}
|
||||
}
|
||||
} else if (isCompositeRoleSpecified()) {
|
||||
@@ -248,7 +234,7 @@ public class GetRolesCmd extends GetCmd {
|
||||
|
||||
uri += all ? "/composites" : "/composites/realm";
|
||||
}
|
||||
super.url = composeResourceUrl(adminRoot, realm, uri);
|
||||
super.uri = composeResourceUrl(adminRoot, realm, uri);
|
||||
|
||||
} else if (isClientSpecified()) {
|
||||
if (cid == null) {
|
||||
@@ -260,10 +246,10 @@ public class GetRolesCmd extends GetCmd {
|
||||
if (rolename == null) {
|
||||
rolename = RoleOperations.getClientRoleNameFromId(adminRoot, realm, auth, cid, roleid);
|
||||
}
|
||||
super.url = composeResourceUrl(adminRoot, realm, "clients/" + cid + "/roles/" + rolename);
|
||||
super.uri = composeResourceUrl(adminRoot, realm, "clients/" + cid + "/roles/" + rolename);
|
||||
} else {
|
||||
// list defined client roles
|
||||
super.url = composeResourceUrl(adminRoot, realm, "clients/" + cid + "/roles");
|
||||
super.uri = composeResourceUrl(adminRoot, realm, "clients/" + cid + "/roles");
|
||||
}
|
||||
} else {
|
||||
if (isRoleSpecified()) {
|
||||
@@ -271,14 +257,14 @@ public class GetRolesCmd extends GetCmd {
|
||||
if (rolename == null) {
|
||||
rolename = RoleOperations.getClientRoleNameFromId(adminRoot, realm, auth, cid, roleid);
|
||||
}
|
||||
super.url = composeResourceUrl(adminRoot, realm, "roles/" + rolename);
|
||||
super.uri = composeResourceUrl(adminRoot, realm, "roles/" + rolename);
|
||||
} else {
|
||||
// list defined realm roles
|
||||
super.url = composeResourceUrl(adminRoot, realm, "roles");
|
||||
super.uri = composeResourceUrl(adminRoot, realm, "roles");
|
||||
}
|
||||
}
|
||||
|
||||
return super.process(commandInvocation);
|
||||
super.process();
|
||||
}
|
||||
|
||||
private boolean isRoleSpecified() {
|
||||
@@ -301,14 +287,12 @@ public class GetRolesCmd extends GetCmd {
|
||||
return uid != null || uusername != null;
|
||||
}
|
||||
|
||||
protected String suggestHelp() {
|
||||
return "";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean nothingToDo() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String help() {
|
||||
return usage();
|
||||
}
|
||||
|
||||
@@ -16,92 +16,81 @@
|
||||
*/
|
||||
package org.keycloak.client.admin.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 picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Parameters;
|
||||
|
||||
import static org.keycloak.client.admin.cli.util.IoUtil.printOut;
|
||||
|
||||
@Command(name = "help", description = "This Help")
|
||||
public class HelpCmd implements Runnable {
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
@CommandDefinition(name = "help", description = "This help")
|
||||
public class HelpCmd implements Command {
|
||||
|
||||
@Arguments
|
||||
@Parameters
|
||||
List<String> args;
|
||||
|
||||
@Override
|
||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
||||
try {
|
||||
if (args == null || args.size() == 0) {
|
||||
printOut(KcAdmCmd.usage());
|
||||
} else {
|
||||
outer:
|
||||
switch (args.get(0)) {
|
||||
case "config": {
|
||||
if (args.size() > 1) {
|
||||
switch (args.get(1)) {
|
||||
case "credentials": {
|
||||
printOut(ConfigCredentialsCmd.usage());
|
||||
break outer;
|
||||
}
|
||||
case "truststore": {
|
||||
printOut(ConfigTruststoreCmd.usage());
|
||||
break outer;
|
||||
}
|
||||
}
|
||||
}
|
||||
printOut(ConfigCmd.usage());
|
||||
break;
|
||||
public void run() {
|
||||
if (args == null || args.size() == 0) {
|
||||
printOut(KcAdmCmd.usage());
|
||||
} else {
|
||||
outer: switch (args.get(0)) {
|
||||
case "config": {
|
||||
if (args.size() > 1) {
|
||||
switch (args.get(1)) {
|
||||
case "credentials": {
|
||||
printOut(ConfigCredentialsCmd.usage());
|
||||
break outer;
|
||||
}
|
||||
case "create": {
|
||||
printOut(CreateCmd.usage());
|
||||
break;
|
||||
case "truststore": {
|
||||
printOut(ConfigTruststoreCmd.usage());
|
||||
break outer;
|
||||
}
|
||||
case "get": {
|
||||
printOut(GetCmd.usage());
|
||||
break;
|
||||
}
|
||||
case "update": {
|
||||
printOut(UpdateCmd.usage());
|
||||
break;
|
||||
}
|
||||
case "delete": {
|
||||
printOut(DeleteCmd.usage());
|
||||
break;
|
||||
}
|
||||
case "get-roles": {
|
||||
printOut(GetRolesCmd.usage());
|
||||
break;
|
||||
}
|
||||
case "add-roles": {
|
||||
printOut(AddRolesCmd.usage());
|
||||
break;
|
||||
}
|
||||
case "remove-roles": {
|
||||
printOut(RemoveRolesCmd.usage());
|
||||
break;
|
||||
}
|
||||
case "set-password": {
|
||||
printOut(SetPasswordCmd.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 "get-roles": {
|
||||
printOut(GetRolesCmd.usage());
|
||||
break;
|
||||
}
|
||||
case "add-roles": {
|
||||
printOut(AddRolesCmd.usage());
|
||||
break;
|
||||
}
|
||||
case "remove-roles": {
|
||||
printOut(RemoveRolesCmd.usage());
|
||||
break;
|
||||
}
|
||||
case "set-password": {
|
||||
printOut(SetPasswordCmd.usage());
|
||||
break;
|
||||
}
|
||||
case "new-object": {
|
||||
printOut(NewObjectCmd.usage());
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new IllegalArgumentException("Unknown command: " + args.get(0));
|
||||
}
|
||||
}
|
||||
|
||||
return CommandResult.SUCCESS;
|
||||
} finally {
|
||||
commandInvocation.stop();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,47 +16,42 @@
|
||||
*/
|
||||
package org.keycloak.client.admin.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.StringWriter;
|
||||
|
||||
import picocli.CommandLine.Command;
|
||||
|
||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
||||
import static org.keycloak.client.admin.cli.util.IoUtil.printErr;
|
||||
import static org.keycloak.client.admin.cli.util.IoUtil.printOut;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
|
||||
@GroupCommandDefinition(name = "kcadm", description = "COMMAND [ARGUMENTS]", groupCommands = {
|
||||
HelpCmd.class, ConfigCmd.class, NewObjectCmd.class, CreateCmd.class, GetCmd.class, UpdateCmd.class, DeleteCmd.class,
|
||||
AddRolesCmd.class, RemoveRolesCmd.class, GetRolesCmd.class, SetPasswordCmd.class} )
|
||||
@Command(name = "kcadm",
|
||||
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,
|
||||
NewObjectCmd.class,
|
||||
CreateCmd.class,
|
||||
GetCmd.class,
|
||||
UpdateCmd.class,
|
||||
DeleteCmd.class,
|
||||
AddRolesCmd.class,
|
||||
RemoveRolesCmd.class,
|
||||
GetRolesCmd.class,
|
||||
SetPasswordCmd.class
|
||||
})
|
||||
public class KcAdmCmd extends AbstractGlobalOptionsCmd {
|
||||
|
||||
@Override
|
||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
||||
try {
|
||||
// if --help was requested then status is SUCCESS
|
||||
// if not we print help anyway, but status is FAILURE
|
||||
if (printHelp()) {
|
||||
return CommandResult.SUCCESS;
|
||||
} else if (args != null && args.size() > 0) {
|
||||
printErr("Unknown command: " + args.get(0));
|
||||
return CommandResult.FAILURE;
|
||||
} else {
|
||||
printOut(usage());
|
||||
return CommandResult.FAILURE;
|
||||
}
|
||||
} finally {
|
||||
commandInvocation.stop();
|
||||
}
|
||||
protected boolean nothingToDo() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public static String usage() {
|
||||
|
||||
@@ -17,11 +17,10 @@
|
||||
package org.keycloak.client.admin.cli.commands;
|
||||
|
||||
import com.fasterxml.jackson.databind.JsonNode;
|
||||
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 picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Option;
|
||||
|
||||
import org.keycloak.client.admin.cli.common.AttributeOperation;
|
||||
import org.keycloak.client.admin.cli.common.CmdStdinContext;
|
||||
import org.keycloak.client.admin.cli.util.AccessibleBufferOutputStream;
|
||||
@@ -32,15 +31,14 @@ import java.io.InputStream;
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.keycloak.client.admin.cli.common.AttributeOperation.Type.SET;
|
||||
import static org.keycloak.client.admin.cli.util.IoUtil.copyStream;
|
||||
import static org.keycloak.client.admin.cli.util.IoUtil.printErr;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.EOL;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.OS_ARCH;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||
import static org.keycloak.client.admin.cli.util.OutputUtil.MAPPER;
|
||||
@@ -51,59 +49,24 @@ import static org.keycloak.client.admin.cli.util.ParseUtil.parseKeyVal;
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
@CommandDefinition(name = "new-object", description = "Command to create new JSON objects locally")
|
||||
@Command(name = "new-object", description = "Command to create new JSON objects locally")
|
||||
public class NewObjectCmd extends AbstractGlobalOptionsCmd {
|
||||
|
||||
@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 '-'")
|
||||
String file;
|
||||
|
||||
@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")
|
||||
boolean compressed;
|
||||
|
||||
//@OptionGroup(shortName = 's', name = "set", description = "Set attribute to the specified value")
|
||||
//Map<String, String> attributes = new LinkedHashMap<>();
|
||||
|
||||
@Option(names = {"-s", "--set"}, description = "Set a specific attribute NAME to a specified value VALUE")
|
||||
List<String> values = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
||||
try {
|
||||
if (printHelp()) {
|
||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
||||
}
|
||||
|
||||
processGlobalOptions();
|
||||
|
||||
return process(commandInvocation);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
|
||||
} finally {
|
||||
commandInvocation.stop();
|
||||
}
|
||||
}
|
||||
|
||||
public CommandResult process(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
||||
|
||||
List<AttributeOperation> attrs = new LinkedList<>();
|
||||
|
||||
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("Invalid option: " + option);
|
||||
}
|
||||
}
|
||||
}
|
||||
public void process() {
|
||||
List<AttributeOperation> attrs = values.stream().map(it -> {
|
||||
String[] keyVal = parseKeyVal(it);
|
||||
return new AttributeOperation(SET, keyVal[0], keyVal[1]);
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
InputStream body = null;
|
||||
|
||||
@@ -142,20 +105,14 @@ public class NewObjectCmd extends AbstractGlobalOptionsCmd {
|
||||
if (lastByte != -1 && lastByte != 13 && lastByte != 10) {
|
||||
printErr("");
|
||||
}
|
||||
|
||||
return CommandResult.SUCCESS;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected boolean nothingToDo() {
|
||||
return file == null && (args == null || args.size() == 0);
|
||||
}
|
||||
|
||||
protected String suggestHelp() {
|
||||
return EOL + "Try '" + CMD + " help create' for more information";
|
||||
return file == null && values.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String help() {
|
||||
return usage();
|
||||
}
|
||||
|
||||
@@ -16,250 +16,218 @@
|
||||
*/
|
||||
package org.keycloak.client.admin.cli.commands;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
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.admin.cli.config.ConfigData;
|
||||
import org.keycloak.client.admin.cli.operations.ClientOperations;
|
||||
import org.keycloak.client.admin.cli.operations.GroupOperations;
|
||||
import org.keycloak.client.admin.cli.operations.RoleOperations;
|
||||
import org.keycloak.client.admin.cli.operations.LocalSearch;
|
||||
import org.keycloak.client.admin.cli.operations.RoleOperations;
|
||||
import org.keycloak.client.admin.cli.operations.UserOperations;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Option;
|
||||
|
||||
import static org.keycloak.client.admin.cli.util.AuthUtil.ensureToken;
|
||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.credentialsAvailable;
|
||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.loadConfig;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.EOL;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
@CommandDefinition(name = "remove-roles", description = "[ARGUMENTS]")
|
||||
@Command(name = "remove-roles", description = "[ARGUMENTS]")
|
||||
public class RemoveRolesCmd extends AbstractAuthOptionsCmd {
|
||||
|
||||
@Option(name = "uusername", description = "Target user's 'username'")
|
||||
@Option(names = "--uusername", description = "Target user's 'username'")
|
||||
String uusername;
|
||||
|
||||
@Option(name = "uid", description = "Target user's 'id'")
|
||||
@Option(names = "--uid", description = "Target user's 'id'")
|
||||
String uid;
|
||||
|
||||
@Option(name = "gname", description = "Target group's 'name'")
|
||||
@Option(names = "--gname", description = "Target group's 'name'")
|
||||
String gname;
|
||||
|
||||
@Option(name = "gpath", description = "Target group's 'path'")
|
||||
@Option(names = "--gpath", description = "Target group's 'path'")
|
||||
String gpath;
|
||||
|
||||
@Option(name = "gid", description = "Target group's 'id'")
|
||||
@Option(names = "--gid", description = "Target group's 'id'")
|
||||
String gid;
|
||||
|
||||
@Option(name = "rname", description = "Composite role's 'name'")
|
||||
@Option(names = "--rname", description = "Composite role's 'name'")
|
||||
String rname;
|
||||
|
||||
@Option(name = "rid", description = "Composite role's 'id'")
|
||||
@Option(names = "--rid", description = "Composite role's 'id'")
|
||||
String rid;
|
||||
|
||||
@Option(name = "cclientid", description = "Target client's 'clientId'")
|
||||
@Option(names = "--cclientid", description = "Target client's 'clientId'")
|
||||
String cclientid;
|
||||
|
||||
@Option(name = "cid", description = "Target client's 'id'")
|
||||
@Option(names = "--cid", description = "Target client's 'id'")
|
||||
String cid;
|
||||
|
||||
@Option(names = "--rolename", description = "Role's 'name' attribute")
|
||||
List<String> roleNames = new ArrayList<>();
|
||||
|
||||
@Option(names = "--roleid", description = "Role's 'id' attribute")
|
||||
List<String> roleIds = new ArrayList<>();
|
||||
|
||||
@Override
|
||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
||||
protected void process() {
|
||||
if (uid != null && uusername != null) {
|
||||
throw new IllegalArgumentException("Incompatible options: --uid and --uusername are mutually exclusive");
|
||||
}
|
||||
|
||||
List<String> roleNames = new LinkedList<>();
|
||||
List<String> roleIds = new LinkedList<>();
|
||||
if ((gid != null && gname != null) || (gid != null && gpath != null) || (gname != null && gpath != null)) {
|
||||
throw new IllegalArgumentException(
|
||||
"Incompatible options: --gid, --gname and --gpath are mutually exclusive");
|
||||
}
|
||||
|
||||
try {
|
||||
if (printHelp()) {
|
||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
||||
if (roleNames.isEmpty() && roleIds.isEmpty()) {
|
||||
throw new IllegalArgumentException(
|
||||
"No role to remove specified. Use --rolename or --roleid to specify roles to remove");
|
||||
}
|
||||
|
||||
if (cid != null && cclientid != null) {
|
||||
throw new IllegalArgumentException("Incompatible options: --cid and --cclientid are mutually exclusive");
|
||||
}
|
||||
|
||||
if (rid != null && rname != null) {
|
||||
throw new IllegalArgumentException("Incompatible options: --rid and --rname are mutually exclusive");
|
||||
}
|
||||
|
||||
if (isUserSpecified() && isGroupSpecified()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Incompatible options: --uusername / --uid can't be used at the same time as --gname / --gid / --gpath");
|
||||
}
|
||||
|
||||
if (isUserSpecified() && isCompositeRoleSpecified()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Incompatible options: --uusername / --uid can't be used at the same time as --rname / --rid");
|
||||
}
|
||||
|
||||
if (isGroupSpecified() && isCompositeRoleSpecified()) {
|
||||
throw new IllegalArgumentException(
|
||||
"Incompatible options: --rname / --rid can't be used at the same time as --gname / --gid / --gpath");
|
||||
}
|
||||
|
||||
if (!isUserSpecified() && !isGroupSpecified() && !isCompositeRoleSpecified()) {
|
||||
throw new IllegalArgumentException(
|
||||
"No user nor group nor composite role specified. Use --uusername / --uid to specify user or --gname / --gid / --gpath to specify group or --rname / --rid to specify a composite role");
|
||||
}
|
||||
|
||||
ConfigData config = loadConfig();
|
||||
config = copyWithServerInfo(config);
|
||||
|
||||
setupTruststore(config);
|
||||
|
||||
String 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 = getTargetRealm(config);
|
||||
final String adminRoot = adminRestRoot != null ? adminRestRoot : composeAdminRoot(server);
|
||||
|
||||
if (isUserSpecified()) {
|
||||
if (uid == null) {
|
||||
uid = UserOperations.getIdFromUsername(adminRoot, realm, auth, uusername);
|
||||
}
|
||||
|
||||
processGlobalOptions();
|
||||
|
||||
Iterator<String> it = args.iterator();
|
||||
|
||||
while (it.hasNext()) {
|
||||
String option = it.next();
|
||||
switch (option) {
|
||||
case "--rolename": {
|
||||
optionRequiresValueCheck(it, option);
|
||||
roleNames.add(it.next());
|
||||
break;
|
||||
}
|
||||
case "--roleid": {
|
||||
optionRequiresValueCheck(it, option);
|
||||
roleIds.add(it.next());
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
throw new IllegalArgumentException("Invalid option: " + option);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (uid != null && uusername != null) {
|
||||
throw new IllegalArgumentException("Incompatible options: --uid and --uusername are mutually exclusive");
|
||||
}
|
||||
|
||||
if ((gid != null && gname != null) || (gid != null && gpath != null) || (gname != null && gpath != null)) {
|
||||
throw new IllegalArgumentException("Incompatible options: --gid, --gname and --gpath are mutually exclusive");
|
||||
}
|
||||
|
||||
if (roleNames.isEmpty() && roleIds.isEmpty()) {
|
||||
throw new IllegalArgumentException("No role to remove specified. Use --rolename or --roleid to specify roles to remove");
|
||||
}
|
||||
|
||||
if (cid != null && cclientid != null) {
|
||||
throw new IllegalArgumentException("Incompatible options: --cid and --cclientid are mutually exclusive");
|
||||
}
|
||||
|
||||
if (rid != null && rname != null) {
|
||||
throw new IllegalArgumentException("Incompatible options: --rid and --rname are mutually exclusive");
|
||||
}
|
||||
|
||||
if (isUserSpecified() && isGroupSpecified()) {
|
||||
throw new IllegalArgumentException("Incompatible options: --uusername / --uid can't be used at the same time as --gname / --gid / --gpath");
|
||||
}
|
||||
|
||||
if (isUserSpecified() && isCompositeRoleSpecified()) {
|
||||
throw new IllegalArgumentException("Incompatible options: --uusername / --uid can't be used at the same time as --rname / --rid");
|
||||
}
|
||||
|
||||
if (isGroupSpecified() && isCompositeRoleSpecified()) {
|
||||
throw new IllegalArgumentException("Incompatible options: --rname / --rid can't be used at the same time as --gname / --gid / --gpath");
|
||||
}
|
||||
|
||||
if (!isUserSpecified() && !isGroupSpecified() && !isCompositeRoleSpecified()) {
|
||||
throw new IllegalArgumentException("No user nor group nor composite role specified. Use --uusername / --uid to specify user or --gname / --gid / --gpath to specify group or --rname / --rid to specify a composite role");
|
||||
}
|
||||
|
||||
|
||||
ConfigData config = loadConfig();
|
||||
config = copyWithServerInfo(config);
|
||||
|
||||
setupTruststore(config, commandInvocation);
|
||||
|
||||
String 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 = getTargetRealm(config);
|
||||
final String adminRoot = adminRestRoot != null ? adminRestRoot : composeAdminRoot(server);
|
||||
|
||||
|
||||
if (isUserSpecified()) {
|
||||
if (uid == null) {
|
||||
uid = UserOperations.getIdFromUsername(adminRoot, realm, auth, uusername);
|
||||
}
|
||||
if (isClientSpecified()) {
|
||||
// remove client roles from a user
|
||||
if (cid == null) {
|
||||
cid = ClientOperations.getIdFromClientId(adminRoot, realm, auth, cclientid);
|
||||
}
|
||||
|
||||
List<ObjectNode> roles = RoleOperations.getClientRoles(adminRoot, realm, cid, auth);
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds, new LocalSearch(roles));
|
||||
|
||||
// now remove the roles
|
||||
UserOperations.removeClientRoles(adminRoot, realm, auth, uid, cid, new ArrayList<>(rolesToAdd));
|
||||
|
||||
} else {
|
||||
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds,
|
||||
new LocalSearch(RoleOperations.getRealmRolesAsNodes(adminRoot, realm, auth)));
|
||||
|
||||
// now remove the roles
|
||||
UserOperations.removeRealmRoles(adminRoot, realm, auth, uid, new ArrayList<>(rolesToAdd));
|
||||
if (isClientSpecified()) {
|
||||
// remove client roles from a user
|
||||
if (cid == null) {
|
||||
cid = ClientOperations.getIdFromClientId(adminRoot, realm, auth, cclientid);
|
||||
}
|
||||
|
||||
} else if (isGroupSpecified()) {
|
||||
if (gname != null) {
|
||||
gid = GroupOperations.getIdFromName(adminRoot, realm, auth, gname);
|
||||
} else if (gpath != null) {
|
||||
gid = GroupOperations.getIdFromPath(adminRoot, realm, auth, gpath);
|
||||
}
|
||||
if (isClientSpecified()) {
|
||||
// remove client roles from a group
|
||||
if (cid == null) {
|
||||
cid = ClientOperations.getIdFromClientId(adminRoot, realm, auth, cclientid);
|
||||
}
|
||||
List<ObjectNode> roles = RoleOperations.getClientRoles(adminRoot, realm, cid, auth);
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds, new LocalSearch(roles));
|
||||
|
||||
List<ObjectNode> roles = RoleOperations.getClientRoles(adminRoot, realm, cid, auth);
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds, new LocalSearch(roles));
|
||||
|
||||
// now remove the roles
|
||||
GroupOperations.removeClientRoles(adminRoot, realm, auth, gid, cid, new ArrayList<>(rolesToAdd));
|
||||
|
||||
} else {
|
||||
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds,
|
||||
new LocalSearch(RoleOperations.getRealmRolesAsNodes(adminRoot, realm, auth)));
|
||||
|
||||
// now remove the roles
|
||||
GroupOperations.removeRealmRoles(adminRoot, realm, auth, gid, new ArrayList<>(rolesToAdd));
|
||||
}
|
||||
|
||||
} else if (isCompositeRoleSpecified()) {
|
||||
if (rid == null) {
|
||||
rid = RoleOperations.getIdFromRoleName(adminRoot, realm, auth, rname);
|
||||
}
|
||||
if (isClientSpecified()) {
|
||||
// remove client roles from a role
|
||||
if (cid == null) {
|
||||
cid = ClientOperations.getIdFromClientId(adminRoot, realm, auth, cclientid);
|
||||
}
|
||||
|
||||
List<ObjectNode> roles = RoleOperations.getClientRoles(adminRoot, realm, cid, auth);
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds, new LocalSearch(roles));
|
||||
|
||||
// now remove the roles
|
||||
RoleOperations.removeClientRoles(adminRoot, realm, auth, rid, new ArrayList<>(rolesToAdd));
|
||||
|
||||
} else {
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds,
|
||||
new LocalSearch(RoleOperations.getRealmRolesAsNodes(adminRoot, realm, auth)));
|
||||
|
||||
// now remove the roles
|
||||
RoleOperations.removeRealmRoles(adminRoot, realm, auth, rid, new ArrayList<>(rolesToAdd));
|
||||
}
|
||||
// now remove the roles
|
||||
UserOperations.removeClientRoles(adminRoot, realm, auth, uid, cid, new ArrayList<>(rolesToAdd));
|
||||
|
||||
} else {
|
||||
throw new IllegalArgumentException("No user nor group, nor composite role specified. Use --uusername / --uid to specify user or --gname / --gid / --gpath to specify group or --rname / --rid to specify a composite role");
|
||||
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds,
|
||||
new LocalSearch(RoleOperations.getRealmRolesAsNodes(adminRoot, realm, auth)));
|
||||
|
||||
// now remove the roles
|
||||
UserOperations.removeRealmRoles(adminRoot, realm, auth, uid, new ArrayList<>(rolesToAdd));
|
||||
}
|
||||
|
||||
return CommandResult.SUCCESS;
|
||||
} else if (isGroupSpecified()) {
|
||||
if (gname != null) {
|
||||
gid = GroupOperations.getIdFromName(adminRoot, realm, auth, gname);
|
||||
} else if (gpath != null) {
|
||||
gid = GroupOperations.getIdFromPath(adminRoot, realm, auth, gpath);
|
||||
}
|
||||
if (isClientSpecified()) {
|
||||
// remove client roles from a group
|
||||
if (cid == null) {
|
||||
cid = ClientOperations.getIdFromClientId(adminRoot, realm, auth, cclientid);
|
||||
}
|
||||
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
|
||||
} finally {
|
||||
commandInvocation.stop();
|
||||
List<ObjectNode> roles = RoleOperations.getClientRoles(adminRoot, realm, cid, auth);
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds, new LocalSearch(roles));
|
||||
|
||||
// now remove the roles
|
||||
GroupOperations.removeClientRoles(adminRoot, realm, auth, gid, cid, new ArrayList<>(rolesToAdd));
|
||||
|
||||
} else {
|
||||
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds,
|
||||
new LocalSearch(RoleOperations.getRealmRolesAsNodes(adminRoot, realm, auth)));
|
||||
|
||||
// now remove the roles
|
||||
GroupOperations.removeRealmRoles(adminRoot, realm, auth, gid, new ArrayList<>(rolesToAdd));
|
||||
}
|
||||
|
||||
} else if (isCompositeRoleSpecified()) {
|
||||
if (rid == null) {
|
||||
rid = RoleOperations.getIdFromRoleName(adminRoot, realm, auth, rname);
|
||||
}
|
||||
if (isClientSpecified()) {
|
||||
// remove client roles from a role
|
||||
if (cid == null) {
|
||||
cid = ClientOperations.getIdFromClientId(adminRoot, realm, auth, cclientid);
|
||||
}
|
||||
|
||||
List<ObjectNode> roles = RoleOperations.getClientRoles(adminRoot, realm, cid, auth);
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds, new LocalSearch(roles));
|
||||
|
||||
// now remove the roles
|
||||
RoleOperations.removeClientRoles(adminRoot, realm, auth, rid, new ArrayList<>(rolesToAdd));
|
||||
|
||||
} else {
|
||||
Set<ObjectNode> rolesToAdd = getRoleRepresentations(roleNames, roleIds,
|
||||
new LocalSearch(RoleOperations.getRealmRolesAsNodes(adminRoot, realm, auth)));
|
||||
|
||||
// now remove the roles
|
||||
RoleOperations.removeRealmRoles(adminRoot, realm, auth, rid, new ArrayList<>(rolesToAdd));
|
||||
}
|
||||
|
||||
} else {
|
||||
throw new IllegalArgumentException(
|
||||
"No user nor group, nor composite role specified. Use --uusername / --uid to specify user or --gname / --gid / --gpath to specify group or --rname / --rid to specify a composite role");
|
||||
}
|
||||
}
|
||||
|
||||
private Set<ObjectNode> getRoleRepresentations(List<String> roleNames, List<String> roleIds, LocalSearch roleSearch) {
|
||||
private Set<ObjectNode> getRoleRepresentations(List<String> roleNames, List<String> roleIds,
|
||||
LocalSearch roleSearch) {
|
||||
Set<ObjectNode> rolesToAdd = new HashSet<>();
|
||||
|
||||
// now we process roles
|
||||
@@ -280,12 +248,6 @@ public class RemoveRolesCmd extends AbstractAuthOptionsCmd {
|
||||
return rolesToAdd;
|
||||
}
|
||||
|
||||
private void optionRequiresValueCheck(Iterator<String> it, String option) {
|
||||
if (!it.hasNext()) {
|
||||
throw new IllegalArgumentException("Option " + option + " requires a value");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isClientSpecified() {
|
||||
return cid != null || cclientid != null;
|
||||
}
|
||||
@@ -304,13 +266,11 @@ public class RemoveRolesCmd extends AbstractAuthOptionsCmd {
|
||||
|
||||
@Override
|
||||
protected boolean nothingToDo() {
|
||||
return noOptions() && uusername == null && uid == null && cclientid == null && (args == null || args.size() == 0);
|
||||
}
|
||||
|
||||
protected String suggestHelp() {
|
||||
return EOL + "Try '" + CMD + " help remove-roles' for more information";
|
||||
return super.nothingToDo() && uusername == null && uid == null && cclientid == null
|
||||
&& roleIds.isEmpty() && roleNames.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String help() {
|
||||
return usage();
|
||||
}
|
||||
|
||||
@@ -16,16 +16,14 @@
|
||||
*/
|
||||
package org.keycloak.client.admin.cli.commands;
|
||||
|
||||
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.admin.cli.config.ConfigData;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Option;
|
||||
|
||||
import static org.keycloak.client.admin.cli.operations.UserOperations.getIdFromUsername;
|
||||
import static org.keycloak.client.admin.cli.operations.UserOperations.resetUserPassword;
|
||||
import static org.keycloak.client.admin.cli.util.AuthUtil.ensureToken;
|
||||
@@ -34,52 +32,28 @@ import static org.keycloak.client.admin.cli.util.ConfigUtil.credentialsAvailable
|
||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.loadConfig;
|
||||
import static org.keycloak.client.admin.cli.util.IoUtil.readSecret;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.EOL;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
@CommandDefinition(name = "set-password", description = "[ARGUMENTS]")
|
||||
@Command(name = "set-password", description = "[ARGUMENTS]")
|
||||
public class SetPasswordCmd extends AbstractAuthOptionsCmd {
|
||||
|
||||
@Option(name = "username", description = "Username")
|
||||
@Option(names = "--username", description = "Username")
|
||||
String username;
|
||||
|
||||
@Option(name = "userid", description = "User ID")
|
||||
@Option(names = "--userid", description = "User ID")
|
||||
String userid;
|
||||
|
||||
@Option(shortName = 'p', name = "new-password", description = "New password")
|
||||
@Option(names = {"-p", "--new-password"}, description = "New password")
|
||||
String pass;
|
||||
|
||||
@Option(shortName = 't', name = "temporary", description = "is password temporary", hasValue = false)
|
||||
@Option(names = {"-t", "--temporary"}, description = "is password temporary")
|
||||
boolean temporary;
|
||||
|
||||
|
||||
@Override
|
||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
||||
try {
|
||||
if (printHelp()) {
|
||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
||||
}
|
||||
|
||||
processGlobalOptions();
|
||||
|
||||
return process(commandInvocation);
|
||||
} catch (IllegalArgumentException e) {
|
||||
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
|
||||
} finally {
|
||||
commandInvocation.stop();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public CommandResult process(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
||||
|
||||
if (args != null && args.size() > 0) {
|
||||
throw new IllegalArgumentException("Invalid option: " + args.get(0));
|
||||
}
|
||||
|
||||
protected void process() {
|
||||
if (userid == null && username == null) {
|
||||
throw new IllegalArgumentException("No user specified. Use --username or --userid to specify user");
|
||||
}
|
||||
@@ -89,17 +63,17 @@ public class SetPasswordCmd extends AbstractAuthOptionsCmd {
|
||||
}
|
||||
|
||||
if (pass == null) {
|
||||
pass = readSecret("Enter password: ", commandInvocation);
|
||||
pass = readSecret("Enter password: ");
|
||||
}
|
||||
|
||||
ConfigData config = loadConfig();
|
||||
config = copyWithServerInfo(config);
|
||||
|
||||
setupTruststore(config, commandInvocation);
|
||||
setupTruststore(config);
|
||||
|
||||
String auth = null;
|
||||
|
||||
config = ensureAuthInfo(config, commandInvocation);
|
||||
config = ensureAuthInfo(config);
|
||||
config = copyWithServerInfo(config);
|
||||
if (credentialsAvailable(config)) {
|
||||
auth = ensureToken(config);
|
||||
@@ -117,20 +91,14 @@ public class SetPasswordCmd extends AbstractAuthOptionsCmd {
|
||||
}
|
||||
|
||||
resetUserPassword(adminRoot, realm, auth, userid, pass, temporary);
|
||||
|
||||
return CommandResult.SUCCESS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean nothingToDo() {
|
||||
return noOptions() && username == null && userid == null && pass == null;
|
||||
return super.nothingToDo() && username == null && userid == null && pass == null;
|
||||
}
|
||||
|
||||
protected String suggestHelp() {
|
||||
return EOL + "Try '" + CMD + " help set-password' for more information";
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected String help() {
|
||||
return usage();
|
||||
}
|
||||
|
||||
@@ -17,77 +17,68 @@
|
||||
|
||||
package org.keycloak.client.admin.cli.commands;
|
||||
|
||||
import org.jboss.aesh.cl.CommandDefinition;
|
||||
import org.jboss.aesh.cl.Option;
|
||||
|
||||
import java.io.PrintWriter;
|
||||
import java.io.StringWriter;
|
||||
|
||||
import picocli.CommandLine.Command;
|
||||
import picocli.CommandLine.Option;
|
||||
|
||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.EOL;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.OS_ARCH;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
*/
|
||||
@CommandDefinition(name = "update", description = "CLIENT_ID [ARGUMENTS]")
|
||||
@Command(name = "update", description = "CLIENT_ID [ARGUMENTS]")
|
||||
public class UpdateCmd extends AbstractRequestCmd {
|
||||
|
||||
@Option(shortName = 'f', name = "file", description = "Read object from file or standard input if FILENAME is set to '-'")
|
||||
String file;
|
||||
public UpdateCmd() {
|
||||
this.httpVerb = "put";
|
||||
}
|
||||
|
||||
@Option(shortName = 'b', name = "body", description = "JSON object to be sent as-is or used as a template")
|
||||
String body;
|
||||
@Option(names = {"-f", "--file"}, description = "Read object from file or standard input if FILENAME is set to '-'")
|
||||
public void setFile(String file) {
|
||||
this.file = file;
|
||||
}
|
||||
|
||||
@Option(shortName = 'F', name = "fields", description = "A pattern specifying which attributes of JSON response body to actually display as result - causes mismatch with Content-Length header")
|
||||
String fields;
|
||||
@Option(names = {"-b", "--body"}, description = "JSON object to be sent as-is or used as a template")
|
||||
public void setBody(String body) {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
@Option(shortName = 'H', name = "print-headers", description = "Print response headers", hasValue = false)
|
||||
boolean printHeaders;
|
||||
@Option(names = {"-F", "--fields"}, description = "A pattern specifying which attributes of JSON response body to actually display as result - causes mismatch with Content-Length header")
|
||||
public void setFields(String fields) {
|
||||
this.fields = fields;
|
||||
}
|
||||
|
||||
@Option(shortName = 'm', name = "merge", description = "Merge new values with existing configuration on the server - for when the default is not to merge (i.e. if --file is used)", hasValue = false)
|
||||
boolean mergeMode;
|
||||
@Option(names = {"-H", "--print-headers"}, description = "Print response headers")
|
||||
public void setPrintHeaders(boolean printHeaders) {
|
||||
this.printHeaders = printHeaders;
|
||||
}
|
||||
|
||||
@Option(shortName = 'n', name = "no-merge", description = "Don't merge new values with existing configuration on the server - for when the default is to merge (i.e. is --set is used while --file is not used)", hasValue = false)
|
||||
boolean noMerge;
|
||||
@Option(names = {"-m", "--merge"}, description = "Merge new values with existing configuration on the server - for when the default is not to merge (i.e. if --file is used)")
|
||||
public void setMergeMode(boolean mergeMode) {
|
||||
this.mergeMode = mergeMode;
|
||||
}
|
||||
|
||||
@Option(shortName = 'o', name = "output", description = "After update output the new client configuration", hasValue = false)
|
||||
boolean outputResult;
|
||||
@Option(names = {"-n", "--no-merge"}, description = "Don't merge new values with existing configuration on the server - for when the default is to merge (i.e. is --set is used while --file is not used)")
|
||||
public void setNoMerge(boolean noMerge) {
|
||||
this.noMerge = noMerge;
|
||||
}
|
||||
|
||||
@Option(shortName = 'c', name = "compressed", description = "Don't pretty print the output", hasValue = false)
|
||||
boolean compressed;
|
||||
@Option(names = {"-o", "--output"}, description = "After update output the new client configuration")
|
||||
public void setOutputResult(boolean outputResult) {
|
||||
this.outputResult = outputResult;
|
||||
}
|
||||
|
||||
//@GroupOption(shortName = 's', name = "set", description = "Set specific attribute to a specified value", hasValue = true)
|
||||
//private List<String> attributes = new ArrayList<>();
|
||||
|
||||
|
||||
@Override
|
||||
void initOptions() {
|
||||
// set options on parent
|
||||
super.file = file;
|
||||
super.body = body;
|
||||
super.fields = fields;
|
||||
super.printHeaders = printHeaders;
|
||||
super.returnId = false;
|
||||
super.outputResult = true;
|
||||
super.compressed = compressed;
|
||||
super.mergeMode = mergeMode;
|
||||
super.noMerge = noMerge;
|
||||
super.outputResult = outputResult;
|
||||
super.httpVerb = "put";
|
||||
@Option(names = {"-c", "--compressed"}, description = "Don't pretty print the output")
|
||||
public void setCompressed(boolean compressed) {
|
||||
this.compressed = compressed;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean nothingToDo() {
|
||||
return noOptions() && file == null && body == null && (args == null || args.size() == 0);
|
||||
}
|
||||
|
||||
protected String suggestHelp() {
|
||||
return EOL + "Try '" + CMD + " help update' for more information";
|
||||
}
|
||||
|
||||
protected String help() {
|
||||
return usage();
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ public class ConfigUtil {
|
||||
|
||||
public static void checkServerInfo(ConfigData config) {
|
||||
if (config.getServerUrl() == null) {
|
||||
throw new RuntimeException("No server specified. Use --server, or '" + OsUtil.CMD + " config credentials or connection'.");
|
||||
throw new RuntimeException("No server specified. Use --server, or '" + OsUtil.CMD + " config credentials'.");
|
||||
}
|
||||
if (config.getRealm() == null && config.getExternalToken() == null) {
|
||||
throw new RuntimeException("No realm or token specified. Use --realm, --token, or '" + OsUtil.CMD + " config credentials'.");
|
||||
|
||||
@@ -16,14 +16,7 @@
|
||||
*/
|
||||
package org.keycloak.client.admin.cli.util;
|
||||
|
||||
import org.jboss.aesh.console.AeshConsoleBufferBuilder;
|
||||
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.admin.cli.aesh.Globals;
|
||||
|
||||
import java.io.Console;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
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.isDirectory;
|
||||
import static java.nio.file.Files.isRegularFile;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.OS_ARCH;
|
||||
|
||||
/**
|
||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||
@@ -81,43 +73,16 @@ public class IoUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static String readSecret(String prompt, CommandInvocation invocation) {
|
||||
|
||||
// TODO Windows hack - masking not working on Windows
|
||||
char maskChar = OS_ARCH.isWindows() ? 0 : '*';
|
||||
ConsoleBuffer consoleBuffer = new AeshConsoleBufferBuilder()
|
||||
.shell(invocation.getShell())
|
||||
.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);
|
||||
public static String readSecret(String prompt) {
|
||||
Console cons = System.console();
|
||||
if (cons == null) {
|
||||
throw new RuntimeException("Console is not active, but a password is required");
|
||||
}
|
||||
/*
|
||||
if (!Globals.stdin.isStdinAvailable()) {
|
||||
try {
|
||||
return readLine(new InputStreamReader(System.in));
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("Standard input not available");
|
||||
}
|
||||
char[] passwd;
|
||||
if ((passwd = cons.readPassword("%s", prompt)) != null) {
|
||||
return new String(passwd);
|
||||
}
|
||||
*/
|
||||
// Windows hack - get rid of any \n
|
||||
result = result.replaceAll("\\n", "");
|
||||
return result;
|
||||
throw new RuntimeException("No password provided");
|
||||
}
|
||||
|
||||
public static String readFully(InputStream is) {
|
||||
|
||||
@@ -38,7 +38,7 @@ public class ParseUtil {
|
||||
// we expect = as a separator
|
||||
int pos = keyval.indexOf("=");
|
||||
if (pos <= 0) {
|
||||
throw new RuntimeException("Invalid key=value parameter: [" + keyval + "]");
|
||||
throw new IllegalArgumentException("Invalid key=value parameter: [" + keyval + "]");
|
||||
}
|
||||
|
||||
String [] parsed = new String[2];
|
||||
|
||||
Reference in New Issue
Block a user