changeset 776:ab9cd47eb066

teared auth code into single files, basic built-in moin_login method and also session handling code is still in auth/__init__.py
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Sat, 10 Jun 2006 10:12:59 +0200
parents 0e3327b36bc5
children e80f12c41f77
files MoinMoin/auth/__init__.py MoinMoin/auth/http.py MoinMoin/auth/interwiki.py MoinMoin/auth/ldap_login.py MoinMoin/auth/log.py MoinMoin/auth/mysql_group.py MoinMoin/auth/php_session.py MoinMoin/auth/smb_mount.py MoinMoin/auth/sslclientcert.py docs/CHANGES
diffstat 10 files changed, 513 insertions(+), 425 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/auth/__init__.py	Sat Jun 10 00:24:50 2006 +0200
+++ b/MoinMoin/auth/__init__.py	Sat Jun 10 10:12:59 2006 +0200
@@ -59,20 +59,10 @@
     return ''.join([random.choice(safe) for i in range(random_length)])
 
 def make_security_hash(request, data, securitystring=''):
-    """ generate a hash string based on site configuration's cfg.cookie_security,
+    """ generate a hash string based on site configuration's cfg.cookie_secret,
         securitystring and the data.
     """
-    return hmac.new(request.cfg.cookie_security + securitystring, data).hexdigest()
-
-def log(request, **kw):
-    """ just log the call, do nothing else """
-    username = kw.get('name')
-    password = kw.get('password')
-    login = kw.get('login')
-    logout = kw.get('logout')
-    user_obj = kw.get('user_obj')
-    request.log("auth.log: name=%s login=%r logout=%r user_obj=%r" % (username, login, logout, user_obj))
-    return user_obj, True
+    return hmac.new(request.cfg.cookie_secret + securitystring, data).hexdigest()
 
 def makeCookie(request, cookie_name, cookie_string, maxage, expires):
     """ create an appropriate cookie """
@@ -272,415 +262,3 @@
     setSessionCookie(request, u) # refreshes cookie lifetime
     return u, True # use True to get other methods called, too
 
-def http(request, **kw):
-    """ authenticate via http basic/digest/ntlm auth """
-    from MoinMoin.request import TWISTED, CLI
-    user_obj = kw.get('user_obj')
-    u = None
-    # check if we are running Twisted
-    if isinstance(request, TWISTED.Request):
-        username = request.twistd.getUser()
-        password = request.twistd.getPassword()
-        # when using Twisted http auth, we use username and password from
-        # the moin user profile, so both can be changed by user.
-        u = user.User(request, auth_username=username, password=password,
-                      auth_method='http', auth_attribs=())
-
-    elif not isinstance(request, CLI.Request):
-        env = request.env
-        auth_type = env.get('AUTH_TYPE','')
-        if auth_type in ['Basic', 'Digest', 'NTLM', 'Negotiate',]:
-            username = env.get('REMOTE_USER','')
-            if auth_type in ('NTLM', 'Negotiate',):
-                # converting to standard case so the user can even enter wrong case
-                # (added since windows does not distinguish between e.g.
-                #  "Mike" and "mike")
-                username = username.split('\\')[-1] # split off domain e.g.
-                                                    # from DOMAIN\user
-                # this "normalizes" the login name from {meier, Meier, MEIER} to Meier
-                # put a comment sign in front of next line if you don't want that:
-                username = username.title()
-            # when using http auth, we have external user name and password,
-            # we don't use the moin user profile for those attributes.
-            u = user.User(request, auth_username=username,
-                          auth_method='http', auth_attribs=('name', 'password'))
-
-    if u:
-        u.create_or_update()
-    if u and u.valid:
-        return u, True # True to get other methods called, too
-    else:
-        return user_obj, True
-
-def sslclientcert(request, **kw):
-    """ authenticate via SSL client certificate """
-    from MoinMoin.request import TWISTED
-    user_obj = kw.get('user_obj')
-    u = None
-    changed = False
-    # check if we are running Twisted
-    if isinstance(request, TWISTED.Request):
-        return user_obj, True # not supported if we run twisted
-        # Addendum: this seems to need quite some twisted insight and coding.
-        # A pointer i got on #twisted: divmod's vertex.sslverify
-        # If you really need this, feel free to implement and test it and
-        # submit a patch if it works.
-    else:
-        env = request.env
-        if env.get('SSL_CLIENT_VERIFY', 'FAILURE') == 'SUCCESS':
-            # if we only want to accept some specific CA, do a check like:
-            # if env.get('SSL_CLIENT_I_DN_OU') == "http://www.cacert.org"
-            email = env.get('SSL_CLIENT_S_DN_Email', '')
-            email_lower = email.lower()
-            commonname = env.get('SSL_CLIENT_S_DN_CN', '')
-            commonname_lower = commonname.lower()
-            if email_lower or commonname_lower:
-                for uid in user.getUserList(request):
-                    u = user.User(request, uid,
-                                  auth_method='sslclientcert', auth_attribs=())
-                    if email_lower and u.email.lower() == email_lower:
-                        u.auth_attribs = ('email', 'password')
-                        #this is only useful if same name should be used, as
-                        #commonname is likely no CamelCase WikiName
-                        #if commonname_lower != u.name.lower():
-                        #    u.name = commonname
-                        #    changed = True
-                        #u.auth_attribs = ('email', 'name', 'password')
-                        break
-                    if commonname_lower and u.name.lower() == commonname_lower:
-                        u.auth_attribs = ('name', 'password')
-                        #this is only useful if same email should be used as
-                        #specified in certificate.
-                        #if email_lower != u.email.lower():
-                        #    u.email = email
-                        #    changed = True
-                        #u.auth_attribs = ('name', 'email', 'password')
-                        break
-                else:
-                    u = None
-                if u is None:
-                    # user wasn't found, so let's create a new user object
-                    u = user.User(request, name=commonname_lower, auth_username=commonname_lower)
-
-    if u:
-        u.create_or_update(changed)
-    if u and u.valid:
-        return u, True
-    else:
-        return user_obj, True
-
-def smb_mount(request, **kw):
-    """ (u)mount a SMB server's share for username (using username/password for
-        authentication at the SMB server). This can be used if you need access
-        to files on some share via the wiki, but needs more code to be useful.
-        If you don't need it, don't use it.
-    """
-    username = kw.get('name')
-    password = kw.get('password')
-    login = kw.get('login')
-    logout = kw.get('logout')
-    user_obj = kw.get('user_obj')
-    cfg = request.cfg
-    verbose = cfg.smb_verbose
-    if verbose: request.log("got name=%s login=%r logout=%r" % (username, login, logout))
-    
-    # we just intercept login to mount and logout to umount the smb share
-    if login or logout:
-        import os, pwd, subprocess
-        web_username = cfg.smb_dir_user
-        web_uid = pwd.getpwnam(web_username)[2] # XXX better just use current uid?
-        if logout and user_obj: # logout -> we don't have username in form
-            username = user_obj.name # so we take it from previous auth method (moin_cookie e.g.)
-        mountpoint = cfg.smb_mountpoint % {
-            'username': username,
-        }
-        if login:
-            cmd = u"sudo mount -t cifs -o user=%(user)s,domain=%(domain)s,uid=%(uid)d,dir_mode=%(dir_mode)s,file_mode=%(file_mode)s,iocharset=%(iocharset)s //%(server)s/%(share)s %(mountpoint)s >>%(log)s 2>&1"
-        elif logout:
-            cmd = u"sudo umount %(mountpoint)s >>%(log)s 2>&1"
-            
-        cmd = cmd % {
-            'user': username,
-            'uid': web_uid,
-            'domain': cfg.smb_domain,
-            'server': cfg.smb_server,
-            'share': cfg.smb_share,
-            'mountpoint': mountpoint,
-            'dir_mode': cfg.smb_dir_mode,
-            'file_mode': cfg.smb_file_mode,
-            'iocharset': cfg.smb_iocharset,
-            'log': cfg.smb_log,
-        }
-        env = os.environ.copy()
-        if login:
-            try:
-                os.makedirs(mountpoint) # the dir containing the mountpoint must be writeable for us!
-            except OSError, err:
-                pass
-            env['PASSWD'] = password.encode(cfg.smb_coding)
-        subprocess.call(cmd.encode(cfg.smb_coding), env=env, shell=True)
-    return user_obj, True
-
-def ldap_login(request, **kw):
-    """ get authentication data from form, authenticate against LDAP (or Active Directory),
-        fetch some user infos from LDAP and create a user profile for that user that must
-        be used by subsequent auth plugins (like moin_cookie) as we never return a user
-        object from ldap_login.
-    """
-    username = kw.get('name')
-    password = kw.get('password')
-    login = kw.get('login')
-    logout = kw.get('logout')
-    user_obj = kw.get('user_obj')
-
-    cfg = request.cfg
-    verbose = cfg.ldap_verbose
-    
-    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
-    # handled by another auth handler
-    if not login and not logout:
-        return user_obj, True
-    
-    import sys, re
-    import ldap
-    import traceback
-
-    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)
-
-        filterstr = "(%s=%s)" % (cfg.ldap_name_attribute, 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
-
-        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))
-
-        try:
-            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
-            aliasname = aliasname.decode(coding)
-            
-            u = user.User(request, auth_username=username, password="{SHA}NotStored", auth_method='ldap', auth_attribs=('name', 'password', 'email', 'mailto_author',))
-            u.name = username
-            u.aliasname = aliasname
-            u.email = email
-            u.remember_me = 0 # 0 enforces cookie_lifetime config param
-            if verbose: request.log("LDAP: creating userprefs with name %s email %s alias %s" % (username, email, aliasname))
-            
-        except ldap.INVALID_CREDENTIALS, err:
-            request.log("LDAP: invalid credentials (wrong password?) for dn %s (username: %s)" % (dn, username))
-
-    except:
-        info = sys.exc_info()
-        request.log("LDAP: caught an exception, traceback follows...")
-        request.log(''.join(traceback.format_exception(*info)))
-
-    if u:
-        u.create_or_update(True)
-    return u, True # moin_cookie has to set the cookie and return the user obj
-
-def interwiki(request, **kw):
-    # TODO use auth_method and auth_attribs for User object
-    username = kw.get('name')
-    password = kw.get('password')
-    login = kw.get('login')
-    logout = kw.get('logout')
-    user_obj = kw.get('user_obj')
-
-    if login:
-        wikitag, wikiurl, wikitail, err = wikiutil.resolve_wiki(username)
-
-        if err or wikitag not in request.cfg.trusted_wikis:
-            return user_obj, True
-        
-        if password:
-            import xmlrpclib
-            homewiki = xmlrpclib.Server(wikiurl + "?action=xmlrpc2")
-            account_data = homewiki.getUser(wikitail, password)
-            if isinstance(account_data, str):
-                # show error message
-                return user_obj, True
-            
-            u = user.User(request, name=username)
-            for key, value in account_data.iteritems():
-                if key not in ["may", "id", "valid", "trusted"
-                               "auth_username",
-                               "name", "aliasname",
-                               "enc_passwd"]:
-                    setattr(u, key, value)
-            u.save()
-            setSessionCookie(request, u)
-            return u, True
-        else:
-            pass
-            # XXX redirect to homewiki
-    
-    return user_obj, True
-
-class php_session:
-    """ Authentication module for PHP based frameworks
-        Authenticates via PHP session cookie. Currently supported systems:
-
-        * eGroupware 1.2 ("egw")
-         * You need to configure eGroupware in the "header setup" to use
-           "php sessions plus restore"
-
-        @copyright: 2005 by MoinMoin:AlexanderSchremmer
-            - Thanks to Spreadshirt
-    """
-    def __init__(self, apps=['egw'], s_path="/tmp", s_prefix="sess_"):
-        """ @param apps: A list of the enabled applications. See above for
-            possible keys.
-            @param s_path: The path where the PHP sessions are stored.
-            @param s_prefix: The prefix of the session files.
-        """
-        
-        self.s_path = s_path
-        self.s_prefix = s_prefix
-        self.apps = apps
-
-    def __call__(self, request, **kw):
-        def handle_egroupware(session):
-            """ Extracts name, fullname and email from the session. """
-            username = session['egw_session']['session_lid'].split("@", 1)[0]
-            known_accounts = session['egw_info_cache']['accounts']['cache']['account_data']
-            
-            # if the next line breaks, then the cache was not filled with the current
-            # user information
-            user_info = [value for key, value in known_accounts.items()
-                         if value['account_lid'] == username][0]
-            name = user_info.get('fullname', '')
-            email = user_info.get('email', '')
-            
-            dec = lambda x: x and x.decode("iso-8859-1")
-            
-            return dec(username), dec(email), dec(name)
-        
-        import Cookie, urllib
-        from MoinMoin.user import User
-        from MoinMoin.auth import _PHPsessionParser
-    
-        user_obj = kw.get('user_obj')
-        try:
-            cookie = Cookie.SimpleCookie(request.saved_cookie)
-        except Cookie.CookieError: # ignore invalid cookies
-            cookie = None
-        if cookie:
-            for cookiename in cookie.keys():
-                cookievalue = urllib.unquote(cookie[cookiename].value).decode('iso-8859-1')
-                session = _PHPsessionParser.loadSession(cookievalue, path=self.s_path, prefix=self.s_prefix)
-                if session:
-                    if "egw" in self.apps and session.get('egw_session', None):
-                        username, email, name = handle_egroupware(session)
-                        break
-            else:
-                return user_obj, True
-            
-            user = User(request, name=username, auth_username=username)
-            
-            changed = False
-            if name != user.aliasname:
-                user.aliasname = name
-                changed = True
-            if email != user.email:
-                user.email = email
-                changed = True
-            
-            if user:
-                user.create_or_update(changed)
-            if user and user.valid:
-                return user, True # True to get other methods called, too
-        return user_obj, True # continue with next method in auth list
-
-def mysql_group(request, **kw):
-    """ Authorize via MySQL group DB.
-    
-    We require an already-authenticated user_obj.
-    We don't worry about the type of request (login, logout, neither).
-    We just check user is part of authorized group.
-    """
-    import MySQLdb
-    
-    username = kw.get('name')
-#    login = kw.get('login')
-#    logout = kw.get('logout')
-    user_obj = kw.get('user_obj')
-
-    cfg = request.cfg
-    verbose = False
-
-    if hasattr(cfg, 'mysql_group_verbose'):
-        verbose = cfg.mysql_group_verbose
-    
-    if verbose: request.log("auth.mysql_group: name=%s user_obj=%r" % (username, user_obj))
-
-    # Has any other method successfully authenticated?
-    if user_obj is not None and user_obj.valid:
-        # Yes - we can do stuff!
-        if verbose: request.log("mysql_group got valid user from previous auth method, trying authz...")
-        if verbose: request.log("mysql_group got auth_username %s." % user_obj.auth_username)
-
-        # XXX Check auth_username for dodgy chars (should be none as it is authenticated, but...)
-
-        # OK, now check mysql!
-        try:
-            m = MySQLdb.connect(host=cfg.mysql_group_dbhost,
-                                user=cfg.mysql_group_dbuser,
-                                passwd=cfg.mysql_group_dbpass,
-                                db=cfg.mysql_group_dbname,
-                                )
-        except:
-            import sys
-            import traceback
-            info = sys.exc_info()
-            request.log("mysql_group: authorization failed due to exception connecting to DB, traceback follows...")
-            request.log(''.join(traceback.format_exception(*info)))
-            return None, False
-        
-        c = m.cursor()
-        c.execute(cfg.mysql_group_query, user_obj.auth_username)
-        results = c.fetchall()
-        if results:
-            # Checked out OK
-            if verbose: request.log("mysql_group got %d results -- authorized!" % len(results))
-            return user_obj, True # we make continuing possible, e.g. for smbmount
-        else:
-            if verbose: request.log("mysql_group did not get match from DB -- not authorized")
-            return None, False
-    else:
-        # No other method succeeded, so we cannot authorize -- must fail
-        if verbose: request.log("mysql_group did not get valid user from previous auth method, cannot authorize")
-        return None, False
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/auth/http.py	Sat Jun 10 10:12:59 2006 +0200
@@ -0,0 +1,53 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - http authentication
+
+    You need either your webserver configured for doing HTTP auth (like Apache
+    reading some .htpasswd file) or Twisted (will accept HTTP auth against
+    password stored in moin user profile, but currently will NOT ask for auth).
+
+    @copyright: 2006 by MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+from MoinMoin import user
+from MoinMoin.request import TWISTED, CLI
+
+def http(request, **kw):
+    """ authenticate via http basic/digest/ntlm auth """
+    user_obj = kw.get('user_obj')
+    u = None
+    # check if we are running Twisted
+    if isinstance(request, TWISTED.Request):
+        username = request.twistd.getUser()
+        password = request.twistd.getPassword()
+        # when using Twisted http auth, we use username and password from
+        # the moin user profile, so both can be changed by user.
+        u = user.User(request, auth_username=username, password=password,
+                      auth_method='http', auth_attribs=())
+
+    elif not isinstance(request, CLI.Request):
+        env = request.env
+        auth_type = env.get('AUTH_TYPE','')
+        if auth_type in ['Basic', 'Digest', 'NTLM', 'Negotiate',]:
+            username = env.get('REMOTE_USER','')
+            if auth_type in ('NTLM', 'Negotiate',):
+                # converting to standard case so the user can even enter wrong case
+                # (added since windows does not distinguish between e.g.
+                #  "Mike" and "mike")
+                username = username.split('\\')[-1] # split off domain e.g.
+                                                    # from DOMAIN\user
+                # this "normalizes" the login name from {meier, Meier, MEIER} to Meier
+                # put a comment sign in front of next line if you don't want that:
+                username = username.title()
+            # when using http auth, we have external user name and password,
+            # we don't use the moin user profile for those attributes.
+            u = user.User(request, auth_username=username,
+                          auth_method='http', auth_attribs=('name', 'password'))
+
+    if u:
+        u.create_or_update()
+    if u and u.valid:
+        return u, True # True to get other methods called, too
+    else:
+        return user_obj, True
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/auth/interwiki.py	Sat Jun 10 10:12:59 2006 +0200
@@ -0,0 +1,50 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - authentication using a remote wiki
+
+    This is completely untested and rather has to be seen as an idea
+    than a working implementation.
+
+    @copyright: 2005 by ???
+    @license: GNU GPL, see COPYING for details.
+"""
+import xmlrpclib
+from MoinMoin import auth, wikiutil, user
+
+def interwiki(request, **kw):
+    # TODO use auth_method and auth_attribs for User object
+    username = kw.get('name')
+    password = kw.get('password')
+    login = kw.get('login')
+    logout = kw.get('logout')
+    user_obj = kw.get('user_obj')
+
+    if login:
+        wikitag, wikiurl, wikitail, err = wikiutil.resolve_wiki(username)
+
+        if err or wikitag not in request.cfg.trusted_wikis:
+            return user_obj, True
+        
+        if password:
+            homewiki = xmlrpclib.Server(wikiurl + "?action=xmlrpc2")
+            account_data = homewiki.getUser(wikitail, password)
+            if isinstance(account_data, str):
+                # show error message
+                return user_obj, True
+            
+            u = user.User(request, name=username)
+            for key, value in account_data.iteritems():
+                if key not in ["may", "id", "valid", "trusted"
+                               "auth_username",
+                               "name", "aliasname",
+                               "enc_passwd"]:
+                    setattr(u, key, value)
+            u.save()
+            auth.setSessionCookie(request, u)
+            return u, True
+        else:
+            pass
+            # XXX redirect to homewiki
+    
+    return user_obj, True
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/auth/ldap_login.py	Sat Jun 10 10:12:59 2006 +0200
@@ -0,0 +1,101 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - LDAP / Active Directory authentication
+
+    This code only creates a user object, the session has to be established by
+    the auth.moin_session auth plugin.
+    
+    @copyright: 2006 by MoinMoin:ThomasWaldmann, Nick Phillips
+    @license: GNU GPL, see COPYING for details.
+"""
+import sys, re
+import traceback
+import ldap
+from MoinMoin import user
+
+def ldap_login(request, **kw):
+    """ get authentication data from form, authenticate against LDAP (or Active Directory),
+        fetch some user infos from LDAP and create a user profile for that user that must
+        be used by subsequent auth plugins (like moin_cookie) as we never return a user
+        object from ldap_login.
+    """
+    username = kw.get('name')
+    password = kw.get('password')
+    login = kw.get('login')
+    logout = kw.get('logout')
+    user_obj = kw.get('user_obj')
+
+    cfg = request.cfg
+    verbose = cfg.ldap_verbose
+    
+    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
+    # handled by another auth handler
+    if not login and not logout:
+        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)
+
+        filterstr = "(%s=%s)" % (cfg.ldap_name_attribute, 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
+
+        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))
+
+        try:
+            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
+            aliasname = aliasname.decode(coding)
+            
+            u = user.User(request, auth_username=username, password="{SHA}NotStored", auth_method='ldap', auth_attribs=('name', 'password', 'email', 'mailto_author',))
+            u.name = username
+            u.aliasname = aliasname
+            u.email = email
+            u.remember_me = 0 # 0 enforces cookie_lifetime config param
+            if verbose: request.log("LDAP: creating userprefs with name %s email %s alias %s" % (username, email, aliasname))
+            
+        except ldap.INVALID_CREDENTIALS, err:
+            request.log("LDAP: invalid credentials (wrong password?) for dn %s (username: %s)" % (dn, username))
+
+    except:
+        info = sys.exc_info()
+        request.log("LDAP: caught an exception, traceback follows...")
+        request.log(''.join(traceback.format_exception(*info)))
+
+    if u:
+        u.create_or_update(True)
+    return u, True # moin_session has to set the cookie
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/auth/log.py	Sat Jun 10 10:12:59 2006 +0200
@@ -0,0 +1,21 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - logging auth plugin
+
+    This does nothing except logging the auth parameters (the password is NOT
+    logged, of course).
+
+    @copyright: 2006 by MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+
+def log(request, **kw):
+    """ just log the call, do nothing else """
+    username = kw.get('name')
+    password = kw.get('password')
+    login = kw.get('login')
+    logout = kw.get('logout')
+    user_obj = kw.get('user_obj')
+    request.log("auth.log: name=%s login=%r logout=%r user_obj=%r" % (username, login, logout, user_obj))
+    return user_obj, True
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/auth/mysql_group.py	Sat Jun 10 10:12:59 2006 +0200
@@ -0,0 +1,71 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - auth plugin doing a check against MySQL group db
+
+    ...
+
+    @copyright: 2006 by Nick Phillips
+    @license: GNU GPL, see COPYING for details.
+"""
+
+import MySQLdb
+
+def mysql_group(request, **kw):
+    """ Authorize via MySQL group DB.
+    
+    We require an already-authenticated user_obj.
+    We don't worry about the type of request (login, logout, neither).
+    We just check user is part of authorized group.
+    """
+    
+    username = kw.get('name')
+#    login = kw.get('login')
+#    logout = kw.get('logout')
+    user_obj = kw.get('user_obj')
+
+    cfg = request.cfg
+    verbose = False
+
+    if hasattr(cfg, 'mysql_group_verbose'):
+        verbose = cfg.mysql_group_verbose
+    
+    if verbose: request.log("auth.mysql_group: name=%s user_obj=%r" % (username, user_obj))
+
+    # Has any other method successfully authenticated?
+    if user_obj is not None and user_obj.valid:
+        # Yes - we can do stuff!
+        if verbose: request.log("mysql_group got valid user from previous auth method, trying authz...")
+        if verbose: request.log("mysql_group got auth_username %s." % user_obj.auth_username)
+
+        # XXX Check auth_username for dodgy chars (should be none as it is authenticated, but...)
+
+        # OK, now check mysql!
+        try:
+            m = MySQLdb.connect(host=cfg.mysql_group_dbhost,
+                                user=cfg.mysql_group_dbuser,
+                                passwd=cfg.mysql_group_dbpass,
+                                db=cfg.mysql_group_dbname,
+                                )
+        except:
+            import sys
+            import traceback
+            info = sys.exc_info()
+            request.log("mysql_group: authorization failed due to exception connecting to DB, traceback follows...")
+            request.log(''.join(traceback.format_exception(*info)))
+            return None, False
+        
+        c = m.cursor()
+        c.execute(cfg.mysql_group_query, user_obj.auth_username)
+        results = c.fetchall()
+        if results:
+            # Checked out OK
+            if verbose: request.log("mysql_group got %d results -- authorized!" % len(results))
+            return user_obj, True # we make continuing possible, e.g. for smbmount
+        else:
+            if verbose: request.log("mysql_group did not get match from DB -- not authorized")
+            return None, False
+    else:
+        # No other method succeeded, so we cannot authorize -- must fail
+        if verbose: request.log("mysql_group did not get valid user from previous auth method, cannot authorize")
+        return None, False
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/auth/php_session.py	Sat Jun 10 10:12:59 2006 +0200
@@ -0,0 +1,80 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - PHP session cookie authentication
+    
+    Currently supported systems:
+
+        * eGroupware 1.2 ("egw")
+         * You need to configure eGroupware in the "header setup" to use
+           "php sessions plus restore"
+
+    @copyright: 2005 by MoinMoin:AlexanderSchremmer (Thanks to Spreadshirt)
+    @license: GNU GPL, see COPYING for details.
+"""
+
+import Cookie, urllib
+from MoinMoin import user
+from MoinMoin.auth import _PHPsessionParser
+
+class php_session:
+    """ PHP session cookie authentication """
+    def __init__(self, apps=['egw'], s_path="/tmp", s_prefix="sess_"):
+        """ @param apps: A list of the enabled applications. See above for
+            possible keys.
+            @param s_path: The path where the PHP sessions are stored.
+            @param s_prefix: The prefix of the session files.
+        """
+        
+        self.s_path = s_path
+        self.s_prefix = s_prefix
+        self.apps = apps
+
+    def __call__(self, request, **kw):
+        def handle_egroupware(session):
+            """ Extracts name, fullname and email from the session. """
+            username = session['egw_session']['session_lid'].split("@", 1)[0]
+            known_accounts = session['egw_info_cache']['accounts']['cache']['account_data']
+            
+            # if the next line breaks, then the cache was not filled with the current
+            # user information
+            user_info = [value for key, value in known_accounts.items()
+                         if value['account_lid'] == username][0]
+            name = user_info.get('fullname', '')
+            email = user_info.get('email', '')
+            
+            dec = lambda x: x and x.decode("iso-8859-1")
+            
+            return dec(username), dec(email), dec(name)
+        
+        user_obj = kw.get('user_obj')
+        try:
+            cookie = Cookie.SimpleCookie(request.saved_cookie)
+        except Cookie.CookieError: # ignore invalid cookies
+            cookie = None
+        if cookie:
+            for cookiename in cookie.keys():
+                cookievalue = urllib.unquote(cookie[cookiename].value).decode('iso-8859-1')
+                session = _PHPsessionParser.loadSession(cookievalue, path=self.s_path, prefix=self.s_prefix)
+                if session:
+                    if "egw" in self.apps and session.get('egw_session', None):
+                        username, email, name = handle_egroupware(session)
+                        break
+            else:
+                return user_obj, True
+            
+            user = user.User(request, name=username, auth_username=username)
+            
+            changed = False
+            if name != user.aliasname:
+                user.aliasname = name
+                changed = True
+            if email != user.email:
+                user.email = email
+                changed = True
+            
+            if user:
+                user.create_or_update(changed)
+            if user and user.valid:
+                return user, True # True to get other methods called, too
+        return user_obj, True # continue with next method in auth list
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/auth/smb_mount.py	Sat Jun 10 10:12:59 2006 +0200
@@ -0,0 +1,61 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - auth plugin for (un)mounting a smb share
+
+    (u)mount a SMB server's share for username (using username/password for
+    authentication at the SMB server). This can be used if you need access
+    to files on some share via the wiki, but needs more code to be useful.
+
+    @copyright: 2006 by MoinMoin:ThomasWaldmann
+    @license: GNU GPL, see COPYING for details.
+"""
+
+
+def smb_mount(request, **kw):
+    """ auth plugin for (un)mounting an smb share """
+    username = kw.get('name')
+    password = kw.get('password')
+    login = kw.get('login')
+    logout = kw.get('logout')
+    user_obj = kw.get('user_obj')
+    cfg = request.cfg
+    verbose = cfg.smb_verbose
+    if verbose: request.log("got name=%s login=%r logout=%r" % (username, login, logout))
+    
+    # we just intercept login to mount and logout to umount the smb share
+    if login or logout:
+        import os, pwd, subprocess
+        web_username = cfg.smb_dir_user
+        web_uid = pwd.getpwnam(web_username)[2] # XXX better just use current uid?
+        if logout and user_obj: # logout -> we don't have username in form
+            username = user_obj.name # so we take it from previous auth method (moin_cookie e.g.)
+        mountpoint = cfg.smb_mountpoint % {
+            'username': username,
+        }
+        if login:
+            cmd = u"sudo mount -t cifs -o user=%(user)s,domain=%(domain)s,uid=%(uid)d,dir_mode=%(dir_mode)s,file_mode=%(file_mode)s,iocharset=%(iocharset)s //%(server)s/%(share)s %(mountpoint)s >>%(log)s 2>&1"
+        elif logout:
+            cmd = u"sudo umount %(mountpoint)s >>%(log)s 2>&1"
+            
+        cmd = cmd % {
+            'user': username,
+            'uid': web_uid,
+            'domain': cfg.smb_domain,
+            'server': cfg.smb_server,
+            'share': cfg.smb_share,
+            'mountpoint': mountpoint,
+            'dir_mode': cfg.smb_dir_mode,
+            'file_mode': cfg.smb_file_mode,
+            'iocharset': cfg.smb_iocharset,
+            'log': cfg.smb_log,
+        }
+        env = os.environ.copy()
+        if login:
+            try:
+                os.makedirs(mountpoint) # the dir containing the mountpoint must be writeable for us!
+            except OSError, err:
+                pass
+            env['PASSWD'] = password.encode(cfg.smb_coding)
+        subprocess.call(cmd.encode(cfg.smb_coding), env=env, shell=True)
+    return user_obj, True
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/auth/sslclientcert.py	Sat Jun 10 10:12:59 2006 +0200
@@ -0,0 +1,71 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - SSL client certificate authentication
+
+    Currently not supported for Twisted web server, but only for web servers
+    setting SSL_CLIENT_* environment (e.g. Apache).
+    
+    @copyright: 2006 by MoinMoin:ThomasWaldmann,
+                2003 by Martin v. L÷wis
+    @license: GNU GPL, see COPYING for details.
+"""
+
+from MoinMoin import user
+from MoinMoin.request import TWISTED
+
+def sslclientcert(request, **kw):
+    """ authenticate via SSL client certificate """
+    user_obj = kw.get('user_obj')
+    u = None
+    changed = False
+    # check if we are running Twisted
+    if isinstance(request, TWISTED.Request):
+        return user_obj, True # not supported if we run twisted
+        # Addendum: this seems to need quite some twisted insight and coding.
+        # A pointer i got on #twisted: divmod's vertex.sslverify
+        # If you really need this, feel free to implement and test it and
+        # submit a patch if it works.
+    else:
+        env = request.env
+        if env.get('SSL_CLIENT_VERIFY', 'FAILURE') == 'SUCCESS':
+            # if we only want to accept some specific CA, do a check like:
+            # if env.get('SSL_CLIENT_I_DN_OU') == "http://www.cacert.org"
+            email = env.get('SSL_CLIENT_S_DN_Email', '')
+            email_lower = email.lower()
+            commonname = env.get('SSL_CLIENT_S_DN_CN', '')
+            commonname_lower = commonname.lower()
+            if email_lower or commonname_lower:
+                for uid in user.getUserList(request):
+                    u = user.User(request, uid,
+                                  auth_method='sslclientcert', auth_attribs=())
+                    if email_lower and u.email.lower() == email_lower:
+                        u.auth_attribs = ('email', 'password')
+                        #this is only useful if same name should be used, as
+                        #commonname is likely no CamelCase WikiName
+                        #if commonname_lower != u.name.lower():
+                        #    u.name = commonname
+                        #    changed = True
+                        #u.auth_attribs = ('email', 'name', 'password')
+                        break
+                    if commonname_lower and u.name.lower() == commonname_lower:
+                        u.auth_attribs = ('name', 'password')
+                        #this is only useful if same email should be used as
+                        #specified in certificate.
+                        #if email_lower != u.email.lower():
+                        #    u.email = email
+                        #    changed = True
+                        #u.auth_attribs = ('name', 'email', 'password')
+                        break
+                else:
+                    u = None
+                if u is None:
+                    # user wasn't found, so let's create a new user object
+                    u = user.User(request, name=commonname_lower, auth_username=commonname_lower)
+
+    if u:
+        u.create_or_update(changed)
+    if u and u.valid:
+        return u, True
+    else:
+        return user_obj, True
+
--- a/docs/CHANGES	Sat Jun 10 00:24:50 2006 +0200
+++ b/docs/CHANGES	Sat Jun 10 10:12:59 2006 +0200
@@ -65,7 +65,9 @@
     * moved mailimport.py to mail/mailimport.py,
       moved util/mail.py to mail/sendmail.py
     * moved auth.py to auth/__init__.py,
-      moved util/sessionParser.py to auth/_PHPsessionParser.py
+      moved util/sessionParser.py to auth/_PHPsessionParser.py,
+      teared auth code into single modules under auth/* - moin_session handling
+      and the builting moin_login method are in auth/__init__.py.
     * added wikiutil.MimeType class (works internally with sanitized mime
       types because the official ones suck)
     * renamed parsers to module names representing sane mimetypes, e.g.: