diff --git a/js/apps/keycloak-server/scripts/security-admin-console-v2.json b/js/apps/keycloak-server/scripts/security-admin-console-v2.json index 336558984c..0a73476837 100644 --- a/js/apps/keycloak-server/scripts/security-admin-console-v2.json +++ b/js/apps/keycloak-server/scripts/security-admin-console-v2.json @@ -22,7 +22,9 @@ "publicClient": true, "frontchannelLogout": false, "protocol": "openid-connect", - "attributes": {}, + "attributes": { + "security.admin.console": "true" + }, "authenticationFlowBindingOverrides": {}, "fullScopeAllowed": true, "nodeReRegistrationTimeout": -1, @@ -44,4 +46,4 @@ "configure": true, "manage": true } -} \ No newline at end of file +} diff --git a/server-spi-private/src/main/java/org/keycloak/models/Constants.java b/server-spi-private/src/main/java/org/keycloak/models/Constants.java index 59e579a356..c0203f15f4 100755 --- a/server-spi-private/src/main/java/org/keycloak/models/Constants.java +++ b/server-spi-private/src/main/java/org/keycloak/models/Constants.java @@ -171,4 +171,7 @@ public final class Constants { // Sent to clients when authentication session expired, but user is already logged-in in current browser public static final String AUTHENTICATION_EXPIRED_MESSAGE = "authentication_expired"; + + // attribute name used in apps to mark that it is an admin console and its azp is allowed + public static final String SECURITY_ADMIN_CONSOLE_ATTR = "security.admin.console"; } diff --git a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java index c024af1f4c..1c4a0e3a23 100644 --- a/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/AdminConsole.java @@ -216,8 +216,16 @@ public class AdminConsole { throw new NotAuthorizedException("Bearer"); } - if (!Constants.ADMIN_CONSOLE_CLIENT_ID.equals(authResult.getToken().getIssuedFor())) { - throw new ForbiddenException("Token not valid for admin console"); + final String issuedFor = authResult.getToken().getIssuedFor(); + if (!Constants.ADMIN_CONSOLE_CLIENT_ID.equals(issuedFor)) { + if (issuedFor == null) { + throw new ForbiddenException("No azp claim in the token"); + } + // check the attribute to see if the app is defined as an admin console + ClientModel client = session.clients().getClientByClientId(realm, issuedFor); + if (client == null || !Boolean.parseBoolean(client.getAttribute(Constants.SECURITY_ADMIN_CONSOLE_ATTR))) { + throw new ForbiddenException("Token issued for an application that is not the admin console: " + issuedFor); + } } UserModel user= authResult.getUser(); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AdminConsoleWhoAmILocaleTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AdminConsoleWhoAmILocaleTest.java index b4bfc24b5e..a233f81354 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AdminConsoleWhoAmILocaleTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/AdminConsoleWhoAmILocaleTest.java @@ -25,6 +25,7 @@ import org.keycloak.testsuite.AbstractKeycloakTest; import org.keycloak.testsuite.auth.page.AuthRealm; import org.keycloak.testsuite.broker.util.SimpleHttpDefault; import org.keycloak.testsuite.console.page.AdminConsole; +import org.keycloak.testsuite.updaters.ClientAttributeUpdater; import org.keycloak.testsuite.util.AdminClientUtil; import org.keycloak.testsuite.util.OAuthClient; import org.keycloak.testsuite.util.RealmBuilder; @@ -289,4 +290,25 @@ public class AdminConsoleWhoAmILocaleTest extends AbstractKeycloakTest { } } } + + @Test + public void testLocaleRealmTokenForOtherClientButAllowed() throws Exception { + try (ClientAttributeUpdater updater = ClientAttributeUpdater.forClient(adminClient, REALM_I18N_ON, Constants.ADMIN_CLI_CLIENT_ID) + .setAttribute(Constants.SECURITY_ADMIN_CONSOLE_ATTR, Boolean.TRUE.toString()) + .update(); + Keycloak adminCliClient = AdminClientUtil.createAdminClient(true, REALM_I18N_ON, + USER_WITH_LOCALE, PASSWORD, Constants.ADMIN_CLI_CLIENT_ID, null)) { + AccessTokenResponse accessToken = adminCliClient.tokenManager().getAccessToken(); + Assert.assertNotNull(accessToken); + String token = accessToken.getToken(); + JsonNode whoAmI = SimpleHttpDefault + .doGet(whoAmiUrl(REALM_I18N_ON), client) + .header("Accept", "application/json") + .auth(token) + .asJson(); + Assert.assertEquals(REALM_I18N_ON, whoAmI.get("realm").asText()); + Assert.assertEquals(USER_LOCALE, whoAmI.get("locale").asText()); + checkRealmAccess(REALM_I18N_ON, whoAmI); + } + } }