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:
parent
a74d833f22
commit
e9ad9d0564
34 changed files with 975 additions and 1614 deletions
|
@ -61,6 +61,10 @@ The module `org.keycloak:keycloak-model-legacy` module was deprecated in a previ
|
|||
|
||||
The old behavior to preload offline sessions at startup is now removed after it has been deprecated in the previous release.
|
||||
|
||||
= kcadm Changes
|
||||
|
||||
How kcadm parses and handles options and parameters has changed. Error messages from usage errors, the wrong option or parameter, may be slightly different than previous versions. Also usage errors will have an exit code of 2 instead of 1.
|
||||
|
||||
= Removing custom user attribute indexes
|
||||
|
||||
When searching for users by user attribute, Keycloak no longer searches for user attribute names forcing lower case comparisons. This means Keycloak's native index on the user attribute table will now be used when searching. If you have created your own index based on `lower(name)`to speed up searches, you can now remove it.
|
||||
|
|
|
@ -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>
|
||||
|
@ -48,52 +41,21 @@ public class KcAdmMain {
|
|||
|
||||
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];
|
||||
|
|
|
@ -43,20 +43,12 @@ public abstract class AbstractCliTest extends AbstractKeycloakTest {
|
|||
public void assertExitCodeAndStreamSizes(AbstractExec exe, int exitCode, int stdOutLineCount, int stdErrLineCount) {
|
||||
Assert.assertEquals("exitCode == " + exitCode, exitCode, exe.exitCode());
|
||||
if (stdOutLineCount != -1) {
|
||||
try {
|
||||
assertLineCount("stdout output", exe.stdoutLines(), stdOutLineCount);
|
||||
} catch (Throwable e) {
|
||||
throw new AssertionError("STDOUT: " + exe.stdoutString(), e);
|
||||
}
|
||||
assertLineCount("STDOUT: " + exe.stdoutString(), exe.stdoutLines(), stdOutLineCount);
|
||||
}
|
||||
// There is additional logging in case that BC FIPS libraries are used, so the count of logged lines don't match with the case with plain BC used
|
||||
// Hence we test count of lines just with FIPS disabled
|
||||
if (stdErrLineCount != -1 && isFipsDisabled()) {
|
||||
try {
|
||||
assertLineCount("stderr output", exe.stderrLines(), stdErrLineCount);
|
||||
} catch (Throwable e) {
|
||||
throw new AssertionError("STDERR: " + exe.stderrString(), e);
|
||||
}
|
||||
assertLineCount("STDERR: " + exe.stderrString(), exe.stderrLines(), stdErrLineCount);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,9 +29,9 @@ public class KcAdmSessionTest extends AbstractAdmCliTest {
|
|||
@Test
|
||||
public void test() throws IOException {
|
||||
|
||||
FileConfigHandler handler = initCustomConfigFile();
|
||||
initCustomConfigFile();
|
||||
|
||||
try (TempFileResource configFile = new TempFileResource(handler.getConfigFile())) {
|
||||
try (TempFileResource configFile = new TempFileResource(FileConfigHandler.getConfigFile())) {
|
||||
|
||||
// login as admin
|
||||
loginAsUser(configFile.getFile(), serverUrl, "master", "admin", "admin");
|
||||
|
@ -196,8 +196,8 @@ public class KcAdmSessionTest extends AbstractAdmCliTest {
|
|||
@Test
|
||||
public void testCompositeRoleCreationWithHigherVolumeOfRoles() throws Exception {
|
||||
|
||||
FileConfigHandler handler = initCustomConfigFile();
|
||||
try (TempFileResource configFile = new TempFileResource(handler.getConfigFile())) {
|
||||
initCustomConfigFile();
|
||||
try (TempFileResource configFile = new TempFileResource(FileConfigHandler.getConfigFile())) {
|
||||
|
||||
// login as admin
|
||||
loginAsUser(configFile.getFile(), serverUrl, "master", "admin", "admin");
|
||||
|
|
|
@ -34,8 +34,8 @@ public class KcAdmTest extends AbstractAdmCliTest {
|
|||
*/
|
||||
KcAdmExec exe = execute("nonexistent");
|
||||
|
||||
assertExitCodeAndStreamSizes(exe, 1, 0, 1);
|
||||
Assert.assertEquals("stderr first line", "Unknown command: nonexistent", exe.stderrLines().get(0));
|
||||
assertExitCodeAndStreamSizes(exe, 2, 0, 3);
|
||||
Assert.assertEquals("stderr first line", "Unmatched argument at index 0: 'nonexistent'", exe.stderrLines().get(0));
|
||||
}
|
||||
|
||||
|
||||
|
@ -46,7 +46,7 @@ public class KcAdmTest extends AbstractAdmCliTest {
|
|||
*/
|
||||
KcAdmExec exe = KcAdmExec.execute("");
|
||||
|
||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
||||
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||
|
||||
List<String> lines = exe.stdoutLines();
|
||||
Assert.assertTrue("stdout output not empty", lines.size() > 0);
|
||||
|
@ -59,41 +59,41 @@ public class KcAdmTest extends AbstractAdmCliTest {
|
|||
* Test commands without arguments
|
||||
*/
|
||||
exe = KcAdmExec.execute("config");
|
||||
assertExitCodeAndStreamSizes(exe, 1, 0, 1);
|
||||
assertExitCodeAndStreamSizes(exe, 2, 8, 0);
|
||||
Assert.assertEquals("error message",
|
||||
"Sub-command required by '" + CMD + " config' - one of: 'credentials', 'truststore'",
|
||||
exe.stderrLines().get(0));
|
||||
"Usage: kcadm.sh config SUB_COMMAND [ARGUMENTS]",
|
||||
exe.stdoutLines().get(0));
|
||||
|
||||
exe = KcAdmExec.execute("config credentials");
|
||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
||||
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||
Assert.assertEquals("help message", "Usage: " + CMD + " config credentials --server SERVER_URL --realm REALM --user USER [--password PASSWORD] [ARGUMENTS]", exe.stdoutLines().get(0));
|
||||
|
||||
exe = KcAdmExec.execute("config truststore");
|
||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
||||
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||
Assert.assertEquals("help message", "Usage: " + CMD + " config truststore [TRUSTSTORE | --delete] [--trustpass PASSWORD] [ARGUMENTS]", exe.stdoutLines().get(0));
|
||||
|
||||
exe = KcAdmExec.execute("create");
|
||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
||||
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||
Assert.assertEquals("help message", "Usage: " + CMD + " create ENDPOINT_URI [ARGUMENTS]", exe.stdoutLines().get(0));
|
||||
//Assert.assertEquals("error message", "No file nor attribute values specified", exe.stderrLines().get(0));
|
||||
|
||||
exe = KcAdmExec.execute("get");
|
||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
||||
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||
Assert.assertEquals("help message", "Usage: " + CMD + " get ENDPOINT_URI [ARGUMENTS]", exe.stdoutLines().get(0));
|
||||
//Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0));
|
||||
|
||||
exe = KcAdmExec.execute("update");
|
||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
||||
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||
Assert.assertEquals("help message", "Usage: " + CMD + " update ENDPOINT_URI [ARGUMENTS]", exe.stdoutLines().get(0));
|
||||
//Assert.assertEquals("error message", "No file nor attribute values specified", exe.stderrLines().get(0));
|
||||
|
||||
exe = KcAdmExec.execute("delete");
|
||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
||||
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||
Assert.assertEquals("help message", "Usage: " + CMD + " delete ENDPOINT_URI [ARGUMENTS]", exe.stdoutLines().get(0));
|
||||
//Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0));
|
||||
|
@ -112,18 +112,18 @@ public class KcAdmTest extends AbstractAdmCliTest {
|
|||
//Assert.assertEquals("help message", "Usage: " + CMD + " get-roles [--cclientid CLIENT_ID | --cid ID] [ARGUMENTS]", exe.stdoutLines().get(0));
|
||||
|
||||
exe = KcAdmExec.execute("add-roles");
|
||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
||||
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||
Assert.assertEquals("help message", "Usage: " + CMD + " add-roles (--uusername USERNAME | --uid ID) [--cclientid CLIENT_ID | --cid ID] (--rolename NAME | --roleid ID)+ [ARGUMENTS]", exe.stdoutLines().get(0));
|
||||
//Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0));
|
||||
|
||||
exe = KcAdmExec.execute("remove-roles");
|
||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
||||
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||
Assert.assertEquals("help message", "Usage: " + CMD + " remove-roles (--uusername USERNAME | --uid ID) [--cclientid CLIENT_ID | --cid ID] (--rolename NAME | --roleid ID)+ [ARGUMENTS]", exe.stdoutLines().get(0));
|
||||
|
||||
exe = KcAdmExec.execute("set-password");
|
||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
||||
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||
Assert.assertEquals("help message", "Usage: " + CMD + " set-password (--username USERNAME | --userid ID) [--new-password PASSWORD] [ARGUMENTS]", exe.stdoutLines().get(0));
|
||||
//Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0));
|
||||
|
@ -202,8 +202,8 @@ public class KcAdmTest extends AbstractAdmCliTest {
|
|||
*/
|
||||
KcAdmExec exe = KcAdmExec.execute("--nonexistent");
|
||||
|
||||
assertExitCodeAndStreamSizes(exe, 1, 0, 1);
|
||||
Assert.assertEquals("stderr first line", "Unknown command: --nonexistent", exe.stderrLines().get(0));
|
||||
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||
Assert.assertEquals("stderr first line", "Unknown option: '--nonexistent'", exe.stderrLines().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -214,25 +214,23 @@ public class KcAdmTest extends AbstractAdmCliTest {
|
|||
|
||||
KcAdmExec exe = KcAdmExec.execute("get users --nonexistent");
|
||||
|
||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
||||
Assert.assertEquals("stderr first line", "Invalid option: --nonexistent", exe.stderrLines().get(0));
|
||||
Assert.assertEquals("try help", "Try '" + CMD + " help get' for more information", exe.stderrLines().get(1));
|
||||
assertExitCodeAndStreamSizes(exe, 2, 0, 3);
|
||||
Assert.assertEquals("stderr first line", "Unknown option: '--nonexistent'", exe.stderrLines().get(0));
|
||||
Assert.assertEquals("try help", "Try '" + CMD + " get --help' for more information on the available options.", exe.stderrLines().get(2));
|
||||
|
||||
// set-password doesn't use @Arguments injection thus unsupported options are handled by Aesh
|
||||
exe = KcAdmExec.execute("set-password --nonexistent");
|
||||
|
||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
||||
Assert.assertEquals("stderr first line", "Invalid option: --nonexistent", exe.stderrLines().get(0));
|
||||
Assert.assertEquals("try help", "Try '" + CMD + " help set-password' for more information", exe.stderrLines().get(1));
|
||||
assertExitCodeAndStreamSizes(exe, 2, 0, 3);
|
||||
Assert.assertEquals("stderr first line", "Unknown option: '--nonexistent'", exe.stderrLines().get(0));
|
||||
Assert.assertEquals("try help", "Try '" + CMD + " set-password --help' for more information on the available options.", exe.stderrLines().get(2));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testBadOverlappingOption() {
|
||||
KcAdmExec exe = KcAdmExec.execute("config credentials --server http://localhost:8080 --realm master --username admin --password admin");
|
||||
|
||||
assertExitCodeAndStreamSizes(exe, 1, 0, 1);
|
||||
Assert.assertEquals("stderr first line", "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.", exe.stderrLines().get(0));
|
||||
assertExitCodeAndStreamSizes(exe, 2, 0, 3);
|
||||
Assert.assertEquals("stderr first line", "Unknown options: '--username', 'admin'", exe.stderrLines().get(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -252,9 +250,9 @@ public class KcAdmTest extends AbstractAdmCliTest {
|
|||
*/
|
||||
KcAdmExec exe = KcAdmExec.execute("config credentials --realm master --user admin --password admin");
|
||||
|
||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
||||
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||
Assert.assertEquals("stderr first line", "Required option not specified: --server", exe.stderrLines().get(0));
|
||||
Assert.assertEquals("try help", "Try '" + CMD + " help config credentials' for more information", exe.stderrLines().get(1));
|
||||
Assert.assertEquals("try help", "Try '" + CMD + " config credentials --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -264,9 +262,9 @@ public class KcAdmTest extends AbstractAdmCliTest {
|
|||
*/
|
||||
KcAdmExec exe = KcAdmExec.execute("config credentials --server " + serverUrl + " --user admin --password admin");
|
||||
|
||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
||||
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||
Assert.assertEquals("stderr first line", "Required option not specified: --realm", exe.stderrLines().get(0));
|
||||
Assert.assertEquals("try help", "Try '" + CMD + " help config credentials' for more information", exe.stderrLines().get(1));
|
||||
Assert.assertEquals("try help", "Try '" + CMD + " config credentials --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -276,9 +274,9 @@ public class KcAdmTest extends AbstractAdmCliTest {
|
|||
*/
|
||||
KcAdmExec exe = KcAdmExec.execute("config credentials --no-config --server " + serverUrl + " --realm master --user admin --password admin");
|
||||
|
||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
||||
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||
Assert.assertEquals("stderr first line", "Unsupported option: --no-config", exe.stderrLines().get(0));
|
||||
Assert.assertEquals("try help", "Try '" + CMD + " help config credentials' for more information", exe.stderrLines().get(1));
|
||||
Assert.assertEquals("try help", "Try '" + CMD + " config credentials --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -396,7 +394,7 @@ public class KcAdmTest extends AbstractAdmCliTest {
|
|||
*/
|
||||
FileConfigHandler handler = initCustomConfigFile();
|
||||
|
||||
File configFile = new File(handler.getConfigFile());
|
||||
File configFile = new File(FileConfigHandler.getConfigFile());
|
||||
try {
|
||||
KcAdmExec exe = KcAdmExec.execute("config credentials --server " + serverUrl + " --realm master" +
|
||||
" --user admin --password admin --config '" + configFile.getName() + "'");
|
||||
|
@ -434,7 +432,7 @@ public class KcAdmTest extends AbstractAdmCliTest {
|
|||
// prepare for loading a config file
|
||||
FileConfigHandler handler = initCustomConfigFile();
|
||||
|
||||
try (TempFileResource configFile = new TempFileResource(handler.getConfigFile())) {
|
||||
try (TempFileResource configFile = new TempFileResource(FileConfigHandler.getConfigFile())) {
|
||||
|
||||
KcAdmExec exe = KcAdmExec.execute("config credentials --server " + serverUrl +
|
||||
" --realm master --user admin --password admin --config '" + configFile.getName() + "'");
|
||||
|
|
|
@ -6,6 +6,7 @@ import org.keycloak.client.admin.cli.config.ConfigData;
|
|||
import org.keycloak.client.admin.cli.config.FileConfigHandler;
|
||||
import org.keycloak.client.admin.cli.util.OsUtil;
|
||||
import org.keycloak.testsuite.cli.KcAdmExec;
|
||||
import org.keycloak.testsuite.util.OAuthClient;
|
||||
import org.keycloak.testsuite.util.TempFileResource;
|
||||
|
||||
import java.io.File;
|
||||
|
@ -14,7 +15,6 @@ import java.io.IOException;
|
|||
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_PATH;
|
||||
import static org.keycloak.client.admin.cli.util.OsUtil.EOL;
|
||||
import static org.keycloak.testsuite.util.ServerURLs.AUTH_SERVER_SSL_REQUIRED;
|
||||
import static org.keycloak.testsuite.cli.KcAdmExec.CMD;
|
||||
import static org.keycloak.testsuite.cli.KcAdmExec.execute;
|
||||
|
||||
/**
|
||||
|
@ -29,9 +29,9 @@ public class KcAdmTruststoreTest extends AbstractAdmCliTest {
|
|||
|
||||
KcAdmExec exe = execute("config truststore --no-config '" + truststore.getAbsolutePath() + "'");
|
||||
|
||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
||||
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||
Assert.assertEquals("stderr first line", "Unsupported option: --no-config", exe.stderrLines().get(0));
|
||||
Assert.assertEquals("try help", "Try '" + OsUtil.CMD + " help config truststore' for more information", exe.stderrLines().get(1));
|
||||
Assert.assertEquals("try help", "Try '" + OsUtil.CMD + " config truststore --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||
|
||||
// only run this test if ssl protected keycloak server is available
|
||||
if (!AUTH_SERVER_SSL_REQUIRED) {
|
||||
|
@ -39,9 +39,9 @@ public class KcAdmTruststoreTest extends AbstractAdmCliTest {
|
|||
return;
|
||||
}
|
||||
|
||||
FileConfigHandler handler = initCustomConfigFile();
|
||||
initCustomConfigFile();
|
||||
|
||||
try (TempFileResource configFile = new TempFileResource(handler.getConfigFile())) {
|
||||
try (TempFileResource configFile = new TempFileResource(FileConfigHandler.getConfigFile())) {
|
||||
|
||||
if (runIntermittentlyFailingTests()) {
|
||||
// configure truststore
|
||||
|
@ -52,7 +52,7 @@ public class KcAdmTruststoreTest extends AbstractAdmCliTest {
|
|||
|
||||
// perform authentication against server - asks for password, then for truststore password
|
||||
exe = KcAdmExec.newBuilder()
|
||||
.argsLine("config credentials --server " + oauth.AUTH_SERVER_ROOT + " --realm test --user user1" +
|
||||
.argsLine("config credentials --server " + OAuthClient.AUTH_SERVER_ROOT + " --realm test --user user1" +
|
||||
" --config '" + configFile.getName() + "'")
|
||||
.executeAsync();
|
||||
|
||||
|
@ -72,7 +72,7 @@ public class KcAdmTruststoreTest extends AbstractAdmCliTest {
|
|||
|
||||
// perform authentication against server - asks for password, then for truststore password
|
||||
exe = KcAdmExec.newBuilder()
|
||||
.argsLine("config credentials --server " + oauth.AUTH_SERVER_ROOT + " --realm test --user user1" +
|
||||
.argsLine("config credentials --server " + OAuthClient.AUTH_SERVER_ROOT + " --realm test --user user1" +
|
||||
" --config '" + configFile.getName() + "'")
|
||||
.executeAsync();
|
||||
|
||||
|
@ -99,17 +99,17 @@ public class KcAdmTruststoreTest extends AbstractAdmCliTest {
|
|||
assertExitCodeAndStreamSizes(exe, 0, 0, 0);
|
||||
|
||||
exe = execute("config truststore --delete '" + truststore.getAbsolutePath() + "'");
|
||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
||||
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||
Assert.assertEquals("incompatible", "Option --delete is mutually exclusive with specifying a TRUSTSTORE", exe.stderrLines().get(0));
|
||||
Assert.assertEquals("try help", "Try '" + CMD + " help config truststore' for more information", exe.stderrLines().get(1));
|
||||
Assert.assertEquals("try help", "Try '" + OsUtil.CMD + " config truststore --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||
|
||||
exe = execute("config truststore --delete --trustpass secret");
|
||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
||||
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||
Assert.assertEquals("no truststore error", "Options --trustpass and --delete are mutually exclusive", exe.stderrLines().get(0));
|
||||
Assert.assertEquals("try help", "Try '" + CMD + " help config truststore' for more information", exe.stderrLines().get(1));
|
||||
Assert.assertEquals("try help", "Try '" + OsUtil.CMD + " config truststore --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||
|
||||
FileConfigHandler cfghandler = new FileConfigHandler();
|
||||
cfghandler.setConfigFile(DEFAULT_CONFIG_FILE_PATH);
|
||||
FileConfigHandler.setConfigFile(DEFAULT_CONFIG_FILE_PATH);
|
||||
ConfigData config = cfghandler.loadConfig();
|
||||
Assert.assertNull("truststore null", config.getTruststore());
|
||||
Assert.assertNull("trustpass null", config.getTrustpass());
|
||||
|
|
|
@ -50,8 +50,8 @@ public class KcAdmUpdateTest extends AbstractAdmCliTest {
|
|||
.build();
|
||||
|
||||
try (Closeable ipc = new IdentityProviderCreator(realmResource, identityProvider)) {
|
||||
FileConfigHandler handler = initCustomConfigFile();
|
||||
try (TempFileResource configFile = new TempFileResource(handler.getConfigFile())) {
|
||||
initCustomConfigFile();
|
||||
try (TempFileResource configFile = new TempFileResource(FileConfigHandler.getConfigFile())) {
|
||||
loginAsUser(configFile.getFile(), serverUrl, realm, "user1", "userpass");
|
||||
|
||||
KcAdmExec exe = execute("get identity-provider/instances/idpAlias -r " + realm + " --config " + configFile.getFile());
|
||||
|
@ -69,9 +69,9 @@ public class KcAdmUpdateTest extends AbstractAdmCliTest {
|
|||
@Test
|
||||
public void testUpdateThoroughly() throws IOException {
|
||||
|
||||
FileConfigHandler handler = initCustomConfigFile();
|
||||
initCustomConfigFile();
|
||||
|
||||
try (TempFileResource configFile = new TempFileResource(handler.getConfigFile())) {
|
||||
try (TempFileResource configFile = new TempFileResource(FileConfigHandler.getConfigFile())) {
|
||||
|
||||
final String realm = "test";
|
||||
|
||||
|
@ -136,9 +136,9 @@ public class KcAdmUpdateTest extends AbstractAdmCliTest {
|
|||
// check that using an invalid attribute key is not ignored
|
||||
exe = execute("update clients/" + client.getId() + " --nonexisting --config '" + configFile.getName() + "'");
|
||||
|
||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
||||
Assert.assertEquals("error message", "Invalid option: --nonexisting", exe.stderrLines().get(0));
|
||||
Assert.assertEquals("try help", "Try '" + CMD + " help update' for more information", exe.stderrLines().get(1));
|
||||
assertExitCodeAndStreamSizes(exe, 2, 0, 3);
|
||||
Assert.assertEquals("error message", "Unknown option: '--nonexisting'", exe.stderrLines().get(0));
|
||||
Assert.assertEquals("try help", "Try '" + CMD + " update --help' for more information on the available options.", exe.stderrLines().get(2));
|
||||
|
||||
|
||||
// test overwrite from file
|
||||
|
|
Loading…
Reference in a new issue