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.
|
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
|
= 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.
|
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>
|
<name>Keycloak Admin CLI</name>
|
||||||
<description/>
|
<description/>
|
||||||
|
|
||||||
<properties>
|
|
||||||
<jansi.version>1.18</jansi.version>
|
|
||||||
</properties>
|
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.jboss.aesh</groupId>
|
<groupId>info.picocli</groupId>
|
||||||
<artifactId>aesh</artifactId>
|
<artifactId>picocli</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>
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.keycloak</groupId>
|
<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
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package org.keycloak.client.admin.cli.aesh;
|
package org.keycloak.client.admin.cli;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
|
@ -25,7 +23,6 @@ public class Globals {
|
||||||
|
|
||||||
public static boolean dumpTrace = false;
|
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;
|
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.commands.KcAdmCmd;
|
||||||
import org.keycloak.client.admin.cli.util.ClassLoaderUtil;
|
import org.keycloak.client.admin.cli.util.ClassLoaderUtil;
|
||||||
|
import org.keycloak.client.admin.cli.util.OsUtil;
|
||||||
import org.keycloak.common.crypto.CryptoIntegration;
|
import org.keycloak.common.crypto.CryptoIntegration;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.io.PrintWriter;
|
||||||
import java.util.Arrays;
|
|
||||||
|
import picocli.CommandLine;
|
||||||
|
import picocli.CommandLine.Model.CommandSpec;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
|
@ -48,52 +41,21 @@ public class KcAdmMain {
|
||||||
|
|
||||||
CryptoIntegration.init(cl);
|
CryptoIntegration.init(cl);
|
||||||
|
|
||||||
Globals.stdin = new ValveInputStream();
|
CommandLine cli = createCommandLine();
|
||||||
|
int exitCode = cli.execute(args);
|
||||||
Settings settings = new SettingsBuilder()
|
System.exit(exitCode);
|
||||||
.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());
|
public static CommandLine createCommandLine() {
|
||||||
|
CommandSpec spec = CommandSpec.forAnnotatedObject(new KcAdmCmd()).name(OsUtil.CMD);
|
||||||
|
|
||||||
console.start();
|
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;
|
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.OAuth2Constants;
|
||||||
import org.keycloak.client.admin.cli.config.ConfigData;
|
import org.keycloak.client.admin.cli.config.ConfigData;
|
||||||
import org.keycloak.client.admin.cli.config.ConfigHandler;
|
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 java.io.File;
|
||||||
|
|
||||||
|
import picocli.CommandLine.Option;
|
||||||
|
|
||||||
import static org.keycloak.client.admin.cli.config.FileConfigHandler.setConfigFile;
|
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.DEFAULT_CLIENT;
|
||||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.checkAuthInfo;
|
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 {
|
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;
|
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;
|
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;
|
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;
|
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;
|
String targetRealm;
|
||||||
|
|
||||||
@Option(name = "realm", description = "Realm name to authenticate against")
|
@Option(names = "--realm", description = "Realm name to authenticate against")
|
||||||
String realm;
|
String realm;
|
||||||
|
|
||||||
@Option(name = "client", description = "Realm name to authenticate against")
|
@Option(names = "--client", description = "Realm name to authenticate against")
|
||||||
String clientId;
|
String clientId;
|
||||||
|
|
||||||
@Option(name = "user", description = "Username to login with")
|
@Option(names = "--user", description = "Username to login with")
|
||||||
String user;
|
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;
|
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;
|
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;
|
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;
|
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;
|
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;
|
String alias;
|
||||||
|
|
||||||
@Option(name = "truststore", description = "Path to a truststore")
|
@Option(names = "--truststore", description = "Path to a truststore")
|
||||||
String 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;
|
String trustPass;
|
||||||
|
|
||||||
@Option(name = "insecure", description = "Turns off TLS validation", hasValue = false)
|
@Option(names = "--insecure", description = "Turns off TLS validation")
|
||||||
boolean insecure;
|
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;
|
String externalToken;
|
||||||
|
|
||||||
|
|
||||||
protected void initFromParent(AbstractAuthOptionsCmd parent) {
|
protected void initFromParent(AbstractAuthOptionsCmd parent) {
|
||||||
|
|
||||||
super.initFromParent(parent);
|
|
||||||
|
|
||||||
noconfig = parent.noconfig;
|
noconfig = parent.noconfig;
|
||||||
config = parent.config;
|
config = parent.config;
|
||||||
server = parent.server;
|
server = parent.server;
|
||||||
|
@ -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 &&
|
return externalToken == null && server == null && realm == null && clientId == null && secret == null &&
|
||||||
user == null && password == null &&
|
user == null && password == null &&
|
||||||
keystore == null && storePass == null && keyPass == null && alias == 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();
|
return targetRealm != null ? targetRealm : config.getRealm();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void processGlobalOptions() {
|
@Override
|
||||||
|
protected void processOptions() {
|
||||||
super.processGlobalOptions();
|
|
||||||
|
|
||||||
if (config != null && noconfig) {
|
if (config != null && noconfig) {
|
||||||
throw new RuntimeException("Options --config and --no-config are mutually exclusive");
|
throw new IllegalArgumentException("Options --config and --no-config are mutually exclusive");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!noconfig) {
|
if (!noconfig) {
|
||||||
|
@ -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:")) {
|
if (!configData.getServerUrl().startsWith("https:")) {
|
||||||
return;
|
return;
|
||||||
|
@ -173,7 +168,7 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
|
||||||
pass = configData.getTrustpass();
|
pass = configData.getTrustpass();
|
||||||
}
|
}
|
||||||
if (pass == null) {
|
if (pass == null) {
|
||||||
pass = IoUtil.readSecret("Enter truststore password: ", invocation);
|
pass = IoUtil.readSecret("Enter truststore password: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
@ -188,7 +183,7 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected ConfigData ensureAuthInfo(ConfigData config, CommandInvocation commandInvocation) {
|
protected ConfigData ensureAuthInfo(ConfigData config) {
|
||||||
|
|
||||||
if (requiresLogin()) {
|
if (requiresLogin()) {
|
||||||
// make sure current handler is in-memory handler
|
// make sure current handler is in-memory handler
|
||||||
|
@ -204,7 +199,7 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
|
||||||
ConfigCredentialsCmd login = new ConfigCredentialsCmd();
|
ConfigCredentialsCmd login = new ConfigCredentialsCmd();
|
||||||
login.initFromParent(this);
|
login.initFromParent(this);
|
||||||
login.init(config);
|
login.init(config);
|
||||||
login.process(commandInvocation);
|
login.process();
|
||||||
|
|
||||||
// this must be executed before finally block which restores config handler
|
// this must be executed before finally block which restores config handler
|
||||||
return loadConfig();
|
return loadConfig();
|
||||||
|
@ -269,22 +264,4 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd {
|
||||||
rdata.setGrantTypeForAuthentication(grantTypeForAuthentication);
|
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;
|
package org.keycloak.client.admin.cli.commands;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import org.keycloak.client.admin.cli.Globals;
|
||||||
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.util.FilterUtil;
|
import org.keycloak.client.admin.cli.util.FilterUtil;
|
||||||
import org.keycloak.client.admin.cli.util.ReturnFields;
|
import org.keycloak.client.admin.cli.util.ReturnFields;
|
||||||
|
|
||||||
import java.io.IOException;
|
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.HttpUtil.normalize;
|
||||||
import static org.keycloak.client.admin.cli.util.IoUtil.printOut;
|
import static org.keycloak.client.admin.cli.util.IoUtil.printOut;
|
||||||
|
|
||||||
/**
|
public abstract class AbstractGlobalOptionsCmd implements Runnable {
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
|
||||||
*/
|
|
||||||
public abstract class AbstractGlobalOptionsCmd implements Command {
|
|
||||||
|
|
||||||
@Option(shortName = 'x', description = "Print full stack trace when exiting with error", hasValue = false)
|
@Option(names = "--help",
|
||||||
boolean dumpTrace;
|
description = "Print command specific help")
|
||||||
|
public void setHelp(boolean help) {
|
||||||
@Option(name = "help", description = "Print command specific help", hasValue = false)
|
Globals.help = help;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void processGlobalOptions() {
|
@Option(names = "-x",
|
||||||
|
description = "Print full stack trace when exiting with error")
|
||||||
|
public void setDumpTrace(boolean dumpTrace) {
|
||||||
Globals.dumpTrace = dumpTrace;
|
Globals.dumpTrace = dumpTrace;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean printHelp() {
|
protected void printHelpIfNeeded() {
|
||||||
if (help || nothingToDo()) {
|
if (Globals.help) {
|
||||||
printOut(help());
|
printOut(help());
|
||||||
return true;
|
System.exit(CommandLine.ExitCode.OK);
|
||||||
|
} else if (nothingToDo()) {
|
||||||
|
printOut(help());
|
||||||
|
System.exit(CommandLine.ExitCode.USAGE);
|
||||||
}
|
}
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean nothingToDo() {
|
protected boolean nothingToDo() {
|
||||||
|
@ -80,13 +67,6 @@ public abstract class AbstractGlobalOptionsCmd implements Command {
|
||||||
return normalize(server) + "admin";
|
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) {
|
protected String extractTypeNameFromUri(String resourceUrl) {
|
||||||
String type = extractLastComponentOfUri(resourceUrl);
|
String type = extractLastComponentOfUri(resourceUrl);
|
||||||
if (type.endsWith("s")) {
|
if (type.endsWith("s")) {
|
||||||
|
@ -110,4 +90,47 @@ public abstract class AbstractGlobalOptionsCmd implements Command {
|
||||||
throw new RuntimeException("Failed to apply fields filter", e);
|
throw new RuntimeException("Failed to apply fields filter", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void run() {
|
||||||
|
printHelpIfNeeded();
|
||||||
|
|
||||||
|
checkUnsupportedOptions(getUnsupportedOptions());
|
||||||
|
|
||||||
|
processOptions();
|
||||||
|
|
||||||
|
process();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String[] getUnsupportedOptions() {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void processOptions() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void process() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void checkUnsupportedOptions(String ... options) {
|
||||||
|
if (options.length % 2 != 0) {
|
||||||
|
throw new IllegalArgumentException("Even number of argument required");
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < options.length; i++) {
|
||||||
|
String name = options[i];
|
||||||
|
String value = options[++i];
|
||||||
|
|
||||||
|
if (value != null) {
|
||||||
|
throw new IllegalArgumentException("Unsupported option: " + name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static String booleanOptionForCheck(boolean value) {
|
||||||
|
return value ? "true" : null;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,13 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.client.admin.cli.commands;
|
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.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.AttributeOperation;
|
||||||
import org.keycloak.client.admin.cli.common.CmdStdinContext;
|
import org.keycloak.client.admin.cli.common.CmdStdinContext;
|
||||||
import org.keycloak.client.admin.cli.config.ConfigData;
|
import org.keycloak.client.admin.cli.config.ConfigData;
|
||||||
|
@ -44,13 +38,20 @@ import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
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.DELETE;
|
||||||
import static org.keycloak.client.admin.cli.common.AttributeOperation.Type.SET;
|
import static org.keycloak.client.admin.cli.common.AttributeOperation.Type.SET;
|
||||||
import static org.keycloak.client.admin.cli.util.AuthUtil.ensureToken;
|
import static org.keycloak.client.admin.cli.util.AuthUtil.ensureToken;
|
||||||
|
@ -103,79 +104,47 @@ public abstract class AbstractRequestCmd extends AbstractAuthOptionsCmd {
|
||||||
|
|
||||||
String httpVerb;
|
String httpVerb;
|
||||||
|
|
||||||
Headers headers = new Headers();
|
@Option(names = {"-h", "--header"}, description = "Set request header NAME to VALUE")
|
||||||
|
List<String> rawHeaders = new LinkedList<>();
|
||||||
|
|
||||||
|
// to maintain relative positions of set and delete operations
|
||||||
|
static class AttributeOperations {
|
||||||
|
@Option(names = {"-s", "--set"}, required = true) String set;
|
||||||
|
@Option(names = {"-d", "--delete"}, required = true) String delete;
|
||||||
|
}
|
||||||
|
|
||||||
|
@ArgGroup(exclusive = true, multiplicity = "0..*")
|
||||||
|
List<AttributeOperations> rawAttributeOperations = new ArrayList<>();
|
||||||
|
|
||||||
|
@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;
|
||||||
|
|
||||||
List<AttributeOperation> attrs = new LinkedList<>();
|
List<AttributeOperation> attrs = new LinkedList<>();
|
||||||
|
Headers headers = new Headers();
|
||||||
Map<String, String> filter = new HashMap<>();
|
Map<String, String> filter = new HashMap<>();
|
||||||
|
|
||||||
String url = null;
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
protected void processOptions() {
|
||||||
try {
|
super.processOptions();
|
||||||
initOptions();
|
|
||||||
|
|
||||||
if (printHelp()) {
|
for (AttributeOperations entry : rawAttributeOperations) {
|
||||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
if (entry.delete != null) {
|
||||||
}
|
attrs.add(new AttributeOperation(DELETE, entry.delete));
|
||||||
|
} else {
|
||||||
processGlobalOptions();
|
String[] keyVal = parseKeyVal(entry.set);
|
||||||
|
|
||||||
processOptions(commandInvocation);
|
|
||||||
|
|
||||||
return process(commandInvocation);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
|
|
||||||
} finally {
|
|
||||||
commandInvocation.stop();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
abstract void initOptions();
|
|
||||||
|
|
||||||
abstract String suggestHelp();
|
|
||||||
|
|
||||||
|
|
||||||
void processOptions(CommandInvocation commandInvocation) {
|
|
||||||
|
|
||||||
if (args == null || args.isEmpty()) {
|
|
||||||
throw new IllegalArgumentException("URI not specified");
|
|
||||||
}
|
|
||||||
|
|
||||||
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]));
|
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": {
|
for (String header : rawHeaders) {
|
||||||
requireValue(it, option);
|
String[] keyVal = parseKeyVal(header);
|
||||||
String[] keyVal = parseKeyVal(it.next());
|
|
||||||
headers.add(keyVal[0], keyVal[1]);
|
headers.add(keyVal[0], keyVal[1]);
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
case "-q":
|
|
||||||
case "--query": {
|
for (String arg : rawFilters) {
|
||||||
if (!it.hasNext()) {
|
|
||||||
throw new IllegalArgumentException("Option " + option + " requires a value");
|
|
||||||
}
|
|
||||||
String arg = it.next();
|
|
||||||
String[] keyVal;
|
String[] keyVal;
|
||||||
if (arg.indexOf("=") == -1) {
|
if (arg.indexOf("=") == -1) {
|
||||||
keyVal = new String[] {"", arg};
|
keyVal = new String[] {"", arg};
|
||||||
|
@ -183,20 +152,9 @@ public abstract class AbstractRequestCmd extends AbstractAuthOptionsCmd {
|
||||||
keyVal = parseKeyVal(arg);
|
keyVal = parseKeyVal(arg);
|
||||||
}
|
}
|
||||||
filter.put(keyVal[0], keyVal[1]);
|
filter.put(keyVal[0], keyVal[1]);
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
if (url == null) {
|
|
||||||
url = option;
|
|
||||||
} else {
|
|
||||||
throw new IllegalArgumentException("Invalid option: " + option);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (uri == null) {
|
||||||
if (url == null) {
|
|
||||||
throw new IllegalArgumentException("Resource URI not specified");
|
throw new IllegalArgumentException("Resource URI not specified");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -207,7 +165,7 @@ public abstract class AbstractRequestCmd extends AbstractAuthOptionsCmd {
|
||||||
try {
|
try {
|
||||||
outputFormat = OutputFormat.valueOf(format.toUpperCase());
|
outputFormat = OutputFormat.valueOf(format.toUpperCase());
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("Unsupported output format: " + format);
|
throw new IllegalArgumentException("Unsupported output format: " + format);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mergeMode && noMerge) {
|
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();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public CommandResult process(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
protected void process() {
|
||||||
|
|
||||||
// see if Content-Type header is explicitly set to non-json value
|
// see if Content-Type header is explicitly set to non-json value
|
||||||
Header ctype = headers.get("content-type");
|
Header ctype = headers.get("content-type");
|
||||||
|
|
||||||
|
@ -255,11 +217,11 @@ public abstract class AbstractRequestCmd extends AbstractAuthOptionsCmd {
|
||||||
ConfigData config = loadConfig();
|
ConfigData config = loadConfig();
|
||||||
config = copyWithServerInfo(config);
|
config = copyWithServerInfo(config);
|
||||||
|
|
||||||
setupTruststore(config, commandInvocation);
|
setupTruststore(config);
|
||||||
|
|
||||||
String auth = null;
|
String auth = null;
|
||||||
|
|
||||||
config = ensureAuthInfo(config, commandInvocation);
|
config = ensureAuthInfo(config);
|
||||||
config = copyWithServerInfo(config);
|
config = copyWithServerInfo(config);
|
||||||
if (credentialsAvailable(config)) {
|
if (credentialsAvailable(config)) {
|
||||||
auth = ensureToken(config);
|
auth = ensureToken(config);
|
||||||
|
@ -277,7 +239,7 @@ public abstract class AbstractRequestCmd extends AbstractAuthOptionsCmd {
|
||||||
final String adminRoot = adminRestRoot != null ? adminRestRoot : composeAdminRoot(server);
|
final String adminRoot = adminRestRoot != null ? adminRestRoot : composeAdminRoot(server);
|
||||||
|
|
||||||
|
|
||||||
String resourceUrl = composeResourceUrl(adminRoot, realm, url);
|
String resourceUrl = composeResourceUrl(adminRoot, realm, uri);
|
||||||
String typeName = extractTypeNameFromUri(resourceUrl);
|
String typeName = extractTypeNameFromUri(resourceUrl);
|
||||||
|
|
||||||
|
|
||||||
|
@ -385,7 +347,7 @@ public abstract class AbstractRequestCmd extends AbstractAuthOptionsCmd {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (outputResult) {
|
if (outputResult) {
|
||||||
if (isCreateOrUpdate() && (response.getStatusCode() == 204 || id != null) && isGetByID(url)) {
|
if (isCreateOrUpdate() && (response.getStatusCode() == 204 || id != null) && isGetByID(uri)) {
|
||||||
// get object for id
|
// get object for id
|
||||||
headers = new Headers();
|
headers = new Headers();
|
||||||
if (auth != null) {
|
if (auth != null) {
|
||||||
|
@ -423,7 +385,7 @@ public abstract class AbstractRequestCmd extends AbstractAuthOptionsCmd {
|
||||||
} else {
|
} else {
|
||||||
if (outputFormat != OutputFormat.JSON || returnFields != null) {
|
if (outputFormat != OutputFormat.JSON || returnFields != null) {
|
||||||
printErr("Cannot create CSV nor filter returned fields because the response is " + (compressed ? "compressed":"not json"));
|
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
|
// 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
|
// 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) {
|
if (lastByte != -1 && lastByte != 13 && lastByte != 10) {
|
||||||
printErr("");
|
printErr("");
|
||||||
}
|
}
|
||||||
|
|
||||||
return CommandResult.SUCCESS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isUpdate() {
|
private boolean isUpdate() {
|
||||||
|
|
|
@ -17,11 +17,10 @@
|
||||||
package org.keycloak.client.admin.cli.commands;
|
package org.keycloak.client.admin.cli.commands;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
import org.jboss.aesh.cl.CommandDefinition;
|
|
||||||
import org.jboss.aesh.cl.Option;
|
import picocli.CommandLine.Command;
|
||||||
import org.jboss.aesh.console.command.CommandException;
|
import picocli.CommandLine.Option;
|
||||||
import org.jboss.aesh.console.command.CommandResult;
|
|
||||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|
||||||
import org.keycloak.client.admin.cli.config.ConfigData;
|
import org.keycloak.client.admin.cli.config.ConfigData;
|
||||||
import org.keycloak.client.admin.cli.operations.ClientOperations;
|
import org.keycloak.client.admin.cli.operations.ClientOperations;
|
||||||
import org.keycloak.client.admin.cli.operations.GroupOperations;
|
import org.keycloak.client.admin.cli.operations.GroupOperations;
|
||||||
|
@ -33,8 +32,6 @@ import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
|
@ -43,76 +40,49 @@ 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.credentialsAvailable;
|
||||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.loadConfig;
|
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.CMD;
|
||||||
import static org.keycloak.client.admin.cli.util.OsUtil.EOL;
|
|
||||||
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
@CommandDefinition(name = "add-roles", description = "[ARGUMENTS]")
|
@Command(name = "add-roles", description = "[ARGUMENTS]")
|
||||||
public class AddRolesCmd extends AbstractAuthOptionsCmd {
|
public class AddRolesCmd extends AbstractAuthOptionsCmd {
|
||||||
|
|
||||||
@Option(name = "uusername", description = "Target user's 'username'")
|
@Option(names = "--uusername", description = "Target user's 'username'")
|
||||||
String uusername;
|
String uusername;
|
||||||
|
|
||||||
@Option(name = "uid", description = "Target user's 'id'")
|
@Option(names = "--uid", description = "Target user's 'id'")
|
||||||
String uid;
|
String uid;
|
||||||
|
|
||||||
@Option(name = "gname", description = "Target group's 'name'")
|
@Option(names = "--gname", description = "Target group's 'name'")
|
||||||
String gname;
|
String gname;
|
||||||
|
|
||||||
@Option(name = "gpath", description = "Target group's 'path'")
|
@Option(names = "--gpath", description = "Target group's 'path'")
|
||||||
String gpath;
|
String gpath;
|
||||||
|
|
||||||
@Option(name = "gid", description = "Target group's 'id'")
|
@Option(names = "--gid", description = "Target group's 'id'")
|
||||||
String gid;
|
String gid;
|
||||||
|
|
||||||
@Option(name = "rname", description = "Composite role's 'name'")
|
@Option(names = "--rname", description = "Composite role's 'name'")
|
||||||
String rname;
|
String rname;
|
||||||
|
|
||||||
@Option(name = "rid", description = "Composite role's 'id'")
|
@Option(names = "--rid", description = "Composite role's 'id'")
|
||||||
String rid;
|
String rid;
|
||||||
|
|
||||||
@Option(name = "cclientid", description = "Target client's 'clientId'")
|
@Option(names = "--cclientid", description = "Target client's 'clientId'")
|
||||||
String cclientid;
|
String cclientid;
|
||||||
|
|
||||||
@Option(name = "cid", description = "Target client's 'id'")
|
@Option(names = "--cid", description = "Target client's 'id'")
|
||||||
String cid;
|
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
|
@Override
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
protected void process() {
|
||||||
|
|
||||||
List<String> roleNames = new LinkedList<>();
|
|
||||||
List<String> roleIds = new LinkedList<>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (printHelp()) {
|
|
||||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
if (uid != null && uusername != null) {
|
||||||
throw new IllegalArgumentException("Incompatible options: --uid and --uusername are mutually exclusive");
|
throw new IllegalArgumentException("Incompatible options: --uid and --uusername are mutually exclusive");
|
||||||
}
|
}
|
||||||
|
@ -153,11 +123,11 @@ public class AddRolesCmd extends AbstractAuthOptionsCmd {
|
||||||
ConfigData config = loadConfig();
|
ConfigData config = loadConfig();
|
||||||
config = copyWithServerInfo(config);
|
config = copyWithServerInfo(config);
|
||||||
|
|
||||||
setupTruststore(config, commandInvocation);
|
setupTruststore(config);
|
||||||
|
|
||||||
String auth = null;
|
String auth = null;
|
||||||
|
|
||||||
config = ensureAuthInfo(config, commandInvocation);
|
config = ensureAuthInfo(config);
|
||||||
config = copyWithServerInfo(config);
|
config = copyWithServerInfo(config);
|
||||||
if (credentialsAvailable(config)) {
|
if (credentialsAvailable(config)) {
|
||||||
auth = ensureToken(config);
|
auth = ensureToken(config);
|
||||||
|
@ -249,14 +219,6 @@ public class AddRolesCmd extends AbstractAuthOptionsCmd {
|
||||||
} else {
|
} 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");
|
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");
|
||||||
}
|
}
|
||||||
|
|
||||||
return CommandResult.SUCCESS;
|
|
||||||
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
|
|
||||||
} finally {
|
|
||||||
commandInvocation.stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<ObjectNode> getRoleRepresentations(List<String> roleNames, List<String> roleIds, LocalSearch roleSearch) {
|
private Set<ObjectNode> getRoleRepresentations(List<String> roleNames, List<String> roleIds, LocalSearch roleSearch) {
|
||||||
|
@ -280,12 +242,6 @@ public class AddRolesCmd extends AbstractAuthOptionsCmd {
|
||||||
return rolesToAdd;
|
return rolesToAdd;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void optionRequiresValueCheck(Iterator<String> it, String option) {
|
|
||||||
if (!it.hasNext()) {
|
|
||||||
throw new IllegalArgumentException("Option " + option + " requires a value");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isClientSpecified() {
|
private boolean isClientSpecified() {
|
||||||
return cid != null || cclientid != null;
|
return cid != null || cclientid != null;
|
||||||
}
|
}
|
||||||
|
@ -304,13 +260,10 @@ public class AddRolesCmd extends AbstractAuthOptionsCmd {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean nothingToDo() {
|
protected boolean nothingToDo() {
|
||||||
return noOptions() && uusername == null && uid == null && cclientid == null && (args == null || args.size() == 0);
|
return super.nothingToDo() && uusername == null && uid == null && cclientid == null && roleIds.isEmpty() && roleNames.isEmpty();
|
||||||
}
|
|
||||||
|
|
||||||
protected String suggestHelp() {
|
|
||||||
return EOL + "Try '" + CMD + " help add-roles' for more information";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected String help() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,66 +16,35 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.client.admin.cli.commands;
|
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.PrintWriter;
|
||||||
import java.io.StringWriter;
|
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.CMD;
|
||||||
import static org.keycloak.client.admin.cli.util.OsUtil.EOL;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@GroupCommandDefinition(name = "config", description = "COMMAND [ARGUMENTS]", groupCommands = {ConfigCredentialsCmd.class} )
|
@Command(name = "config", description = "COMMAND [ARGUMENTS]", subcommands = {
|
||||||
|
ConfigCredentialsCmd.class,
|
||||||
|
ConfigTruststoreCmd.class
|
||||||
|
} )
|
||||||
public class ConfigCmd extends AbstractAuthOptionsCmd {
|
public class ConfigCmd extends AbstractAuthOptionsCmd {
|
||||||
|
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
@Override
|
||||||
try {
|
protected void process() {
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (printHelp()) {
|
@Override
|
||||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
protected boolean nothingToDo() {
|
||||||
}
|
return true;
|
||||||
|
|
||||||
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 String help() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,10 +16,6 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.client.admin.cli.commands;
|
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.OAuth2Constants;
|
||||||
import org.keycloak.client.admin.cli.config.ConfigData;
|
import org.keycloak.client.admin.cli.config.ConfigData;
|
||||||
import org.keycloak.client.admin.cli.config.RealmConfigData;
|
import org.keycloak.client.admin.cli.config.RealmConfigData;
|
||||||
|
@ -31,6 +27,8 @@ import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
|
|
||||||
|
import picocli.CommandLine.Command;
|
||||||
|
|
||||||
import static org.keycloak.client.admin.cli.util.AuthUtil.getAuthTokens;
|
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.getAuthTokensByJWT;
|
||||||
import static org.keycloak.client.admin.cli.util.AuthUtil.getAuthTokensBySecret;
|
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.printErr;
|
||||||
import static org.keycloak.client.admin.cli.util.IoUtil.readSecret;
|
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.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.OS_ARCH;
|
||||||
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
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>
|
* @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 {
|
public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd {
|
||||||
|
|
||||||
private int sigLifetime = 600;
|
private int sigLifetime = 600;
|
||||||
|
|
||||||
|
|
||||||
public void init(ConfigData configData) {
|
public void init(ConfigData configData) {
|
||||||
if (server == null) {
|
if (server == null) {
|
||||||
server = configData.getServerUrl();
|
server = configData.getServerUrl();
|
||||||
|
@ -76,33 +72,13 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
protected String[] getUnsupportedOptions() {
|
||||||
try {
|
return new String[] {"--no-config", booleanOptionForCheck(noconfig)};
|
||||||
if (printHelp()) {
|
|
||||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
checkUnsupportedOptions("--no-config", booleanOptionForCheck(noconfig));
|
|
||||||
|
|
||||||
processGlobalOptions();
|
|
||||||
|
|
||||||
return process(commandInvocation);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
|
|
||||||
} finally {
|
|
||||||
commandInvocation.stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean nothingToDo() {
|
public void process() {
|
||||||
return noOptions();
|
|
||||||
}
|
|
||||||
|
|
||||||
public CommandResult process(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
|
||||||
|
|
||||||
// check server
|
// check server
|
||||||
if (server == null) {
|
if (server == null) {
|
||||||
throw new IllegalArgumentException("Required option not specified: --server");
|
throw new IllegalArgumentException("Required option not specified: --server");
|
||||||
|
@ -129,18 +105,18 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd {
|
||||||
|
|
||||||
// if user was set there needs to be a password so we can authenticate
|
// if user was set there needs to be a password so we can authenticate
|
||||||
if (password == null) {
|
if (password == null) {
|
||||||
password = readSecret("Enter password: ", commandInvocation);
|
password = readSecret("Enter password: ");
|
||||||
}
|
}
|
||||||
// if secret was set to be read from stdin, then ask for it
|
// if secret was set to be read from stdin, then ask for it
|
||||||
if ("-".equals(secret) && keystore == null) {
|
if ("-".equals(secret) && keystore == null) {
|
||||||
secret = readSecret("Enter client secret: ", commandInvocation);
|
secret = readSecret("Enter client secret: ");
|
||||||
}
|
}
|
||||||
} else if (keystore != null || secret != null || clientSet) {
|
} else if (keystore != null || secret != null || clientSet) {
|
||||||
grantTypeForAuthentication = OAuth2Constants.CLIENT_CREDENTIALS;
|
grantTypeForAuthentication = OAuth2Constants.CLIENT_CREDENTIALS;
|
||||||
printErr("Logging into " + server + " as " + "service-account-" + clientId + " of realm " + realm);
|
printErr("Logging into " + server + " as " + "service-account-" + clientId + " of realm " + realm);
|
||||||
if (keystore == null) {
|
if (keystore == null) {
|
||||||
if (secret == null) {
|
if (secret == null) {
|
||||||
secret = readSecret("Enter client secret: ", commandInvocation);
|
secret = readSecret("Enter client secret: ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -155,8 +131,8 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (storePass == null) {
|
if (storePass == null) {
|
||||||
storePass = readSecret("Enter keystore password: ", commandInvocation);
|
storePass = readSecret("Enter keystore password: ");
|
||||||
keyPass = readSecret("Enter key password: ", commandInvocation);
|
keyPass = readSecret("Enter key password: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (keyPass == null) {
|
if (keyPass == null) {
|
||||||
|
@ -179,10 +155,10 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd {
|
||||||
config.setServerUrl(server);
|
config.setServerUrl(server);
|
||||||
config.setRealm(realm);
|
config.setRealm(realm);
|
||||||
});
|
});
|
||||||
return CommandResult.SUCCESS;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
setupTruststore(copyWithServerInfo(loadConfig()), commandInvocation);
|
setupTruststore(copyWithServerInfo(loadConfig()));
|
||||||
|
|
||||||
// now use the token endpoint to retrieve access token, and refresh token
|
// now use the token endpoint to retrieve access token, and refresh token
|
||||||
AccessTokenResponse tokens = signedRequestToken != null ?
|
AccessTokenResponse tokens = signedRequestToken != null ?
|
||||||
|
@ -195,14 +171,9 @@ public class ConfigCredentialsCmd extends AbstractAuthOptionsCmd {
|
||||||
|
|
||||||
// save tokens to config file
|
// save tokens to config file
|
||||||
saveTokens(tokens, server, realm, clientId, signedRequestToken, sigExpiresAt, secret, grantTypeForAuthentication);
|
saveTokens(tokens, server, realm, clientId, signedRequestToken, sigExpiresAt, secret, grantTypeForAuthentication);
|
||||||
|
|
||||||
return CommandResult.SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String suggestHelp() {
|
|
||||||
return EOL + "Try '" + CMD + " help config credentials' for more information";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected String help() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,93 +16,41 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.client.admin.cli.commands;
|
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.File;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Iterator;
|
import picocli.CommandLine.Command;
|
||||||
import java.util.List;
|
import picocli.CommandLine.Option;
|
||||||
|
import picocli.CommandLine.Parameters;
|
||||||
|
|
||||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
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.ConfigUtil.saveMergeConfig;
|
||||||
import static org.keycloak.client.admin.cli.util.IoUtil.readSecret;
|
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.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.OS_ARCH;
|
||||||
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
@CommandDefinition(name = "truststore", description = "PATH [ARGUMENTS]")
|
@Command(name = "truststore", description = "PATH [ARGUMENTS]")
|
||||||
public class ConfigTruststoreCmd extends AbstractAuthOptionsCmd {
|
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;
|
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
|
@Override
|
||||||
protected boolean nothingToDo() {
|
protected boolean nothingToDo() {
|
||||||
return noOptions();
|
return super.nothingToDo() && store == null && !delete;
|
||||||
}
|
}
|
||||||
|
|
||||||
public CommandResult process(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
@Override
|
||||||
|
protected String[] getUnsupportedOptions() {
|
||||||
List<String> args = new ArrayList<>();
|
return new String[] {"--server", server,
|
||||||
|
|
||||||
Iterator<String> it = parent.args.iterator();
|
|
||||||
|
|
||||||
while (it.hasNext()) {
|
|
||||||
String arg = it.next();
|
|
||||||
switch (arg) {
|
|
||||||
case "-d":
|
|
||||||
case "--delete": {
|
|
||||||
delete = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default: {
|
|
||||||
args.add(arg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (args.size() > 1) {
|
|
||||||
throw new IllegalArgumentException("Invalid option: " + args.get(1));
|
|
||||||
}
|
|
||||||
|
|
||||||
String truststore = null;
|
|
||||||
if (args.size() > 0) {
|
|
||||||
truststore = args.get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
checkUnsupportedOptions("--server", server,
|
|
||||||
"--realm", realm,
|
"--realm", realm,
|
||||||
"--client", clientId,
|
"--client", clientId,
|
||||||
"--user", user,
|
"--user", user,
|
||||||
|
@ -112,39 +60,36 @@ public class ConfigTruststoreCmd extends AbstractAuthOptionsCmd {
|
||||||
"--keystore", keystore,
|
"--keystore", keystore,
|
||||||
"--keypass", keyPass,
|
"--keypass", keyPass,
|
||||||
"--alias", alias,
|
"--alias", alias,
|
||||||
"--no-config", booleanOptionForCheck(noconfig));
|
"--no-config", booleanOptionForCheck(noconfig)};
|
||||||
|
}
|
||||||
|
|
||||||
// now update the config
|
@Override
|
||||||
processGlobalOptions();
|
protected void process() {
|
||||||
|
|
||||||
String store;
|
|
||||||
String pass;
|
String pass;
|
||||||
|
|
||||||
if (!delete) {
|
if (!delete) {
|
||||||
|
|
||||||
if (truststore == null) {
|
if (store == null) {
|
||||||
throw new IllegalArgumentException("No truststore specified");
|
throw new IllegalArgumentException("No truststore specified");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!new File(truststore).isFile()) {
|
if (!new File(store).isFile()) {
|
||||||
throw new RuntimeException("Truststore file not found: " + truststore);
|
throw new RuntimeException("Truststore file not found: " + store);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("-".equals(trustPass)) {
|
if ("-".equals(trustPass)) {
|
||||||
trustPass = readSecret("Enter truststore password: ", commandInvocation);
|
trustPass = readSecret("Enter truststore password: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
store = truststore;
|
|
||||||
pass = trustPass;
|
pass = trustPass;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
if (truststore != null) {
|
if (store != null) {
|
||||||
throw new IllegalArgumentException("Option --delete is mutually exclusive with specifying a TRUSTSTORE");
|
throw new IllegalArgumentException("Option --delete is mutually exclusive with specifying a TRUSTSTORE");
|
||||||
}
|
}
|
||||||
if (trustPass != null) {
|
if (trustPass != null) {
|
||||||
throw new IllegalArgumentException("Options --trustpass and --delete are mutually exclusive");
|
throw new IllegalArgumentException("Options --trustpass and --delete are mutually exclusive");
|
||||||
}
|
}
|
||||||
store = null;
|
|
||||||
pass = null;
|
pass = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -152,14 +97,9 @@ public class ConfigTruststoreCmd extends AbstractAuthOptionsCmd {
|
||||||
config.setTruststore(store);
|
config.setTruststore(store);
|
||||||
config.setTrustpass(pass);
|
config.setTrustpass(pass);
|
||||||
});
|
});
|
||||||
|
|
||||||
return CommandResult.SUCCESS;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected String suggestHelp() {
|
|
||||||
return EOL + "Try '" + CMD + " help config truststore' for more information";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected String help() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,70 +16,63 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.client.admin.cli.commands;
|
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.PrintWriter;
|
||||||
import java.io.StringWriter;
|
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.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
||||||
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
|
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.OS_ARCH;
|
||||||
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
@CommandDefinition(name = "create", description = "Command to create new resources")
|
@Command(name = "create", description = "Command to create new resources")
|
||||||
public class CreateCmd extends AbstractRequestCmd {
|
public class CreateCmd extends AbstractRequestCmd {
|
||||||
|
|
||||||
@Option(shortName = 'f', name = "file", description = "Read object from file or standard input if FILENAME is set to '-'")
|
public CreateCmd() {
|
||||||
String file;
|
this.httpVerb = "post";
|
||||||
|
}
|
||||||
|
|
||||||
@Option(shortName = 'b', name = "body", description = "JSON object to be sent as-is or used as a template")
|
@Option(names = {"-f", "--file"}, description = "Read object from file or standard input if FILENAME is set to '-'")
|
||||||
String body;
|
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)
|
@Option(names = {"-b", "--body"}, description = "JSON object to be sent as-is or used as a template")
|
||||||
String fields;
|
public void setBody(String body) {
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
@Option(shortName = 'H', name = "print-headers", description = "Print response headers", hasValue = false)
|
@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")
|
||||||
boolean printHeaders;
|
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)
|
@Option(names = {"-H", "--print-headers"}, description = "Print response headers")
|
||||||
boolean returnId = false;
|
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)
|
@Option(names = {"-i", "--id"}, description = "After creation only print id of created resource to standard output")
|
||||||
boolean outputResult = false;
|
public void setReturnId(boolean returnId) {
|
||||||
|
this.returnId = returnId;
|
||||||
|
}
|
||||||
|
|
||||||
@Option(shortName = 'c', name = "compressed", description = "Don't pretty print the output", hasValue = false)
|
@Option(names = {"-o", "--output"}, description = "After creation output the new resource to standard output")
|
||||||
boolean compressed = false;
|
public void setOutputResult(boolean outputResult) {
|
||||||
|
this.outputResult = outputResult;
|
||||||
|
}
|
||||||
|
|
||||||
//@OptionGroup(shortName = 's', name = "set", description = "Set attribute to the specified value")
|
@Option(names = {"-c", "--compressed"}, description = "Don't pretty print the output")
|
||||||
//Map<String, String> attributes = new LinkedHashMap<>();
|
public void setCompressed(boolean compressed) {
|
||||||
|
this.compressed = compressed;
|
||||||
@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";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,37 +16,27 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.client.admin.cli.commands;
|
package org.keycloak.client.admin.cli.commands;
|
||||||
|
|
||||||
import org.jboss.aesh.cl.CommandDefinition;
|
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
|
|
||||||
|
import picocli.CommandLine.Command;
|
||||||
|
|
||||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
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.CMD;
|
||||||
import static org.keycloak.client.admin.cli.util.OsUtil.EOL;
|
|
||||||
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
@CommandDefinition(name = "delete", description = "CLIENT [GLOBAL_OPTIONS]")
|
@Command(name = "delete", description = "CLIENT [GLOBAL_OPTIONS]")
|
||||||
public class DeleteCmd extends CreateCmd {
|
public class DeleteCmd extends CreateCmd {
|
||||||
|
|
||||||
void initOptions() {
|
public DeleteCmd() {
|
||||||
super.initOptions();
|
this.httpVerb = "delete";
|
||||||
httpVerb = "delete";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,69 +16,63 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.client.admin.cli.commands;
|
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.PrintWriter;
|
||||||
import java.io.StringWriter;
|
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.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
||||||
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
|
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;
|
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
@CommandDefinition(name = "get", description = "[ARGUMENTS]")
|
@Command(name = "get", description = "[ARGUMENTS]")
|
||||||
public class GetCmd extends AbstractRequestCmd {
|
public class GetCmd extends AbstractRequestCmd {
|
||||||
|
|
||||||
@Option(name = "noquotes", description = "", hasValue = false)
|
public GetCmd() {
|
||||||
boolean unquoted;
|
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")
|
@Option(names = "--noquotes", description = "")
|
||||||
String fields;
|
public void setUnquoted(boolean unquoted) {
|
||||||
|
this.unquoted = unquoted;
|
||||||
|
}
|
||||||
|
|
||||||
@Option(shortName = 'H', name = "print-headers", description = "Print response headers", hasValue = false)
|
@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")
|
||||||
boolean printHeaders;
|
public void setFields(String fields) {
|
||||||
|
this.fields = fields;
|
||||||
|
}
|
||||||
|
|
||||||
@Option(shortName = 'c', name = "compressed", description = "Don't pretty print the output", hasValue = false)
|
@Option(names = {"-H", "--print-headers"}, description = "Print response headers")
|
||||||
boolean compressed;
|
public void setPrintHeaders(boolean printHeaders) {
|
||||||
|
this.printHeaders = printHeaders;
|
||||||
|
}
|
||||||
|
|
||||||
@Option(shortName = 'o', name = "offset", description = "Number of results from beginning of resultset to skip")
|
@Option(names = {"-c", "--compressed"}, description = "Don't pretty print the output")
|
||||||
Integer offset;
|
public void setCompressed(boolean compressed) {
|
||||||
|
this.compressed = compressed;
|
||||||
|
}
|
||||||
|
|
||||||
@Option(shortName = 'l', name = "limit", description = "Maksimum number of results to return")
|
@Option(names = {"-o", "--offset"}, description = "Number of results from beginning of resultset to skip")
|
||||||
Integer limit;
|
public void setOffset(Integer offset) {
|
||||||
|
this.offset = offset;
|
||||||
|
}
|
||||||
|
|
||||||
@Option(name = "format", description = "Output format - one of: json, csv", defaultValue = "json")
|
@Option(names = {"-l", "--limit"}, description = "Maksimum number of results to return")
|
||||||
String format;
|
public void setLimit(Integer limit) {
|
||||||
|
this.limit = limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Option(names = "--format", description = "Output format - one of: json, csv", defaultValue = "json")
|
||||||
@Override
|
public void setFormat(String format) {
|
||||||
void initOptions() {
|
this.format = format;
|
||||||
// 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";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,11 +16,6 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.client.admin.cli.commands;
|
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.config.ConfigData;
|
||||||
import org.keycloak.client.admin.cli.operations.ClientOperations;
|
import org.keycloak.client.admin.cli.operations.ClientOperations;
|
||||||
import org.keycloak.client.admin.cli.operations.GroupOperations;
|
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.PrintWriter;
|
||||||
import java.io.StringWriter;
|
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.AuthUtil.ensureToken;
|
||||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
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>
|
* @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 {
|
public class GetRolesCmd extends GetCmd {
|
||||||
|
|
||||||
@Option(name = "uusername", description = "Target user's 'username'")
|
@Option(names = "--uusername", description = "Target user's 'username'")
|
||||||
String uusername;
|
String uusername;
|
||||||
|
|
||||||
@Option(name = "uid", description = "Target user's 'id'")
|
@Option(names = "--uid", description = "Target user's 'id'")
|
||||||
String uid;
|
String uid;
|
||||||
|
|
||||||
@Option(name = "cclientid", description = "Target client's 'clientId'")
|
@Option(names = "--cclientid", description = "Target client's 'clientId'")
|
||||||
String cclientid;
|
String cclientid;
|
||||||
|
|
||||||
@Option(name = "cid", description = "Target client's 'id'")
|
@Option(names = "--cid", description = "Target client's 'id'")
|
||||||
String cid;
|
String cid;
|
||||||
|
|
||||||
@Option(name = "rname", description = "Composite role's 'name'")
|
@Option(names = "--rname", description = "Composite role's 'name'")
|
||||||
String rname;
|
String rname;
|
||||||
|
|
||||||
@Option(name = "rid", description = "Composite role's 'id'")
|
@Option(names = "--rid", description = "Composite role's 'id'")
|
||||||
String rid;
|
String rid;
|
||||||
|
|
||||||
@Option(name = "gname", description = "Target group's 'name'")
|
@Option(names = "--gname", description = "Target group's 'name'")
|
||||||
String gname;
|
String gname;
|
||||||
|
|
||||||
@Option(name = "gpath", description = "Target group's 'path'")
|
@Option(names = "--gpath", description = "Target group's 'path'")
|
||||||
String gpath;
|
String gpath;
|
||||||
|
|
||||||
@Option(name = "gid", description = "Target group's 'id'")
|
@Option(names = "--gid", description = "Target group's 'id'")
|
||||||
String gid;
|
String gid;
|
||||||
|
|
||||||
@Option(name = "rolename", description = "Target role's 'name'")
|
@Option(names = "--rolename", description = "Target role's 'name'")
|
||||||
String rolename;
|
String rolename;
|
||||||
|
|
||||||
@Option(name = "roleid", description = "Target role's 'id'")
|
@Option(names = "--roleid", description = "Target role's 'id'")
|
||||||
String roleid;
|
String roleid;
|
||||||
|
|
||||||
@Option(name = "available", description = "List only available roles", hasValue = false)
|
@Option(names = "--available", description = "List only available roles")
|
||||||
boolean available;
|
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;
|
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;
|
boolean all;
|
||||||
|
|
||||||
|
@Override
|
||||||
void initOptions() {
|
protected void processOptions() {
|
||||||
|
|
||||||
super.initOptions();
|
|
||||||
|
|
||||||
// hack args so that GetCmd option check doesn't fail
|
// hack args so that GetCmd option check doesn't fail
|
||||||
// set a placeholder
|
// set a placeholder
|
||||||
if (args == null) {
|
if (uri == null) {
|
||||||
args = new ArrayList();
|
uri = "uri";
|
||||||
}
|
}
|
||||||
if (args.size() == 0) {
|
|
||||||
args.add("uri");
|
|
||||||
} else {
|
|
||||||
args.add(0, "uri");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void processOptions(CommandInvocation commandInvocation) {
|
|
||||||
|
|
||||||
if (uid != null && uusername != null) {
|
if (uid != null && uusername != null) {
|
||||||
throw new IllegalArgumentException("Incompatible options: --uid and --uusername are mutually exclusive");
|
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");
|
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();
|
ConfigData config = loadConfig();
|
||||||
config = copyWithServerInfo(config);
|
config = copyWithServerInfo(config);
|
||||||
|
|
||||||
setupTruststore(config, commandInvocation);
|
setupTruststore(config);
|
||||||
|
|
||||||
String auth = null;
|
String auth = null;
|
||||||
|
|
||||||
config = ensureAuthInfo(config, commandInvocation);
|
config = ensureAuthInfo(config);
|
||||||
config = copyWithServerInfo(config);
|
config = copyWithServerInfo(config);
|
||||||
if (credentialsAvailable(config)) {
|
if (credentialsAvailable(config)) {
|
||||||
auth = ensureToken(config);
|
auth = ensureToken(config);
|
||||||
|
@ -180,20 +166,20 @@ public class GetRolesCmd extends GetCmd {
|
||||||
cid = ClientOperations.getIdFromClientId(adminRoot, realm, auth, cclientid);
|
cid = ClientOperations.getIdFromClientId(adminRoot, realm, auth, cclientid);
|
||||||
}
|
}
|
||||||
if (available) {
|
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) {
|
} 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 {
|
} else {
|
||||||
super.url = composeResourceUrl(adminRoot, realm, "users/" + uid + "/role-mappings/clients/" + cid);
|
super.uri = composeResourceUrl(adminRoot, realm, "users/" + uid + "/role-mappings/clients/" + cid);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// list realm roles for a user
|
// list realm roles for a user
|
||||||
if (available) {
|
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) {
|
} 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 {
|
} 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()) {
|
} else if (isGroupSpecified()) {
|
||||||
|
@ -208,20 +194,20 @@ public class GetRolesCmd extends GetCmd {
|
||||||
cid = ClientOperations.getIdFromClientId(adminRoot, realm, auth, cclientid);
|
cid = ClientOperations.getIdFromClientId(adminRoot, realm, auth, cclientid);
|
||||||
}
|
}
|
||||||
if (available) {
|
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) {
|
} 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 {
|
} else {
|
||||||
super.url = composeResourceUrl(adminRoot, realm, "groups/" + gid + "/role-mappings/clients/" + cid);
|
super.uri = composeResourceUrl(adminRoot, realm, "groups/" + gid + "/role-mappings/clients/" + cid);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// list realm roles for a group
|
// list realm roles for a group
|
||||||
if (available) {
|
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) {
|
} 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 {
|
} 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()) {
|
} else if (isCompositeRoleSpecified()) {
|
||||||
|
@ -248,7 +234,7 @@ public class GetRolesCmd extends GetCmd {
|
||||||
|
|
||||||
uri += all ? "/composites" : "/composites/realm";
|
uri += all ? "/composites" : "/composites/realm";
|
||||||
}
|
}
|
||||||
super.url = composeResourceUrl(adminRoot, realm, uri);
|
super.uri = composeResourceUrl(adminRoot, realm, uri);
|
||||||
|
|
||||||
} else if (isClientSpecified()) {
|
} else if (isClientSpecified()) {
|
||||||
if (cid == null) {
|
if (cid == null) {
|
||||||
|
@ -260,10 +246,10 @@ public class GetRolesCmd extends GetCmd {
|
||||||
if (rolename == null) {
|
if (rolename == null) {
|
||||||
rolename = RoleOperations.getClientRoleNameFromId(adminRoot, realm, auth, cid, roleid);
|
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 {
|
} else {
|
||||||
// list defined client roles
|
// list defined client roles
|
||||||
super.url = composeResourceUrl(adminRoot, realm, "clients/" + cid + "/roles");
|
super.uri = composeResourceUrl(adminRoot, realm, "clients/" + cid + "/roles");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (isRoleSpecified()) {
|
if (isRoleSpecified()) {
|
||||||
|
@ -271,14 +257,14 @@ public class GetRolesCmd extends GetCmd {
|
||||||
if (rolename == null) {
|
if (rolename == null) {
|
||||||
rolename = RoleOperations.getClientRoleNameFromId(adminRoot, realm, auth, cid, roleid);
|
rolename = RoleOperations.getClientRoleNameFromId(adminRoot, realm, auth, cid, roleid);
|
||||||
}
|
}
|
||||||
super.url = composeResourceUrl(adminRoot, realm, "roles/" + rolename);
|
super.uri = composeResourceUrl(adminRoot, realm, "roles/" + rolename);
|
||||||
} else {
|
} else {
|
||||||
// list defined realm roles
|
// 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() {
|
private boolean isRoleSpecified() {
|
||||||
|
@ -301,14 +287,12 @@ public class GetRolesCmd extends GetCmd {
|
||||||
return uid != null || uusername != null;
|
return uid != null || uusername != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String suggestHelp() {
|
@Override
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean nothingToDo() {
|
protected boolean nothingToDo() {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected String help() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,35 +16,25 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.client.admin.cli.commands;
|
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 java.util.List;
|
||||||
|
|
||||||
|
import picocli.CommandLine.Command;
|
||||||
|
import picocli.CommandLine.Parameters;
|
||||||
|
|
||||||
import static org.keycloak.client.admin.cli.util.IoUtil.printOut;
|
import static org.keycloak.client.admin.cli.util.IoUtil.printOut;
|
||||||
|
|
||||||
|
@Command(name = "help", description = "This Help")
|
||||||
|
public class HelpCmd implements Runnable {
|
||||||
|
|
||||||
/**
|
@Parameters
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
|
||||||
*/
|
|
||||||
@CommandDefinition(name = "help", description = "This help")
|
|
||||||
public class HelpCmd implements Command {
|
|
||||||
|
|
||||||
@Arguments
|
|
||||||
List<String> args;
|
List<String> args;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
public void run() {
|
||||||
try {
|
|
||||||
if (args == null || args.size() == 0) {
|
if (args == null || args.size() == 0) {
|
||||||
printOut(KcAdmCmd.usage());
|
printOut(KcAdmCmd.usage());
|
||||||
} else {
|
} else {
|
||||||
outer:
|
outer: switch (args.get(0)) {
|
||||||
switch (args.get(0)) {
|
|
||||||
case "config": {
|
case "config": {
|
||||||
if (args.size() > 1) {
|
if (args.size() > 1) {
|
||||||
switch (args.get(1)) {
|
switch (args.get(1)) {
|
||||||
|
@ -93,15 +83,14 @@ public class HelpCmd implements Command {
|
||||||
printOut(SetPasswordCmd.usage());
|
printOut(SetPasswordCmd.usage());
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case "new-object": {
|
||||||
|
printOut(NewObjectCmd.usage());
|
||||||
|
break;
|
||||||
|
}
|
||||||
default: {
|
default: {
|
||||||
throw new RuntimeException("Unknown command: " + args.get(0));
|
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;
|
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.PrintWriter;
|
||||||
import java.io.StringWriter;
|
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.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.CMD;
|
||||||
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||||
|
|
||||||
|
@Command(name = "kcadm",
|
||||||
/**
|
header = {
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
"Keycloak - Open Source Identity and Access Management",
|
||||||
*/
|
"",
|
||||||
|
"Find more information at: https://www.keycloak.org/docs/latest"
|
||||||
@GroupCommandDefinition(name = "kcadm", description = "COMMAND [ARGUMENTS]", groupCommands = {
|
},
|
||||||
HelpCmd.class, ConfigCmd.class, NewObjectCmd.class, CreateCmd.class, GetCmd.class, UpdateCmd.class, DeleteCmd.class,
|
description = {
|
||||||
AddRolesCmd.class, RemoveRolesCmd.class, GetRolesCmd.class, SetPasswordCmd.class} )
|
"%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 {
|
public class KcAdmCmd extends AbstractGlobalOptionsCmd {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
protected boolean nothingToDo() {
|
||||||
try {
|
return true;
|
||||||
// if --help was requested then status is SUCCESS
|
|
||||||
// if not we print help anyway, but status is FAILURE
|
|
||||||
if (printHelp()) {
|
|
||||||
return CommandResult.SUCCESS;
|
|
||||||
} else if (args != null && args.size() > 0) {
|
|
||||||
printErr("Unknown command: " + args.get(0));
|
|
||||||
return CommandResult.FAILURE;
|
|
||||||
} else {
|
|
||||||
printOut(usage());
|
|
||||||
return CommandResult.FAILURE;
|
|
||||||
}
|
|
||||||
} finally {
|
|
||||||
commandInvocation.stop();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String usage() {
|
public static String usage() {
|
||||||
|
|
|
@ -17,11 +17,10 @@
|
||||||
package org.keycloak.client.admin.cli.commands;
|
package org.keycloak.client.admin.cli.commands;
|
||||||
|
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import org.jboss.aesh.cl.CommandDefinition;
|
|
||||||
import org.jboss.aesh.cl.Option;
|
import picocli.CommandLine.Command;
|
||||||
import org.jboss.aesh.console.command.CommandException;
|
import picocli.CommandLine.Option;
|
||||||
import org.jboss.aesh.console.command.CommandResult;
|
|
||||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|
||||||
import org.keycloak.client.admin.cli.common.AttributeOperation;
|
import org.keycloak.client.admin.cli.common.AttributeOperation;
|
||||||
import org.keycloak.client.admin.cli.common.CmdStdinContext;
|
import org.keycloak.client.admin.cli.common.CmdStdinContext;
|
||||||
import org.keycloak.client.admin.cli.util.AccessibleBufferOutputStream;
|
import org.keycloak.client.admin.cli.util.AccessibleBufferOutputStream;
|
||||||
|
@ -32,15 +31,14 @@ import java.io.InputStream;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.Iterator;
|
import java.util.ArrayList;
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
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.common.AttributeOperation.Type.SET;
|
||||||
import static org.keycloak.client.admin.cli.util.IoUtil.copyStream;
|
import static org.keycloak.client.admin.cli.util.IoUtil.copyStream;
|
||||||
import static org.keycloak.client.admin.cli.util.IoUtil.printErr;
|
import static org.keycloak.client.admin.cli.util.IoUtil.printErr;
|
||||||
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
|
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.OS_ARCH;
|
||||||
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||||
import static org.keycloak.client.admin.cli.util.OutputUtil.MAPPER;
|
import static org.keycloak.client.admin.cli.util.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>
|
* @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 {
|
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;
|
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;
|
boolean compressed;
|
||||||
|
|
||||||
//@OptionGroup(shortName = 's', name = "set", description = "Set attribute to the specified value")
|
@Option(names = {"-s", "--set"}, description = "Set a specific attribute NAME to a specified value VALUE")
|
||||||
//Map<String, String> attributes = new LinkedHashMap<>();
|
List<String> values = new ArrayList<>();
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
public void process() {
|
||||||
try {
|
List<AttributeOperation> attrs = values.stream().map(it -> {
|
||||||
if (printHelp()) {
|
String[] keyVal = parseKeyVal(it);
|
||||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
return new AttributeOperation(SET, keyVal[0], keyVal[1]);
|
||||||
}
|
}).collect(Collectors.toList());
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
InputStream body = null;
|
InputStream body = null;
|
||||||
|
|
||||||
|
@ -142,20 +105,14 @@ public class NewObjectCmd extends AbstractGlobalOptionsCmd {
|
||||||
if (lastByte != -1 && lastByte != 13 && lastByte != 10) {
|
if (lastByte != -1 && lastByte != 13 && lastByte != 10) {
|
||||||
printErr("");
|
printErr("");
|
||||||
}
|
}
|
||||||
|
|
||||||
return CommandResult.SUCCESS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean nothingToDo() {
|
protected boolean nothingToDo() {
|
||||||
return file == null && (args == null || args.size() == 0);
|
return file == null && values.isEmpty();
|
||||||
}
|
|
||||||
|
|
||||||
protected String suggestHelp() {
|
|
||||||
return EOL + "Try '" + CMD + " help create' for more information";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected String help() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,113 +16,85 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.client.admin.cli.commands;
|
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.config.ConfigData;
|
||||||
import org.keycloak.client.admin.cli.operations.ClientOperations;
|
import org.keycloak.client.admin.cli.operations.ClientOperations;
|
||||||
import org.keycloak.client.admin.cli.operations.GroupOperations;
|
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.LocalSearch;
|
||||||
|
import org.keycloak.client.admin.cli.operations.RoleOperations;
|
||||||
import org.keycloak.client.admin.cli.operations.UserOperations;
|
import org.keycloak.client.admin.cli.operations.UserOperations;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
import java.io.StringWriter;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.LinkedList;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
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.AuthUtil.ensureToken;
|
||||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
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.credentialsAvailable;
|
||||||
import static org.keycloak.client.admin.cli.util.ConfigUtil.loadConfig;
|
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.CMD;
|
||||||
import static org.keycloak.client.admin.cli.util.OsUtil.EOL;
|
|
||||||
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
@CommandDefinition(name = "remove-roles", description = "[ARGUMENTS]")
|
@Command(name = "remove-roles", description = "[ARGUMENTS]")
|
||||||
public class RemoveRolesCmd extends AbstractAuthOptionsCmd {
|
public class RemoveRolesCmd extends AbstractAuthOptionsCmd {
|
||||||
|
|
||||||
@Option(name = "uusername", description = "Target user's 'username'")
|
@Option(names = "--uusername", description = "Target user's 'username'")
|
||||||
String uusername;
|
String uusername;
|
||||||
|
|
||||||
@Option(name = "uid", description = "Target user's 'id'")
|
@Option(names = "--uid", description = "Target user's 'id'")
|
||||||
String uid;
|
String uid;
|
||||||
|
|
||||||
@Option(name = "gname", description = "Target group's 'name'")
|
@Option(names = "--gname", description = "Target group's 'name'")
|
||||||
String gname;
|
String gname;
|
||||||
|
|
||||||
@Option(name = "gpath", description = "Target group's 'path'")
|
@Option(names = "--gpath", description = "Target group's 'path'")
|
||||||
String gpath;
|
String gpath;
|
||||||
|
|
||||||
@Option(name = "gid", description = "Target group's 'id'")
|
@Option(names = "--gid", description = "Target group's 'id'")
|
||||||
String gid;
|
String gid;
|
||||||
|
|
||||||
@Option(name = "rname", description = "Composite role's 'name'")
|
@Option(names = "--rname", description = "Composite role's 'name'")
|
||||||
String rname;
|
String rname;
|
||||||
|
|
||||||
@Option(name = "rid", description = "Composite role's 'id'")
|
@Option(names = "--rid", description = "Composite role's 'id'")
|
||||||
String rid;
|
String rid;
|
||||||
|
|
||||||
@Option(name = "cclientid", description = "Target client's 'clientId'")
|
@Option(names = "--cclientid", description = "Target client's 'clientId'")
|
||||||
String cclientid;
|
String cclientid;
|
||||||
|
|
||||||
@Option(name = "cid", description = "Target client's 'id'")
|
@Option(names = "--cid", description = "Target client's 'id'")
|
||||||
String cid;
|
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
|
@Override
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
protected void process() {
|
||||||
|
|
||||||
List<String> roleNames = new LinkedList<>();
|
|
||||||
List<String> roleIds = new LinkedList<>();
|
|
||||||
|
|
||||||
try {
|
|
||||||
if (printHelp()) {
|
|
||||||
return help ? CommandResult.SUCCESS : CommandResult.FAILURE;
|
|
||||||
}
|
|
||||||
|
|
||||||
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) {
|
if (uid != null && uusername != null) {
|
||||||
throw new IllegalArgumentException("Incompatible options: --uid and --uusername are mutually exclusive");
|
throw new IllegalArgumentException("Incompatible options: --uid and --uusername are mutually exclusive");
|
||||||
}
|
}
|
||||||
|
|
||||||
if ((gid != null && gname != null) || (gid != null && gpath != null) || (gname != null && gpath != null)) {
|
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");
|
throw new IllegalArgumentException(
|
||||||
|
"Incompatible options: --gid, --gname and --gpath are mutually exclusive");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (roleNames.isEmpty() && roleIds.isEmpty()) {
|
if (roleNames.isEmpty() && roleIds.isEmpty()) {
|
||||||
throw new IllegalArgumentException("No role to remove specified. Use --rolename or --roleid to specify roles to remove");
|
throw new IllegalArgumentException(
|
||||||
|
"No role to remove specified. Use --rolename or --roleid to specify roles to remove");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (cid != null && cclientid != null) {
|
if (cid != null && cclientid != null) {
|
||||||
|
@ -134,30 +106,33 @@ public class RemoveRolesCmd extends AbstractAuthOptionsCmd {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isUserSpecified() && isGroupSpecified()) {
|
if (isUserSpecified() && isGroupSpecified()) {
|
||||||
throw new IllegalArgumentException("Incompatible options: --uusername / --uid can't be used at the same time as --gname / --gid / --gpath");
|
throw new IllegalArgumentException(
|
||||||
|
"Incompatible options: --uusername / --uid can't be used at the same time as --gname / --gid / --gpath");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isUserSpecified() && isCompositeRoleSpecified()) {
|
if (isUserSpecified() && isCompositeRoleSpecified()) {
|
||||||
throw new IllegalArgumentException("Incompatible options: --uusername / --uid can't be used at the same time as --rname / --rid");
|
throw new IllegalArgumentException(
|
||||||
|
"Incompatible options: --uusername / --uid can't be used at the same time as --rname / --rid");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isGroupSpecified() && isCompositeRoleSpecified()) {
|
if (isGroupSpecified() && isCompositeRoleSpecified()) {
|
||||||
throw new IllegalArgumentException("Incompatible options: --rname / --rid can't be used at the same time as --gname / --gid / --gpath");
|
throw new IllegalArgumentException(
|
||||||
|
"Incompatible options: --rname / --rid can't be used at the same time as --gname / --gid / --gpath");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isUserSpecified() && !isGroupSpecified() && !isCompositeRoleSpecified()) {
|
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");
|
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();
|
ConfigData config = loadConfig();
|
||||||
config = copyWithServerInfo(config);
|
config = copyWithServerInfo(config);
|
||||||
|
|
||||||
setupTruststore(config, commandInvocation);
|
setupTruststore(config);
|
||||||
|
|
||||||
String auth = null;
|
String auth = null;
|
||||||
|
|
||||||
config = ensureAuthInfo(config, commandInvocation);
|
config = ensureAuthInfo(config);
|
||||||
config = copyWithServerInfo(config);
|
config = copyWithServerInfo(config);
|
||||||
if (credentialsAvailable(config)) {
|
if (credentialsAvailable(config)) {
|
||||||
auth = ensureToken(config);
|
auth = ensureToken(config);
|
||||||
|
@ -169,7 +144,6 @@ public class RemoveRolesCmd extends AbstractAuthOptionsCmd {
|
||||||
final String realm = getTargetRealm(config);
|
final String realm = getTargetRealm(config);
|
||||||
final String adminRoot = adminRestRoot != null ? adminRestRoot : composeAdminRoot(server);
|
final String adminRoot = adminRestRoot != null ? adminRestRoot : composeAdminRoot(server);
|
||||||
|
|
||||||
|
|
||||||
if (isUserSpecified()) {
|
if (isUserSpecified()) {
|
||||||
if (uid == null) {
|
if (uid == null) {
|
||||||
uid = UserOperations.getIdFromUsername(adminRoot, realm, auth, uusername);
|
uid = UserOperations.getIdFromUsername(adminRoot, realm, auth, uusername);
|
||||||
|
@ -247,19 +221,13 @@ public class RemoveRolesCmd extends AbstractAuthOptionsCmd {
|
||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} 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");
|
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");
|
||||||
|
|
||||||
return CommandResult.SUCCESS;
|
|
||||||
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
throw new IllegalArgumentException(e.getMessage() + suggestHelp(), e);
|
|
||||||
} finally {
|
|
||||||
commandInvocation.stop();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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<>();
|
Set<ObjectNode> rolesToAdd = new HashSet<>();
|
||||||
|
|
||||||
// now we process roles
|
// now we process roles
|
||||||
|
@ -280,12 +248,6 @@ public class RemoveRolesCmd extends AbstractAuthOptionsCmd {
|
||||||
return rolesToAdd;
|
return rolesToAdd;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void optionRequiresValueCheck(Iterator<String> it, String option) {
|
|
||||||
if (!it.hasNext()) {
|
|
||||||
throw new IllegalArgumentException("Option " + option + " requires a value");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean isClientSpecified() {
|
private boolean isClientSpecified() {
|
||||||
return cid != null || cclientid != null;
|
return cid != null || cclientid != null;
|
||||||
}
|
}
|
||||||
|
@ -304,13 +266,11 @@ public class RemoveRolesCmd extends AbstractAuthOptionsCmd {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean nothingToDo() {
|
protected boolean nothingToDo() {
|
||||||
return noOptions() && uusername == null && uid == null && cclientid == null && (args == null || args.size() == 0);
|
return super.nothingToDo() && uusername == null && uid == null && cclientid == null
|
||||||
}
|
&& roleIds.isEmpty() && roleNames.isEmpty();
|
||||||
|
|
||||||
protected String suggestHelp() {
|
|
||||||
return EOL + "Try '" + CMD + " help remove-roles' for more information";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
protected String help() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,16 +16,14 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.client.admin.cli.commands;
|
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.config.ConfigData;
|
||||||
|
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.io.StringWriter;
|
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.getIdFromUsername;
|
||||||
import static org.keycloak.client.admin.cli.operations.UserOperations.resetUserPassword;
|
import static org.keycloak.client.admin.cli.operations.UserOperations.resetUserPassword;
|
||||||
import static org.keycloak.client.admin.cli.util.AuthUtil.ensureToken;
|
import static org.keycloak.client.admin.cli.util.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.ConfigUtil.loadConfig;
|
||||||
import static org.keycloak.client.admin.cli.util.IoUtil.readSecret;
|
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.CMD;
|
||||||
import static org.keycloak.client.admin.cli.util.OsUtil.EOL;
|
|
||||||
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
@CommandDefinition(name = "set-password", description = "[ARGUMENTS]")
|
@Command(name = "set-password", description = "[ARGUMENTS]")
|
||||||
public class SetPasswordCmd extends AbstractAuthOptionsCmd {
|
public class SetPasswordCmd extends AbstractAuthOptionsCmd {
|
||||||
|
|
||||||
@Option(name = "username", description = "Username")
|
@Option(names = "--username", description = "Username")
|
||||||
String username;
|
String username;
|
||||||
|
|
||||||
@Option(name = "userid", description = "User ID")
|
@Option(names = "--userid", description = "User ID")
|
||||||
String userid;
|
String userid;
|
||||||
|
|
||||||
@Option(shortName = 'p', name = "new-password", description = "New password")
|
@Option(names = {"-p", "--new-password"}, description = "New password")
|
||||||
String pass;
|
String pass;
|
||||||
|
|
||||||
@Option(shortName = 't', name = "temporary", description = "is password temporary", hasValue = false)
|
@Option(names = {"-t", "--temporary"}, description = "is password temporary")
|
||||||
boolean temporary;
|
boolean temporary;
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public CommandResult execute(CommandInvocation commandInvocation) throws CommandException, InterruptedException {
|
protected void process() {
|
||||||
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));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (userid == null && username == null) {
|
if (userid == null && username == null) {
|
||||||
throw new IllegalArgumentException("No user specified. Use --username or --userid to specify user");
|
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) {
|
if (pass == null) {
|
||||||
pass = readSecret("Enter password: ", commandInvocation);
|
pass = readSecret("Enter password: ");
|
||||||
}
|
}
|
||||||
|
|
||||||
ConfigData config = loadConfig();
|
ConfigData config = loadConfig();
|
||||||
config = copyWithServerInfo(config);
|
config = copyWithServerInfo(config);
|
||||||
|
|
||||||
setupTruststore(config, commandInvocation);
|
setupTruststore(config);
|
||||||
|
|
||||||
String auth = null;
|
String auth = null;
|
||||||
|
|
||||||
config = ensureAuthInfo(config, commandInvocation);
|
config = ensureAuthInfo(config);
|
||||||
config = copyWithServerInfo(config);
|
config = copyWithServerInfo(config);
|
||||||
if (credentialsAvailable(config)) {
|
if (credentialsAvailable(config)) {
|
||||||
auth = ensureToken(config);
|
auth = ensureToken(config);
|
||||||
|
@ -117,20 +91,14 @@ public class SetPasswordCmd extends AbstractAuthOptionsCmd {
|
||||||
}
|
}
|
||||||
|
|
||||||
resetUserPassword(adminRoot, realm, auth, userid, pass, temporary);
|
resetUserPassword(adminRoot, realm, auth, userid, pass, temporary);
|
||||||
|
|
||||||
return CommandResult.SUCCESS;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean nothingToDo() {
|
protected boolean nothingToDo() {
|
||||||
return noOptions() && username == null && userid == null && pass == null;
|
return super.nothingToDo() && username == null && userid == null && pass == null;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected String suggestHelp() {
|
@Override
|
||||||
return EOL + "Try '" + CMD + " help set-password' for more information";
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
protected String help() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,77 +17,68 @@
|
||||||
|
|
||||||
package org.keycloak.client.admin.cli.commands;
|
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.PrintWriter;
|
||||||
import java.io.StringWriter;
|
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.ConfigUtil.DEFAULT_CONFIG_FILE_STRING;
|
||||||
import static org.keycloak.client.admin.cli.util.OsUtil.CMD;
|
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.OS_ARCH;
|
||||||
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
import static org.keycloak.client.admin.cli.util.OsUtil.PROMPT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
*/
|
*/
|
||||||
@CommandDefinition(name = "update", description = "CLIENT_ID [ARGUMENTS]")
|
@Command(name = "update", description = "CLIENT_ID [ARGUMENTS]")
|
||||||
public class UpdateCmd extends AbstractRequestCmd {
|
public class UpdateCmd extends AbstractRequestCmd {
|
||||||
|
|
||||||
@Option(shortName = 'f', name = "file", description = "Read object from file or standard input if FILENAME is set to '-'")
|
public UpdateCmd() {
|
||||||
String file;
|
this.httpVerb = "put";
|
||||||
|
}
|
||||||
|
|
||||||
@Option(shortName = 'b', name = "body", description = "JSON object to be sent as-is or used as a template")
|
@Option(names = {"-f", "--file"}, description = "Read object from file or standard input if FILENAME is set to '-'")
|
||||||
String body;
|
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")
|
@Option(names = {"-b", "--body"}, description = "JSON object to be sent as-is or used as a template")
|
||||||
String fields;
|
public void setBody(String body) {
|
||||||
|
this.body = body;
|
||||||
|
}
|
||||||
|
|
||||||
@Option(shortName = 'H', name = "print-headers", description = "Print response headers", hasValue = false)
|
@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")
|
||||||
boolean printHeaders;
|
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)
|
@Option(names = {"-H", "--print-headers"}, description = "Print response headers")
|
||||||
boolean mergeMode;
|
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)
|
@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)")
|
||||||
boolean noMerge;
|
public void setMergeMode(boolean mergeMode) {
|
||||||
|
this.mergeMode = mergeMode;
|
||||||
|
}
|
||||||
|
|
||||||
@Option(shortName = 'o', name = "output", description = "After update output the new client configuration", hasValue = false)
|
@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)")
|
||||||
boolean outputResult;
|
public void setNoMerge(boolean noMerge) {
|
||||||
|
this.noMerge = noMerge;
|
||||||
|
}
|
||||||
|
|
||||||
@Option(shortName = 'c', name = "compressed", description = "Don't pretty print the output", hasValue = false)
|
@Option(names = {"-o", "--output"}, description = "After update output the new client configuration")
|
||||||
boolean compressed;
|
public void setOutputResult(boolean outputResult) {
|
||||||
|
this.outputResult = outputResult;
|
||||||
|
}
|
||||||
|
|
||||||
//@GroupOption(shortName = 's', name = "set", description = "Set specific attribute to a specified value", hasValue = true)
|
@Option(names = {"-c", "--compressed"}, description = "Don't pretty print the output")
|
||||||
//private List<String> attributes = new ArrayList<>();
|
public void setCompressed(boolean compressed) {
|
||||||
|
this.compressed = compressed;
|
||||||
|
|
||||||
@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";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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() {
|
protected String help() {
|
||||||
return usage();
|
return usage();
|
||||||
}
|
}
|
||||||
|
|
|
@ -69,7 +69,7 @@ public class ConfigUtil {
|
||||||
|
|
||||||
public static void checkServerInfo(ConfigData config) {
|
public static void checkServerInfo(ConfigData config) {
|
||||||
if (config.getServerUrl() == null) {
|
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) {
|
if (config.getRealm() == null && config.getExternalToken() == null) {
|
||||||
throw new RuntimeException("No realm or token specified. Use --realm, --token, or '" + OsUtil.CMD + " config credentials'.");
|
throw new RuntimeException("No realm or token specified. Use --realm, --token, or '" + OsUtil.CMD + " config credentials'.");
|
||||||
|
|
|
@ -16,14 +16,7 @@
|
||||||
*/
|
*/
|
||||||
package org.keycloak.client.admin.cli.util;
|
package org.keycloak.client.admin.cli.util;
|
||||||
|
|
||||||
import org.jboss.aesh.console.AeshConsoleBufferBuilder;
|
import java.io.Console;
|
||||||
import org.jboss.aesh.console.AeshInputProcessorBuilder;
|
|
||||||
import org.jboss.aesh.console.ConsoleBuffer;
|
|
||||||
import org.jboss.aesh.console.InputProcessor;
|
|
||||||
import org.jboss.aesh.console.Prompt;
|
|
||||||
import org.jboss.aesh.console.command.invocation.CommandInvocation;
|
|
||||||
import org.keycloak.client.admin.cli.aesh.Globals;
|
|
||||||
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
@ -50,7 +43,6 @@ import static java.nio.file.Files.createDirectories;
|
||||||
import static java.nio.file.Files.createFile;
|
import static java.nio.file.Files.createFile;
|
||||||
import static java.nio.file.Files.isDirectory;
|
import static java.nio.file.Files.isDirectory;
|
||||||
import static java.nio.file.Files.isRegularFile;
|
import static java.nio.file.Files.isRegularFile;
|
||||||
import static org.keycloak.client.admin.cli.util.OsUtil.OS_ARCH;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
* @author <a href="mailto:mstrukel@redhat.com">Marko Strukelj</a>
|
||||||
|
@ -81,43 +73,16 @@ public class IoUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String readSecret(String prompt, CommandInvocation invocation) {
|
public static String readSecret(String prompt) {
|
||||||
|
Console cons = System.console();
|
||||||
// TODO Windows hack - masking not working on Windows
|
if (cons == null) {
|
||||||
char maskChar = OS_ARCH.isWindows() ? 0 : '*';
|
throw new RuntimeException("Console is not active, but a password is required");
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
/*
|
char[] passwd;
|
||||||
if (!Globals.stdin.isStdinAvailable()) {
|
if ((passwd = cons.readPassword("%s", prompt)) != null) {
|
||||||
try {
|
return new String(passwd);
|
||||||
return readLine(new InputStreamReader(System.in));
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new RuntimeException("Standard input not available");
|
|
||||||
}
|
}
|
||||||
}
|
throw new RuntimeException("No password provided");
|
||||||
*/
|
|
||||||
// Windows hack - get rid of any \n
|
|
||||||
result = result.replaceAll("\\n", "");
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String readFully(InputStream is) {
|
public static String readFully(InputStream is) {
|
||||||
|
|
|
@ -38,7 +38,7 @@ public class ParseUtil {
|
||||||
// we expect = as a separator
|
// we expect = as a separator
|
||||||
int pos = keyval.indexOf("=");
|
int pos = keyval.indexOf("=");
|
||||||
if (pos <= 0) {
|
if (pos <= 0) {
|
||||||
throw new RuntimeException("Invalid key=value parameter: [" + keyval + "]");
|
throw new IllegalArgumentException("Invalid key=value parameter: [" + keyval + "]");
|
||||||
}
|
}
|
||||||
|
|
||||||
String [] parsed = new String[2];
|
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) {
|
public void assertExitCodeAndStreamSizes(AbstractExec exe, int exitCode, int stdOutLineCount, int stdErrLineCount) {
|
||||||
Assert.assertEquals("exitCode == " + exitCode, exitCode, exe.exitCode());
|
Assert.assertEquals("exitCode == " + exitCode, exitCode, exe.exitCode());
|
||||||
if (stdOutLineCount != -1) {
|
if (stdOutLineCount != -1) {
|
||||||
try {
|
assertLineCount("STDOUT: " + exe.stdoutString(), exe.stdoutLines(), stdOutLineCount);
|
||||||
assertLineCount("stdout output", exe.stdoutLines(), stdOutLineCount);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
throw new AssertionError("STDOUT: " + exe.stdoutString(), e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// 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
|
// 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
|
// Hence we test count of lines just with FIPS disabled
|
||||||
if (stdErrLineCount != -1 && isFipsDisabled()) {
|
if (stdErrLineCount != -1 && isFipsDisabled()) {
|
||||||
try {
|
assertLineCount("STDERR: " + exe.stderrString(), exe.stderrLines(), stdErrLineCount);
|
||||||
assertLineCount("stderr output", exe.stderrLines(), stdErrLineCount);
|
|
||||||
} catch (Throwable e) {
|
|
||||||
throw new AssertionError("STDERR: " + exe.stderrString(), e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,9 +29,9 @@ public class KcAdmSessionTest extends AbstractAdmCliTest {
|
||||||
@Test
|
@Test
|
||||||
public void test() throws IOException {
|
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
|
// login as admin
|
||||||
loginAsUser(configFile.getFile(), serverUrl, "master", "admin", "admin");
|
loginAsUser(configFile.getFile(), serverUrl, "master", "admin", "admin");
|
||||||
|
@ -196,8 +196,8 @@ public class KcAdmSessionTest extends AbstractAdmCliTest {
|
||||||
@Test
|
@Test
|
||||||
public void testCompositeRoleCreationWithHigherVolumeOfRoles() throws Exception {
|
public void testCompositeRoleCreationWithHigherVolumeOfRoles() throws Exception {
|
||||||
|
|
||||||
FileConfigHandler handler = initCustomConfigFile();
|
initCustomConfigFile();
|
||||||
try (TempFileResource configFile = new TempFileResource(handler.getConfigFile())) {
|
try (TempFileResource configFile = new TempFileResource(FileConfigHandler.getConfigFile())) {
|
||||||
|
|
||||||
// login as admin
|
// login as admin
|
||||||
loginAsUser(configFile.getFile(), serverUrl, "master", "admin", "admin");
|
loginAsUser(configFile.getFile(), serverUrl, "master", "admin", "admin");
|
||||||
|
|
|
@ -34,8 +34,8 @@ public class KcAdmTest extends AbstractAdmCliTest {
|
||||||
*/
|
*/
|
||||||
KcAdmExec exe = execute("nonexistent");
|
KcAdmExec exe = execute("nonexistent");
|
||||||
|
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 1);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 3);
|
||||||
Assert.assertEquals("stderr first line", "Unknown command: nonexistent", exe.stderrLines().get(0));
|
Assert.assertEquals("stderr first line", "Unmatched argument at index 0: 'nonexistent'", exe.stderrLines().get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -46,7 +46,7 @@ public class KcAdmTest extends AbstractAdmCliTest {
|
||||||
*/
|
*/
|
||||||
KcAdmExec exe = KcAdmExec.execute("");
|
KcAdmExec exe = KcAdmExec.execute("");
|
||||||
|
|
||||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||||
|
|
||||||
List<String> lines = exe.stdoutLines();
|
List<String> lines = exe.stdoutLines();
|
||||||
Assert.assertTrue("stdout output not empty", lines.size() > 0);
|
Assert.assertTrue("stdout output not empty", lines.size() > 0);
|
||||||
|
@ -59,41 +59,41 @@ public class KcAdmTest extends AbstractAdmCliTest {
|
||||||
* Test commands without arguments
|
* Test commands without arguments
|
||||||
*/
|
*/
|
||||||
exe = KcAdmExec.execute("config");
|
exe = KcAdmExec.execute("config");
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 1);
|
assertExitCodeAndStreamSizes(exe, 2, 8, 0);
|
||||||
Assert.assertEquals("error message",
|
Assert.assertEquals("error message",
|
||||||
"Sub-command required by '" + CMD + " config' - one of: 'credentials', 'truststore'",
|
"Usage: kcadm.sh config SUB_COMMAND [ARGUMENTS]",
|
||||||
exe.stderrLines().get(0));
|
exe.stdoutLines().get(0));
|
||||||
|
|
||||||
exe = KcAdmExec.execute("config credentials");
|
exe = KcAdmExec.execute("config credentials");
|
||||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||||
Assert.assertEquals("help message", "Usage: " + CMD + " config credentials --server SERVER_URL --realm REALM --user USER [--password PASSWORD] [ARGUMENTS]", exe.stdoutLines().get(0));
|
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");
|
exe = KcAdmExec.execute("config truststore");
|
||||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||||
Assert.assertEquals("help message", "Usage: " + CMD + " config truststore [TRUSTSTORE | --delete] [--trustpass PASSWORD] [ARGUMENTS]", exe.stdoutLines().get(0));
|
Assert.assertEquals("help message", "Usage: " + CMD + " config truststore [TRUSTSTORE | --delete] [--trustpass PASSWORD] [ARGUMENTS]", exe.stdoutLines().get(0));
|
||||||
|
|
||||||
exe = KcAdmExec.execute("create");
|
exe = KcAdmExec.execute("create");
|
||||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||||
Assert.assertEquals("help message", "Usage: " + CMD + " create ENDPOINT_URI [ARGUMENTS]", exe.stdoutLines().get(0));
|
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));
|
//Assert.assertEquals("error message", "No file nor attribute values specified", exe.stderrLines().get(0));
|
||||||
|
|
||||||
exe = KcAdmExec.execute("get");
|
exe = KcAdmExec.execute("get");
|
||||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||||
Assert.assertEquals("help message", "Usage: " + CMD + " get ENDPOINT_URI [ARGUMENTS]", exe.stdoutLines().get(0));
|
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));
|
//Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0));
|
||||||
|
|
||||||
exe = KcAdmExec.execute("update");
|
exe = KcAdmExec.execute("update");
|
||||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||||
Assert.assertEquals("help message", "Usage: " + CMD + " update ENDPOINT_URI [ARGUMENTS]", exe.stdoutLines().get(0));
|
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));
|
//Assert.assertEquals("error message", "No file nor attribute values specified", exe.stderrLines().get(0));
|
||||||
|
|
||||||
exe = KcAdmExec.execute("delete");
|
exe = KcAdmExec.execute("delete");
|
||||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||||
Assert.assertEquals("help message", "Usage: " + CMD + " delete ENDPOINT_URI [ARGUMENTS]", exe.stdoutLines().get(0));
|
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));
|
//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));
|
//Assert.assertEquals("help message", "Usage: " + CMD + " get-roles [--cclientid CLIENT_ID | --cid ID] [ARGUMENTS]", exe.stdoutLines().get(0));
|
||||||
|
|
||||||
exe = KcAdmExec.execute("add-roles");
|
exe = KcAdmExec.execute("add-roles");
|
||||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||||
Assert.assertEquals("help message", "Usage: " + CMD + " add-roles (--uusername USERNAME | --uid ID) [--cclientid CLIENT_ID | --cid ID] (--rolename NAME | --roleid ID)+ [ARGUMENTS]", exe.stdoutLines().get(0));
|
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));
|
//Assert.assertEquals("error message", "CLIENT not specified", exe.stderrLines().get(0));
|
||||||
|
|
||||||
exe = KcAdmExec.execute("remove-roles");
|
exe = KcAdmExec.execute("remove-roles");
|
||||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||||
Assert.assertEquals("help message", "Usage: " + CMD + " remove-roles (--uusername USERNAME | --uid ID) [--cclientid CLIENT_ID | --cid ID] (--rolename NAME | --roleid ID)+ [ARGUMENTS]", exe.stdoutLines().get(0));
|
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");
|
exe = KcAdmExec.execute("set-password");
|
||||||
assertExitCodeAndStdErrSize(exe, 1, 0);
|
assertExitCodeAndStdErrSize(exe, 2, 0);
|
||||||
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
Assert.assertTrue("help message returned", exe.stdoutLines().size() > 10);
|
||||||
Assert.assertEquals("help message", "Usage: " + CMD + " set-password (--username USERNAME | --userid ID) [--new-password PASSWORD] [ARGUMENTS]", exe.stdoutLines().get(0));
|
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));
|
//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");
|
KcAdmExec exe = KcAdmExec.execute("--nonexistent");
|
||||||
|
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 1);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||||
Assert.assertEquals("stderr first line", "Unknown command: --nonexistent", exe.stderrLines().get(0));
|
Assert.assertEquals("stderr first line", "Unknown option: '--nonexistent'", exe.stderrLines().get(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -214,25 +214,23 @@ public class KcAdmTest extends AbstractAdmCliTest {
|
||||||
|
|
||||||
KcAdmExec exe = KcAdmExec.execute("get users --nonexistent");
|
KcAdmExec exe = KcAdmExec.execute("get users --nonexistent");
|
||||||
|
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 3);
|
||||||
Assert.assertEquals("stderr first line", "Invalid option: --nonexistent", exe.stderrLines().get(0));
|
Assert.assertEquals("stderr first line", "Unknown option: '--nonexistent'", exe.stderrLines().get(0));
|
||||||
Assert.assertEquals("try help", "Try '" + CMD + " help get' for more information", exe.stderrLines().get(1));
|
Assert.assertEquals("try help", "Try '" + CMD + " get --help' for more information on the available options.", exe.stderrLines().get(2));
|
||||||
|
|
||||||
// set-password doesn't use @Arguments injection thus unsupported options are handled by Aesh
|
|
||||||
exe = KcAdmExec.execute("set-password --nonexistent");
|
exe = KcAdmExec.execute("set-password --nonexistent");
|
||||||
|
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 3);
|
||||||
Assert.assertEquals("stderr first line", "Invalid option: --nonexistent", exe.stderrLines().get(0));
|
Assert.assertEquals("stderr first line", "Unknown option: '--nonexistent'", exe.stderrLines().get(0));
|
||||||
Assert.assertEquals("try help", "Try '" + CMD + " help set-password' for more information", exe.stderrLines().get(1));
|
Assert.assertEquals("try help", "Try '" + CMD + " set-password --help' for more information on the available options.", exe.stderrLines().get(2));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testBadOverlappingOption() {
|
public void testBadOverlappingOption() {
|
||||||
KcAdmExec exe = KcAdmExec.execute("config credentials --server http://localhost:8080 --realm master --username admin --password admin");
|
KcAdmExec exe = KcAdmExec.execute("config credentials --server http://localhost:8080 --realm master --username admin --password admin");
|
||||||
|
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 1);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 3);
|
||||||
Assert.assertEquals("stderr first line", "Please double check your command options, one or more of them are not specified correctly. "
|
Assert.assertEquals("stderr first line", "Unknown options: '--username', 'admin'", exe.stderrLines().get(0));
|
||||||
+ "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));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -252,9 +250,9 @@ public class KcAdmTest extends AbstractAdmCliTest {
|
||||||
*/
|
*/
|
||||||
KcAdmExec exe = KcAdmExec.execute("config credentials --realm master --user admin --password admin");
|
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("stderr first line", "Required option not specified: --server", exe.stderrLines().get(0));
|
||||||
Assert.assertEquals("try help", "Try '" + CMD + " help config credentials' for more information", exe.stderrLines().get(1));
|
Assert.assertEquals("try help", "Try '" + CMD + " config credentials --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -264,9 +262,9 @@ public class KcAdmTest extends AbstractAdmCliTest {
|
||||||
*/
|
*/
|
||||||
KcAdmExec exe = KcAdmExec.execute("config credentials --server " + serverUrl + " --user admin --password admin");
|
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("stderr first line", "Required option not specified: --realm", exe.stderrLines().get(0));
|
||||||
Assert.assertEquals("try help", "Try '" + CMD + " help config credentials' for more information", exe.stderrLines().get(1));
|
Assert.assertEquals("try help", "Try '" + CMD + " config credentials --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -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");
|
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("stderr first line", "Unsupported option: --no-config", exe.stderrLines().get(0));
|
||||||
Assert.assertEquals("try help", "Try '" + CMD + " help config credentials' for more information", exe.stderrLines().get(1));
|
Assert.assertEquals("try help", "Try '" + CMD + " config credentials --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -396,7 +394,7 @@ public class KcAdmTest extends AbstractAdmCliTest {
|
||||||
*/
|
*/
|
||||||
FileConfigHandler handler = initCustomConfigFile();
|
FileConfigHandler handler = initCustomConfigFile();
|
||||||
|
|
||||||
File configFile = new File(handler.getConfigFile());
|
File configFile = new File(FileConfigHandler.getConfigFile());
|
||||||
try {
|
try {
|
||||||
KcAdmExec exe = KcAdmExec.execute("config credentials --server " + serverUrl + " --realm master" +
|
KcAdmExec exe = KcAdmExec.execute("config credentials --server " + serverUrl + " --realm master" +
|
||||||
" --user admin --password admin --config '" + configFile.getName() + "'");
|
" --user admin --password admin --config '" + configFile.getName() + "'");
|
||||||
|
@ -434,7 +432,7 @@ public class KcAdmTest extends AbstractAdmCliTest {
|
||||||
// prepare for loading a config file
|
// prepare for loading a config file
|
||||||
FileConfigHandler handler = initCustomConfigFile();
|
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 +
|
KcAdmExec exe = KcAdmExec.execute("config credentials --server " + serverUrl +
|
||||||
" --realm master --user admin --password admin --config '" + configFile.getName() + "'");
|
" --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.config.FileConfigHandler;
|
||||||
import org.keycloak.client.admin.cli.util.OsUtil;
|
import org.keycloak.client.admin.cli.util.OsUtil;
|
||||||
import org.keycloak.testsuite.cli.KcAdmExec;
|
import org.keycloak.testsuite.cli.KcAdmExec;
|
||||||
|
import org.keycloak.testsuite.util.OAuthClient;
|
||||||
import org.keycloak.testsuite.util.TempFileResource;
|
import org.keycloak.testsuite.util.TempFileResource;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
@ -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.ConfigUtil.DEFAULT_CONFIG_FILE_PATH;
|
||||||
import static org.keycloak.client.admin.cli.util.OsUtil.EOL;
|
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.util.ServerURLs.AUTH_SERVER_SSL_REQUIRED;
|
||||||
import static org.keycloak.testsuite.cli.KcAdmExec.CMD;
|
|
||||||
import static org.keycloak.testsuite.cli.KcAdmExec.execute;
|
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() + "'");
|
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("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
|
// only run this test if ssl protected keycloak server is available
|
||||||
if (!AUTH_SERVER_SSL_REQUIRED) {
|
if (!AUTH_SERVER_SSL_REQUIRED) {
|
||||||
|
@ -39,9 +39,9 @@ public class KcAdmTruststoreTest extends AbstractAdmCliTest {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FileConfigHandler handler = initCustomConfigFile();
|
initCustomConfigFile();
|
||||||
|
|
||||||
try (TempFileResource configFile = new TempFileResource(handler.getConfigFile())) {
|
try (TempFileResource configFile = new TempFileResource(FileConfigHandler.getConfigFile())) {
|
||||||
|
|
||||||
if (runIntermittentlyFailingTests()) {
|
if (runIntermittentlyFailingTests()) {
|
||||||
// configure truststore
|
// configure truststore
|
||||||
|
@ -52,7 +52,7 @@ public class KcAdmTruststoreTest extends AbstractAdmCliTest {
|
||||||
|
|
||||||
// perform authentication against server - asks for password, then for truststore password
|
// perform authentication against server - asks for password, then for truststore password
|
||||||
exe = KcAdmExec.newBuilder()
|
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() + "'")
|
" --config '" + configFile.getName() + "'")
|
||||||
.executeAsync();
|
.executeAsync();
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ public class KcAdmTruststoreTest extends AbstractAdmCliTest {
|
||||||
|
|
||||||
// perform authentication against server - asks for password, then for truststore password
|
// perform authentication against server - asks for password, then for truststore password
|
||||||
exe = KcAdmExec.newBuilder()
|
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() + "'")
|
" --config '" + configFile.getName() + "'")
|
||||||
.executeAsync();
|
.executeAsync();
|
||||||
|
|
||||||
|
@ -99,17 +99,17 @@ public class KcAdmTruststoreTest extends AbstractAdmCliTest {
|
||||||
assertExitCodeAndStreamSizes(exe, 0, 0, 0);
|
assertExitCodeAndStreamSizes(exe, 0, 0, 0);
|
||||||
|
|
||||||
exe = execute("config truststore --delete '" + truststore.getAbsolutePath() + "'");
|
exe = execute("config truststore --delete '" + truststore.getAbsolutePath() + "'");
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||||
Assert.assertEquals("incompatible", "Option --delete is mutually exclusive with specifying a TRUSTSTORE", exe.stderrLines().get(0));
|
Assert.assertEquals("incompatible", "Option --delete is mutually exclusive with specifying a TRUSTSTORE", exe.stderrLines().get(0));
|
||||||
Assert.assertEquals("try help", "Try '" + CMD + " help config truststore' for more information", exe.stderrLines().get(1));
|
Assert.assertEquals("try help", "Try '" + OsUtil.CMD + " config truststore --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||||
|
|
||||||
exe = execute("config truststore --delete --trustpass secret");
|
exe = execute("config truststore --delete --trustpass secret");
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 2);
|
||||||
Assert.assertEquals("no truststore error", "Options --trustpass and --delete are mutually exclusive", exe.stderrLines().get(0));
|
Assert.assertEquals("no truststore error", "Options --trustpass and --delete are mutually exclusive", exe.stderrLines().get(0));
|
||||||
Assert.assertEquals("try help", "Try '" + CMD + " help config truststore' for more information", exe.stderrLines().get(1));
|
Assert.assertEquals("try help", "Try '" + OsUtil.CMD + " config truststore --help' for more information on the available options.", exe.stderrLines().get(1));
|
||||||
|
|
||||||
FileConfigHandler cfghandler = new FileConfigHandler();
|
FileConfigHandler cfghandler = new FileConfigHandler();
|
||||||
cfghandler.setConfigFile(DEFAULT_CONFIG_FILE_PATH);
|
FileConfigHandler.setConfigFile(DEFAULT_CONFIG_FILE_PATH);
|
||||||
ConfigData config = cfghandler.loadConfig();
|
ConfigData config = cfghandler.loadConfig();
|
||||||
Assert.assertNull("truststore null", config.getTruststore());
|
Assert.assertNull("truststore null", config.getTruststore());
|
||||||
Assert.assertNull("trustpass null", config.getTrustpass());
|
Assert.assertNull("trustpass null", config.getTrustpass());
|
||||||
|
|
|
@ -50,8 +50,8 @@ public class KcAdmUpdateTest extends AbstractAdmCliTest {
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
try (Closeable ipc = new IdentityProviderCreator(realmResource, identityProvider)) {
|
try (Closeable ipc = new IdentityProviderCreator(realmResource, identityProvider)) {
|
||||||
FileConfigHandler handler = initCustomConfigFile();
|
initCustomConfigFile();
|
||||||
try (TempFileResource configFile = new TempFileResource(handler.getConfigFile())) {
|
try (TempFileResource configFile = new TempFileResource(FileConfigHandler.getConfigFile())) {
|
||||||
loginAsUser(configFile.getFile(), serverUrl, realm, "user1", "userpass");
|
loginAsUser(configFile.getFile(), serverUrl, realm, "user1", "userpass");
|
||||||
|
|
||||||
KcAdmExec exe = execute("get identity-provider/instances/idpAlias -r " + realm + " --config " + configFile.getFile());
|
KcAdmExec exe = execute("get identity-provider/instances/idpAlias -r " + realm + " --config " + configFile.getFile());
|
||||||
|
@ -69,9 +69,9 @@ public class KcAdmUpdateTest extends AbstractAdmCliTest {
|
||||||
@Test
|
@Test
|
||||||
public void testUpdateThoroughly() throws IOException {
|
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";
|
final String realm = "test";
|
||||||
|
|
||||||
|
@ -136,9 +136,9 @@ public class KcAdmUpdateTest extends AbstractAdmCliTest {
|
||||||
// check that using an invalid attribute key is not ignored
|
// check that using an invalid attribute key is not ignored
|
||||||
exe = execute("update clients/" + client.getId() + " --nonexisting --config '" + configFile.getName() + "'");
|
exe = execute("update clients/" + client.getId() + " --nonexisting --config '" + configFile.getName() + "'");
|
||||||
|
|
||||||
assertExitCodeAndStreamSizes(exe, 1, 0, 2);
|
assertExitCodeAndStreamSizes(exe, 2, 0, 3);
|
||||||
Assert.assertEquals("error message", "Invalid option: --nonexisting", exe.stderrLines().get(0));
|
Assert.assertEquals("error message", "Unknown option: '--nonexisting'", exe.stderrLines().get(0));
|
||||||
Assert.assertEquals("try help", "Try '" + CMD + " help update' for more information", exe.stderrLines().get(1));
|
Assert.assertEquals("try help", "Try '" + CMD + " update --help' for more information on the available options.", exe.stderrLines().get(2));
|
||||||
|
|
||||||
|
|
||||||
// test overwrite from file
|
// test overwrite from file
|
||||||
|
|
Loading…
Reference in a new issue