changeset 1769:2778c4ce1ea4

improved ldap auth (ported from 1.5)
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Tue, 30 Jan 2007 23:58:29 +0100
parents 5215c7e04a61
children 009e73eabc21
files MoinMoin/auth/ldap_login.py wiki/config/more_samples/ldap_smb_farmconfig.py
diffstat 2 files changed, 82 insertions(+), 48 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/auth/ldap_login.py	Tue Jan 30 23:07:33 2007 +0100
+++ b/MoinMoin/auth/ldap_login.py	Tue Jan 30 23:58:29 2007 +0100
@@ -9,8 +9,8 @@
     @license: GNU GPL, see COPYING for details.
 """
 import sys, re
-import traceback
 import ldap
+
 from MoinMoin import user
 
 def ldap_login(request, **kw):
@@ -30,60 +30,87 @@
 
     if verbose: request.log("got name=%s login=%r logout=%r" % (username, login, logout))
 
-    # we just intercept login and logout for ldap, other requests have to be
+    # we just intercept login for ldap, other requests have to be
     # handled by another auth handler
-    if not login and not logout:
+    if not login:
         return user_obj, True
 
-    u = None
-    coding = cfg.ldap_coding
-    try:
-        if verbose: request.log("LDAP: Trying to initialize %s." % cfg.ldap_uri)
-        l = ldap.initialize(cfg.ldap_uri)
-        if verbose: request.log("LDAP: Connected to LDAP server %s." % cfg.ldap_uri)
-        # you can use %(username)s and %(password)s here to get the stuff entered in the form:
-        ldap_binddn = cfg.ldap_binddn % locals()
-        ldap_bindpw = cfg.ldap_bindpw % locals()
-        l.simple_bind_s(ldap_binddn.encode(coding), ldap_bindpw.encode(coding))
-        if verbose: request.log("LDAP: Bound with binddn %s" % ldap_binddn)
+    # we require non-empty password as ldap bind does a anon (not password
+    # protected) bind if the password is empty and SUCCEEDS!
+    if not password:
+        return None, False
 
-        # normal usage: ldap_filter = "(%(ldap_name_attribute)s=%(username)s)"
-        # you can also do more complex filtering like:
-        # "(&(%(ldap_name_attribute)s=%(username)s)(memberOf=CN=WikiUsers,OU=Groups,DC=example,DC=org))"
-        filterstr = cfg.ldap_filter % {
-            'ldap_name_attribute': cfg.ldap_name_attribute,
-            'username': username,
-        }
-        if verbose: request.log("LDAP: Searching %s" % filterstr)
-        lusers = l.search_st(cfg.ldap_base, cfg.ldap_scope,
-                             filterstr.encode(coding), timeout=cfg.ldap_timeout)
-        result_length = len(lusers)
-        if result_length != 1:
-            if result_length > 1:
-                request.log("LDAP: Search found more than one (%d) matches for %s." % (len(lusers), filterstr))
-            if result_length == 0:
-                if verbose: request.log("LDAP: Search found no matches for %s." % (filterstr, ))
-            return user_obj, True
+    try:
+        try:
+            u = None
+            dn = None
+            coding = cfg.ldap_coding
+            if verbose: request.log("LDAP: Setting misc. options...")
+            # needed for Active Directory:
+            ldap.set_option(ldap.OPT_REFERRALS, 0)
 
-        dn, ldap_dict = lusers[0]
-        if verbose:
-            request.log("LDAP: debug lusers = %r" % lusers)
-            for key, val in ldap_dict.items():
-                request.log("LDAP: %s: %s" % (key, val))
+            server = cfg.ldap_uri
+            if server.startswith('ldaps:'):
+                # this is needed for self-signed ssl certs:
+                ldap.set_option(ldap.OPT_X_TLS_REQUIRE_CERT, ldap.OPT_X_TLS_NEVER)
+                # more stuff to try:
+                #ldap.set_option(ldap.OPT_X_TLS_ALLOW, 1)
+                #ldap.set_option(ldap.OPT_X_TLS_CERTFILE, LDAP_CACERTFILE)
+                #ldap.set_option(ldap.OPT_X_TLS_CACERTFILE,'/etc/httpd/ssl.crt/myCA-cacerts.pem')
 
-        try:
+            if verbose: request.log("LDAP: Trying to initialize %s." % server)
+            l = ldap.initialize(server)
+            if verbose: request.log("LDAP: Connected to LDAP server %s." % server)
+
+            # you can use %(username)s and %(password)s here to get the stuff entered in the form:
+            ldap_binddn = cfg.ldap_binddn % locals()
+            ldap_bindpw = cfg.ldap_bindpw % locals()
+            l.simple_bind_s(ldap_binddn.encode(coding), ldap_bindpw.encode(coding))
+            if verbose: request.log("LDAP: Bound with binddn %s" % ldap_binddn)
+
+            # you can use %(username)s here to get the stuff entered in the form:
+            filterstr = cfg.ldap_filter % locals()
+            if verbose: request.log("LDAP: Searching %s" % filterstr)
+            lusers = l.search_st(cfg.ldap_base, cfg.ldap_scope, filterstr.encode(coding),
+                                 attrlist=[cfg.ldap_email_attribute,
+                                           cfg.ldap_aliasname_attribute,
+                                           cfg.ldap_surname_attribute,
+                                           cfg.ldap_givenname_attribute,
+                                 ], timeout=cfg.ldap_timeout)
+            # we remove entries with dn == None to get the real result list:
+            lusers = [(dn, ldap_dict) for dn, ldap_dict in lusers if dn is not None]
+            if verbose:
+                for dn, ldap_dict in lusers:
+                    request.log("LDAP: dn:%s" % dn)
+                    for key, val in ldap_dict.items():
+                        request.log("    %s: %s" % (key, val))
+
+            result_length = len(lusers)
+            if result_length != 1:
+                if result_length > 1:
+                    request.log("LDAP: Search found more than one (%d) matches for %s." % (result_length, filterstr))
+                if result_length == 0:
+                    if verbose: request.log("LDAP: Search found no matches for %s." % (filterstr, ))
+                return None, False # if ldap returns unusable results, we veto the user and don't let him in
+
+            dn, ldap_dict = lusers[0]
             if verbose: request.log("LDAP: DN found is %s, trying to bind with pw" % dn)
             l.simple_bind_s(dn, password.encode(coding))
             if verbose: request.log("LDAP: Bound with dn %s (username: %s)" % (dn, username))
 
             email = ldap_dict.get(cfg.ldap_email_attribute, [''])[0]
             email = email.decode(coding)
-            sn, gn = ldap_dict.get('sn', [''])[0], ldap_dict.get('givenName', [''])[0]
+
             aliasname = ''
-            if sn and gn:
-                aliasname = "%s, %s" % (sn, gn)
-            elif sn:
-                aliasname = sn
+            try:
+                aliasname = ldap_dict[cfg.ldap_aliasname_attribute][0]
+            except (KeyError, IndexError):
+                sn = ldap_dict.get(cfg.ldap_surname_attribute, [''])[0]
+                gn = ldap_dict.get(cfg.ldap_givenname_attribute, [''])[0]
+                if sn and gn:
+                    aliasname = "%s, %s" % (sn, gn)
+                elif sn:
+                    aliasname = sn
             aliasname = aliasname.decode(coding)
 
             u = user.User(request, auth_username=username, password="{SHA}NotStored", auth_method='ldap', auth_attribs=('name', 'password', 'email', 'mailto_author',))
@@ -95,13 +122,16 @@
 
         except ldap.INVALID_CREDENTIALS, err:
             request.log("LDAP: invalid credentials (wrong password?) for dn %s (username: %s)" % (dn, username))
+            return None, False # if ldap says no, we veto the user and don't let him in
+
+        if u:
+            u.create_or_update(True)
+        return u, True # moin_session has to set the cookie
 
     except:
+        import traceback
         info = sys.exc_info()
         request.log("LDAP: caught an exception, traceback follows...")
         request.log(''.join(traceback.format_exception(*info)))
+        return None, False # something went completely wrong, in doubt we veto the login
 
-    if u:
-        u.create_or_update(True)
-    return u, True # moin_session has to set the cookie
-
--- a/wiki/config/more_samples/ldap_smb_farmconfig.py	Tue Jan 30 23:07:33 2007 +0100
+++ b/wiki/config/more_samples/ldap_smb_farmconfig.py	Tue Jan 30 23:58:29 2007 +0100
@@ -95,8 +95,12 @@
 
     ldap_base = 'ou=SOMEUNIT,dc=example,dc=org' # base DN we use for searching
     ldap_scope = ldap.SCOPE_SUBTREE # scope of the search we do
-    ldap_filter = "(%(ldap_name_attribute)s=%(username)s)" # available: ldap_name_attribute (see below) and username 
-    ldap_name_attribute = 'sAMAccountName' # ldap attribute we get the user name from
+    ldap_filter = '(sAMAccountName=%(username)s)' # ldap filter used for searching
+    # you can also do more complex filtering like:
+    # "(&(cn=%(username)s)(memberOf=CN=WikiUsers,OU=Groups,DC=example,DC=org))"
+    ldap_givenname_attribute = 'givenName' # ldap attribute we get the first name from
+    ldap_surname_attribute = 'sn' # ldap attribute we get the family name from
+    ldap_aliasname_attribute = 'displayName' # ldap attribute we get the aliasname from
     ldap_email_attribute = 'mail' # ldap attribute we get the email address from
     ldap_coding = 'utf-8' # coding used for ldap queries and result values
     ldap_timeout = 10 # how long we wait for the ldap server [s]