From f16eb4d8b98241e563e024e861c79bbf0065ca0d Mon Sep 17 00:00:00 2001 From: Thomas Darimont Date: Tue, 3 Aug 2021 18:30:56 +0200 Subject: [PATCH] KEYCLOAK-18954 Refactor user consent list retrieval to avoid ConcurrentModificationException This avoids a ConcurrentModificationException to be thrown in UserResource.getConsents() calls that got introduced in 4e8b18f560c26f85529070c2c7847be7cca6ac09 by filtering the resulting stream explicitly instead of removing items from the collection that we iterate over, which triggered the CME in the first place. Signed-off-by: Thomas Darimont --- .../services/resources/admin/UserResource.java | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java index e0dd87906a6..6c34b33b578 100755 --- a/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java +++ b/services/src/main/java/org/keycloak/services/resources/admin/UserResource.java @@ -103,6 +103,7 @@ import java.text.MessageFormat; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -450,11 +451,18 @@ public class UserResource { Set offlineClients = new UserSessionManager(session).findClientsWithOfflineToken(realm, user); - return Stream.concat( - session.users().getConsentsStream(realm, user.getId()) - .map(consent -> toConsent(consent, offlineClients)), + Set clientsWithUserConsents = new HashSet<>(); + List userConsents = session.users().getConsentsStream(realm, user.getId()) + // collect clients with explicit user consents for later filtering + .peek(ucm -> clientsWithUserConsents.add(ucm.getClient())) + .collect(Collectors.toList()); - offlineClients.stream().map(this::toConsent) + return Stream.concat( + userConsents.stream().map(consent -> toConsent(consent, offlineClients)), + offlineClients.stream() + // filter out clients with explicit user consents to avoid rendering them twice + .filter(c -> !clientsWithUserConsents.contains(c)) + .map(this::toConsent) ); } @@ -492,7 +500,6 @@ public class UserResource { offlineTokens.put("client", consent.getClient().getId()); offlineTokens.put("key", "Offline Token"); additionalGrants.add(offlineTokens); - offlineClients.remove(consent.getClient()); } currentRep.put("additionalGrants", additionalGrants); return currentRep;