keycloak-uncached
Changes
testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ScopeManagementTest.java 6(+6 -0)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomAuthFlowOTPTest.java 12(+2 -10)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ProfileTest.java 9(+2 -7)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/TermsAndConditionsTest.java 4(+2 -2)
testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java 28(+14 -14)
Details
diff --git a/core/src/main/java/org/keycloak/json/StringListMapDeserializer.java b/core/src/main/java/org/keycloak/json/StringListMapDeserializer.java
new file mode 100644
index 0000000..cd8ab4c
--- /dev/null
+++ b/core/src/main/java/org/keycloak/json/StringListMapDeserializer.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2016 Red Hat, Inc. and/or its affiliates
+ * and other contributors as indicated by the @author tags.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.keycloak.json;
+
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+public class StringListMapDeserializer extends JsonDeserializer<Object> {
+
+    @Override
+    public Object deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
+        JsonNode jsonNode = jsonParser.readValueAsTree();
+        Iterator<Map.Entry<String, JsonNode>> itr = jsonNode.fields();
+        Map<String, List<String>> map = new HashMap<>();
+        while (itr.hasNext()) {
+            Map.Entry<String, JsonNode> e = itr.next();
+            List<String> values = new LinkedList<>();
+            if (!e.getValue().isArray()) {
+                values.add(e.getValue().asText());
+            } else {
+                ArrayNode a = (ArrayNode) e.getValue();
+                Iterator<JsonNode> vitr = a.elements();
+                while (vitr.hasNext()) {
+                    values.add(vitr.next().asText());
+                }
+            }
+            map.put(e.getKey(), values);
+        }
+        return map;
+    }
+
+}
                diff --git a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
index b715bf5..26bcdf6 100755
--- a/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
+++ b/core/src/main/java/org/keycloak/representations/idm/UserRepresentation.java
@@ -17,7 +17,8 @@
 
 package org.keycloak.representations.idm;
 
-import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import org.keycloak.json.StringListMapDeserializer;
 
 import java.util.Arrays;
 import java.util.HashMap;
@@ -43,8 +44,8 @@ public class UserRepresentation {
     protected String federationLink;
     protected String serviceAccountClientId; // For rep, it points to clientId (not DB ID)
 
-    // Currently there is Map<String, List<String>> but for backwards compatibility, we also need to support Map<String, String>
-    protected Map<String, Object> attributes;
+    @JsonDeserialize(using = StringListMapDeserializer.class)
+    protected Map<String, List<String>> attributes;
     protected List<CredentialRepresentation> credentials;
     protected List<String> requiredActions;
     protected List<FederatedIdentityRepresentation> federatedIdentities;
@@ -141,17 +142,11 @@ public class UserRepresentation {
         this.emailVerified = emailVerified;
     }
 
-    public Map<String, Object> getAttributes() {
+    public Map<String, List<String>> getAttributes() {
         return attributes;
     }
 
-    // This method can be removed once we can remove backwards compatibility with Keycloak 1.3 (then getAttributes() can be changed to return Map<String, List<String>> )
-    @JsonIgnore
-    public Map<String, List<String>> getAttributesAsListValues() {
-        return (Map) attributes;
-    }
-
-    public void setAttributes(Map<String, Object> attributes) {
+    public void setAttributes(Map<String, List<String>> attributes) {
         this.attributes = attributes;
     }
 
                diff --git a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
index e647aec..2317352 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/ModelToRepresentation.java
@@ -197,7 +197,7 @@ public class ModelToRepresentation {
         rep.setRequiredActions(reqActions);
 
         if (user.getAttributes() != null && !user.getAttributes().isEmpty()) {
-            Map<String, Object> attrs = new HashMap<>();
+            Map<String, List<String>> attrs = new HashMap<>();
             attrs.putAll(user.getAttributes());
             rep.setAttributes(attrs);
         }
                diff --git a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
index f1e4ea7..8ae1caa 100755
--- a/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
+++ b/server-spi/src/main/java/org/keycloak/models/utils/RepresentationToModel.java
@@ -1342,16 +1342,10 @@ public class RepresentationToModel {
         user.setLastName(userRep.getLastName());
         user.setFederationLink(userRep.getFederationLink());
         if (userRep.getAttributes() != null) {
-            for (Map.Entry<String, Object> entry : userRep.getAttributes().entrySet()) {
-                Object value = entry.getValue();
-
-                if (value instanceof Collection) {
-                    Collection<String> colVal = (Collection<String>) value;
-                    user.setAttribute(entry.getKey(), new ArrayList<>(colVal));
-                } else if (value instanceof String) {
-                    // TODO: This is here just for backwards compatibility with KC 1.3 and earlier
-                    String stringVal = (String) value;
-                    user.setSingleAttribute(entry.getKey(), stringVal);
+            for (Map.Entry<String, List<String>> entry : userRep.getAttributes().entrySet()) {
+                List<String> value = entry.getValue();
+                if (value != null) {
+                    user.setAttribute(entry.getKey(), new ArrayList<>(value));
                 }
             }
         }
@@ -2226,20 +2220,11 @@ public class RepresentationToModel {
     public static void importFederatedUser(KeycloakSession session, RealmModel newRealm, UserRepresentation userRep) {
         UserFederatedStorageProvider federatedStorage = session.userFederatedStorage();
         if (userRep.getAttributes() != null) {
-            for (Map.Entry<String, Object> entry : userRep.getAttributes().entrySet()) {
+            for (Map.Entry<String, List<String>> entry : userRep.getAttributes().entrySet()) {
                 String key = entry.getKey();
-                Object value = entry.getValue();
-                if (value == null) continue;
-
-                if (value instanceof Collection) {
-                    Collection<String> colVal = (Collection<String>) value;
-                    List<String> list = new LinkedList<>();
-                    list.addAll(colVal);
-                    federatedStorage.setAttribute(newRealm, userRep.getId(), key, list);
-                } else if (value instanceof String) {
-                    // TODO: This is here just for backwards compatibility with KC 1.3 and earlier
-                    String stringVal = (String) value;
-                    federatedStorage.setSingleAttribute(newRealm, userRep.getId(), key, stringVal);
+                List<String> value = entry.getValue();
+                if (value != null) {
+                    federatedStorage.setAttribute(newRealm, userRep.getId(), key, new LinkedList<>(value));
                 }
             }
         }
                diff --git a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
index 96d514c..62c3157 100755
--- a/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
+++ b/services/src/main/java/org/keycloak/exportimport/util/ExportUtils.java
@@ -635,7 +635,7 @@ public class ExportUtils {
         userRep.setId(id);
         MultivaluedHashMap<String, String> attributes = session.userFederatedStorage().getAttributes(realm, id);
         if (attributes.size() > 0) {
-            Map<String, Object> attrs = new HashMap<>();
+            Map<String, List<String>> attrs = new HashMap<>();
             attrs.putAll(attributes);
             userRep.setAttributes(attrs);
         }
                diff --git a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
index f76761f..7728ecf 100755
--- a/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
+++ b/services/src/main/java/org/keycloak/services/resources/admin/UsersResource.java
@@ -262,8 +262,8 @@ public class UsersResource {
             }
         }
 
-        if (rep.getAttributesAsListValues() != null) {
-            for (Map.Entry<String, List<String>> attr : rep.getAttributesAsListValues().entrySet()) {
+        if (rep.getAttributes() != null) {
+            for (Map.Entry<String, List<String>> attr : rep.getAttributes().entrySet()) {
                 user.setAttribute(attr.getKey(), attr.getValue());
             }
 
                diff --git a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ScopeManagementTest.java b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ScopeManagementTest.java
index 4695e55..b2e1a42 100644
--- a/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ScopeManagementTest.java
+++ b/testsuite/integration/src/test/java/org/keycloak/testsuite/authorization/ScopeManagementTest.java
@@ -18,9 +18,12 @@
 
 package org.keycloak.testsuite.authorization;
 
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
 import org.junit.Test;
 import org.keycloak.authorization.model.Scope;
+import org.keycloak.representations.idm.UserRepresentation;
 import org.keycloak.representations.idm.authorization.ScopeRepresentation;
+import org.keycloak.util.JsonSerialization;
 
 import javax.ws.rs.client.Entity;
 import javax.ws.rs.client.Invocation.Builder;
@@ -28,6 +31,9 @@ import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
 
+import java.io.IOException;
+import java.util.LinkedList;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomAuthFlowOTPTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomAuthFlowOTPTest.java
index 8d34b02..0230842 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomAuthFlowOTPTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/custom/CustomAuthFlowOTPTest.java
@@ -159,11 +159,7 @@ public class CustomAuthFlowOTPTest extends AbstractCustomAccountManagementTest {
         setConditionalOTPForm(config);
 
         //add skip user attribute to user
-        Map<String, Object> userAttributes = new HashMap<>();
-        List<String> attributeValues = new ArrayList<>();
-        attributeValues.add("skip");
-        userAttributes.put("userSkipAttribute", attributeValues);
-        testUser.setAttributes(userAttributes);
+        testUser.singleAttribute("userSkipAttribute", "skip");
         testRealmResource().users().get(testUser.getId()).update(testUser);
         
         //test OTP is skipped
@@ -182,11 +178,7 @@ public class CustomAuthFlowOTPTest extends AbstractCustomAccountManagementTest {
         setConditionalOTPForm(config);
 
         //add force user attribute to user
-        Map<String, Object> userAttributes = new HashMap<>();
-        List<String> attributeValues = new ArrayList<>();
-        attributeValues.add("force");
-        userAttributes.put("userSkipAttribute", attributeValues);
-        testUser.setAttributes(userAttributes);
+        testUser.singleAttribute("userSkipAttribute", "force");
         testRealmResource().users().get(testUser.getId()).update(testUser);
         
         //test OTP is required
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ProfileTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ProfileTest.java
index 4d83bd9..7e2614b 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ProfileTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/account/ProfileTest.java
@@ -72,13 +72,8 @@ public class ProfileTest extends TestRealmKeycloakTest {
         UserRepresentation user = RealmRepUtil.findUser(testRealm, "test-user@localhost");
         user.setFirstName("First");
         user.setLastName("Last");
-        Map<String, Object> attributes = user.getAttributes();
-        if (attributes == null) {
-            attributes = new HashMap<>();
-            user.setAttributes(attributes);
-        }
-        attributes.put("key1", "value1");
-        attributes.put("key2", "value2");
+        user.singleAttribute("key1", "value1");
+        user.singleAttribute("key2", "value2");
 
         UserRepresentation user2 = UserBuilder.create()
                                               .enabled(true)
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/TermsAndConditionsTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/TermsAndConditionsTest.java
index 6fbd614..c7da3a9 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/TermsAndConditionsTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/actions/TermsAndConditionsTest.java
@@ -94,7 +94,7 @@ public class TermsAndConditionsTest extends TestRealmKeycloakTest {
 
         // assert user attribute is properly set
         UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
-        Map<String,List<String>> attributes = user.getAttributesAsListValues();
+        Map<String,List<String>> attributes = user.getAttributes();
         assertNotNull("timestamp for terms acceptance was not stored in user attributes", attributes);
         List<String> termsAndConditions = attributes.get(TermsAndConditions.USER_ATTRIBUTE);
         assertTrue("timestamp for terms acceptance was not stored in user attributes as "
@@ -128,7 +128,7 @@ public class TermsAndConditionsTest extends TestRealmKeycloakTest {
 
         // assert user attribute is properly removed
         UserRepresentation user = ActionUtil.findUserWithAdminClient(adminClient, "test-user@localhost");
-        Map<String,List<String>> attributes = user.getAttributesAsListValues();
+        Map<String,List<String>> attributes = user.getAttributes();
         if (attributes != null) {
             assertNull("expected null for terms acceptance user attribute " + TermsAndConditions.USER_ATTRIBUTE,
                     attributes.get(TermsAndConditions.USER_ATTRIBUTE));
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java
index c3f6c0d..81af8c6 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/admin/UserTest.java
@@ -417,19 +417,19 @@ public class UserTest extends AbstractAdminTest {
         List<String> vals = new ArrayList<>();
         vals.add("value2user2");
         vals.add("value2user2_2");
-        user2.getAttributesAsListValues().put("attr2", vals);
+        user2.getAttributes().put("attr2", vals);
 
         String user2Id = createUser(user2);
 
         user1 = realm.users().get(user1Id).toRepresentation();
-        assertEquals(2, user1.getAttributesAsListValues().size());
-        assertAttributeValue("value1user1", user1.getAttributesAsListValues().get("attr1"));
-        assertAttributeValue("value2user1", user1.getAttributesAsListValues().get("attr2"));
+        assertEquals(2, user1.getAttributes().size());
+        assertAttributeValue("value1user1", user1.getAttributes().get("attr1"));
+        assertAttributeValue("value2user1", user1.getAttributes().get("attr2"));
 
         user2 = realm.users().get(user2Id).toRepresentation();
-        assertEquals(2, user2.getAttributesAsListValues().size());
-        assertAttributeValue("value1user2", user2.getAttributesAsListValues().get("attr1"));
-        vals = user2.getAttributesAsListValues().get("attr2");
+        assertEquals(2, user2.getAttributes().size());
+        assertAttributeValue("value1user2", user2.getAttributes().get("attr1"));
+        vals = user2.getAttributes().get("attr2");
         assertEquals(2, vals.size());
         assertTrue(vals.contains("value2user2") && vals.contains("value2user2_2"));
 
@@ -439,18 +439,18 @@ public class UserTest extends AbstractAdminTest {
         updateUser(realm.users().get(user1Id), user1);
 
         user1 = realm.users().get(user1Id).toRepresentation();
-        assertEquals(3, user1.getAttributesAsListValues().size());
-        assertAttributeValue("value3user1", user1.getAttributesAsListValues().get("attr1"));
-        assertAttributeValue("value2user1", user1.getAttributesAsListValues().get("attr2"));
-        assertAttributeValue("value4user1", user1.getAttributesAsListValues().get("attr3"));
+        assertEquals(3, user1.getAttributes().size());
+        assertAttributeValue("value3user1", user1.getAttributes().get("attr1"));
+        assertAttributeValue("value2user1", user1.getAttributes().get("attr2"));
+        assertAttributeValue("value4user1", user1.getAttributes().get("attr3"));
 
         user1.getAttributes().remove("attr1");
         updateUser(realm.users().get(user1Id), user1);
 
         user1 = realm.users().get(user1Id).toRepresentation();
-        assertEquals(2, user1.getAttributesAsListValues().size());
-        assertAttributeValue("value2user1", user1.getAttributesAsListValues().get("attr2"));
-        assertAttributeValue("value4user1", user1.getAttributesAsListValues().get("attr3"));
+        assertEquals(2, user1.getAttributes().size());
+        assertAttributeValue("value2user1", user1.getAttributes().get("attr2"));
+        assertAttributeValue("value4user1", user1.getAttributes().get("attr3"));
 
         user1.getAttributes().clear();
         updateUser(realm.users().get(user1Id), user1);
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java
index 030330c..386b291 100644
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/exportimport/ExportImportUtil.java
@@ -164,13 +164,13 @@ public class ExportImportUtil {
         Assert.assertEquals("app-admin", appRoles.iterator().next().getName());
 
         // Test attributes
-        Map<String, List<String>> attrs = wburke.getAttributesAsListValues();
+        Map<String, List<String>> attrs = wburke.getAttributes();
         Assert.assertEquals(1, attrs.size());
         List<String> attrVals = attrs.get("email");
         Assert.assertEquals(1, attrVals.size());
         Assert.assertEquals("bburke@redhat.com", attrVals.get(0));
 
-        attrs = admin.getAttributesAsListValues();
+        attrs = admin.getAttributes();
         Assert.assertEquals(2, attrs.size());
         attrVals = attrs.get("key1");
         Assert.assertEquals(1, attrVals.size());
                diff --git a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java
index d42a91b..42f85e6 100755
--- a/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java
+++ b/testsuite/integration-arquillian/tests/base/src/test/java/org/keycloak/testsuite/i18n/EmailTest.java
@@ -52,8 +52,7 @@ public class EmailTest extends AbstractI18NTest {
 
     private void changeUserLocale(String locale) {
         UserRepresentation user = findUser("login-test");
-        if (user.getAttributes() == null) user.setAttributes(new HashMap<String, Object>());
-        user.getAttributes().put(UserModel.LOCALE, Collections.singletonList(locale));
+        user.singleAttribute(UserModel.LOCALE, locale);
         ApiUtil.findUserByUsernameId(testRealm(), "login-test").update(user);
     }