Repository: privacyidea/privacyidea

Issue:
Add "multivalue-attributes" to ldap resolver
In regards to #880 we should add a configuration setting ``multivalue_attributes`` to the ldap resolver.

The default would be

    ["mobile"]

which would keep the backward compatiblity.
This could be changed to 

    ["mobile", "memberOf"]

and thus we would gain the functionality of #880 on a per-resolver-level.


Discussion:

Resolving patch:
diff --git a/privacyidea/lib/resolvers/LDAPIdResolver.py b/privacyidea/lib/resolvers/LDAPIdResolver.py
--- a/privacyidea/lib/resolvers/LDAPIdResolver.py
+++ b/privacyidea/lib/resolvers/LDAPIdResolver.py
@@ -2,6 +2,9 @@
 #  Copyright (C) 2014 Cornelius Kölbel
 #  contact:  corny@cornelinux.de
 #
+#  2017-12-22 Cornelius Kölbel <cornelius.koelbel@netknights.it>
+#             Add configurable multi-value-attributes
+#             with the help of Nomen Nescio
 #  2017-07-20 Cornelius Kölbel <cornelius.koelbel@netknights.it>
 #             Fix unicode usernames
 #  2017-01-23 Cornelius Kölbel <cornelius.koelbel@netknights.it>
@@ -197,6 +200,7 @@ def __init__(self):
         self.loginname_attribute = [""]
         self.searchfilter = u""
         self.userinfo = {}
+        self.multivalueattributes = []
         self.uidtype = ""
         self.noreferrals = False
         self._editable = False
@@ -433,14 +437,13 @@ def _ldap_attributes_to_user_object(self, attributes):
         :return: dict with privacyIDEA users.
         """
         ret = {}
-        MULTI_VALUE_ATTRIBUTE = ["mobile", "memberOf"]
         for ldap_k, ldap_v in attributes.items():
             for map_k, map_v in self.userinfo.items():
                 if ldap_k == map_v:
                     if ldap_k == "objectGUID":
                         ret[map_k] = ldap_v[0]
-                    elif type(ldap_v) == list and map_k not in MULTI_VALUE_ATTRIBUTE:
-                        # lists that are not in MULTI_VALUE_ATTRIBUTE return first value
+                    elif type(ldap_v) == list and map_k not in self.multivalueattributes:
+                        # lists that are not in self.multivalueattributes return first value
                         # as a string. Multi-value-attributes are returned as a list
                         if ldap_v:
                             ret[map_k] = ldap_v[0]
@@ -625,6 +628,8 @@ def loadConfig(self, config):
         userinfo = config.get("USERINFO", "{}")
         self.userinfo = yaml.safe_load(userinfo)
         self.userinfo["username"] = self.loginname_attribute[0]
+        multivalueattributes = config.get("MULTIVALUEATTRIBUTES") or '["mobile"]'
+        self.multivalueattributes = yaml.safe_load(multivalueattributes)
         self.map = yaml.safe_load(userinfo)
         self.uidtype = config.get("UIDTYPE", "DN")
         self.noreferrals = is_true(config.get("NOREFERRALS", False))


Test patch:
diff --git a/tests/test_lib_resolver.py b/tests/test_lib_resolver.py
--- a/tests/test_lib_resolver.py
+++ b/tests/test_lib_resolver.py
@@ -76,6 +76,7 @@
                                  "userPassword": "mySecret",
                                  "accoutnExpires": 9223372036854775807,
                                  "objectGUID": objectGUIDs[3],
+                                 "someAttr": ["value1", "value2"],
                                  "oid": "4"}}]
 
 LDAPDirectory_small = [{"dn": 'cn=bob,ou=example,o=test',
@@ -1408,6 +1409,37 @@ def test_27_LDAP_multiple_loginnames(self):
         user_id = y.getUserId(username)
         self.assertEqual(user_id, "cn=kölbel,ou=example,o=test")
 
+    @ldap3mock.activate
+    def test_28_LDAP_multivalues(self):
+        # This tests usernames are entered in the LDAPresolver as unicode.
+        ldap3mock.setLDAPDirectory(LDAPDirectory)
+        y = LDAPResolver()
+        y.loadConfig({'LDAPURI': 'ldap://localhost',
+                      'LDAPBASE': 'o=test',
+                      'BINDDN': 'cn=manager,ou=example,o=test',
+                      'BINDPW': 'ldaptest',
+                      'LOGINNAMEATTRIBUTE': 'cn, email',
+                      'LDAPSEARCHFILTER': '(cn=*)',
+                      'USERINFO': '{"phone" : "telephoneNumber", '
+                                  '"mobile" : "mobile"'
+                                  ', "email" : "email", '
+                                  '"surname" : "sn", '
+                                  '"givenname" : "givenName",'
+                                  '"piAttr": "someAttr"}',
+                      'UIDTYPE': 'DN',
+                      'MULTIVALUEATTRIBUTES': "['piAttr']"
+                      })
+
+        result = y.getUserList({'username': '*'})
+        self.assertEqual(len(result), len(LDAPDirectory))
+
+        user = u"kölbel"
+        user_id = y.getUserId(user)
+        self.assertEqual(user_id, "cn=kölbel,ou=example,o=test")
+        info = y.getUserInfo(user_id)
+        self.assertTrue("value1" in info.get("piAttr"))
+        self.assertTrue("value2" in info.get("piAttr"))
+
 
 class BaseResolverTestCase(MyTestCase):