changeset 752:d2858428f370

moved auth related stuff to auth/*
author Thomas Waldmann <tw AT waldmann-edv DOT de>
date Wed, 07 Jun 2006 11:34:13 +0200
parents a098ff6781f4
children d48400378d4c
files MoinMoin/auth.py MoinMoin/auth/_PHPsessionParser.py MoinMoin/auth/__init__.py MoinMoin/util/sessionParser.py docs/CHANGES
diffstat 5 files changed, 670 insertions(+), 668 deletions(-) [+]
line wrap: on
line diff
--- a/MoinMoin/auth.py	Wed Jun 07 11:19:27 2006 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,523 +0,0 @@
-# -*- coding: iso-8859-1 -*-
-"""
-    MoinMoin - modular authentication code
-
-    Here are some methods moin can use in cfg.auth authentication method list.
-    The methods from that list get called (from request.py) in that sequence.
-    They get request as first argument and also some more kw arguments:
-       name: the value we did get from a POST of the UserPreferences page
-             in the "name" form field (or None)
-       password: the value of the password form field (or None)
-       login: True if user has clicked on Login button
-       logout: True if user has clicked on Logout button
-       user_obj: the user_obj we have until now (user_obj returned from
-                 previous auth method or None for first auth method)
-       (we maybe add some more here)
-
-    Use code like this to get them:
-        name = kw.get('name') or ''
-        password = kw.get('password') or ''
-        login = kw.get('login')
-        logout = kw.get('logout')
-        request.log("got name=%s len(password)=%d login=%r logout=%r" % (name, len(password), login, logout))
-    
-    The called auth method then must return a tuple (user_obj, continue_flag).
-    user_obj can be one of:
-    * a (newly created) User object
-    * None if we want to inhibit log in from previous auth methods
-    * what we got as kw argument user_obj (meaning: no change).
-    continue_flag is a boolean indication whether the auth loop shall continue
-    trying other auth methods (or not).
-
-    The methods give a kw arg "auth_attribs" to User.__init__ that tells
-    which user attribute names are DETERMINED and set by this auth method and
-    must not get changed by the user using the UserPreferences form.
-    It also gives a kw arg "auth_method" that tells the name of the auth
-    method that authentified the user.
-    
-    @copyright: 2005-2006 Bastian Blank, Florian Festi, Thomas Waldmann
-    @copyright: 2005-2006 MoinMoin:AlexanderSchremmer
-    @license: GNU GPL, see COPYING for details.
-"""
-
-import time, Cookie
-from MoinMoin import user
-
-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
-
-# some cookie functions used by moin_cookie auth
-def makeCookie(request, moin_id, maxage, expires):
-    """ calculate a MOIN_ID cookie """
-    c = Cookie.SimpleCookie()
-    cfg = request.cfg
-    c['MOIN_ID'] = moin_id
-    c['MOIN_ID']['max-age'] = maxage
-    if cfg.cookie_domain:
-        c['MOIN_ID']['domain'] = cfg.cookie_domain
-    if cfg.cookie_path:
-        c['MOIN_ID']['path'] = cfg.cookie_path
-    else:
-        path = request.getScriptname()
-        if not path:
-            path = '/'
-        c['MOIN_ID']['path'] = path
-    # Set expires for older clients
-    c['MOIN_ID']['expires'] = request.httpDate(when=expires, rfc='850')        
-    return c.output()
-
-def setCookie(request, u):
-    """ Set cookie for the user obj u
-    
-    cfg.cookie_lifetime and the user 'remember_me' setting set the
-    lifetime of the cookie. lifetime in int hours, see table:
-    
-    value   cookie lifetime
-    ----------------------------------------------------------------
-     = 0    forever, ignoring user 'remember_me' setting
-     > 0    n hours, or forever if user checked 'remember_me'
-     < 0    -n hours, ignoring user 'remember_me' setting
-    """
-    # Calculate cookie maxage and expires
-    lifetime = int(request.cfg.cookie_lifetime) * 3600 
-    forever = 10*365*24*3600 # 10 years
-    now = time.time()
-    if not lifetime:
-        maxage = forever
-    elif lifetime > 0:
-        if u.remember_me:
-            maxage = forever
-        else:
-            maxage = lifetime
-    elif lifetime < 0:
-        maxage = (-lifetime)
-    expires = now + maxage
-    
-    cookie = makeCookie(request, u.id, maxage, expires)
-    # Set cookie
-    request.setHttpHeader(cookie)
-    # IMPORTANT: Prevent caching of current page and cookie
-    request.disableHttpCaching()
-
-def deleteCookie(request):
-    """ Delete the user cookie by sending expired cookie with null value
-
-    According to http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2109.html#sec-4.2.2
-    Deleted cookie should have Max-Age=0. We also have expires
-    attribute, which is probably needed for older browsers.
-
-    Finally, delete the saved cookie and create a new user based on the new settings.
-    """
-    moin_id = ''
-    maxage = 0
-    # Set expires to one year ago for older clients
-    expires = time.time() - (3600 * 24 * 365) # 1 year ago
-    cookie = makeCookie(request, moin_id, maxage, expires) 
-    # Set cookie
-    request.setHttpHeader(cookie)
-    # IMPORTANT: Prevent caching of current page and cookie        
-    request.disableHttpCaching()
-
-def moin_cookie(request, **kw):
-    """ authenticate via the MOIN_ID cookie """
-    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.moin_cookie: name=%s login=%r logout=%r user_obj=%r" % (username, login, logout, user_obj))
-    if login:
-        u = user.User(request, name=username, password=password,
-                      auth_method='login_userpassword')
-        if u.valid:
-            setCookie(request, u)
-            return u, True # we make continuing possible, e.g. for smbmount
-        return user_obj, True
-
-    try:
-        cookie = Cookie.SimpleCookie(request.saved_cookie)
-    except Cookie.CookieError:
-        # ignore invalid cookies, else user can't relogin
-        cookie = None
-    if cookie and cookie.has_key('MOIN_ID'):
-        u = user.User(request, id=cookie['MOIN_ID'].value,
-                      auth_method='moin_cookie', auth_attribs=())
-
-        if logout:
-            u.valid = 0 # just make user invalid, but remember him
-
-        if u.valid:
-            setCookie(request, u) # refreshes cookie lifetime
-            return u, True # use True to get other methods called, too
-        else: # logout or invalid user
-            deleteCookie(request)
-            return u, True # we return a invalidated user object, so that
-                           # following auth methods can get the name of
-                           # the user who logged out
-    return user_obj, True
-
-
-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=password, 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 user_obj, 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()
-            setCookie(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.util import sessionParser
-    
-        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 = sessionParser.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
-
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/auth/_PHPsessionParser.py	Wed Jun 07 11:34:13 2006 +0200
@@ -0,0 +1,143 @@
+"""
+    MoinMoin - Parsing of PHP session files
+
+    @copyright: 2005 by MoinMoin:AlexanderSchremmer
+        - Thanks to Spreadshirt
+    @license: GNU GPL, see COPYING for details.
+"""
+
+#Known minor bugs/questions/ideas:
+#How does object demarshalling work?
+#The order of the python dictionaries is not stable compared to the PHP arrays
+#The loader does not check the owner of the files, so be aware of faked session
+#files.
+
+import os
+from MoinMoin import wikiutil
+
+s_prefix = "sess_"
+s_path = "/tmp"
+
+class UnknownObject(object):
+    """ Used in the return value if the input data could not be parsed. """
+    def __init__(self, pos):
+        self.pos = pos
+
+    def __repr__(self):
+        return "<Unknown object at pos %i>" % self.pos
+
+def transformList(items):
+    """ Transforms a list [1, 2, 3, 4, ...] into a
+        [(1, 2), (3, 4), ...] generator. """
+    for i in xrange(0, len(items), 2):
+        yield (items[i], items[i+1])
+    raise StopIteration
+
+def parseValue(string, start=0):
+    """ Parses the inner structure. """
+    #print "Parsing %r" % (string[start:], )
+
+    val_type = string[start]
+    header_end = string.find(':', 3+start)
+    if header_end != -1:
+        first_data = string[start+2:header_end]
+    else:
+        first_data = None
+    
+    #print "Saw type %r, first_data is %r." % (val_type, first_data)
+    if val_type == 'a': # array (in Python rather a mixture of a list and a dict)
+        i = 0
+        items = []
+        
+        current_pos = header_end+2
+        data = string
+        while i != (int(first_data) * 2):
+            item, current_pos = parseValue(data, current_pos)
+            items.append(item)
+            i += 1
+            current_pos += 1
+        
+        t_list = list(transformList(items))
+        try:
+            result = dict(t_list) # note that dict does not retain the order
+        except TypeError:
+            result = list(t_list)
+            #print "Warning, could not convert to dict: %r" %  (result, )
+        return result, current_pos
+    
+    if val_type == 's': # string
+        current_pos = header_end+2
+        end = current_pos + int(first_data)
+        data = string[current_pos:end]
+        current_pos = end+1
+        if data.startswith("a:"): #Sometimes, arrays are marshalled as strings.
+            try:
+                data = parseValue(data, 0)[0]
+            except ValueError: #Hmm, wrongly guessed. Just an ordinary string
+                pass
+        return data, current_pos
+
+    if val_type in ('i', 'b'): # integer or boolean
+        current_pos = start+2
+        str_buffer = ""
+        while current_pos != len(string):
+            cur_char = string[current_pos]
+            if cur_char.isdigit() or cur_char == "-":
+                str_buffer += cur_char
+            else:
+                cast = (val_type == 'i') and int or (lambda x: bool(int(x)))
+                return cast(str_buffer), current_pos
+            current_pos += 1
+
+    if val_type == "N": # Null, called None in Python
+        return None, start+1
+        
+    return UnknownObject(start), start+1
+
+def parseSession(boxed):
+    """ Parses the outer structure that is similar to a dict. """
+    current_pos = 0
+    session_dict = {}
+    while current_pos < len(boxed):
+        name_end = boxed.find("|", current_pos)
+        name = boxed[current_pos:name_end]
+        current_pos = name_end+1
+        data, current_pos = parseValue(boxed, current_pos)
+        current_pos += 1
+        session_dict[name] = data
+
+    return session_dict
+
+def loadSession(key, path=s_path, prefix=s_prefix):
+    """ Loads a particular session from the directory. The key needs to be the
+        session id. """
+    key = key.lower()
+    filename = os.path.join(path, prefix + wikiutil.taintfilename(key))
+
+    try:
+        f = open(filename, "rb")
+    except IOError, e:
+        if e.errno == 2:
+            return None # session does not exist
+        else:
+            raise
+
+    blob = f.read()
+    f.close()
+    return parseSession(blob)
+
+def listSessions(path=s_path, prefix=s_prefix):
+    """ Lists all sessions in a particular directory. """
+    return [os.path.basename(x).replace(s_prefix, '') for x in os.listdir(s_path)
+            if x.startswith(s_prefix)]
+
+if __name__ == '__main__':
+    # testing code
+    import time
+    a=time.clock()
+    
+    #print s
+    p_s = loadSession("...")
+    import pprint; pprint.pprint(p_s)
+    print time.clock() - a
+    print listSessions()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/MoinMoin/auth/__init__.py	Wed Jun 07 11:34:13 2006 +0200
@@ -0,0 +1,523 @@
+# -*- coding: iso-8859-1 -*-
+"""
+    MoinMoin - modular authentication code
+
+    Here are some methods moin can use in cfg.auth authentication method list.
+    The methods from that list get called (from request.py) in that sequence.
+    They get request as first argument and also some more kw arguments:
+       name: the value we did get from a POST of the UserPreferences page
+             in the "name" form field (or None)
+       password: the value of the password form field (or None)
+       login: True if user has clicked on Login button
+       logout: True if user has clicked on Logout button
+       user_obj: the user_obj we have until now (user_obj returned from
+                 previous auth method or None for first auth method)
+       (we maybe add some more here)
+
+    Use code like this to get them:
+        name = kw.get('name') or ''
+        password = kw.get('password') or ''
+        login = kw.get('login')
+        logout = kw.get('logout')
+        request.log("got name=%s len(password)=%d login=%r logout=%r" % (name, len(password), login, logout))
+    
+    The called auth method then must return a tuple (user_obj, continue_flag).
+    user_obj can be one of:
+    * a (newly created) User object
+    * None if we want to inhibit log in from previous auth methods
+    * what we got as kw argument user_obj (meaning: no change).
+    continue_flag is a boolean indication whether the auth loop shall continue
+    trying other auth methods (or not).
+
+    The methods give a kw arg "auth_attribs" to User.__init__ that tells
+    which user attribute names are DETERMINED and set by this auth method and
+    must not get changed by the user using the UserPreferences form.
+    It also gives a kw arg "auth_method" that tells the name of the auth
+    method that authentified the user.
+    
+    @copyright: 2005-2006 Bastian Blank, Florian Festi, Thomas Waldmann
+    @copyright: 2005-2006 MoinMoin:AlexanderSchremmer
+    @license: GNU GPL, see COPYING for details.
+"""
+
+import time, Cookie
+from MoinMoin import user
+
+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
+
+# some cookie functions used by moin_cookie auth
+def makeCookie(request, moin_id, maxage, expires):
+    """ calculate a MOIN_ID cookie """
+    c = Cookie.SimpleCookie()
+    cfg = request.cfg
+    c['MOIN_ID'] = moin_id
+    c['MOIN_ID']['max-age'] = maxage
+    if cfg.cookie_domain:
+        c['MOIN_ID']['domain'] = cfg.cookie_domain
+    if cfg.cookie_path:
+        c['MOIN_ID']['path'] = cfg.cookie_path
+    else:
+        path = request.getScriptname()
+        if not path:
+            path = '/'
+        c['MOIN_ID']['path'] = path
+    # Set expires for older clients
+    c['MOIN_ID']['expires'] = request.httpDate(when=expires, rfc='850')        
+    return c.output()
+
+def setCookie(request, u):
+    """ Set cookie for the user obj u
+    
+    cfg.cookie_lifetime and the user 'remember_me' setting set the
+    lifetime of the cookie. lifetime in int hours, see table:
+    
+    value   cookie lifetime
+    ----------------------------------------------------------------
+     = 0    forever, ignoring user 'remember_me' setting
+     > 0    n hours, or forever if user checked 'remember_me'
+     < 0    -n hours, ignoring user 'remember_me' setting
+    """
+    # Calculate cookie maxage and expires
+    lifetime = int(request.cfg.cookie_lifetime) * 3600 
+    forever = 10*365*24*3600 # 10 years
+    now = time.time()
+    if not lifetime:
+        maxage = forever
+    elif lifetime > 0:
+        if u.remember_me:
+            maxage = forever
+        else:
+            maxage = lifetime
+    elif lifetime < 0:
+        maxage = (-lifetime)
+    expires = now + maxage
+    
+    cookie = makeCookie(request, u.id, maxage, expires)
+    # Set cookie
+    request.setHttpHeader(cookie)
+    # IMPORTANT: Prevent caching of current page and cookie
+    request.disableHttpCaching()
+
+def deleteCookie(request):
+    """ Delete the user cookie by sending expired cookie with null value
+
+    According to http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2109.html#sec-4.2.2
+    Deleted cookie should have Max-Age=0. We also have expires
+    attribute, which is probably needed for older browsers.
+
+    Finally, delete the saved cookie and create a new user based on the new settings.
+    """
+    moin_id = ''
+    maxage = 0
+    # Set expires to one year ago for older clients
+    expires = time.time() - (3600 * 24 * 365) # 1 year ago
+    cookie = makeCookie(request, moin_id, maxage, expires) 
+    # Set cookie
+    request.setHttpHeader(cookie)
+    # IMPORTANT: Prevent caching of current page and cookie        
+    request.disableHttpCaching()
+
+def moin_cookie(request, **kw):
+    """ authenticate via the MOIN_ID cookie """
+    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.moin_cookie: name=%s login=%r logout=%r user_obj=%r" % (username, login, logout, user_obj))
+    if login:
+        u = user.User(request, name=username, password=password,
+                      auth_method='login_userpassword')
+        if u.valid:
+            setCookie(request, u)
+            return u, True # we make continuing possible, e.g. for smbmount
+        return user_obj, True
+
+    try:
+        cookie = Cookie.SimpleCookie(request.saved_cookie)
+    except Cookie.CookieError:
+        # ignore invalid cookies, else user can't relogin
+        cookie = None
+    if cookie and cookie.has_key('MOIN_ID'):
+        u = user.User(request, id=cookie['MOIN_ID'].value,
+                      auth_method='moin_cookie', auth_attribs=())
+
+        if logout:
+            u.valid = 0 # just make user invalid, but remember him
+
+        if u.valid:
+            setCookie(request, u) # refreshes cookie lifetime
+            return u, True # use True to get other methods called, too
+        else: # logout or invalid user
+            deleteCookie(request)
+            return u, True # we return a invalidated user object, so that
+                           # following auth methods can get the name of
+                           # the user who logged out
+    return user_obj, True
+
+
+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=password, 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 user_obj, 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()
+            setCookie(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
+
--- a/MoinMoin/util/sessionParser.py	Wed Jun 07 11:19:27 2006 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,143 +0,0 @@
-"""
-    MoinMoin - Parsing of PHP session files
-
-    @copyright: 2005 by MoinMoin:AlexanderSchremmer
-        - Thanks to Spreadshirt
-    @license: GNU GPL, see COPYING for details.
-"""
-
-#Known minor bugs/questions/ideas:
-#How does object demarshalling work?
-#The order of the python dictionaries is not stable compared to the PHP arrays
-#The loader does not check the owner of the files, so be aware of faked session
-#files.
-
-import os
-from MoinMoin import wikiutil
-
-s_prefix = "sess_"
-s_path = "/tmp"
-
-class UnknownObject(object):
-    """ Used in the return value if the input data could not be parsed. """
-    def __init__(self, pos):
-        self.pos = pos
-
-    def __repr__(self):
-        return "<Unknown object at pos %i>" % self.pos
-
-def transformList(items):
-    """ Transforms a list [1, 2, 3, 4, ...] into a
-        [(1, 2), (3, 4), ...] generator. """
-    for i in xrange(0, len(items), 2):
-        yield (items[i], items[i+1])
-    raise StopIteration
-
-def parseValue(string, start=0):
-    """ Parses the inner structure. """
-    #print "Parsing %r" % (string[start:], )
-
-    val_type = string[start]
-    header_end = string.find(':', 3+start)
-    if header_end != -1:
-        first_data = string[start+2:header_end]
-    else:
-        first_data = None
-    
-    #print "Saw type %r, first_data is %r." % (val_type, first_data)
-    if val_type == 'a': # array (in Python rather a mixture of a list and a dict)
-        i = 0
-        items = []
-        
-        current_pos = header_end+2
-        data = string
-        while i != (int(first_data) * 2):
-            item, current_pos = parseValue(data, current_pos)
-            items.append(item)
-            i += 1
-            current_pos += 1
-        
-        t_list = list(transformList(items))
-        try:
-            result = dict(t_list) # note that dict does not retain the order
-        except TypeError:
-            result = list(t_list)
-            #print "Warning, could not convert to dict: %r" %  (result, )
-        return result, current_pos
-    
-    if val_type == 's': # string
-        current_pos = header_end+2
-        end = current_pos + int(first_data)
-        data = string[current_pos:end]
-        current_pos = end+1
-        if data.startswith("a:"): #Sometimes, arrays are marshalled as strings.
-            try:
-                data = parseValue(data, 0)[0]
-            except ValueError: #Hmm, wrongly guessed. Just an ordinary string
-                pass
-        return data, current_pos
-
-    if val_type in ('i', 'b'): # integer or boolean
-        current_pos = start+2
-        str_buffer = ""
-        while current_pos != len(string):
-            cur_char = string[current_pos]
-            if cur_char.isdigit() or cur_char == "-":
-                str_buffer += cur_char
-            else:
-                cast = (val_type == 'i') and int or (lambda x: bool(int(x)))
-                return cast(str_buffer), current_pos
-            current_pos += 1
-
-    if val_type == "N": # Null, called None in Python
-        return None, start+1
-        
-    return UnknownObject(start), start+1
-
-def parseSession(boxed):
-    """ Parses the outer structure that is similar to a dict. """
-    current_pos = 0
-    session_dict = {}
-    while current_pos < len(boxed):
-        name_end = boxed.find("|", current_pos)
-        name = boxed[current_pos:name_end]
-        current_pos = name_end+1
-        data, current_pos = parseValue(boxed, current_pos)
-        current_pos += 1
-        session_dict[name] = data
-
-    return session_dict
-
-def loadSession(key, path=s_path, prefix=s_prefix):
-    """ Loads a particular session from the directory. The key needs to be the
-        session id. """
-    key = key.lower()
-    filename = os.path.join(path, prefix + wikiutil.taintfilename(key))
-
-    try:
-        f = open(filename, "rb")
-    except IOError, e:
-        if e.errno == 2:
-            return None # session does not exist
-        else:
-            raise
-
-    blob = f.read()
-    f.close()
-    return parseSession(blob)
-
-def listSessions(path=s_path, prefix=s_prefix):
-    """ Lists all sessions in a particular directory. """
-    return [os.path.basename(x).replace(s_prefix, '') for x in os.listdir(s_path)
-            if x.startswith(s_prefix)]
-
-if __name__ == '__main__':
-    # testing code
-    import time
-    a=time.clock()
-    
-    #print s
-    p_s = loadSession("...")
-    import pprint; pprint.pprint(p_s)
-    print time.clock() - a
-    print listSessions()
--- a/docs/CHANGES	Wed Jun 07 11:19:27 2006 +0200
+++ b/docs/CHANGES	Wed Jun 07 11:34:13 2006 +0200
@@ -35,6 +35,8 @@
     get reverted.
 
   Developer notes (these should be moved to the end in the release):
+    * killed "processors" (finally), formatter method changed to:
+      formatter.parser(parsername, lines)
     * refactored some actions to use ActionBase base class
     * moved "test" action from wikiaction to MoinMoin/action/
       (and use ActionBase)
@@ -45,8 +47,6 @@
     * moved wikitest.py stuff to action/test.py (only used from there)
     * moved formatter/base.py to formatter/__init__.py (FormatterBase)
     * moved util/ParserBase.py to parser/ParserBase.py
-    * killed "processors" (finally), formatter method changed to:
-      formatter.parser(parsername, lines)
     * moved / splitted request.py into MoinMoin/request/*
       Most stuff will be broken, please help fixing it (usually some imports
       will be missing and the adaptor script will need a change maybe):
@@ -64,6 +64,8 @@
     * moved logfile/logfile.py to logfile/__init__.py
     * 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
     * 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.: