From 8a5428808e3570744ea78fccffa23d8596b76e3f Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Tue, 3 Apr 2018 21:29:31 -0400 Subject: [PATCH] KEYCLOAK-7044 KEYCLOAK-7046 --- .../admin-cli/src/main/bin/kcadm.sh | 12 +++- .../cli/commands/AbstractAuthOptionsCmd.java | 16 ++++- .../admin/cli/commands/AddRolesCmd.java | 1 + .../client/admin/cli/commands/CreateCmd.java | 1 + .../client/admin/cli/commands/DeleteCmd.java | 1 + .../client/admin/cli/commands/GetCmd.java | 1 + .../admin/cli/commands/GetRolesCmd.java | 1 + .../admin/cli/commands/RemoveRolesCmd.java | 1 + .../admin/cli/commands/SetPasswordCmd.java | 1 + .../client/admin/cli/commands/UpdateCmd.java | 1 + .../client/admin/cli/config/ConfigData.java | 14 +++++ .../client/admin/cli/util/AuthUtil.java | 3 + .../client/admin/cli/util/ConfigUtil.java | 11 ++-- .../admin/permissions/MgmtPermissions.java | 16 +++-- .../integration-arquillian/tests/base/pom.xml | 2 +- .../admin/FineGrainAdminUnitTest.java | 61 ++++++++++++++++--- .../cli/admin/AbstractAdmCliTest.java | 7 ++- .../testsuite/cli/admin/KcAdmTest.java | 16 +++++ 18 files changed, 135 insertions(+), 31 deletions(-) diff --git a/integration/client-cli/admin-cli/src/main/bin/kcadm.sh b/integration/client-cli/admin-cli/src/main/bin/kcadm.sh index 26df7e1604..0c905b8514 100755 --- a/integration/client-cli/admin-cli/src/main/bin/kcadm.sh +++ b/integration/client-cli/admin-cli/src/main/bin/kcadm.sh @@ -20,4 +20,14 @@ if [ "x$RESOLVED_NAME" = "x" ]; then fi DIRNAME=`dirname "$RESOLVED_NAME"` -java $KC_OPTS -cp $DIRNAME/client/keycloak-admin-cli-${project.version}.jar org.keycloak.client.admin.cli.KcAdmMain "$@" \ No newline at end of file + + +# Uncomment out these lines if you are integrating with `kcinit` +#if [ "$1" = "config" ]; then +# java $KC_OPTS -cp $DIRNAME/client/keycloak-admin-cli-${project.version}.jar org.keycloak.client.admin.cli.KcAdmMain "$@" +#else +# java $KC_OPTS -cp $DIRNAME/client/keycloak-admin-cli-${project.version}.jar org.keycloak.client.admin.cli.KcAdmMain "$@" --noconfig --token $(kcinit token admin-cli) --server $(kcinit show server) +#fi +# Remove the next line if you have enabled kcinit +java $KC_OPTS -cp $DIRNAME/client/keycloak-admin-cli-${project.version}.jar org.keycloak.client.admin.cli.KcAdmMain "$@" + diff --git a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/AbstractAuthOptionsCmd.java b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/AbstractAuthOptionsCmd.java index d8d6b56917..5c18ccb4ff 100644 --- a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/AbstractAuthOptionsCmd.java +++ b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/AbstractAuthOptionsCmd.java @@ -89,6 +89,9 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd { @Option(name = "trustpass", description = "Truststore password (prompted for if not specified and --truststore is used)") String trustPass; + @Option(name = "token", description = "Token to use for invocations. With this option set, every other authentication option is ignored") + String externalToken; + protected void initFromParent(AbstractAuthOptionsCmd parent) { @@ -108,6 +111,7 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd { alias = parent.alias; trustStore = parent.trustStore; trustPass = parent.trustPass; + externalToken = parent.externalToken; } protected void applyDefaultOptionValues() { @@ -117,7 +121,7 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd { } protected boolean noOptions() { - return server == null && realm == null && clientId == null && secret == null && + return externalToken == null && server == null && realm == null && clientId == null && secret == null && user == null && password == null && keystore == null && storePass == null && keyPass == null && alias == null && trustStore == null && trustPass == null && config == null && (args == null || args.size() == 0); @@ -215,8 +219,8 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd { } protected boolean requiresLogin() { - return user != null || password != null || secret != null || keystore != null - || keyPass != null || storePass != null || alias != null; + return externalToken == null && (user != null || password != null || secret != null || keystore != null + || keyPass != null || storePass != null || alias != null); } protected ConfigData copyWithServerInfo(ConfigData config) { @@ -229,6 +233,9 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd { if (realm != null) { result.setRealm(realm); } + if (externalToken != null) { + result.setExternalToken(externalToken); + } checkServerInfo(result); return result; @@ -241,6 +248,9 @@ public abstract class AbstractAuthOptionsCmd extends AbstractGlobalOptionsCmd { data.setRealm(realm); if (trustStore != null) data.setTruststore(trustStore); + if (externalToken != null) { + data.setExternalToken(externalToken); + } RealmConfigData rdata = data.sessionRealmConfigData(); if (clientId != null) diff --git a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/AddRolesCmd.java b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/AddRolesCmd.java index 549a6c73d0..fccf5e8064 100644 --- a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/AddRolesCmd.java +++ b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/AddRolesCmd.java @@ -339,6 +339,7 @@ public class AddRolesCmd extends AbstractAuthOptionsCmd { out.println(" -x Print full stack trace when exiting with error"); out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)"); out.println(" --no-config Don't use config file - no authentication info is loaded or saved"); + out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set."); out.println(" --truststore PATH Path to a truststore containing trusted certificates"); out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)"); out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + CMD + " config credentials' in order to establish"); diff --git a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/CreateCmd.java b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/CreateCmd.java index 6bdc7eca57..c45f0daf55 100644 --- a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/CreateCmd.java +++ b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/CreateCmd.java @@ -100,6 +100,7 @@ public class CreateCmd extends AbstractRequestCmd { out.println(" -x Print full stack trace when exiting with error"); out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)"); out.println(" --no-config Don't use config file - no authentication info is loaded or saved"); + out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set."); out.println(" --truststore PATH Path to a truststore containing trusted certificates"); out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)"); out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + CMD + " config credentials' in order to establish"); diff --git a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/DeleteCmd.java b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/DeleteCmd.java index 1b43489794..d9e2b959bc 100644 --- a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/DeleteCmd.java +++ b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/DeleteCmd.java @@ -67,6 +67,7 @@ public class DeleteCmd extends CreateCmd { out.println(" -x Print full stack trace when exiting with error"); out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)"); out.println(" --no-config Don't use config file - no authentication info is loaded or saved"); + out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set."); out.println(" --truststore PATH Path to a truststore containing trusted certificates"); out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)"); out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + CMD + " config credentials' in order to establish"); diff --git a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/GetCmd.java b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/GetCmd.java index 4667db0216..9c8cd786f2 100644 --- a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/GetCmd.java +++ b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/GetCmd.java @@ -99,6 +99,7 @@ public class GetCmd extends AbstractRequestCmd { out.println(" -x Print full stack trace when exiting with error"); out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)"); out.println(" --no-config Don't use config file - no authentication info is loaded or saved"); + out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set."); out.println(" --truststore PATH Path to a truststore containing trusted certificates"); out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)"); out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + CMD + " config credentials' in order to establish"); diff --git a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/GetRolesCmd.java b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/GetRolesCmd.java index 96f79c4714..91637cb8ea 100644 --- a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/GetRolesCmd.java +++ b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/GetRolesCmd.java @@ -343,6 +343,7 @@ public class GetRolesCmd extends GetCmd { out.println(" -x Print full stack trace when exiting with error"); out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)"); out.println(" --no-config Don't use config file - no authentication info is loaded or saved"); + out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set."); out.println(" --truststore PATH Path to a truststore containing trusted certificates"); out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)"); out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + CMD + " config credentials' in order to establish"); diff --git a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/RemoveRolesCmd.java b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/RemoveRolesCmd.java index 4f9c49681d..ffc600d83f 100644 --- a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/RemoveRolesCmd.java +++ b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/RemoveRolesCmd.java @@ -339,6 +339,7 @@ public class RemoveRolesCmd extends AbstractAuthOptionsCmd { out.println(" -x Print full stack trace when exiting with error"); out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)"); out.println(" --no-config Don't use config file - no authentication info is loaded or saved"); + out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set."); out.println(" --truststore PATH Path to a truststore containing trusted certificates"); out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)"); out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + CMD + " config credentials' in order to establish"); diff --git a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/SetPasswordCmd.java b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/SetPasswordCmd.java index c0317ad365..cbf6758745 100644 --- a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/SetPasswordCmd.java +++ b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/SetPasswordCmd.java @@ -151,6 +151,7 @@ public class SetPasswordCmd extends AbstractAuthOptionsCmd { out.println(" -x Print full stack trace when exiting with error"); out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)"); out.println(" --no-config Don't use config file - no authentication info is loaded or saved"); + out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set."); out.println(" --truststore PATH Path to a truststore containing trusted certificates"); out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)"); out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + CMD + " config credentials' in order to establish"); diff --git a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/UpdateCmd.java b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/UpdateCmd.java index 2c0f404cf1..b6b17daf98 100644 --- a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/UpdateCmd.java +++ b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/commands/UpdateCmd.java @@ -108,6 +108,7 @@ public class UpdateCmd extends AbstractRequestCmd { out.println(" -x Print full stack trace when exiting with error"); out.println(" --config Path to the config file (" + DEFAULT_CONFIG_FILE_STRING + " by default)"); out.println(" --no-config Don't use config file - no authentication info is loaded or saved"); + out.println(" --token Token to use to invoke on Keycloak. Other credential may be ignored if this flag is set."); out.println(" --truststore PATH Path to a truststore containing trusted certificates"); out.println(" --trustpass PASSWORD Truststore password (prompted for if not specified and --truststore is used)"); out.println(" CREDENTIALS OPTIONS Same set of options as accepted by '" + CMD + " config credentials' in order to establish"); diff --git a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/config/ConfigData.java b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/config/ConfigData.java index e327335903..9cdec9f129 100644 --- a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/config/ConfigData.java +++ b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/config/ConfigData.java @@ -16,6 +16,7 @@ */ package org.keycloak.client.admin.cli.config; +import com.fasterxml.jackson.annotation.JsonIgnore; import org.keycloak.util.JsonSerialization; import java.io.IOException; @@ -27,6 +28,9 @@ import java.util.Map; */ public class ConfigData { + @JsonIgnore + private String externalToken; + private String serverUrl; private String realm; @@ -46,6 +50,16 @@ public class ConfigData { this.serverUrl = serverUrl; } + @JsonIgnore + public String getExternalToken() { + return externalToken; + } + + @JsonIgnore + public void setExternalToken(String externalToken) { + this.externalToken = externalToken; + } + public String getRealm() { return realm; } diff --git a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/AuthUtil.java b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/AuthUtil.java index ddfca0c241..c23757100d 100644 --- a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/AuthUtil.java +++ b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/AuthUtil.java @@ -46,6 +46,9 @@ import static org.keycloak.client.admin.cli.util.HttpUtil.urlencode; public class AuthUtil { public static String ensureToken(ConfigData config) { + if (config.getExternalToken() != null) { + return config.getExternalToken(); + } checkAuthInfo(config); diff --git a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/ConfigUtil.java b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/ConfigUtil.java index 3699048848..af4d60b8a8 100644 --- a/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/ConfigUtil.java +++ b/integration/client-cli/admin-cli/src/main/java/org/keycloak/client/admin/cli/util/ConfigUtil.java @@ -63,8 +63,11 @@ public class ConfigUtil { } public static void checkServerInfo(ConfigData config) { - if (config.getServerUrl() == null || config.getRealm() == null) { - throw new RuntimeException("No server or realm specified. Use --server, --realm, or '" + OsUtil.CMD + " config credentials'."); + if (config.getServerUrl() == null) { + throw new RuntimeException("No server specified. Use --server, or '" + OsUtil.CMD + " config credentials or connection'."); + } + if (config.getRealm() == null && config.getExternalToken() == null) { + throw new RuntimeException("No realm or token specified. Use --realm, --token, or '" + OsUtil.CMD + " config credentials'."); } } @@ -73,8 +76,8 @@ public class ConfigUtil { } public static boolean credentialsAvailable(ConfigData config) { - return config.getServerUrl() != null && config.getRealm() != null - && config.sessionRealmConfigData() != null && config.sessionRealmConfigData().getRefreshToken() != null; + return config.getServerUrl() != null && (config.getExternalToken() != null || (config.getRealm() != null + && config.sessionRealmConfigData() != null && config.sessionRealmConfigData().getRefreshToken() != null)); } public static ConfigData loadConfig() { diff --git a/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java b/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java index 8994718403..9b5e63c904 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/permissions/MgmtPermissions.java @@ -86,21 +86,19 @@ class MgmtPermissions implements AdminPermissionEvaluator, AdminPermissionManage && !auth.getRealm().equals(new RealmManager(session).getKeycloakAdminstrationRealm())) { throw new ForbiddenException(); } - if (auth.getClient().getClientId().equals(Constants.ADMIN_CLI_CLIENT_ID) - || auth.getClient().getClientId().equals(Constants.ADMIN_CONSOLE_CLIENT_ID)) { - this.identity = new UserModelIdentity(auth.getRealm(), auth.getUser()); - - } else { - this.identity = new KeycloakIdentity(auth.getToken(), session); - } + initIdentity(session, auth); } MgmtPermissions(KeycloakSession session, AdminAuth auth) { this.session = session; this.auth = auth; this.admin = auth.getUser(); this.adminsRealm = auth.getRealm(); - if (auth.getClient().getClientId().equals(Constants.ADMIN_CLI_CLIENT_ID) - || auth.getClient().getClientId().equals(Constants.ADMIN_CONSOLE_CLIENT_ID)) { + initIdentity(session, auth); + } + + private void initIdentity(KeycloakSession session, AdminAuth auth) { + if (auth.getToken().hasAudience(Constants.ADMIN_CLI_CLIENT_ID) + || auth.getToken().hasAudience(Constants.ADMIN_CONSOLE_CLIENT_ID)) { this.identity = new UserModelIdentity(auth.getRealm(), auth.getUser()); } else { diff --git a/testsuite/integration-arquillian/tests/base/pom.xml b/testsuite/integration-arquillian/tests/base/pom.xml index 754c6dad32..7bffe006cb 100644 --- a/testsuite/integration-arquillian/tests/base/pom.xml +++ b/testsuite/integration-arquillian/tests/base/pom.xml @@ -264,7 +264,7 @@ github.com/keycloak/kcinit ${project.build.directory}/gopath - 0.4 + 0.5 diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java index 1df661285f..825e681fef 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/FineGrainAdminUnitTest.java @@ -23,10 +23,11 @@ import org.junit.Assert; import org.junit.Test; import org.keycloak.admin.client.Keycloak; import org.keycloak.authorization.model.Resource; -import org.keycloak.models.ClientTemplateModel; -import org.keycloak.models.GroupModel; +import org.keycloak.client.admin.cli.util.ConfigUtil; +import org.keycloak.models.*; import org.keycloak.models.utils.KeycloakModelUtils; import org.keycloak.representations.idm.ClientTemplateRepresentation; +import org.keycloak.representations.idm.authorization.ClientPolicyRepresentation; import org.keycloak.representations.idm.authorization.Logic; import org.keycloak.representations.idm.authorization.UserPolicyRepresentation; import org.keycloak.services.resources.admin.permissions.AdminPermissionEvaluator; @@ -34,20 +35,14 @@ import org.keycloak.services.resources.admin.permissions.AdminPermissionManageme import org.keycloak.services.resources.admin.permissions.AdminPermissions; import org.keycloak.authorization.model.Policy; import org.keycloak.authorization.model.ResourceServer; -import org.keycloak.models.AdminRoles; -import org.keycloak.models.ClientModel; -import org.keycloak.models.Constants; -import org.keycloak.models.KeycloakSession; -import org.keycloak.models.RealmModel; -import org.keycloak.models.RoleModel; -import org.keycloak.models.UserCredentialModel; -import org.keycloak.models.UserModel; import org.keycloak.representations.idm.ClientRepresentation; import org.keycloak.representations.idm.RealmRepresentation; import org.keycloak.representations.idm.RoleRepresentation; import org.keycloak.representations.idm.UserRepresentation; import org.keycloak.representations.idm.authorization.DecisionStrategy; import org.keycloak.testsuite.AbstractKeycloakTest; +import org.keycloak.testsuite.arquillian.AuthServerTestEnricher; +import org.keycloak.testsuite.auth.page.AuthRealm; import org.keycloak.testsuite.runonserver.RunOnServerDeployment; import org.keycloak.testsuite.util.AdminClientUtil; @@ -856,6 +851,52 @@ public class FineGrainAdminUnitTest extends AbstractKeycloakTest { } } + /** + * KEYCLOAK-7406 + * + * @throws Exception + */ + @Test + public void testWithTokenExchange() throws Exception { + testingClient.server().run(session -> { + RealmModel realm = session.realms().getRealmByName("master"); + ClientModel client = session.realms().getClientByClientId("kcinit", realm); + if (client != null) { + return; + } + + ClientModel kcinit = realm.addClient("kcinit"); + kcinit.setEnabled(true); + kcinit.addRedirectUri("http://localhost:*"); + kcinit.setPublicClient(false); + kcinit.setSecret("password"); + kcinit.setDirectAccessGrantsEnabled(true); + + // permission for client to client exchange to "target" client + ClientModel adminCli = realm.getClientByClientId(ConfigUtil.DEFAULT_CLIENT); + AdminPermissionManagement management = AdminPermissions.management(session, realm); + management.clients().setPermissionsEnabled(adminCli, true); + ClientPolicyRepresentation clientRep = new ClientPolicyRepresentation(); + clientRep.setName("to"); + clientRep.addClient(kcinit.getId()); + ResourceServer server = management.realmResourceServer(); + Policy clientPolicy = management.authz().getStoreFactory().getPolicyStore().create(clientRep, server); + management.clients().exchangeToPermission(adminCli).addAssociatedPolicy(clientPolicy); + }); + + oauth.realm("master"); + oauth.clientId("kcinit"); + String token = oauth.doGrantAccessTokenRequest("password", "admin", "admin").getAccessToken(); + Assert.assertNotNull(token); + String exchanged = oauth.doTokenExchange("master", token, "admin-cli", "kcinit", "password").getAccessToken(); + Assert.assertNotNull(exchanged); + + Keycloak client = Keycloak.getInstance(AuthServerTestEnricher.getAuthServerContextRoot() + "/auth", + AuthRealm.MASTER, Constants.ADMIN_CLI_CLIENT_ID, exchanged); + + Assert.assertNotNull(client.realm("master").roles().get("offline_access")); + } + } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/AbstractAdmCliTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/AbstractAdmCliTest.java index d86ddf1a09..f7d15c4b9c 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/AbstractAdmCliTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/AbstractAdmCliTest.java @@ -320,7 +320,8 @@ public abstract class AbstractAdmCliTest extends AbstractCliTest { exe = execute("delete clients/" + client.getId() + " --no-config --server " + serverUrl + " --realm test " + credentials + " " + extraOptions); - assertExitCodeAndStreamSizes(exe, 0, 0, 1); + int linecountOffset = loginMessage.equals("") ? 1 : 0; // if there is no login, then there is one less stdErrLinecount + assertExitCodeAndStreamSizes(exe, 0, 0, 1 - linecountOffset); lastModified2 = configFile.exists() ? configFile.lastModified() : 0; Assert.assertEquals("config file not modified", lastModified, lastModified2); @@ -331,9 +332,9 @@ public abstract class AbstractAdmCliTest extends AbstractCliTest { // subsequent delete should fail exe = execute("delete clients/" + client.getId() + " --no-config --server " + serverUrl + " --realm test " + credentials + " " + extraOptions); - assertExitCodeAndStreamSizes(exe, 1, 0, 2); + assertExitCodeAndStreamSizes(exe, 1, 0, 2 - linecountOffset); String resourceUri = serverUrl + "/admin/realms/test/clients/" + client.getId(); - Assert.assertEquals("error message", "Resource not found for url: " + resourceUri, exe.stderrLines().get(1)); + Assert.assertEquals("error message", "Resource not found for url: " + resourceUri, exe.stderrLines().get(1 - linecountOffset)); lastModified2 = configFile.exists() ? configFile.lastModified() : 0; Assert.assertEquals("config file not modified", lastModified, lastModified2); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java index 077e427791..9c586004f0 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/cli/admin/KcAdmTest.java @@ -570,4 +570,20 @@ public class KcAdmTest extends AbstractAdmCliTest { "--client admin-cli-jwt --keystore '" + keystore.getAbsolutePath() + "' --storepass storepass --keypass keypass --alias admin-cli", "", "Logging into " + serverUrl + " as service-account-admin-cli-jwt of realm test"); } + + @Test + public void testCRUDWithToken() throws Exception { + /* + * Test create, get, update, and delete using on-the-fly authentication - without using any config file. + * Login is performed by each operation again, and again using username, password, and client secret. + */ + oauth.realm("master"); + oauth.clientId("admin-cli"); + String token = oauth.doGrantAccessTokenRequest("", "admin", "admin").getAccessToken(); + testCRUDWithOnTheFlyAuth(serverUrl, " --token " + token, "", + ""); + + } + + }