From bc6b945e327b3c73fecf7ffa1dc365c444c52cd8 Mon Sep 17 00:00:00 2001 From: rmartinc Date: Wed, 21 Jan 2026 17:13:15 +0100 Subject: [PATCH] Check if requested user is enabled for impersonation in TE v1 Closes #45651 Signed-off-by: rmartinc --- .../V1TokenExchangeProvider.java | 2 +- .../updaters/UserAttributeUpdater.java | 5 +++ ...bjectImpersonationTokenExchangeV1Test.java | 35 +++++++++++++++++++ 3 files changed, 41 insertions(+), 1 deletion(-) diff --git a/services/src/main/java/org/keycloak/protocol/oidc/tokenexchange/V1TokenExchangeProvider.java b/services/src/main/java/org/keycloak/protocol/oidc/tokenexchange/V1TokenExchangeProvider.java index 881aa2206656..79205223b0a4 100644 --- a/services/src/main/java/org/keycloak/protocol/oidc/tokenexchange/V1TokenExchangeProvider.java +++ b/services/src/main/java/org/keycloak/protocol/oidc/tokenexchange/V1TokenExchangeProvider.java @@ -140,7 +140,7 @@ protected Response tokenExchange() { requestedUser = session.users().getUserById(realm, requestedSubject); } - if (requestedUser == null) { + if (requestedUser == null || !requestedUser.isEnabled()) { // We always returned access denied to avoid username fishing event.detail(Details.REASON, "requested_subject not found"); event.error(Errors.NOT_ALLOWED); diff --git a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/UserAttributeUpdater.java b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/UserAttributeUpdater.java index 09f036a67c59..478822cc68a1 100644 --- a/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/UserAttributeUpdater.java +++ b/testsuite/integration-arquillian/tests/base/src/main/java/org/keycloak/testsuite/updaters/UserAttributeUpdater.java @@ -106,6 +106,11 @@ public UserAttributeUpdater setEmailVerified(Boolean emailVerified) { return this; } + public UserAttributeUpdater setEnabled(Boolean enabled) { + rep.setEnabled(enabled); + return this; + } + public UserAttributeUpdater setRequiredActions(UserModel.RequiredAction... requiredAction) { rep.setRequiredActions(Arrays.stream(requiredAction) .map(action -> action.name()) diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/tokenexchange/SubjectImpersonationTokenExchangeV1Test.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/tokenexchange/SubjectImpersonationTokenExchangeV1Test.java index 5de881b75365..5b91bd3ae915 100644 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/tokenexchange/SubjectImpersonationTokenExchangeV1Test.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/tokenexchange/SubjectImpersonationTokenExchangeV1Test.java @@ -42,6 +42,7 @@ import org.keycloak.testsuite.arquillian.annotation.DisableFeature; import org.keycloak.testsuite.arquillian.annotation.EnableFeature; import org.keycloak.testsuite.arquillian.annotation.UncaughtServerErrorExpected; +import org.keycloak.testsuite.updaters.UserAttributeUpdater; import org.keycloak.testsuite.util.AdminClientUtil; import org.keycloak.testsuite.util.oauth.AuthorizationEndpointResponse; import org.keycloak.testsuite.util.oauth.OAuthClient; @@ -185,6 +186,24 @@ public void testImpersonation() throws Exception { assertTrue(exchangedToken.getRealmAccess().isUserInRole("example")); } + // disabled user cannot be impersonated + try (UserAttributeUpdater userUpdater = UserAttributeUpdater + .forUserByUsername(adminClient.realm(TEST), "impersonated-user") + .setEnabled(Boolean.FALSE) + .update(); + Response response = exchangeUrl.request() + .header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader("direct-legal", "secret")) + .post(Entity.form( + new Form() + .param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE) + .param(OAuth2Constants.SUBJECT_TOKEN, accessToken) + .param(OAuth2Constants.SUBJECT_TOKEN_TYPE, OAuth2Constants.ACCESS_TOKEN_TYPE) + .param(OAuth2Constants.REQUESTED_SUBJECT, "impersonated-user") + .param(OAuth2Constants.AUDIENCE, "target") + ))) { + Assert.assertEquals(403, response.getStatus()); + } + try (Response response = exchangeUrl.request() .post(Entity.form( new Form() @@ -527,6 +546,22 @@ public void testDirectImpersonation() throws Exception { assertTrue(response.getStatus() >= 400); response.close(); } + + // disabled user cannot be impersonated + try (UserAttributeUpdater userUpdater = UserAttributeUpdater + .forUserByUsername(adminClient.realm(TEST), "impersonated-user") + .setEnabled(Boolean.FALSE) + .update(); + Response response = exchangeUrl.request() + .header(HttpHeaders.AUTHORIZATION, BasicAuthHelper.createHeader("direct-legal", "secret")) + .post(Entity.form( + new Form() + .param(OAuth2Constants.GRANT_TYPE, OAuth2Constants.TOKEN_EXCHANGE_GRANT_TYPE) + .param(OAuth2Constants.REQUESTED_SUBJECT, "impersonated-user") + .param(OAuth2Constants.AUDIENCE, "target") + ))) { + Assert.assertEquals(403, response.getStatus()); + } }