From ba5d4bf165cea12afc37977c39d963889a200652 Mon Sep 17 00:00:00 2001 From: Martin Kanis Date: Thu, 21 May 2026 15:27:30 +0200 Subject: [PATCH] Account resource sharing resolves recipient by username before email, granting access to wrong user Closes #49086 Signed-off-by: Martin Kanis --- .../account/resources/ResourceService.java | 10 +++++-- .../account/ResourcesRestServiceTest.java | 29 +++++++++++++++++++ 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/services/src/main/java/org/keycloak/services/resources/account/resources/ResourceService.java b/services/src/main/java/org/keycloak/services/resources/account/resources/ResourceService.java index fd0831ceea8..7dd8afd0949 100644 --- a/services/src/main/java/org/keycloak/services/resources/account/resources/ResourceService.java +++ b/services/src/main/java/org/keycloak/services/resources/account/resources/ResourceService.java @@ -244,12 +244,16 @@ public class ResourceService extends AbstractResourceService { private UserModel getUser(String requester) { UserProvider users = provider.getKeycloakSession().users(); - UserModel user = users.getUserByUsername(provider.getRealm(), requester); + UserModel userByUsername = users.getUserByUsername(provider.getRealm(), requester); + UserModel userByEmail = users.getUserByEmail(provider.getRealm(), requester); - if (user == null) { - user = users.getUserByEmail(provider.getRealm(), requester); + if (userByUsername != null && userByEmail != null + && !userByUsername.getId().equals(userByEmail.getId())) { + throw new BadRequestException("ambiguous_user"); } + UserModel user = userByUsername != null ? userByUsername : userByEmail; + if (user == null) { throw new NotFoundException(requester); } diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResourcesRestServiceTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResourcesRestServiceTest.java index cd1554df627..bb226b6af0b 100755 --- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResourcesRestServiceTest.java +++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ResourcesRestServiceTest.java @@ -448,6 +448,35 @@ public class ResourcesRestServiceTest extends AbstractRestServiceTest { } } + @Test + public void testShareResourceRejectsAmbiguousUsernameEmail() throws Exception { + RealmRepresentation realm = managedRealm.admin().toRepresentation(); + realm.setLoginWithEmailAllowed(false); + managedRealm.admin().update(realm); + + UserRepresentation alice = findUser("alice"); + + // Create an attacker user whose username matches the email of a legitimate user. + UserRepresentation attacker = createUser("alice@test.com", "password", "Attacker", "X", "attacker@test.com"); + managedRealm.admin().users().create(attacker); + + alice.setEmail("alice@test.com"); + managedRealm.admin().users().get(alice.getId()).update(alice); + + // The resource owner shares with "alice@test.com" intending alice (by email) but there is a clash as "alice@test.com" + // is also the attacker's username -> reject the request due to unambiguity + List permissions = new ArrayList<>(); + permissions.add(new Permission("alice@test.com", "Scope A")); + + String resourceId = getMyResources().get(0).getId(); + SimpleHttpResponse response = SimpleHttpDefault.doPut( + getAccountUrl("resources/" + encodePathAsIs(resourceId) + "/permissions"), httpClient) + .auth(tokenUtil.getToken()) + .json(permissions).asResponse(); + + assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatus()); + } + @Test public void failShareResourceInvalidPermissions() throws Exception { List permissions = new ArrayList<>();