Changeset 170

Show
Ignore:
Timestamp:
11/19/08 12:34:09
Author:
thejimmyg
Message:

Added bad cookie support to fix #65 and twaked render function functionality in forms to accept an environ and state argument

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • AuthKit/trunk/CHANGELOG.txt

    r169 r170  
    440.4.3 
    55 
     6* Added the ability to pass the environ dictionary to render() functions 
     7* Added bad cookie customisation options to fix #65 but also to allow 
     8  bad cookie template customisation. See the docstring of the  
     9  authkit.authenticate.cookie module. 
    610* Added a new algorithm based on ideas from #61 to guess the correct action 
    711  for the form produced by the form middleware but also added support for 
  • AuthKit/trunk/authkit/authenticate/cookie.py

    r159 r170  
    7373    design your application accordingly. In particular you should definietly 
    7474    not store passwords as user data. 
     75 
     76 
     77Bad Cookie Support 
     78================== 
     79 
     80If a cookie has expired or because there is an error parsing the ticket, it  
     81is known as a bad cookie. By default, a simple HTML page is displayed with 
     82the title "Bad Cookie" and a brief message. The headers sent with this page  
     83remove the cookie. 
     84 
     85You may want to disable this functionality and let your application handle 
     86the error condition. You can do so with this option:: 
     87 
     88    authkit.cookie.badcookie.page = false 
     89 
     90When a bad cookie is found the variable ``authkit.cookie.error`` is set in 
     91the environment with a value ``True``. If the error was due to cookie 
     92expiration the value ``authkit.cookie.timeout`` is also set to ``True``. It 
     93is then up to your application to set an appropriate cookie or restrict  
     94access to resources. AuthKit will also remove any ``REMOTE_USER`` present.  
     95 
     96Rather than handling the bad cookie in your application you may just want  
     97to change the template used by AuthKit. You do so like this:: 
     98 
     99    authkit.cookie.badcookie.page = false 
     100    authkit.cookie.badcookie.template.string = <html>Bad Cookie</html> 
     101 
     102You can use any of the template options you use to customise a form so 
     103you can also specify a function to render the template:: 
     104 
     105    authkit.cookie.badcookie.page = false 
     106    authkit.cookie.badcookie.template.obj = mymodule.auth:render_badcookie 
     107 
     108The render function can also take optional ``environ`` and ``state``  
     109arguments which are passed in by AuthKit if the function takes them as named 
     110arguments. 
     111 
     112One thing to be aware of when using this functionality is that because the 
     113render function gets called as the request is first passed along the middleware 
     114chain, many of the tools your application relies on are not yet set up so  
     115you may not be able to use all the tools you usually do. This is unlike 
     116thr forms situation where the form render function is called on the response 
     117after all your usual application infrastructure is in place. 
     118 
    75119""" 
    76120 
     
    83127import md5 
    84128import os 
     129import inspect 
     130import types 
    85131import time 
    86132import logging 
    87133from paste.deploy.converters import asbool 
    88 from authkit.authenticate import strip_base, swap_underscore, AuthKitConfigError, AuthKitUserSetter 
     134from authkit.authenticate import strip_base, swap_underscore 
     135from authkit.authenticate import AuthKitConfigError 
     136from authkit.authenticate import get_template, AuthKitUserSetter 
     137 
     138 
     139def template(): 
     140    t = '''\ 
     141<html><head><title></title></head> 
     142<body><h1>Bad Cookie</h1> 
     143<p>You have been signed out. If this problem persists  
     144please clear your browser's cookies.</p> 
     145</body></html> 
     146''' 
     147    return t 
    89148 
    90149# 
     
    278337        ticket_class=AuthKitTicket,  
    279338        nouserincookie=False, 
    280         session_middleware='beaker.session' 
     339        session_middleware='beaker.session', 
     340        badcookiepage=True, 
     341        badcookietemplate=None 
    281342    ): 
    282343        log.debug("Setting up the cookie middleware") 
     
    300361        self.nouserincookie = nouserincookie 
    301362        self.session_middleware = session_middleware 
     363        self.badcookiepage=badcookiepage 
     364        if self.badcookiepage and not badcookietemplate: 
     365            raise AuthKitConfigError( 
     366                "No badcookiepage.template option was specified for the cookie middleware" 
     367            ) 
     368        self.badcookietemplate = badcookietemplate 
    302369 
    303370    def __call__(self, environ, start_response): 
     
    325392                # checked: 
    326393                remote_addr = '0.0.0.0' 
    327             # @@: This should handle bad signatures better: 
    328             # Also, timeouts should cause cookie refresh 
    329              
    330             # 
    331             # Start changes from the default 
    332             # 
    333             def bad_ticket_app(environ, start_response, msg=None): 
    334                 # Remove the session username 
    335                 if self.nouserincookie: 
    336                     environ[self.session_middleware]['authkit.cookie.user'] = None 
    337                     del environ[self.session_middleware]['authkit.cookie.user'] 
    338                     environ[self.session_middleware].save() 
    339  
    340                 headers = self.logout_user_cookie(environ) 
    341                 headers.append(('Content-type','text/plain')) 
    342                 start_response('200 OK', headers) 
    343                 if not msg: 
    344                     msg = 'Bad cookie, you have been signed out.\n If this' 
    345                     msg += 'problem persists please clear your browser\'s ' 
    346                     msg += 'cookies.' 
    347                 return [msg] 
    348394            try: 
    349395                log.debug("Parsing ticket secret %r, cookie value %r, " 
     
    354400            except BadTicket, e: 
    355401                if e.expected: 
    356                     log.debug("BadTicket: %s Expected: %s", e, e.expected) 
     402                    log.warning("BadTicket: %s Expected: %s", e, e.expected) 
    357403                else: 
    358                     log.debug("BadTicket: %s", e) 
    359                 return bad_ticket_app(environ, start_response) 
     404                    log.warning("BadTicket: %s", e) 
     405                environ['authkit.cookie.error'] = True 
    360406            else: 
    361407                now = time.time() 
     
    366412                if self.cookie_enforce and now - timestamp > \ 
    367413                   float(self.cookie_params['expires']) + 1: 
    368                     return bad_ticket_app(environ, start_response,  
    369                                           msg="Cookie expired.") 
     414                   environ['authkit.cookie.error'] = True 
     415                   environ['authkit.cookie.timeout'] = True 
    370416                else: 
    371417                    environ['paste.auth_tkt.timestamp'] = timestamp 
    372418            # End changes from the default 
    373             environ['REMOTE_USER'] = userid 
    374             if environ.get('REMOTE_USER_TOKENS'): 
    375                 # We want to add tokens/roles to what's there: 
    376                 tokens = str(environ['REMOTE_USER_TOKENS']) + ',' + str(tokens) 
    377             environ['REMOTE_USER_TOKENS'] = tokens 
    378             environ['REMOTE_USER_DATA'] = user_data 
    379             environ['AUTH_TYPE'] = 'cookie' 
     419            if environ.get('authkit.cookie.error', False) and self.badcookiepage: 
     420                def bad_cookie_app(environ, start_response): 
     421                    # If we are using optional session support remove the user from the session: 
     422                    if self.nouserincookie: 
     423                        environ[self.session_middleware]['authkit.cookie.user'] = None 
     424                        del environ[self.session_middleware]['authkit.cookie.user'] 
     425                        environ[self.session_middleware].save() 
     426                    # Now show the bad cookie screen: 
     427                    headers = self.logout_user_cookie(environ)  
     428                    headers.append(('Content-type','text/html'))  
     429                    start_response('200 OK', headers)  
     430                    # Inspect the function to see if we can pass it anything useful: 
     431                    args = {} 
     432                    kargs = {'environ':environ} 
     433                    if environ.has_key('gi.state'): 
     434                        kargs['state'] = environ['gi.state'] 
     435                    for name in inspect.getargspec(self.badcookietemplate)[0]: 
     436                        if kargs.has_key(name): 
     437                            args[name] = kargs[name] 
     438                    response = self.badcookietemplate(**args) 
     439                    return [response] 
     440                return bad_cookie_app(environ, start_response) 
     441            elif not environ.get('authkit.cookie.error', False): 
     442                environ['REMOTE_USER'] = userid 
     443                if environ.get('REMOTE_USER_TOKENS'): 
     444                    # We want to add tokens/roles to what's there: 
     445                    tokens = environ['REMOTE_USER_TOKENS'] + tokens 
     446                environ['REMOTE_USER_TOKENS'] = tokens 
     447                environ['REMOTE_USER_DATA'] = user_data 
     448                environ['AUTH_TYPE'] = 'cookie' 
     449            # Remove REMOTE_USER set by any other application. 
     450            elif environ.has_key('REMOTE_USER'): 
     451                log.warning('Removing the existing REMOTE_USER key because of a bad cookie') 
     452                del environ['REMOTE_USER'] 
    380453        set_cookies = [] 
    381454         
     
    388461        environ['paste.auth_tkt.set_user'] = set_user 
    389462        environ['paste.auth_tkt.logout_user'] = logout_user 
    390         if self.logout_path and environ.get('PATH_INFO') == self.logout_path: 
     463        if (self.logout_path and environ.get('PATH_INFO') == self.logout_path) \ 
     464            or environ.get('authkit.cookie.error', False): 
    391465            logout_user() 
    392466         
     
    459533    prefix='authkit.cookie.' 
    460534): 
     535 
     536    badcookie_conf = strip_base(auth_conf, 'badcookie.') 
     537    template_conf = strip_base(badcookie_conf, 'template.') 
     538    if template_conf: 
     539        template_ = get_template(template_conf, prefix=prefix+'badcookiepage.template.') 
     540    else: 
     541        template_ = template 
    461542    user_setter_params = { 
    462543        'params':  strip_base(auth_conf, 'params.'), 
    463544        'ticket_class':AuthKitTicket, 
     545        'badcookiepage': asbool(badcookie_conf.get('page', True)), 
     546        'badcookietemplate': template_, 
    464547    } 
    465548    for k,v in auth_conf.items(): 
    466         if not k.startswith('params.'): 
     549        if not (k.startswith('params.') or k.startswith('badcookie.')): 
    467550            user_setter_params[k] = v 
    468551    if not user_setter_params.has_key('secret'): 
  • AuthKit/trunk/authkit/authenticate/form.py

    r169 r170  
    2525from authkit.authenticate.multi import MultiHandler, status_checker 
    2626 
     27import inspect 
    2728import logging 
    2829import urllib 
     
    9798        action = self.action or construct_url(environ) 
    9899        log.debug("Form action is: %s", action) 
    99         if self.method == 'post': 
    100             content = self.template() % action 
    101         else: 
    102             content = self.template(method=self.method) % (action) 
     100 
     101        # Inspect the function to see if we can pass it anything useful: 
     102        args = {} 
     103        kargs = {'environ':environ} 
     104        if environ.has_key('gi.state'): 
     105            kargs['state'] = environ['gi.state'] 
     106        for name in inspect.getargspec(self.template)[0]: 
     107            if kargs.has_key(name): 
     108                args[name] = kargs[name] 
     109        if self.method != 'post': 
     110            args['method'] = self.method 
     111        content = self.template(**args) % (action) 
    103112        if self.charset is not None: 
    104113            content = content.encode(self.charset)