diff --git a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java index 9d4fae79926..f67f274c9e8 100755 --- a/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java +++ b/model/infinispan/src/main/java/org/keycloak/models/cache/infinispan/UserAdapter.java @@ -17,6 +17,7 @@ package org.keycloak.models.cache.infinispan; +import org.keycloak.common.util.CollectionUtil; import org.keycloak.credential.CredentialModel; import org.keycloak.models.ClientModel; import org.keycloak.models.GroupModel; @@ -40,6 +41,7 @@ import java.util.Objects; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.function.Supplier; +import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -174,20 +176,39 @@ public class UserAdapter implements CachedUserModel { @Override public void setSingleAttribute(String name, String value) { - getDelegateForUpdate(); if (UserModel.USERNAME.equals(name) || UserModel.EMAIL.equals(name)) { value = KeycloakModelUtils.toLowerCaseSafe(value); } + if (updated == null) { + Set oldEntries = getAttributeStream(name).collect(Collectors.toSet()); + Set newEntries = value != null ? Set.of(value) : Collections.emptySet(); + if (CollectionUtil.collectionEquals(oldEntries, newEntries)) { + return; + } + } + getDelegateForUpdate(); updated.setSingleAttribute(name, value); } @Override public void setAttribute(String name, List values) { - getDelegateForUpdate(); if (UserModel.USERNAME.equals(name) || UserModel.EMAIL.equals(name)) { String lowerCasedFirstValue = KeycloakModelUtils.toLowerCaseSafe((values != null && values.size() > 0) ? values.get(0) : null); if (lowerCasedFirstValue != null) values = Collections.singletonList(lowerCasedFirstValue); } + if (updated == null) { + Set oldEntries = getAttributeStream(name).collect(Collectors.toSet()); + Set newEntries; + if (values == null) { + newEntries = new HashSet<>(); + } else { + newEntries = new HashSet<>(values); + } + if (CollectionUtil.collectionEquals(oldEntries, newEntries)) { + return; + } + } + getDelegateForUpdate(); updated.setAttribute(name, values); } diff --git a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java index 1c074607455..ca28c4d951c 100755 --- a/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java +++ b/model/jpa/src/main/java/org/keycloak/models/jpa/UserAdapter.java @@ -19,6 +19,7 @@ package org.keycloak.models.jpa; import org.keycloak.common.Profile; import org.keycloak.common.Profile.Feature; +import org.keycloak.common.util.CollectionUtil; import org.keycloak.common.util.MultivaluedHashMap; import org.keycloak.common.util.ObjectUtil; import org.keycloak.credential.UserCredentialManager; @@ -51,10 +52,13 @@ import jakarta.persistence.criteria.Root; import org.keycloak.organization.OrganizationProvider; import java.util.ArrayList; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.keycloak.representations.idm.MembershipType; @@ -138,6 +142,11 @@ public class UserAdapter implements UserModel, JpaModel { if (value == null) { user.getAttributes().removeIf(a -> a.getName().equals(name)); } else { + Set oldEntries = getAttributeStream(name).collect(Collectors.toSet()); + Set newEntries = Set.of(value); + if (CollectionUtil.collectionEquals(oldEntries, newEntries)) { + return; + } String firstExistingAttrId = null; List toRemove = new ArrayList<>(); for (UserAttributeEntity attr : user.getAttributes()) { @@ -183,6 +192,18 @@ public class UserAdapter implements UserModel, JpaModel { setUsername(valueToSet); return; } + + Set oldEntries = getAttributeStream(name).collect(Collectors.toSet()); + Set newEntries; + if (values == null) { + newEntries = new HashSet<>(); + } else { + newEntries = new HashSet<>(values); + } + if (CollectionUtil.collectionEquals(oldEntries, newEntries)) { + return; + } + // Remove all existing removeAttribute(name); if (values != null) {