Details

Applies to
moin 1.9.4
Purpose
Implement LDAP/SASL authentication
Description
This patch implements SASL binding for LDAP auth. This has severaladvantages over simple LDAP binding: No bind DN is necessary, and advanced authentication mechanisms are avaiable (e.g. avoiding plain-text transmission of passwords). DIGEST-MD5 tested (that's the only method my LDAP server supports), but other methods should also work.

I am attaching also a small bug fix patch (to be applied before the SASL patch) that fixes the behavior of Moin when LDAPAuth is used before other methods in the auth chain. The latter patch is similar to the patch in MoinMoinBugs/ChainingAuthMethods.

Patch

   1 From bc7ca56f41194a33cc82b261238a224b6a634aa2 Mon Sep 17 00:00:00 2001
   2 From: Martin Wilck <martin.wilck@ts.fujitsu.com>
   3 Date: Fri, 25 May 2012 16:03:07 +0200
   4 Subject: [PATCH 1/2] PATCH: [LDAPAuth] allow authentication to continue
   5 
   6 This patch removes use of CancelLogin when bad LDAP credentials
   7 were encountered, allowing other auth methods to continue after
   8 LDAP failure.
   9 
  10 Furthermore, in the case of LDAP bind failure, the cases with
  11 and with out bind_dn are more cleanly distinguished.
  12 ---
  13  auth/ldap_login.py |   10 ++++++++--
  14  1 files changed, 8 insertions(+), 2 deletions(-)
  15 
  16 diff --git a/auth/ldap_login.py b/auth/ldap_login.py
  17 index 29dba6e..ba93098 100644
  18 --- a/auth/ldap_login.py
  19 +++ b/auth/ldap_login.py
  20 @@ -27,7 +27,7 @@ except ImportError, err:
  21      raise
  22  
  23  from MoinMoin import user
  24 -from MoinMoin.auth import BaseAuth, CancelLogin, ContinueLogin
  25 +from MoinMoin.auth import BaseAuth, ContinueLogin
  26  
  27  
  28  class LDAPAuth(BaseAuth):
  29 @@ -242,8 +242,14 @@ class LDAPAuth(BaseAuth):
  30                  logging.debug("creating user object with name %r email %r alias %r" % (username, email, aliasname))
  31  
  32              except ldap.INVALID_CREDENTIALS, err:
  33 +                if self.bind_once:
  34 +                    if self.report_invalid_credentials:
  35 +                        return ContinueLogin(user_obj, 
  36 +                                             message=_("Invalid username or password."))
  37 +                    else:
  38 +                        return ContinueLogin(user_obj)
  39                  logging.debug("invalid credentials (wrong password?) for dn %r (username: %r)" % (dn, username))
  40 -                return CancelLogin(_("Invalid username or password."))
  41 +                return ContinueLogin(user_obj, _("Bind to LDAP server %(server)s failed." % {'server': server}))
  42  
  43              if u and self.autocreate:
  44                  logging.debug("calling create_or_update to autocreate user %r" % u.name)
  45 -- 
  46 1.7.7.6
0001-PATCH-LDAPAuth-allow-authentication-to-continue.patch This is the bug fix patch "[PATCH 1/2] PATCH: [LDAPAuth] allow authentication to continue"

   1 From 829f13caed40bb7702d3653d76dc56a3c2bded19 Mon Sep 17 00:00:00 2001
   2 From: Martin Wilck <martin.wilck@ts.fujitsu.com>
   3 Date: Fri, 25 May 2012 16:06:38 +0200
   4 Subject: [PATCH 2/2] PATCH: [LDAPAuth] implement SASL bind
   5 
   6 This patch implements SASL binding for LDAP auth. This has several
   7 advantages over simple LDAP binding: No bind DN is necessary, and
   8 advanced authentication mechanisms are avaiable (e.g. avoiding
   9 plain-text transmission of passwords).
  10 
  11 DIGEST-MD5 tested (that's the only method my LDAP server supports),
  12 but other methods should also work.
  13 ---
  14  auth/ldap_login.py |   40 ++++++++++++++++++++++++++++++++++++++--
  15  1 files changed, 38 insertions(+), 2 deletions(-)
  16 
  17 diff --git a/auth/ldap_login.py b/auth/ldap_login.py
  18 index ba93098..5482e8a 100644
  19 --- a/auth/ldap_login.py
  20 +++ b/auth/ldap_login.py
  21 @@ -26,6 +26,15 @@ except ImportError, err:
  22      logging.error("You need to have python-ldap installed (%s)." % str(err))
  23      raise
  24  
  25 +try:
  26 +    import ldap.sasl as ldap_sasl
  27 +except ImportError, err:
  28 +    logging.error("SASL module not found, SASL bind will not be possible (%s)." % str(err))
  29 +    ldap.sasl = None
  30 +else:
  31 +    ldap.sasl = ldap_sasl
  32 +    del ldap_sasl
  33 +
  34  from MoinMoin import user
  35  from MoinMoin.auth import BaseAuth, ContinueLogin
  36  
  37 @@ -85,6 +94,7 @@ class LDAPAuth(BaseAuth):
  38          autocreate=False, # set to True if you want to autocreate user profiles
  39          name='ldap', # use e.g. 'ldap_pdc' and 'ldap_bdc' (or 'ldap1' and 'ldap2') if you auth against 2 ldap servers
  40          report_invalid_credentials=True, # whether to emit "invalid username or password" msg at login time or not
  41 +        sasl_mech=None, # The SASL mechnism to use - this activates SASL
  42          ):
  43          self.server_uri = server_uri
  44          self.bind_dn = bind_dn
  45 @@ -117,6 +127,24 @@ class LDAPAuth(BaseAuth):
  46  
  47          self.report_invalid_credentials = report_invalid_credentials
  48  
  49 +        if ldap.sasl is None:
  50 +            self.sasl_mech = None
  51 +        else:
  52 +            self.sasl_mech = sasl_mech.upper()
  53 +            self.bind_once = True # There's only 1 bind with SASL
  54 +
  55 +    def sasl_bind(self, connection, authname, passwd):
  56 +        if self.sasl_mech in ("GSSAPI", "EXTERNAL"):
  57 +            authargs = {}
  58 +        else:
  59 +            authargs = {
  60 +                ldap.sasl.CB_AUTHNAME: authname.encode(self.coding),
  61 +                ldap.sasl.CB_PASS: passwd.encode(self.coding)
  62 +                }
  63 +        logging.debug("Attempting SASL/%s bind for %s" % (self.sasl_mech, authname))
  64 +        authtok = ldap.sasl.sasl(authargs, self.sasl_mech)
  65 +        connection.sasl_interactive_bind_s("", authtok)
  66 +
  67      def login(self, request, user_obj, **kw):
  68          username = kw.get('username')
  69          password = kw.get('password')
  70 @@ -168,8 +196,11 @@ class LDAPAuth(BaseAuth):
  71                  # you can use %(username)s and %(password)s here to get the stuff entered in the form:
  72                  binddn = self.bind_dn % locals()
  73                  bindpw = self.bind_pw % locals()
  74 -                l.simple_bind_s(binddn.encode(coding), bindpw.encode(coding))
  75 -                logging.debug("Bound with binddn %r" % binddn)
  76 +                if self.sasl_mech is None:
  77 +                    l.simple_bind_s(binddn.encode(coding), bindpw.encode(coding))
  78 +                    logging.debug("Bound with binddn %r" % binddn)
  79 +                else:
  80 +                    self.sasl_bind(l, binddn, bindpw)
  81  
  82                  # you can use %(username)s here to get the stuff entered in the form:
  83                  filterstr = self.search_filter % locals()
  84 @@ -265,6 +296,11 @@ class LDAPAuth(BaseAuth):
  85                            "Trying to authenticate with next auth list entry." % (server, str(err)))
  86              return ContinueLogin(user_obj, _("LDAP server %(server)s failed.") % {'server': server})
  87  
  88 +        except (ldap.STRONG_AUTH_NOT_SUPPORTED, ldap.STRONG_AUTH_REQUIRED, ldap.AUTH_UNKNOWN), err:
  89 +            logging.error("Authentication to LDAP server %s failed (%s)."
  90 +                          "Trying to authenticate with next auth list entry." % (server, str(err)))
  91 +            return ContinueLogin(user_obj, _("LDAP server %(server)s failed.") % {'server': server})
  92 +            
  93          except:
  94              logging.exception("caught an exception, traceback follows...")
  95              return ContinueLogin(user_obj)
  96 -- 
  97 1.7.7.6
0002-PATCH-LDAPAuth-implement-SASL-bind.patch This patch implements the SASL authentication.

Discussion

Plan


CategoryMoinMoinPatch

MoinMoin: MoinMoinPatch/AddLdapAuthSasl (last edited 2012-05-25 15:08:09 by uncleremus)