From bee7e4b335b9dc5df050aaf74b45f7fffd2a752f Mon Sep 17 00:00:00 2001 From: Keshav Deshpande Date: Thu, 31 Jul 2025 10:23:14 +0200 Subject: [PATCH] Change error to 400 for unknown user (#40939) Closes #39079 Signed-off-by: Keshav Deshpande --- .../main/java/org/keycloak/events/Errors.java | 1 + .../directgrant/ValidateUsername.java | 9 +++++++ ...urceOwnerPasswordCredentialsGrantTest.java | 26 +++++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/server-spi-private/src/main/java/org/keycloak/events/Errors.java b/server-spi-private/src/main/java/org/keycloak/events/Errors.java index 3b1cdeaf0db..861db9a0324 100755 --- a/server-spi-private/src/main/java/org/keycloak/events/Errors.java +++ b/server-spi-private/src/main/java/org/keycloak/events/Errors.java @@ -42,6 +42,7 @@ public interface Errors { String DIFFERENT_USER_AUTHENTICATING = "different_user_authenticating"; String DIFFERENT_USER_AUTHENTICATED = "different_user_authenticated"; String USER_DELETE_ERROR = "user_delete_error"; + String INVALID_USER = "invalid_user"; String USERNAME_MISSING = "username_missing"; String USERNAME_IN_USE = "username_in_use"; diff --git a/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidateUsername.java b/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidateUsername.java index 39530d359c4..95d07a36aeb 100755 --- a/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidateUsername.java +++ b/services/src/main/java/org/keycloak/authentication/authenticators/directgrant/ValidateUsername.java @@ -79,6 +79,15 @@ public class ValidateUsername extends AbstractDirectGrantAuthenticator { return; } + if (user.getServiceAccountClientLink() != null) { + AuthenticatorUtils.dummyHash(context); + context.getEvent().detail(Details.REASON, "User is a service account"); + context.getEvent().error(Errors.INVALID_USER); + Response challengeResponse = errorResponse(Response.Status.UNAUTHORIZED.getStatusCode(), "invalid_grant", "Invalid user credentials"); + context.failure(AuthenticationFlowError.INVALID_USER, challengeResponse); + return; + } + String bruteForceError = getDisabledByBruteForceEventError(context, user); if (bruteForceError != null) { AuthenticatorUtils.dummyHash(context); diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java index 832eb9c0a09..be3781f093e 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/oauth/ResourceOwnerPasswordCredentialsGrantTest.java @@ -32,6 +32,7 @@ import org.keycloak.OAuth2Constants; import org.keycloak.OAuthErrorException; import org.keycloak.admin.client.resource.ClientResource; import org.keycloak.admin.client.resource.RealmResource; +import org.keycloak.authentication.AuthenticationFlowError; import org.keycloak.authentication.authenticators.client.ClientIdAndSecretAuthenticator; import org.keycloak.common.Profile; import org.keycloak.connections.infinispan.InfinispanConnectionProvider; @@ -775,6 +776,31 @@ public class ResourceOwnerPasswordCredentialsGrantTest extends AbstractKeycloakT assertNull(response.getRefreshToken()); } + @Test + public void grantAccessTokenServiceAccountUserOfOtherClient() throws Exception { + ClientManager.realm(adminClient.realm("test")).clientId("resource-owner").setServiceAccountsEnabled(true); + oauth.client("resource-owner-refresh", "secret"); + AccessTokenResponse response = oauth.doPasswordGrantRequest("service-account-resource-owner", "password"); + + assertEquals(401, response.getStatusCode()); + assertEquals("invalid_grant", response.getError()); + assertEquals("Invalid user credentials", response.getErrorDescription()); + + events.expectLogin() + .client("resource-owner-refresh") + .session((String) null) + .user((String) null) + .detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD) + .detail(Details.REASON, "User is a service account") + .removeDetail(Details.CODE_ID) + .removeDetail(Details.REDIRECT_URI) + .removeDetail(Details.CONSENT) + .error(Errors.INVALID_USER) + .assertEvent(); + + ClientManager.realm(adminClient.realm("test")).clientId("resource-owner").setServiceAccountsEnabled(false); + } + private int getAuthenticationSessionsCount() { return testingClient.testing().cache(InfinispanConnectionProvider.AUTHENTICATION_SESSIONS_CACHE_NAME).size(); }