fix: updating docs around -q parameter (#29151)

closes: #27877

Signed-off-by: Steve Hawkins <shawkins@redhat.com>
This commit is contained in:
Steven Hawkins 2024-05-02 10:48:43 -04:00 committed by GitHub
parent f3227c325a
commit 3b1ca46be2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 27 additions and 19 deletions

View file

@ -1031,15 +1031,15 @@ You can filter users by `username`, `firstName`, `lastName`, or `email`.
For example: For example:
[options="nowrap"] [options="nowrap"]
---- ----
$ kcadm.sh get users -r demorealm -q email=google.com $ kcadm.sh get users -r demorealm -q q=email:google.com
$ kcadm.sh get users -r demorealm -q username=testuser $ kcadm.sh get users -r demorealm -q q=username:testuser
---- ----
[NOTE] [NOTE]
==== ====
Filtering does not use exact matching. This example matches the value of the `username` attribute against the `\*testuser*` pattern. Filtering does not use exact matching. This example matches the value of the `username` attribute against the `\*testuser*` pattern.
==== ====
You can filter across multiple attributes by specifying multiple `-q` options. {project_name} returns users that match the condition for all the attributes only. For clients, groups, and users you can filter across multiple attributes by specifying a more complex `q` query parameter. you may use something like -q q="field1:value1 field2:value2". {project_name} returns users that match the condition for all the attributes only.
[discrete] [discrete]
==== Getting a specific user ==== Getting a specific user

View file

@ -113,7 +113,7 @@ public abstract class AbstractRequestCmd extends AbstractAuthOptionsCmd {
@ArgGroup(exclusive = true, multiplicity = "0..*") @ArgGroup(exclusive = true, multiplicity = "0..*")
List<AttributeOperations> rawAttributeOperations = new ArrayList<>(); List<AttributeOperations> rawAttributeOperations = new ArrayList<>();
@Option(names = {"-q", "--query"}, description = "Add to request URI a NAME query parameter with value VALUE") @Option(names = {"-q", "--query"}, description = "Add to request URI a NAME query parameter with value VALUE, for example --query q=username:admin")
List<String> rawFilters = new LinkedList<>(); List<String> rawFilters = new LinkedList<>();
@Parameters(arity = "0..1") @Parameters(arity = "0..1")
@ -142,12 +142,7 @@ public abstract class AbstractRequestCmd extends AbstractAuthOptionsCmd {
} }
for (String arg : rawFilters) { for (String arg : rawFilters) {
String[] keyVal; String[] keyVal = parseKeyVal(arg);
if (arg.indexOf("=") == -1) {
keyVal = new String[] {"", arg};
} else {
keyVal = parseKeyVal(arg);
}
filter.put(keyVal[0], keyVal[1]); filter.put(keyVal[0], keyVal[1]);
} }

View file

@ -110,7 +110,7 @@ public class CreateCmd extends AbstractRequestCmd {
out.println(" -d, --delete NAME Remove a specific attribute NAME from JSON request body"); out.println(" -d, --delete NAME Remove a specific attribute NAME from JSON request body");
out.println(" -f, --file FILENAME Read object from file or standard input if FILENAME is set to '-'"); out.println(" -f, --file FILENAME Read object from file or standard input if FILENAME is set to '-'");
out.println(" -b, --body CONTENT Content to be sent as-is or used as a JSON object template"); out.println(" -b, --body CONTENT Content to be sent as-is or used as a JSON object template");
out.println(" -q, --query NAME=VALUE Add to request URI a NAME query parameter with value VALUE"); out.println(" -q, --query NAME=VALUE Add to request URI a NAME query parameter with value VALUE, for example --query q=username:admin");
out.println(" -h, --header NAME=VALUE Set request header NAME to VALUE"); out.println(" -h, --header NAME=VALUE Set request header NAME to VALUE");
out.println(); out.println();
out.println(" -H, --print-headers Print response headers"); out.println(" -H, --print-headers Print response headers");

View file

@ -74,7 +74,7 @@ public class DeleteCmd extends CreateCmd {
out.println(" -d, --delete NAME Remove a specific attribute NAME from JSON request body"); out.println(" -d, --delete NAME Remove a specific attribute NAME from JSON request body");
out.println(" -f, --file FILENAME Send a body with request - read object from file or standard input if FILENAME is set to '-'"); out.println(" -f, --file FILENAME Send a body with request - read object from file or standard input if FILENAME is set to '-'");
out.println(" -b, --body CONTENT Content to be sent as-is or used as a JSON object template"); out.println(" -b, --body CONTENT Content to be sent as-is or used as a JSON object template");
out.println(" -q, --query NAME=VALUE Add to request URI a NAME query parameter with value VALUE"); out.println(" -q, --query NAME=VALUE Add to request URI a NAME query parameter with value VALUE, for example --query q=username:admin");
out.println(" -h, --header NAME=VALUE Set request header NAME to VALUE"); out.println(" -h, --header NAME=VALUE Set request header NAME to VALUE");
out.println(); out.println();
out.println(" -H, --print-headers Print response headers"); out.println(" -H, --print-headers Print response headers");

View file

@ -106,7 +106,7 @@ public class GetCmd extends AbstractRequestCmd {
out.println(" realms, users, roles, groups, clients, keys, serverinfo, components ..."); out.println(" realms, users, roles, groups, clients, keys, serverinfo, components ...");
out.println(" If it starts with 'http://' then it will be used as target resource url"); out.println(" If it starts with 'http://' then it will be used as target resource url");
out.println(" -r, --target-realm REALM Target realm to issue requests against if not the one authenticated against"); out.println(" -r, --target-realm REALM Target realm to issue requests against if not the one authenticated against");
out.println(" -q, --query NAME=VALUE Add to request URI a NAME query parameter with value VALUE"); out.println(" -q, --query NAME=VALUE Add to request URI a NAME query parameter with value VALUE, for example --query q=username:admin");
out.println(" -h, --header NAME=VALUE Set request header NAME to VALUE"); out.println(" -h, --header NAME=VALUE Set request header NAME to VALUE");
out.println(" -o, --offset OFFSET Set paging offset - adds a query parameter 'first' which some endpoints recognize"); out.println(" -o, --offset OFFSET Set paging offset - adds a query parameter 'first' which some endpoints recognize");
out.println(" -l, --limit LIMIT Set limit to number of items in result - adds a query parameter 'max' "); out.println(" -l, --limit LIMIT Set limit to number of items in result - adds a query parameter 'max' ");
@ -164,7 +164,7 @@ public class GetCmd extends AbstractRequestCmd {
out.println("Note: 'users' endpoint knows how to handle --offset and --limit. Most other endpoints don't."); out.println("Note: 'users' endpoint knows how to handle --offset and --limit. Most other endpoints don't.");
out.println(); out.println();
out.println("Get all users whose 'username' matches '*test*' pattern, and 'email' matches '*@google.com*':"); out.println("Get all users whose 'username' matches '*test*' pattern, and 'email' matches '*@google.com*':");
out.println(" " + PROMPT + " " + CMD + " get users -r demorealm -q username=test -q email=@google.com"); out.println(" " + PROMPT + " " + CMD + " get users -r demorealm -q q=\"username:test email:@google.com\"");
out.println(); out.println();
out.println("Note: it is the 'users' endpoint that interprets query parameters 'username', and 'email' in such a way that"); out.println("Note: it is the 'users' endpoint that interprets query parameters 'username', and 'email' in such a way that");
out.println("it results in the described semantics. Another endpoint may provide a different semantics."); out.println("it results in the described semantics. Another endpoint may provide a different semantics.");

View file

@ -117,7 +117,7 @@ public class UpdateCmd extends AbstractRequestCmd {
out.println(" -d, --delete NAME Remove a specific attribute NAME from JSON request body"); out.println(" -d, --delete NAME Remove a specific attribute NAME from JSON request body");
out.println(" -f, --file FILENAME Read object from file or standard input if FILENAME is set to '-'"); out.println(" -f, --file FILENAME Read object from file or standard input if FILENAME is set to '-'");
out.println(" -b, --body CONTENT Content to be sent as-is or used as a JSON object template"); out.println(" -b, --body CONTENT Content to be sent as-is or used as a JSON object template");
out.println(" -q, --query NAME=VALUE Add to request URI a NAME query parameter with value VALUE"); out.println(" -q, --query NAME=VALUE Add to request URI a NAME query parameter with value VALUE, for example --query q=username:admin");
out.println(" -h, --header NAME=VALUE Set request header NAME to VALUE"); out.println(" -h, --header NAME=VALUE Set request header NAME to VALUE");
out.println(" -m, --merge Merge new values with existing configuration on the server"); out.println(" -m, --merge Merge new values with existing configuration on the server");
out.println(" Merge is automatically enabled unless --file is specified"); out.println(" Merge is automatically enabled unless --file is specified");

View file

@ -314,7 +314,7 @@ public class HttpUtil {
} }
query.append(params.getKey()).append("=").append(URLEncoder.encode(params.getValue(), "utf-8")); query.append(params.getKey()).append("=").append(URLEncoder.encode(params.getValue(), "utf-8"));
} catch (Exception e) { } catch (Exception e) {
throw new RuntimeException("Failed to encode query params: " + params.getKey() + "=" + params.getValue()); throw new IllegalArgumentException("Failed to encode query params: " + params.getKey() + "=" + params.getValue());
} }
} }

View file

@ -1,7 +1,5 @@
package org.keycloak.testsuite.cli.admin; package org.keycloak.testsuite.cli.admin;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import org.keycloak.client.cli.config.FileConfigHandler; import org.keycloak.client.cli.config.FileConfigHandler;
@ -13,9 +11,14 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.StreamSupport; import java.util.stream.StreamSupport;
import static org.hamcrest.Matchers.equalTo;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.node.ObjectNode;
import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson; import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
import static org.keycloak.testsuite.cli.KcAdmExec.execute; import static org.keycloak.testsuite.cli.KcAdmExec.execute;
@ -48,6 +51,16 @@ public class KcAdmSessionTest extends AbstractAdmCliTest {
assertExitCodeAndStreamSizes(exe, 0, 1, 0); assertExitCodeAndStreamSizes(exe, 0, 1, 0);
String userId = exe.stdoutLines().get(0); String userId = exe.stdoutLines().get(0);
exe = execute("get users --config '" + configFile.getName() + "' -r demorealm -q q=username:testuser");
assertExitCodeAndStdErrSize(exe, 0, 0);
String result = exe.stdoutString();
assertTrue(result.contains(userId));
exe = execute("get users --config '" + configFile.getName() + "' -r demorealm -q q=username:non-existent");
assertExitCodeAndStdErrSize(exe, 0, 0);
String emptyResult = exe.stdoutString();
assertFalse(emptyResult.contains(userId));
// add realm admin capabilities to user // add realm admin capabilities to user
exe = execute("add-roles --config '" + configFile.getName() + "' -r demorealm --uusername testuser --cclientid realm-management --rolename realm-admin"); exe = execute("add-roles --config '" + configFile.getName() + "' -r demorealm --uusername testuser --cclientid realm-management --rolename realm-admin");