Account resource sharing resolves recipient by username before email, granting access to wrong user

Closes #49086

Signed-off-by: Martin Kanis <mkanis@ibm.com>
This commit is contained in:
Martin Kanis
2026-05-21 15:27:30 +02:00
committed by GitHub
parent 4a1defaeab
commit ba5d4bf165
2 changed files with 36 additions and 3 deletions
@@ -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);
}
@@ -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<Permission> 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<Permission> permissions = new ArrayList<>();