diff --git a/model/map/src/main/java/org/keycloak/models/map/client/MapClientAdapter.java b/model/map/src/main/java/org/keycloak/models/map/client/MapClientAdapter.java index 44768a72b9..7e50978899 100644 --- a/model/map/src/main/java/org/keycloak/models/map/client/MapClientAdapter.java +++ b/model/map/src/main/java/org/keycloak/models/map/client/MapClientAdapter.java @@ -23,10 +23,13 @@ import org.keycloak.models.RealmModel; import org.keycloak.models.RoleModel; import org.keycloak.models.utils.KeycloakModelUtils; import java.security.MessageDigest; +import java.util.Collections; import java.util.HashMap; +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; /** @@ -246,7 +249,7 @@ public abstract class MapClientAdapter extends AbstractClientModel extends AbstractClientModel attribute = entity.getAttribute(name); + if (attribute.isEmpty()) return null; + return attribute.get(0); } @Override public Map getAttributes() { - return entity.getAttributes(); + return entity.getAttributes().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, + entry -> { + if (entry.getValue().isEmpty()) return null; + return entry.getValue().get(0); + }) + ); } @Override diff --git a/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntity.java b/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntity.java index 77278e967f..b07580d5a8 100644 --- a/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntity.java +++ b/model/map/src/main/java/org/keycloak/models/map/client/MapClientEntity.java @@ -19,9 +19,11 @@ package org.keycloak.models.map.client; import org.keycloak.models.ProtocolMapperModel; import org.keycloak.models.map.common.AbstractEntity; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Objects; @@ -49,7 +51,7 @@ public class MapClientEntity implements AbstractEntity { private String secret; private String registrationToken; private String protocol; - private Map attributes = new HashMap<>(); + private Map> attributes = new HashMap<>(); private Map authFlowBindings = new HashMap<>(); private boolean publicClient; private boolean fullScopeAllowed; @@ -190,13 +192,12 @@ public class MapClientEntity implements AbstractEntity { this.protocol = protocol; } - public Map getAttributes() { + public Map> getAttributes() { return attributes; } - public void setAttributes(Map attributes) { - this.updated |= ! Objects.equals(this.attributes, attributes); - this.attributes = attributes; + public void setAttribute(String name, List values) { + this.updated |= ! Objects.equals(this.attributes.put(name, values), values); } public Map getAuthFlowBindings() { @@ -411,17 +412,12 @@ public class MapClientEntity implements AbstractEntity { updated |= this.redirectUris.remove(redirectUri); } - public void setAttribute(String name, String value) { - this.updated = true; - this.attributes.put(name, value); - } - public void removeAttribute(String name) { this.updated |= this.attributes.remove(name) != null; } - public String getAttribute(String name) { - return this.attributes.get(name); + public List getAttribute(String name) { + return attributes.getOrDefault(name, Collections.EMPTY_LIST); } public String getAuthenticationFlowBindingOverride(String binding) { diff --git a/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeAdapter.java b/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeAdapter.java index 69596b4c57..9a3032baf9 100644 --- a/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeAdapter.java +++ b/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeAdapter.java @@ -16,9 +16,12 @@ */ package org.keycloak.models.map.clientscope; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.stream.Collectors; import java.util.stream.Stream; import org.keycloak.models.ClientScopeModel; import org.keycloak.models.KeycloakSession; @@ -66,7 +69,7 @@ public abstract class MapClientScopeAdapter extends AbstractClientScopeModel< @Override public void setAttribute(String name, String value) { - entity.setAttribute(name, value); + entity.setAttribute(name, Collections.singletonList(value)); } @Override @@ -76,12 +79,19 @@ public abstract class MapClientScopeAdapter extends AbstractClientScopeModel< @Override public String getAttribute(String name) { - return entity.getAttribute(name); + List attribute = entity.getAttribute(name); + if (attribute.isEmpty()) return null; + return attribute.get(0); } @Override public Map getAttributes() { - return entity.getAttributes(); + return entity.getAttributes().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, + entry -> { + if (entry.getValue().isEmpty()) return null; + return entry.getValue().get(0); + }) + ); } @Override diff --git a/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeEntity.java b/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeEntity.java index ab09aa451b..77d29227cc 100644 --- a/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeEntity.java +++ b/model/map/src/main/java/org/keycloak/models/map/clientscope/MapClientScopeEntity.java @@ -17,8 +17,10 @@ package org.keycloak.models.map.clientscope; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.LinkedHashSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -39,7 +41,7 @@ public class MapClientScopeEntity implements AbstractEntity { private final Set scopeMappings = new LinkedHashSet<>(); private final Map protocolMappers = new HashMap<>(); - private final Map attributes = new HashMap<>(); + private final Map> attributes = new HashMap<>(); /** * Flag signalizing that any of the setters has been meaningfully used. @@ -96,14 +98,12 @@ public class MapClientScopeEntity implements AbstractEntity { this.protocol = protocol; } - public Map getAttributes() { + public Map> getAttributes() { return attributes; } - public void setAttributes(Map attributes) { - this.updated |= ! Objects.equals(this.attributes, attributes); - this.attributes.clear(); - this.attributes.putAll(attributes); + public void setAttribute(String name, List values) { + this.updated |= ! Objects.equals(this.attributes.put(name, values), values); } public ProtocolMapperModel addProtocolMapper(ProtocolMapperModel model) { @@ -136,17 +136,12 @@ public class MapClientScopeEntity implements AbstractEntity { return id == null ? null : protocolMappers.get(id); } - public void setAttribute(String name, String value) { - this.updated = true; - this.attributes.put(name, value); - } - public void removeAttribute(String name) { this.updated |= this.attributes.remove(name) != null; } - public String getAttribute(String name) { - return this.attributes.get(name); + public List getAttribute(String name) { + return attributes.getOrDefault(name, Collections.EMPTY_LIST); } public String getRealmId() { diff --git a/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmAdapter.java b/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmAdapter.java index 2cc286cb57..b8aa457c5a 100644 --- a/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmAdapter.java +++ b/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmAdapter.java @@ -17,6 +17,7 @@ package org.keycloak.models.map.realm; import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Objects; import static java.util.Objects.nonNull; @@ -181,7 +182,7 @@ public abstract class MapRealmAdapter extends AbstractRealmModel extends AbstractRealmModel attribute = entity.getAttribute(name); + if (attribute.isEmpty()) return null; + return attribute.get(0); } @Override public Map getAttributes() { - return entity.getAttributes(); + return entity.getAttributes().entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, + entry -> { + if (entry.getValue().isEmpty()) return null; + return entry.getValue().get(0); + }) + ); } @Override @@ -435,11 +443,11 @@ public abstract class MapRealmAdapter extends AbstractRealmModel getUserActionTokenLifespans() { Map tokenLifespans = entity.getAttributes().entrySet().stream() .filter(Objects::nonNull) - .filter(entry -> nonNull(entry.getValue())) + .filter(entry -> nonNull(entry.getValue()) && ! entry.getValue().isEmpty()) .filter(entry -> entry.getKey().startsWith(ACTION_TOKEN_GENERATED_BY_USER_LIFESPAN + ".")) .collect(Collectors.toMap( entry -> entry.getKey().substring(ACTION_TOKEN_GENERATED_BY_USER_LIFESPAN.length() + 1), - entry -> Integer.valueOf(entry.getValue()))); + entry -> Integer.valueOf(entry.getValue().get(0)))); return Collections.unmodifiableMap(tokenLifespans); } diff --git a/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmEntity.java b/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmEntity.java index 1886ad6f39..2d3f9315b9 100644 --- a/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmEntity.java +++ b/model/map/src/main/java/org/keycloak/models/map/realm/MapRealmEntity.java @@ -20,6 +20,7 @@ import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; @@ -111,7 +112,7 @@ public class MapRealmEntity implements AbstractEntity { private final Set defaultGroupIds = new HashSet<>(); private final Set defaultClientScopes = new HashSet<>(); private final Set optionalClientScopes = new HashSet<>(); - private final Map attributes = new HashMap<>(); + private final Map> attributes = new HashMap<>(); private final Map> localizationTexts = new HashMap<>(); private final Map clientInitialAccesses = new HashMap<>(); private final Map components = new HashMap<>(); @@ -665,19 +666,19 @@ public class MapRealmEntity implements AbstractEntity { this.webAuthnPolicyPasswordless = webAuthnPolicyPasswordless; } - public void setAttribute(String name, String value) { - this.updated |= !Objects.equals(this.attributes.put(name, value), value); + public void setAttribute(String name, List values) { + this.updated |= ! Objects.equals(this.attributes.put(name, values), values); } public void removeAttribute(String name) { this.updated |= attributes.remove(name) != null; } - public String getAttribute(String name) { - return attributes.get(name); + public List getAttribute(String name) { + return attributes.getOrDefault(name, Collections.EMPTY_LIST); } - public Map getAttributes() { + public Map> getAttributes() { return attributes; } diff --git a/model/map/src/main/java/org/keycloak/models/map/storage/MapFieldPredicates.java b/model/map/src/main/java/org/keycloak/models/map/storage/MapFieldPredicates.java index 591b3f4cfc..5a99d51bd7 100644 --- a/model/map/src/main/java/org/keycloak/models/map/storage/MapFieldPredicates.java +++ b/model/map/src/main/java/org/keycloak/models/map/storage/MapFieldPredicates.java @@ -56,6 +56,7 @@ import org.keycloak.sessions.RootAuthenticationSessionModel; import org.keycloak.storage.StorageId; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Objects; import java.util.function.Function; @@ -299,10 +300,15 @@ public class MapFieldPredicates { throw new CriterionNotSupportedException(ClientModel.SearchableFields.ATTRIBUTE, op, "Invalid arguments, expected (String attribute_name), got: " + Arrays.toString(values)); } String attrNameS = (String) attrName; - Function, ?> getter = ue -> ue.getAttribute(attrNameS); - Object[] realValue = {values[1]}; + Object[] realValues = new Object[values.length - 1]; + System.arraycopy(values, 1, realValues, 0, values.length - 1); + Predicate valueComparator = CriteriaOperator.predicateFor(op, realValues); + Function, ?> getter = ue -> { + final List attrs = ue.getAttribute(attrNameS); + return attrs != null && attrs.stream().anyMatch(valueComparator); + }; - return mcb.fieldCompare(op, getter, realValue); + return mcb.fieldCompare(Boolean.TRUE::equals, getter); } private static MapModelCriteriaBuilder, UserModel> checkGrantedUserRole(MapModelCriteriaBuilder, UserModel> mcb, Operator op, Object[] values) {