Repository: privacyidea/privacyidea

Issue:
Machine LDAP resolver, hardcoded hostname key attribute
I try to use the LDAP Machine resolver. 

My config looks like:

> Machine ID attribute: cn
> search filter: (objectClass=computer)
> hostname attribute: cn
> ip address attribute: aRecord

Testing the connection seems fine, the message show me that there are 4 machine objects.

On the machine page I don't see any machine objects and in the log I get:

```
[2016-08-10 15:15:18,765][5655][140268073686976][ERROR][privacyidea.lib.machines.ldap:176] Error during fetching LDAP objects: KeyError('hostname',)
[2016-08-10 15:15:18,766][5655][140268073686976][ERROR][privacyidea.lib.machines.ldap:176] Error during fetching LDAP objects: KeyError('hostname',)
[2016-08-10 15:15:18,766][5655][140268073686976][ERROR][privacyidea.lib.machines.ldap:176] Error during fetching LDAP objects: KeyError('hostname',)
[2016-08-10 15:15:18,766][5655][140268073686976][ERROR][privacyidea.lib.machines.ldap:176] Error during fetching LDAP objects: KeyError('hostname',)
```

looking at the [get_machines](https://github.com/privacyidea/privacyidea/blob/master/privacyidea/lib/machines/ldap.py#L115) function in 
I see on line [173](https://github.com/privacyidea/privacyidea/blob/master/privacyidea/lib/machines/ldap.py#L173) that you use 

`hosnmae=machine['hostname']`

and a few lines before you build the machine array with:

```
                    for k, v in attributes.items():
                        key = self.reverse_map.get(k)
                        if key:
                            if type(v) == list:
                                machine[key] = v[0]
                            else:
                                machine[key] = v
```

to me its seems that you use the ldap attribute you get from the search to build the machine array. But in my LDAP Directory, there is no hostname attribute, it is `cn` as defined before. 

Shouln't you use the `hostname_attribute` similar as you do it with `machineid`?

```
                    if self.id_attribute.lower() == "dn":
                        machine['machineid'] = dn
                    else:
                        machine['machineid'] = self._get_uid(attributes)
```



Discussion:

Resolving patch:
diff --git a/privacyidea/api/machine.py b/privacyidea/api/machine.py
--- a/privacyidea/api/machine.py
+++ b/privacyidea/api/machine.py
@@ -118,7 +118,7 @@ def list_machines_api():
     machines = [mobject.get_dict() for mobject in machines]
     g.audit_object.log({'success': True,
                         'info': "hostname: {0!s}, ip: {1!s}".format(hostname, ip)})
-    
+
     return send_result(machines)
 
 
diff --git a/privacyidea/lib/machine.py b/privacyidea/lib/machine.py
--- a/privacyidea/lib/machine.py
+++ b/privacyidea/lib/machine.py
@@ -71,7 +71,8 @@ def get_machines(hostname=None, ip=None, id=None, resolver=None, any=None):
         reso_obj = get_resolver_object(reso)
         resolver_machines = reso_obj.get_machines(hostname=hostname,
                                                   ip=ip,
-                                                  machine_id=id, any=any,
+                                                  machine_id=id,
+                                                  any=any,
                                                   substring=True)
         all_machines += resolver_machines
 
diff --git a/privacyidea/lib/machines/ldap.py b/privacyidea/lib/machines/ldap.py
--- a/privacyidea/lib/machines/ldap.py
+++ b/privacyidea/lib/machines/ldap.py
@@ -70,12 +70,14 @@ def _bind(self):
                 raise Exception("Wrong credentials")
             self.i_am_bound = True
 
-    def _get_uid(self, entry):
-        if type(entry.get(self.id_attribute)) == list:
-            uid = entry.get(self.id_attribute)[0]
+    @staticmethod
+    def _get_entry(entry_attribute, entries):
+        if type(entries.get(entry_attribute)) == list:
+            entry = entries.get(entry_attribute)[0]
         else:
-            uid = entry.get(self.id_attribute)
-        return uid
+            entry = entries.get(entry_attribute)
+        return entry
+
 
     @staticmethod
     def _create_ldap_filter(search_filter,
@@ -85,6 +87,11 @@ def _create_ldap_filter(search_filter,
         filter = "(&" + search_filter
 
         if not any:
+            if id_attribute.lower() != "dn" and machine_id:
+                if substring:
+                    filter += "({0!s}=*{1!s}*)".format(id_attribute, machine_id)
+                else:
+                    filter += "({0!s}={1!s})".format(id_attribute, machine_id)
             if hostname:
                 if substring:
                     filter += "({0!s}=*{1!s}*)".format(hostname_attribute, hostname)
@@ -135,6 +142,7 @@ def get_machines(self, machine_id=None, hostname=None, ip=None, any=None,
             attributes.append(self.ip_attribute)
         if self.hostname_attribute:
             attributes.append(self.hostname_attribute)
+
         # do the filter depending on the searchDict
         filter = self._create_ldap_filter(self.search_filter,
                                           self.id_attribute, machine_id,
@@ -142,36 +150,43 @@ def get_machines(self, machine_id=None, hostname=None, ip=None, any=None,
                                           self.ip_attribute, ip,
                                           substring, any)
 
-        self.l.search(search_base=self.basedn,
-                      search_scope=ldap3.SUBTREE,
-                      search_filter=filter,
-                      attributes=attributes,
-                      paged_size=self.sizelimit)
+        if self.id_attribute.lower() == "dn" and machine_id:
+            self.l.search(search_base=machine_id,
+                          search_scope=ldap3.BASE,
+                          search_filter=filter,
+                          attributes=attributes,
+                          paged_size=self.sizelimit)
+        else:
+            self.l.search(search_base=self.basedn,
+                          search_scope=ldap3.SUBTREE,
+                          search_filter=filter,
+                          attributes=attributes,
+                          paged_size=self.sizelimit)
+
         # returns a list of dictionaries
         for entry in self.l.response:
             dn = entry.get("dn")
             attributes = entry.get("attributes")
+
             try:
                 if entry.get("type") == "searchResEntry":
                     machine = {}
+
                     if self.id_attribute.lower() == "dn":
                         machine['machineid'] = dn
                     else:
-                        machine['machineid'] = self._get_uid(attributes)
-                    for k, v in attributes.items():
-                        key = self.reverse_map.get(k)
-                        if key:
-                            if type(v) == list:
-                                machine[key] = v[0]
-                            else:
-                                machine[key] = v
-                    machine_ip = None
-                    if machine.get("ip"):
-                        machine_ip = netaddr.IPAddress(machine.get("ip"))
+                        machine['machineid'] = self._get_entry(self.id_attribute, attributes)
+
+                    machine['hostname'] = self._get_entry(self.hostname_attribute, attributes)
+                    machine['ip'] = self._get_entry(self.ip_attribute, attributes)
+
+                    if machine['ip']:
+                        machine['ip'] = netaddr.IPAddress(machine['ip'])
+
                     machines.append(Machine(self.name,
                                             machine['machineid'],
                                             hostname=machine['hostname'],
-                                            ip=machine_ip))
+                                            ip=machine['ip']))
             except Exception as exx:  # pragma: no cover
                 log.error("Error during fetching LDAP objects: {0!r}".format(exx))
                 log.debug("{0!s}".format(traceback.format_exc()))
@@ -179,6 +194,7 @@ def get_machines(self, machine_id=None, hostname=None, ip=None, any=None,
         return machines
 
     def get_machine_id(self, hostname=None, ip=None):
+
         """
         Returns the machine id for a given hostname or IP address.
 
@@ -233,9 +249,7 @@ def load_config(self, config):
         self.ip_attribute = config.get("IPATTRIBUTE")
         self.search_filter = config.get("SEARCHFILTER",
                                         "(objectClass=computer)")
-        self.reverse_map = {self.ip_attribute: "ip",
-                            self.hostname_attribute: "hostname",
-                            self.id_attribute: "id"}
+
         self.noreferrals = config.get("NOREFERRALS", False)
         self.editable = config.get("EDITABLE", False)
         self.certificate = config.get("CACERTIFICATE")


Test patch:
diff --git a/tests/test_lib_machine_resolver_ldap.py b/tests/test_lib_machine_resolver_ldap.py
--- a/tests/test_lib_machine_resolver_ldap.py
+++ b/tests/test_lib_machine_resolver_ldap.py
@@ -115,7 +115,10 @@ def test_04_get_machines_any(self):
 
 
     def test_05_get_single_machine(self):
-        machine = self.mreso.get_machines(machine_id="cn=machine1,ou=example,o=test")[0]
+
+        machines = self.mreso.get_machines(machine_id="cn=machine1,ou=example,o=test")
+        self.assertEqual(len(machines), 1)
+        machine = machines[0]
         self.assertEqual(machine.id, "cn=machine1,ou=example,o=test")
         self.assertTrue(machine.has_hostname("machine1.example.test"))