#300 LDAP Authentication - Lookup User DN Via Search Filter Before Authentication
Opened 6 years ago by kmontgomery. Modified 6 years ago
kmontgomery/ipsilon ldap-plugin-lookup-changes  into  master

file modified
+46 -7
@@ -6,6 +6,7 @@ 

  from ipsilon.util.policy import Policy

  from ipsilon.util import config as pconfig

  import ldap

+ import ldap.filter

  import subprocess

  

  
@@ -42,6 +43,14 @@ 

                  'user dn template',

                  'Template to turn username into DN.',

                  'uid=%(username)s,ou=People,dc=example,dc=com'),

+             pconfig.Condition(

+                 'lookup dn',

+                 'Look up user DNs?',

+                 False),

+             pconfig.String(

+                 'lookup dn filter template',

+                 'Search filter template used to look up DNs.',

+                 '(mail=%(username)s)'),

              pconfig.Pick(

                  'tls',

                  'What TLS level show be required',
@@ -81,6 +90,14 @@ 

          return self.get_config_value('user dn template')

  

      @property

+     def lookup_dn(self):

+         return self.get_config_value('lookup dn')

+ 

+     @property

+     def lookup_dn_filter_template(self):

+         return self.get_config_value('lookup dn filter template')

+ 

+     @property

      def base_dn(self):

          return self.get_config_value('base dn')

  
@@ -126,10 +143,12 @@ 

              data[name] = value

          return data

  

-     def _get_user_groups(self, conn, base, username):

+     def _get_user_groups(self, conn, base, username, group_filter=None):

+         if group_filter is None:

+             group_filter = "memberuid=%s" % username

+         self.debug("Searching for groups using filter %s" % group_filter)

          # TODO: fixme to support RFC2307bis schemas

-         results = conn.search_s(base, ldap.SCOPE_SUBTREE,

-                                 filterstr='memberuid=%s' % username)

+         results = conn.search_s(base, ldap.SCOPE_SUBTREE, filterstr=group_filter)

          if results is None or results == []:

              self.debug('No groups for %s' % username)

              return []
@@ -139,13 +158,13 @@ 

                  groups.append(r[1]['cn'][0])

          return groups

  

-     def get_user_data_from_conn(self, conn, dn, base, username):

+     def get_user_data_from_conn(self, conn, dn, base, username, group_filter):

          reply = dict()

          try:

              ldapattrs = self._get_user_data(conn, dn)

              self.debug('LDAP attrs for %s: %s' % (dn, ldapattrs))

              userattrs, extras = self.mapper.map_attributes(ldapattrs)

-             groups = self._get_user_groups(conn, base, username)

+             groups = self._get_user_groups(conn, base, username, group_filter)

              reply = userattrs

              reply['_groups'] = groups

              reply['_extras'] = {'ldap': extras}
@@ -155,8 +174,28 @@ 

          return reply

  

      def get_user_attrs(self, user):

+         username = user

          try:

-             dn = self.user_dn_tmpl % {'username': user}

+             if self.lookup_dn:

+                 conn = self._ldap_bind()

+                 conn.simple_bind_s(self.lookup_bind_dn, self.lookup_bind_pw)

+                 search_filter = self.lookup_dn_filter_template % { 'username': ldap.filter.escape_filter_chars(user) }

+                 result = conn.search_s(self.base_dn, ldap.SCOPE_SUBTREE, search_filter, ["dn"])

+                 if result is not None and result != []:

+                     if len(result) > 1:

+                         self.error("User query failed to yeild a unique result.")

+                     else:

+                         dn = result[0][0]

+                         # In case we're using get_user_info below, get the UID or CN attribute that uniquely identifies the user...

+                         uidattr = dn.split(',')[0]

+                         uid = uidattr.split('=')[1]

+                         username = uid

+                 else:

+                     self.error("User not found.")

+                 # reset the ldap connection

+                 conn.unbind()

+             else:

+                 dn = self.user_dn_tmpl % {'username': user}

          except ValueError as e:

              self.error(

                  'DN generation failed with template %s, user %s: %s'
@@ -173,7 +212,7 @@ 

          try:

              base = self.base_dn

              conn = self._ldap_bind()

-             return self.get_user_data_from_conn(conn, dn, base, user)

+             return self.get_user_data_from_conn(conn, dn, base, username)

          except ldap.LDAPError as e:

              self.error(

                  'LDAP search failed for DN %s on base %s: %s' %

file modified
+68 -1
@@ -7,6 +7,7 @@ 

  from ipsilon.util import config as pconfig

  from ipsilon.info.infoldap import InfoProvider as LDAPInfo

  import ldap

+ import ldap.filter

  import subprocess

  import logging

  
@@ -33,6 +34,8 @@ 

  

      return conn

  

+ def ldap_escape(str_to_escape):

+     return ldap.filter.escape_filter_chars(str_to_escape)

  

  class LDAP(LoginFormBase, Log):

  
@@ -47,6 +50,29 @@ 

  

          conn = self._ldap_connect()

          dn = self.lm.bind_dn_tmpl % {'username': username}

+         if self.lm.lookup_dn:

+             if self.lm.lookup_bind_dn == None or self.lm.lookup_bind_pw == None:

+                 self.error('LDAP Lookup Bind DN or Bind PW value is missing.')

+             else:

+                 conn.simple_bind_s(self.lm.lookup_bind_dn, self.lm.lookup_bind_pw)

+                 search_filter = self.lm.lookup_dn_filter_template % {'username': ldap_escape(username)}

+                 result = conn.search_s(self.lm.base_dn, ldap.SCOPE_SUBTREE, search_filter, ["dn"])

+                 if result is not None and result != []:

+                     if len(result) > 1:

+                       self.error("User query failed to yeild a unique result.")

+                     else:

+                       dn = result[0][0]

+                       if self.lm.get_user_info:

+                         # In case we're using get_user_info below, get the UID or CN attribute that uniquely identifies the user...

+                         uidattr = dn.split(',')[0]

+                         uid = uidattr.split('=')[1]

+                         username = uid

+                 else:

+                     self.error("User not found.")

+                 # reset the ldap connection

+                 conn.unbind()

+                 conn = self._ldap_connect()

+ 

          conn.simple_bind_s(dn, password)

  

          # Bypass info plugins to optimize data retrieval
@@ -57,8 +83,9 @@ 

                  self.ldap_info = LDAPInfo(self._site)

  

              base = self.lm.base_dn

+             group_filter = self.lm.group_filter_template % { 'username': username }

              return self.ldap_info.get_user_data_from_conn(conn, dn, base,

-                                                           username)

+                                                           username, group_filter)

  

          return None

  
@@ -123,10 +150,30 @@ 

                  'server url',

                  'The LDAP server url.',

                  'ldap://example.com'),

+             pconfig.Condition(

+                 'lookup dn',

+                 'Look up user DNs?',

+                 False),

+             pconfig.String(

+                 'lookup bind dn',

+                 'Proxy user used to look up DNs.',

+                 ''),

+             pconfig.String(

+                 'lookup bind pw',

+                 'Password for proxy user used to look up DNs.',

+                 ''),

+             pconfig.Template(

+                 'lookup dn filter template',

+                 'Search filter template used to look up DNs.',

+                 '(mail=%(username)s)'),

              pconfig.Template(

                  'bind dn template',

                  'Template to turn username into DN.',

                  'uid=%(username)s,ou=People,dc=example,dc=com'),

+             pconfig.Template(

+                 'group filter template',

+                 'Search filter template used to find groups for user.',

+                 '(memberuid=%(username)s)'),

              pconfig.String(

                  'base dn',

                  'The base dn to look for users and groups',
@@ -183,6 +230,26 @@ 

          return self.get_config_value('bind dn template')

  

      @property

+     def group_filter_template(self):

+         return self.get_config_value('group filter template')

+ 

+     @property

+     def lookup_dn(self):

+         return self.get_config_value('lookup dn')

+ 

+     @property

+     def lookup_bind_dn(self):

+         return self.get_config_value('lookup bind dn')

+ 

+     @property

+     def lookup_bind_pw(self):

+         return self.get_config_value('lookup bind pw')

+ 

+     @property

+     def lookup_dn_filter_template(self):

+         return self.get_config_value('lookup dn filter template')

+ 

+     @property

      def base_dn(self):

          return self.get_config_value('base dn')

  

Signed-off-by: Kendal Montgomery kmontgomery@cbuscollaboratory.com

Added configuration options and code for optional LDAP functionality to look up user's DN - search for user's DN based on a filter rather than assuming the DN by format string. This allows the administrator to configure a lookup user and search filter to find the authenticating user's DN based on alternate attributes (such as mail), and then authenticate the user.

Discussion and comments can be found on pull request 299: https://pagure.io/ipsilon/pull-request/299. Recommendations were implemented from this conversation in this pull request.