changeset 456:12b6367214e3

feed current user_obj to auth methods, continue auth list in most cases, moved cookie code to auth module imported from: moin--main--1.5--patch-460
author Thomas Waldmann <tw@waldmann-edv.de>
date Sun, 26 Feb 2006 18:21:38 +0000
parents e5609b8b7647
children f423a8496ae3
files ChangeLog MoinMoin/auth.py MoinMoin/request.py MoinMoin/userform.py docs/CHANGES
diffstat 5 files changed, 255 insertions(+), 209 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Sat Feb 25 12:29:22 2006 +0000
+++ b/ChangeLog	Sun Feb 26 18:21:38 2006 +0000
@@ -2,6 +2,21 @@
 # arch-tag: automatic-ChangeLog--arch@arch.thinkmo.de--2003-archives/moin--main--1.5
 #
 
+2006-02-26 19:21:38 GMT	Thomas Waldmann <tw@waldmann-edv.de>	patch-460
+
+    Summary:
+      feed current user_obj to auth methods, continue auth list in most cases, moved cookie code to auth module
+    Revision:
+      moin--main--1.5--patch-460
+
+    feed current user_obj to auth methods, continue auth list in most cases, moved cookie code to auth module
+    
+
+    modified files:
+     ChangeLog MoinMoin/auth.py MoinMoin/request.py
+     MoinMoin/userform.py docs/CHANGES
+
+
 2006-02-25 13:29:22 GMT	Thomas Waldmann <tw@waldmann-edv.de>	patch-459
 
     Summary:
--- a/MoinMoin/auth.py	Sat Feb 25 12:29:22 2006 +0000
+++ b/MoinMoin/auth.py	Sun Feb 26 18:21:38 2006 +0000
@@ -10,6 +10,8 @@
        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:
@@ -20,18 +22,13 @@
         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 is either a User object or None if it could not make one.
+    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).
 
-    There are the possible cases for the returned tuple:
-    user, False == we managed to authentify a user and we don't need to continue
-    user, True  == makes no sense (unused)
-    None, False == we could not authenticate the user and this is final, we
-                   don't want to try other auth methods to authenticate him
-    None, True  == we could not authentifacte the user, but we want to continue
-                   trying with other auth methods
-
     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.
@@ -43,29 +40,104 @@
     @license: GNU GPL, see COPYING for details.
 """
 
-import Cookie
+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:
+        c['MOIN_ID']['path'] = request.getScriptname()
+    # 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 """
-    if kw.get('login'):
-        name = kw.get('name')
-        password = kw.get('password')
-        u = user.User(request, name=name, password=password,
+    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:
-            request.user = u # needed by setCookie
-            request.setCookie()
-            return u, False
-        return None, True
+            setCookie(request, u)
+            return u, True # we make continuing possible, e.g. for smbmount
+        return user_obj, True
 
-    if kw.get('logout'):
-        # clear the cookie in the browser and locally. Does not
-        # check if we have a valid user logged, just make sure we
-        # don't have one after this call.
-        request.deleteCookie()
-        return None, True
-    
     try:
         cookie = Cookie.SimpleCookie(request.saved_cookie)
     except Cookie.CookieError:
@@ -74,26 +146,25 @@
     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:
-            request.user = u
-            request.setCookie()
-            return u, False
-    return None, True
+            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
 
 
-#
-#   idea: maybe we should call back to the request object like:
-#         username, password, authenticated, authtype = request.getUserPassAuth()
-#	 WhoEver   geheim    false          basic      (twisted, doityourself pw check)
-#	 WhoEver   None      true           basic/...  (apache)
-#	 
-#        thus, the server specific code would stay in request object implementation.
-#
-#     THIS IS NOT A WIKI PAGE ;-)
-	 
 def http(request, **kw):
     """ authenticate via http basic/digest/ntlm auth """
     from MoinMoin.request import RequestTwisted, RequestCLI
+    user_obj = kw.get('user_obj')
     u = None
     # check if we are running Twisted
     if isinstance(request, RequestTwisted):
@@ -126,18 +197,19 @@
     if u:
         u.create_or_update()
     if u and u.valid:
-        return u, False
+        return u, True # True to get other methods called, too
     else:
-        return None, True
+        return user_obj, True
 
 def sslclientcert(request, **kw):
     """ authenticate via SSL client certificate """
     from MoinMoin.request import RequestTwisted
+    user_obj = kw.get('user_obj')
     u = None
     changed = False
     # check if we are running Twisted
     if isinstance(request, RequestTwisted):
-        return u # not supported if we run twisted
+        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
@@ -179,13 +251,13 @@
     if u:
         u.create_or_update(changed)
     if u and u.valid:
-        return u, False
+        return u, True
     else:
-        return None, True
+        return user_obj, True
 
 
 def smb_mount(request, **kw):
-    """ mount a SMB server's share for username (using username/password for
+    """ (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.
@@ -194,38 +266,45 @@
     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 the smb share
-    if not login:
-        return None, True
-    
-    import os, pwd, subprocess
-    web_username = cfg.smb_dir_user
-    web_uid = pwd.getpwnam(web_username)[2] # XXX better just use current uid?
-    mountpoint = cfg.smb_mountpoint % username
-    mountcmd = 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" % {
-        '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,
-    }
-    try:
-        os.makedirs(mountpoint) # the dir containing the mountpoint must be writeable for us!
-    except OSError, err:
-        pass
-    env = os.environ.copy()
-    env['PASSWD'] = password.encode(cfg.smb_coding)
-    subprocess.call(mountcmd.encode(cfg.smb_coding), env=env, shell=True)
-    return None, True
+    # 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
+        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):
@@ -238,16 +317,17 @@
     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, thus we return None, True.
+    # handled by another auth handler
     if not login and not logout:
-        return None, True
+        return user_obj, True
     
     import sys, re
     import ldap
@@ -275,7 +355,7 @@
                 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 None, True
+            return user_obj, True
 
         dn, ldap_dict = lusers[0]
         if verbose:
@@ -315,47 +395,46 @@
 
     if u:
         u.create_or_update(True)
-    return None, True # moin_cookie has to set the cookie and return the user obj
+    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
-    # TODO use tuples as return value
-    if request.form.has_key("user"):
-        username = request.form["user"][0]
-    else:
-        return None
-    passwd = None
-    if request.form.has_key("passwd"):
-        passwd = request.form["passwd"][0]
+    username = kw.get('name')
+    password = kw.get('password')
+    login = kw.get('login')
+    logout = kw.get('logout')
+    user_obj = kw.get('user_obj')
 
-    wikitag, wikiurl, wikitail, err = wikiutil.resolve_wiki(username)
+    if login:
+        wikitag, wikiurl, wikitail, err = wikiutil.resolve_wiki(username)
 
-    if err or wikitag not in request.cfg.trusted_wikis:
-        return None
+        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
     
-    if passwd:
-        import xmlrpclib
-        homewiki = xmlrpclib.Server(wikiurl + "?action=xmlrpc2")
-        account_data = homewiki.getUser(wikitail, passwd)
-        if isinstance(account_data, str):
-            # show error message
-            return None
-        
-        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()
-        request.user = u
-        request.setCookie()
-        return u
-    else:
-        pass
-        # XXX redirect to homewiki
+    return user_obj, True
 
 
 class php_session:
@@ -398,12 +477,11 @@
             
             return dec(username), dec(email), dec(name)
         
-        import Cookie
-        import urllib
+        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
@@ -417,7 +495,7 @@
                         username, email, name = handle_egroupware(session)
                         break
             else:
-                return None, True
+                return user_obj, True
             
             user = User(request, name=username, auth_username=username)
             
@@ -432,6 +510,6 @@
             if user:
                 user.create_or_update(changed)
             if user and user.valid:
-                return user, False # return user object and stop processing auth method list
-        return None, True # return None and continue with next method in auth list
+                return user, True # True to get other methods called, too
+        return user_obj, True # continue with next method in auth list
 
--- a/MoinMoin/request.py	Sat Feb 25 12:29:22 2006 +0000
+++ b/MoinMoin/request.py	Sun Feb 26 18:21:38 2006 +0000
@@ -144,7 +144,7 @@
             rootname = u''
             self.rootpage = Page(self, rootname, is_rootpage=1)
 
-            self.user = self.get_user()
+            self.user = self.get_user_from_form()
             
             if not self.query_string.startswith('action=xmlrpc'):
                 if not self.forbidden and self.isForbidden():
@@ -498,25 +498,40 @@
         else:
             path, query = uri, ''
         return wikiutil.url_unquote(path, want_unicode=False), query        
-                
-    def get_user(self):
-        # try to get some values from the maybe present UserPreferences form,
-        # so auth methods can use it as their user interface for login
+
+    def get_user_from_form(self):
+        """ read the maybe present UserPreferences form and call get_user with the values """
         name = self.form.get('name', [None])[0]
         password = self.form.get('password', [None])[0]
         login = self.form.has_key('login')
         logout = self.form.has_key('logout')
-
-        for auth in self.cfg.auth:
-            user_obj, continue_flag = auth(self,
-                                           name=name, password=password,
-                                           login=login, logout=logout)
-            if not continue_flag:
-                break
+        return self.get_user_default_unknown(name=name, password=password,
+                                             login=login, logout=logout,
+                                             user_obj=None)
+    
+    def get_user_default_unknown(self, **kw):
+        """ call do_auth and if it doesnt return a user object, make some "Unknown User" """
+        user_obj = self.get_user_default_None(**kw)
         if user_obj is None:
             user_obj = user.User(self, auth_method="request:427")
         return user_obj
 
+    def get_user_default_None(self, **kw):
+        """ loop over auth handlers, return a user obj or None """
+        name = kw.get('name')
+        password = kw.get('password')
+        login = kw.get('login')
+        logout = kw.get('logout')
+        user_obj = kw.get('user_obj')
+        for auth in self.cfg.auth:
+            user_obj, continue_flag = auth(self,
+                                           name=name, password=password,
+                                           login=login, logout=logout,
+                                           user_obj=user_obj)
+            if not continue_flag:
+                break
+        return user_obj
+        
     def reset(self):
         """ Reset request state.
 
@@ -1061,7 +1076,7 @@
                 msg = _("""Invalid user name {{{'%s'}}}.
 Name may contain any Unicode alpha numeric character, with optional one
 space between words. Group page name is not allowed.""") % self.user.name
-                self.deleteCookie()
+                request.user = self.get_user_default_unknown(name=self.user.name, logout=True)
                 page = wikiutil.getSysPage(self, 'UserPreferences')
                 page.send_page(self, msg=msg)
 
@@ -1284,82 +1299,6 @@
         # See http://www.cse.ohio-state.edu/cgi-bin/rfc/rfc2068.html#sec-14.32
         self.setHttpHeader('Pragma: no-cache')
 
-    def makeCookie(self, moin_id, maxage, expires):
-        from Cookie import SimpleCookie
-        c = SimpleCookie()
-        c['MOIN_ID'] = moin_id
-        c['MOIN_ID']['max-age'] = maxage
-        if self.cfg.cookie_domain:
-            c['MOIN_ID']['domain'] = self.cfg.cookie_domain
-        if self.cfg.cookie_path:
-            c['MOIN_ID']['path'] = self.cfg.cookie_path
-        else:
-            c['MOIN_ID']['path'] = self.getScriptname()
-        # Set expires for older clients
-        c['MOIN_ID']['expires'] = self.httpDate(when=expires, rfc='850')        
-        return c.output()
-        
-    def setCookie(self):
-        """ Set cookie for the current user
-        
-        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(self.cfg.cookie_lifetime) * 3600 
-        forever = 10*365*24*3600 # 10 years
-        now = time.time()
-        if not lifetime:
-            maxage = forever
-        elif lifetime > 0:
-            if self.user.remember_me:
-                maxage = forever
-            else:
-                maxage = lifetime
-        elif lifetime < 0:
-            maxage = (-lifetime)
-        expires = now + maxage
-        
-        cookie = self.makeCookie(self.user.id, maxage, expires)
-        self.setHttpHeader(cookie)
-
-        # Update the saved cookie, so other code works with new setup
-        self.saved_cookie = cookie
-
-        # IMPORTANT: Prevent caching of current page and cookie
-        self.disableHttpCaching()
-
-    def deleteCookie(self):
-        """ 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 = self.makeCookie(moin_id, maxage, expires) 
-        # Set cookie
-        self.setHttpHeader(cookie)
-
-        # Update saved cookie and set new unregistered user
-        self.saved_cookie = ''
-        self.user = user.User(self, auth_method="request:1245")
-
-        # IMPORTANT: Prevent caching of current page and cookie        
-        self.disableHttpCaching()
-
     def finish(self):
         """ General cleanup on end of request
         
--- a/MoinMoin/userform.py	Sat Feb 25 12:29:22 2006 +0000
+++ b/MoinMoin/userform.py	Sun Feb 26 18:21:38 2006 +0000
@@ -157,8 +157,10 @@
             if _debug:
                 result = result + util.dumpFormData(form)
             return result
-            
-        if form.has_key('select_user'): # Select user profile (su user)
+        
+        
+        # Select user profile (su user) - only works with cookie auth active.
+        if form.has_key('select_user'):
             if (wikiutil.checkTicket(self.request.form['ticket'][0]) and
                 self.request.request_method == 'POST' and
                 self.request.user.isSuperUser()):
@@ -167,8 +169,9 @@
                 theuser = user.User(self.request, uid)
                 theuser.disabled = None
                 theuser.save()
+                from MoinMoin import auth
+                auth.setCookie(self.request, theuser)
                 self.request.user = theuser
-                self.request.setCookie()
                 return  _("Use UserPreferences to change settings of the selected user account")
             else:
                 return _("Use UserPreferences to change your settings or create an account.")
--- a/docs/CHANGES	Sat Feb 25 12:29:22 2006 +0000
+++ b/docs/CHANGES	Sun Feb 26 18:21:38 2006 +0000
@@ -46,6 +46,17 @@
       wrong configuration.
     * ldap_login and smb_mount auth methods, see MoinMoin/auth.py and
       wiki/config/more_samples/ldap_smb_farmconfig.py
+    * auth methods now get a user_obj kw argument that is either a user object
+      returned from a previous auth method or None (if no user has been made
+      up yet). The auth method should either return a user object (if it has
+      determined one) or what it got as user_obj (being "passive") or None
+      (if it wants to "veto" some user even if a previous method already has
+      made up some user object).
+    * Changed auth methods return value of continue_flag to be True in most
+      cases (except if wants to "veto" and abort).
+    * moin_cookie auth method now logs out a user by deleting the cookie and
+      setting user_obj.valid = 0. This makes it possible to still get the
+      user's name in subsequent auth method calls within the same request.
 
   Bugfixes:
     * cookie_lifetime didn't work comfortable for low values. The cookie was