Changeset 5091 for TI12-security


Ignore:
Timestamp:
11/03/09 12:32:15 (11 years ago)
Author:
pjkersha
Message:

Fix to logout handling for secured app middleware stack: added LogoutHandlerMiddleware? and placed in top level AuthenticationMiddleware? WSGI. This class wraps logout and OpenID sign in redirects.

Location:
TI12-security/trunk/python
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/__init__.py

    r5084 r5091  
    2525     
    2626    def __init__(self, app, app_conf, prefix='', **local_conf): 
    27         '''Set object attributes directly from app_conf and local_conf inputs 
     27        ''' 
     28        @type app: callable following WSGI interface 
     29        @param app: next middleware application in the chain       
     30        @type app_conf: dict         
     31        @param app_conf: PasteDeploy global configuration dictionary 
    2832        @type prefix: basestring 
    2933        @param prefix: prefix for app_conf parameters e.g. 'ndgsecurity.' - 
    3034        enables other global configuration parameters to be filtered out 
     35        @type local_conf: dict         
     36        @param local_conf: PasteDeploy application specific configuration  
     37        dictionary 
    3138        ''' 
    3239        self._app = app 
     
    7178        return __call__wrapper 
    7279 
     80 
     81    def __call__(self, environ, start_response): 
     82        self._initCall(environ, start_response) 
     83        return self._setResponse(environ, start_response) 
     84     
    7385    def _setResponse(self,  
    7486                     environ,  
     
    314326     
    315327    def __init__(self, *arg, **kw): 
     328        '''See NDGSecurityMiddlewareBase for explanation of args 
     329        @type arg: tuple 
     330        @param arg: single element contains next middleware application in the  
     331        chain and app_conf dict       
     332        @type kw: dict         
     333        @param kw: prefix for app_conf parameters and local_conf dict         
     334        ''' 
    316335        super(NDGSecurityPathFilter, self).__init__(*arg, **kw) 
    317336         
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/authn.py

    r5039 r5091  
    1 """HTTP Basic Authentication Middleware 
    2  
     1"""Module containing: 
     2 * HTTP Basic Authentication Middleware 
     3 * middleware to enable redirection to OpenID Relying Party for login 
     4 * logout middleware for deleting AuthKit cookie and redirecting back to  
     5   referrer 
     6  
    37NERC DataGrid Project 
    4  
    58""" 
    69__author__ = "P J Kershaw" 
     
    3942            return True 
    4043 
     44 
    4145import urllib 
    4246from urlparse import urlparse 
     
    4751    NDGSecurityMiddlewareConfigError 
    4852 
    49 class AuthNMiddleware(NDGSecurityMiddlewareBase):  
    50     '''Wrapper for AuthKit and Redirect middleware so that AuthKit Cookie 
    51     handler is called *before* redirect __call__.  This is to enable the 
    52     latter to correctly check the login status updated by the AuthKit cookie 
    53     handler'''  
     53class AuthNRedirectMiddleware(NDGSecurityMiddlewareBase): 
     54    '''Middleware to redirect to another URI if no user is set in the  
     55    REMOTE_USER key of environ 
     56     
     57    AuthKit.authenticate.middleware must be in place upstream of this  
     58    middleware.  AuthenticationMiddleware wrapper handles this.''' 
     59    propertyDefaults = { 
     60        'redirectURI': None, 
     61        'sessionKey': 'beaker.session' 
     62    } 
     63    propertyDefaults.update(NDGSecurityMiddlewareBase.propertyDefaults) 
     64 
     65    return2URIArgName = 'ndg.security.r' 
     66     
     67    _isAuthenticated = lambda self: 'REMOTE_USER' in self.environ 
     68    isAuthenticated = property(fget=_isAuthenticated, 
     69                               doc='boolean for is user logged in') 
     70 
    5471    triggerStatus = '401' 
    5572    id = 'authNMiddleware' 
    56      
     73 
    5774    def __init__(self, app, global_conf, **app_conf): 
    58         app = AuthNRedirectMiddleware(app, global_conf, **app_conf) 
    59         self._app = authkit.authenticate.middleware(app, app_conf) 
    60          
     75        self._redirectURI = None 
     76        super(AuthNRedirectMiddleware, self).__init__(app,  
     77                                                      global_conf,  
     78                                                      **app_conf) 
     79         
     80    @NDGSecurityMiddlewareBase.initCall 
    6181    def __call__(self, environ, start_response): 
    62         return self._app(environ, start_response) 
     82        '''Invoke redirect if user is not authenticated''' 
     83         
     84        if not self.isAuthenticated: 
     85            # Redirect to OpenID Relying Party URI for user OpenID entry 
     86            return self._setRedirectResponse() 
     87         
     88        else: 
     89            def set403Response(status, header, exc_info=None): 
     90                return start_response(self.getStatusMessage(403), 
     91                                      header, 
     92                                      exc_info) 
     93                 
     94            return self._app(environ, set403Response)             
     95    
     96    def _setRedirectURI(self, uri): 
     97        if not isinstance(uri, basestring): 
     98            raise TypeError("Redirect URI must be set to string type")    
     99          
     100        self._redirectURI = uri 
     101         
     102    def _getRedirectURI(self): 
     103        return self._redirectURI 
     104     
     105    redirectURI = property(fget=_getRedirectURI, 
     106                       fset=_setRedirectURI, 
     107                       doc="URI to redirect to if user is not authenticated") 
     108 
     109    def _setRedirectResponse(self): 
     110        """Construct a redirect response adding in a return to address in a 
     111        URI query argument 
     112         
     113        @rtype: basestring 
     114        @return: redirect response 
     115        """ 
     116         
     117        return2URI = construct_url(self.environ) 
     118        quotedReturn2URI = urllib.quote(return2URI, safe='') 
     119        return2URIQueryArg = urllib.urlencode( 
     120                    {AuthNRedirectMiddleware.return2URIArgName:  
     121                     quotedReturn2URI}) 
     122 
     123        redirectURI = self.redirectURI 
     124         
     125        if '?' in redirectURI: 
     126            if redirectURI.endswith('&'): 
     127                redirectURI += return2URIQueryArg 
     128            else: 
     129                redirectURI += '&' + return2URIQueryArg 
     130        else: 
     131            if redirectURI.endswith('?'): 
     132                redirectURI += return2URIQueryArg 
     133            else: 
     134                redirectURI += '?' + return2URIQueryArg 
     135                 
     136        return self._redirect(redirectURI) 
    63137         
    64138    @classmethod 
     
    71145         
    72146        if status.startswith(cls.triggerStatus): 
    73             log.debug("AuthNMiddleware.checker returning True") 
     147            log.debug("%s.checker returning True" % cls.__name__) 
    74148            return True 
    75149        else: 
    76             log.debug("AuthNMiddleware.checker returning False") 
     150            log.debug("%s.checker returning False" % cls.__name__) 
    77151            return False 
    78          
    79 class AuthNRedirectMiddlewareConfigError(NDGSecurityMiddlewareConfigError): 
    80     '''Authentication Redirect Middleware Configuration error''' 
    81  
    82 class AuthNRedirectMiddleware(NDGSecurityMiddlewareBase): 
    83     '''Middleware to redirect to another URI if no user is set in the  
    84     REMOTE_USER key of environ 
    85      
    86     AuthKit.authenticate.middleware must be in place upstream of this  
    87     middleware.  AuthNMiddleware wrapper handles this.  See above''' 
     152 
     153 
     154class LogoutHandlerMiddleware(NDGSecurityMiddlewareBase): 
     155    '''Middleware to redirect back to referrer URI following call to a logout 
     156    URI as implemented in AuthKit''' 
     157    prefix = 'logout.' 
     158     
     159    logoutReturn2URIArgName = 'ndg.security.logout.r' 
    88160    propertyDefaults = { 
    89         'redirectURI': None, 
     161        'signoutPath': None, 
    90162        'sessionKey': 'beaker.session' 
    91163    } 
    92164    propertyDefaults.update(NDGSecurityMiddlewareBase.propertyDefaults) 
    93165 
    94     return2URIArgName = 'ndg.security.r' 
    95     logoutReturn2URIArgName = 'ndg.security.logout.r' 
    96      
    97     _isAuthenticated = lambda self: 'REMOTE_USER' in self.environ 
    98     isAuthenticated = property(fget=_isAuthenticated, 
    99                                doc='boolean for is user logged in') 
    100  
    101     def __init__(self, app, global_conf, **app_conf): 
    102         self._redirectURI = None 
    103          
    104         # AuthKit params are expected to be present in  
    105         self._signoutPath = app_conf.get('authkit.cookie.signoutpath') 
    106          
    107         super(AuthNRedirectMiddleware, self).__init__(app,  
    108                                                       global_conf,  
     166 
     167    def __init__(self, app, global_conf, prefix='', **app_conf): 
     168        #_prefix = prefix + LogoutHandlerMiddleware.prefix 
     169        super(LogoutHandlerMiddleware, self).__init__(app,  
     170                                                      global_conf, 
     171                                                      prefix=prefix,  
    109172                                                      **app_conf) 
    110173         
    111174    @NDGSecurityMiddlewareBase.initCall 
    112175    def __call__(self, environ, start_response): 
    113         '''Invoke redirect if user is not authenticated''' 
    114176        session = environ[self.sessionKey] 
    115177         
    116         if not self.isAuthenticated: 
    117             # Redirect to OpenID Relying Party URI for user OpenID entry 
    118             return self._setRedirectResponse() 
    119          
    120         elif self._signoutPath is not None and \ 
    121              self.pathInfo == self._signoutPath: 
    122             referer = session.get( 
    123                             AuthNRedirectMiddleware.logoutReturn2URIArgName) 
     178        if self.signoutPath and self.pathInfo == self.signoutPath: 
     179            referer = session.get(self.__class__.logoutReturn2URIArgName) 
    124180            if referer is not None: 
    125                 def setRedirectResponse(status, header, exc_info=None): 
     181                def _start_response(status, header, exc_info=None): 
    126182                    header.extend([('Location', referer)]) 
    127183                    return start_response(self.getStatusMessage(302),  
     
    129185                                          exc_info) 
    130186                     
    131                 return self._app(environ, setRedirectResponse) 
    132187            else: 
    133                 log.debug('No referer set for redirect following logout') 
    134  
    135         else: 
    136             # User is logged in, alter response to 403 to signal that an 
    137             # authorisation check is required and set a return to address for 
    138             # logout 
    139             session[AuthNRedirectMiddleware.logoutReturn2URIArgName] = \ 
    140                                                                 self.pathInfo 
     188                log.error('No referer set for redirect following logout') 
     189                _start_response = start_response 
     190        else: 
     191            # Set a return to address for logout 
     192            session[self.__class__.logoutReturn2URIArgName] = self.pathInfo 
    141193            session.save() 
     194                       
     195            _start_response = start_response 
    142196             
    143             def set403Response(status, header, exc_info=None): 
    144                 return start_response(self.getStatusMessage(403), 
    145                                       header, 
    146                                       exc_info) 
    147                  
    148             return self._app(environ, set403Response)             
    149     
    150     def _setRedirectURI(self, uri): 
    151         if not isinstance(uri, basestring): 
    152             raise TypeError("Redirect URI must be set to string type")    
    153           
    154         self._redirectURI = uri 
    155          
    156     def _getRedirectURI(self): 
    157         return self._redirectURI 
    158      
    159     redirectURI = property(fget=_getRedirectURI, 
    160                        fset=_setRedirectURI, 
    161                        doc="URI to redirect to if user is not authenticated") 
    162  
    163     def _setRedirectResponse(self): 
    164         """Construct a redirect response adding in a return to address in a 
    165         URI query argument 
    166          
    167         @rtype: basestring 
    168         @return: redirect response 
    169         """ 
    170          
    171         return2URI = construct_url(self.environ) 
    172         quotedReturn2URI = urllib.quote(return2URI, safe='') 
    173         return2URIQueryArg = urllib.urlencode( 
    174                     {AuthNRedirectMiddleware.return2URIArgName:  
    175                      quotedReturn2URI}) 
    176  
    177         redirectURI = self.redirectURI 
    178          
    179         if '?' in redirectURI: 
    180             if redirectURI.endswith('&'): 
    181                 redirectURI += return2URIQueryArg 
    182             else: 
    183                 redirectURI += '&' + return2URIQueryArg 
    184         else: 
    185             if redirectURI.endswith('?'): 
    186                 redirectURI += return2URIQueryArg 
    187             else: 
    188                 redirectURI += '?' + return2URIQueryArg 
    189                  
    190         return self._redirect(redirectURI) 
     197        return self._app(environ, _start_response) 
    191198 
    192199 
    193200from authkit.authenticate.multi import MultiHandler 
    194201 
    195 class AuthNRedirectHandlerMiddleware(MultiHandler, NDGSecurityMiddlewareBase): 
     202         
     203class AuthenticationMiddlewareConfigError(NDGSecurityMiddlewareConfigError): 
     204    '''Authentication Middleware Configuration error''' 
     205 
     206class AuthenticationMiddleware(MultiHandler, NDGSecurityMiddlewareBase): 
    196207    '''Handler to intercept 401 Unauthorized HTTP responses and redirect to an 
    197     authentication URI.  Add THIS class to any middleware chain and NOT 
    198     AuthNRedirectMiddleware which it wraps. 
     208    authentication URI.  This class also implements a redirect handler to 
     209    redirect back to the referrer if logout is invoked. 
    199210    ''' 
    200     def __init__(self, app, global_conf, **app_conf): 
     211 
     212    def __init__(self, app, global_conf, prefix='', **app_conf): 
     213        ''' 
     214        @type app: callable following WSGI interface 
     215        @param app: next middleware application in the chain       
     216        @type global_conf: dict         
     217        @param global_conf: PasteDeploy global configuration dictionary 
     218        @type prefix: basestring 
     219        @param prefix: prefix for configuration items 
     220        @type app_conf: dict         
     221        @param app_conf: PasteDeploy application specific configuration  
     222        dictionary 
     223        ''' 
     224         
     225        # Set logout URI parameter from AuthKit settings if not otherwise set 
     226        logoutPrefix = prefix + LogoutHandlerMiddleware.prefix 
     227        signoutPathParamName = logoutPrefix + 'signoutPath' 
     228        if signoutPathParamName not in app_conf: 
     229            try:                    
     230                app_conf[signoutPathParamName] = app_conf[ 
     231                                                'authkit.cookie.signoutpath'] 
     232            except KeyError, e: 
     233                raise AuthenticationMiddlewareConfigError("No %s or %s keys " 
     234                                                    "set" %  
     235                                                    (signoutPathParamName, e)) 
     236         
     237        app = LogoutHandlerMiddleware(app,  
     238                                      global_conf,  
     239                                      prefix=logoutPrefix, 
     240                                      **app_conf) 
     241         
     242        # Remove logout middleware specific parameters 
     243        for k in app_conf.keys(): 
     244            if k.startswith(logoutPrefix): 
     245                del app_conf[k] 
     246                 
     247        app = authkit.authenticate.middleware(app, app_conf) 
    201248        MultiHandler.__init__(self, app) 
    202249 
    203         self.add_method(AuthNMiddleware.id,  
    204                         AuthNMiddleware.filter_app_factory,  
     250        self.add_method(AuthNRedirectMiddleware.id,  
     251                        AuthNRedirectMiddleware.filter_app_factory,  
    205252                        global_conf, 
     253                        prefix=prefix, 
    206254                        **app_conf) 
    207255         
    208         self.add_checker(AuthNMiddleware.id, AuthNMiddleware.checker) 
     256        self.add_checker(AuthNRedirectMiddleware.id,  
     257                         AuthNRedirectMiddleware.checker) 
     258 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/openid/provider/__init__.py

    r5087 r5091  
    33Compliments AuthKit OpenID Middleware used for OpenID *Relying Party* 
    44 
    5 NERC Data Grid Project 
    6  
     5NERC DataGrid Project 
    76""" 
    87__author__ = "P J Kershaw" 
     
    2928from openid.consumer import discover 
    3029 
    31 from ndg.security.server.wsgi import NDGSecurityMiddlewareBase 
     30from ndg.security.server.wsgi import NDGSecurityMiddlewareBase         
    3231 
    3332 
     
    162161        """ 
    163162        raise NotImplementedError() 
    164          
    165          
     163 
     164     
    166165class OpenIDProviderMiddlewareError(Exception): 
    167166    """OpenID Provider WSGI Middleware Error""" 
     
    738737            return self._render.mainPage(environ, start_response)             
    739738        else: 
     739            log.error('Setting response following ID Approval: expecting ' 
     740                      'Yes/No in allow post. %r' % self.query) 
    740741            return self._render.errorPage(environ, start_response, 
    741                                           'Expecting Yes/No in allow ' 
    742                                           'post. %r' % self.query, 
     742                                          'Error setting Yes/No response to ' 
     743                                          'return your credentials back to ' 
     744                                          'the requesting site.  Please ' 
     745                                          'report this fault to your site ' 
     746                                          'administrator.', 
    743747                                          code=400) 
    744748 
     
    11901194                        ('Location', url)]) 
    11911195        return [] 
    1192      
     1196    
    11931197     
    11941198class RenderingInterfaceError(Exception): 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/openid/relyingparty/__init__.py

    r5080 r5091  
    2020from paste.request import parse_querystring, parse_formvars 
    2121import authkit.authenticate 
    22 import beaker.middleware 
     22from beaker.middleware import SessionMiddleware 
    2323 
    2424from ndg.security.server.wsgi import NDGSecurityMiddlewareBase 
     
    256256    def __call__(self, environ, start_response): 
    257257        return self._app(self, environ, start_response) 
     258 
  • TI12-security/trunk/python/ndg.security.test/ndg/security/test/integration/authz/securedapp.ini

    r5087 r5091  
    2727#beaker.session.key = sso 
    2828beaker.session.secret = somesecret 
    29 beaker.session.type = cookie 
    30 beaker.session.validate_key = 0123456789abcdef 
    31 beaker.session.encrypt_key = fedcba9876543210 
    32  
    33 # If you'd like to fine-tune the individual locations of the cache data dirs 
    34 # for the Cache data, or the Session saves, un-comment the desired settings 
    35 # here: 
    36 beaker.cache.data_dir = %(here)s/beaker/cache 
    37 beaker.session.data_dir = %(here)s/beaker/sessions 
     29beaker.cache.data_dir = %(here)s/authn/beaker/cache 
     30beaker.session.data_dir = %(here)s/authn/beaker/sessions 
     31# These options enable cookie only type sessions with the cookie content  
     32# encrypted 
     33#beaker.session.type = cookie 
     34#beaker.session.validate_key = 0123456789abcdef 
     35#beaker.session.encrypt_key = fedcba9876543210 
    3836 
    3937[filter:AuthenticationFilter] 
    40 paste.filter_app_factory = ndg.security.server.wsgi.authn:AuthNRedirectHandlerMiddleware 
     38paste.filter_app_factory = ndg.security.server.wsgi.authn:AuthenticationMiddleware 
    4139prefix = authN. 
    4240 
     
    4644# AuthKit Set-up 
    4745authkit.setup.method=cookie 
     46authkit.cookie.name=ndg.security 
    4847authkit.cookie.secret=secret encryption string 
    4948authkit.cookie.signoutpath = /logout 
  • TI12-security/trunk/python/ndg.security.test/ndg/security/test/integration/authz/securedapp.py

    r5086 r5091  
    138138 
    139139    def test_securedURI(self, environ, start_response): 
    140         response = """<html> 
     140        if 'REMOTE_USER' in environ: 
     141            response = """<html> 
    141142    <head/> 
    142143    <body> 
    143         <h1>Access allowed!</h1> 
     144        <h1>Authorised for path [%s]!</h1> 
    144145        <p><a href="/logout">logout</a></p> 
    145146    </body> 
    146 </html>""" 
    147         start_response('200 OK',  
    148                        [('Content-type', 'text/html'), 
    149                         ('Content-length', str(len(response)))]) 
     147</html>""" % environ['PATH_INFO'] 
     148            start_response('200 OK',  
     149                           [('Content-type', 'text/html'), 
     150                            ('Content-length', str(len(response)))]) 
     151        else: 
     152            response = "Trigger AuthZ..." 
     153            start_response('403 Forbidden',  
     154                           [('Content-type', 'text/plain'), 
     155                            ('Content-length', str(len(response)))]) 
    150156        return response 
    151157     
  • TI12-security/trunk/python/ndg.security.test/ndg/security/test/integration/authz/securityservices.ini

    r5087 r5091  
    178178[filter:SessionMiddlewareFilter] 
    179179paste.filter_app_factory=beaker.middleware:SessionMiddleware 
    180 #beaker.session.key = sso 
     180beaker.session.key = openid 
    181181beaker.session.secret = somesecret 
    182 beaker.session.type = cookie 
    183 beaker.session.validate_key = 0123456789abcdef 
    184 beaker.session.encrypt_key = fedcba9876543210 
     182# These options enable cookie only type sessions with the cookie content  
     183# encrypted 
     184#beaker.session.type = cookie 
     185#beaker.session.validate_key = 0123456789abcdef 
     186#beaker.session.encrypt_key = fedcba9876543210 
    185187 
    186188# If you'd like to fine-tune the individual locations of the cache data dirs 
    187189# for the Cache data, or the Session saves, un-comment the desired settings 
    188190# here: 
    189 beaker.cache.data_dir = %(here)s/beaker/cache 
    190 beaker.session.data_dir = %(here)s/beaker/sessions 
     191beaker.cache.data_dir = %(here)s/openidprovider/beaker/cache 
     192beaker.session.data_dir = %(here)s/openidprovider/beaker/sessions 
    191193 
    192194[filter:OpenIDRelyingPartyFilter] 
     
    216218# AuthKit Set-up 
    217219authkit.setup.method=openid, cookie 
     220authkit.cookie.name=ndg.security 
    218221authkit.cookie.secret=secret encryption string 
    219222authkit.cookie.signoutpath = /logout 
    220223authkit.openid.path.signedin=/ 
    221224authkit.openid.store.type=file 
    222 authkit.openid.store.config=%(here)s/data/openid 
     225authkit.openid.store.config=%(here)s/openidrelyingparty/store 
    223226authkit.openid.session.key = authkit_openid 
    224227authkit.openid.session.secret = random string 
Note: See TracChangeset for help on using the changeset viewer.