Changeset 6264 for TI12-security


Ignore:
Timestamp:
05/01/10 09:45:34 (10 years ago)
Author:
pjkersha
Message:
  • Refactored PEP result handler code from authz into separate ndg.security.server.wsgi.authz.result_handler package
  • Refactored session handling classes from ndg.security.server.wsgi.authn to new ndg.security.server.wsgi.session module
Location:
TI12-security/trunk/NDGSecurity/python
Files:
5 added
1 deleted
3 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/wsgi/authn.py

    r6246 r6264  
    2222from paste.request import construct_url, parse_querystring 
    2323import authkit.authenticate 
    24  
    25 from ndg.security.server.wsgi import NDGSecurityMiddlewareBase, \ 
    26     NDGSecurityMiddlewareError, NDGSecurityMiddlewareConfigError         
     24from authkit.authenticate.multi import MultiHandler 
     25 
     26from ndg.security.server.wsgi import (NDGSecurityMiddlewareBase,  
     27                                      NDGSecurityMiddlewareError,  
     28                                      NDGSecurityMiddlewareConfigError)  
     29from ndg.security.server.wsgi.session import (SessionMiddlewareBase, 
     30                                              SessionHandlerMiddleware)   
     31 
    2732 
    2833class AuthnException(NDGSecurityMiddlewareError): 
     
    4449     
    4550class HTTPBasicAuthMiddleware(NDGSecurityMiddlewareBase): 
    46     '''HTTP Basic Authentication Middleware 
    47      
     51    '''HTTP Basic Authentication Middleware  
    4852    ''' 
    4953     
     
    178182# AuthKit based HTTP basic authentication plugin not currently needed but may  
    179183# need resurrecting 
    180  
    181 #from authkit.permissions import UserIn 
    182 #from ndg.security.server.wsgi.utils.sessionmanagerclient import \ 
    183 #    WSGISessionManagerClient 
    184 #             
    185 #class HTTPBasicAuthentication(object): 
    186 #    '''Authkit based HTTP Basic Authentication.   __call__ defines a  
    187 #    validation function to fit with the pattern for the AuthKit interface 
    188 #    ''' 
    189 #     
    190 #    def __init__(self): 
    191 #        self._userIn = UserIn([]) 
    192 #         
    193 #    def __call__(self, environ, username, password): 
    194 #        """validation function""" 
    195 #        try: 
    196 #            client = WSGISessionManagerClient(environ=environ, 
    197 #                                environKeyName=self.sessionManagerFilterID) 
    198 #            res = client.connect(username, passphrase=password) 
    199 # 
    200 #            if username not in self._userIn.users: 
    201 #                self._userIn.users += [username] 
    202 #             
    203 #            # TODO: set session 
    204 #                 
    205 #        except Exception, e: 
    206 #            return False 
    207 #        else: 
    208 #            return True 
    209  
    210  
    211 class SessionMiddlewareBase(NDGSecurityMiddlewareBase): 
    212     """Base class for Authentication redirect middleware and Session Handler 
    213     middleware 
    214      
    215     @type propertyDefaults: dict 
    216     @cvar propertyDefaults: valid configuration property keywords  
    217     """    
    218     propertyDefaults = { 
    219         'sessionKey': 'beaker.session.ndg.security' 
    220     } 
    221     
    222     _isAuthenticated = lambda self: \ 
    223         SessionMiddlewareBase.USERNAME_SESSION_KEYNAME in \ 
    224         self.environ.get(self.sessionKey, ()) 
    225          
    226     isAuthenticated = property(fget=_isAuthenticated, 
    227                                doc='boolean to indicate is user logged in') 
     184from authkit.permissions import UserIn 
     185             
     186class HTTPBasicAuthentication(object): 
     187    '''Authkit based HTTP Basic Authentication.   __call__ defines a  
     188    validation function to fit with the pattern for the AuthKit interface 
     189    ''' 
     190     
     191    def __init__(self): 
     192        self._userIn = UserIn([]) 
     193         
     194    def __call__(self, environ, username, password): 
     195        """AuthKit HTTP Basic Auth validation function - return True/False""" 
     196        raise NotImplementedError() 
    228197 
    229198         
     
    253222    propertyDefaults.update(AuthnRedirectMiddleware.propertyDefaults) 
    254223     
    255  
    256224    TRIGGER_HTTP_STATUS_CODE = '401' 
    257225    MIDDLEWARE_ID = 'AuthnRedirectInitiatorMiddleware' 
     
    269237        dictionary 
    270238        ''' 
    271         self._redirectURI = None 
     239        self.__redirectURI = None 
    272240        super(AuthnRedirectInitiatorMiddleware, self).__init__(app,  
    273241                                                               global_conf,  
    274242                                                               **app_conf) 
    275          
     243 
    276244    @NDGSecurityMiddlewareBase.initCall 
    277245    def __call__(self, environ, start_response): 
     
    292260            raise TypeError("Redirect URI must be set to string type")    
    293261          
    294         self._redirectURI = uri 
     262        self.__redirectURI = uri 
    295263         
    296264    def _getRedirectURI(self): 
    297         return self._redirectURI 
     265        return self.__redirectURI 
    298266     
    299267    redirectURI = property(fget=_getRedirectURI, 
    300                        fset=_setRedirectURI, 
    301                        doc="URI to redirect to if user is not authenticated") 
     268                           fset=_setRedirectURI, 
     269                           doc="URI to redirect to if user is not " 
     270                               "authenticated") 
    302271 
    303272    def _setRedirectResponse(self): 
     
    404373        return super(AuthKitRedirectResponseMiddleware, self).__call__(environ, 
    405374                                                                start_response) 
    406         
    407  
    408 class SessionHandlerMiddlewareError(AuthnException): 
    409     """Base exception for SessionHandlerMiddleware""" 
    410              
    411 class SessionHandlerMiddlewareConfigError(SessionHandlerMiddlewareError): 
    412     """Configuration errors from SessionHandlerMiddleware""" 
    413      
    414 class OpenIdAXConfigError(SessionHandlerMiddlewareError): 
    415     """Error parsing OpenID Ax (Attribute Exchange) parameters""" 
    416      
    417      
    418 class SessionHandlerMiddleware(SessionMiddlewareBase): 
    419     '''Middleware to: 
    420     - establish user session details following redirect from OpenID Relying  
    421     Party sign-in or SSL Client authentication 
    422     - end session redirecting back to referrer URI following call to a logout 
    423     URI as implemented in AuthKit 
    424     ''' 
    425     AX_SESSION_KEYNAME = 'openid.ax' 
    426     SM_URI_SESSION_KEYNAME = 'sessionManagerURI' 
    427     ID_SESSION_KEYNAME = 'sessionId' 
    428     PEP_CTX_SESSION_KEYNAME = 'pepCtx' 
    429     CREDENTIAL_WALLET_SESSION_KEYNAME = 'credentialWallet' 
    430      
    431     SESSION_KEYNAMES = ( 
    432         SessionMiddlewareBase.USERNAME_SESSION_KEYNAME,  
    433         SM_URI_SESSION_KEYNAME,  
    434         ID_SESSION_KEYNAME,  
    435         PEP_CTX_SESSION_KEYNAME,  
    436         CREDENTIAL_WALLET_SESSION_KEYNAME 
    437     ) 
    438      
    439     AX_KEYNAME = 'ax' 
    440     SM_URI_AX_KEYNAME = 'value.sessionManagerURI.1' 
    441     SESSION_ID_AX_KEYNAME = 'value.sessionId.1' 
    442      
    443     AUTHKIT_COOKIE_SIGNOUT_PARAMNAME = 'authkit.cookie.signoutpath' 
    444     SIGNOUT_PATH_PARAMNAME = 'signoutPath' 
    445     SESSION_KEY_PARAMNAME = 'sessionKey' 
    446     propertyDefaults = { 
    447         SIGNOUT_PATH_PARAMNAME: None, 
    448         SESSION_KEY_PARAMNAME: 'beaker.session.ndg.security' 
    449     } 
    450      
    451     AUTH_TKT_SET_USER_ENVIRON_KEYNAME = 'paste.auth_tkt.set_user' 
    452      
    453     PARAM_PREFIX = 'sessionHandler.' 
    454      
    455     def __init__(self, app, global_conf, prefix=PARAM_PREFIX, **app_conf): 
    456         ''' 
    457         @type app: callable following WSGI interface 
    458         @param app: next middleware application in the chain       
    459         @type global_conf: dict         
    460         @param global_conf: PasteDeploy global configuration dictionary 
    461         @type prefix: basestring 
    462         @param prefix: prefix for configuration items 
    463         @type app_conf: dict         
    464         @param app_conf: PasteDeploy application specific configuration  
    465         dictionary 
    466         ''' 
    467         signoutPathParamName = prefix + \ 
    468                                 SessionHandlerMiddleware.SIGNOUT_PATH_PARAMNAME 
    469          
    470         if signoutPathParamName not in app_conf: 
    471             authKitSignOutPath = app_conf.get( 
    472                     SessionHandlerMiddleware.AUTHKIT_COOKIE_SIGNOUT_PARAMNAME) 
    473              
    474             if authKitSignOutPath: 
    475                 app_conf[signoutPathParamName] = authKitSignOutPath 
    476                  
    477                 log.info('Set signoutPath=%s from "%s" setting',  
    478                      authKitSignOutPath, 
    479                      SessionHandlerMiddleware.AUTHKIT_COOKIE_SIGNOUT_PARAMNAME) 
    480             else: 
    481                 raise SessionHandlerMiddlewareConfigError( 
    482                                         '"signoutPath" parameter is not set') 
    483              
    484         super(SessionHandlerMiddleware, self).__init__(app, 
    485                                                        global_conf, 
    486                                                        prefix=prefix,  
    487                                                        **app_conf) 
    488          
    489     @NDGSecurityMiddlewareBase.initCall 
    490     def __call__(self, environ, start_response): 
    491         """Manage setting of session from AuthKit following OpenID Relying 
    492         Party sign in and manage logout 
    493          
    494         @type environ: dict 
    495         @param environ: WSGI environment variables dictionary 
    496         @type start_response: function 
    497         @param start_response: standard WSGI start response function 
    498         """ 
    499         log.debug("SessionHandlerMiddleware.__call__ ...") 
    500          
    501         session = environ.get(self.sessionKey) 
    502         if session is None: 
    503             raise SessionHandlerMiddlewareConfigError( 
    504                    'SessionHandlerMiddleware.__call__: No beaker session key ' 
    505                    '"%s" found in environ' % self.sessionKey) 
    506          
    507         if self.signoutPath and self.pathInfo == self.signoutPath: 
    508             log.debug("SessionHandlerMiddleware.__call__: caught sign out " 
    509                       "path [%s]", self.signoutPath) 
    510              
    511             referrer = environ.get('HTTP_REFERER') 
    512             if referrer is not None: 
    513                 def _start_response(status, header, exc_info=None): 
    514                     """Alter the header to send a redirect to the logout 
    515                     referrer address""" 
    516                     filteredHeader = [(field, val) for field, val in header  
    517                                       if field.lower() != 'location']         
    518                     filteredHeader.extend([('Location', referrer)]) 
    519                     return start_response(self.getStatusMessage(302),  
    520                                           filteredHeader, 
    521                                           exc_info) 
    522                      
    523             else: 
    524                 log.error('No referrer set for redirect following logout') 
    525                 _start_response = start_response 
    526                  
    527             # Clear user details from beaker session 
    528             for keyName in self.__class__.SESSION_KEYNAMES: 
    529                 session.pop(keyName, None) 
    530             session.save() 
    531         else: 
    532             log.debug("SessionHandlerMiddleware.__call__: checking for " 
    533                       "REMOTE_* environment variable settings set by OpenID " 
    534                       "Relying Party signin...") 
    535              
    536             if SessionHandlerMiddleware.USERNAME_SESSION_KEYNAME not in session\ 
    537                and SessionHandlerMiddleware.USERNAME_ENVIRON_KEYNAME in environ: 
    538                 log.debug("SessionHandlerMiddleware.__call__: updating session " 
    539                           "username=%s", environ[ 
    540                             SessionHandlerMiddleware.USERNAME_ENVIRON_KEYNAME]) 
    541                  
    542                 session[SessionHandlerMiddleware.USERNAME_SESSION_KEYNAME 
    543                         ] = environ[ 
    544                             SessionHandlerMiddleware.USERNAME_ENVIRON_KEYNAME] 
    545                 session.save() 
    546                  
    547             remoteUserData = environ.get( 
    548                         SessionHandlerMiddleware.USERDATA_ENVIRON_KEYNAME, '')     
    549             if remoteUserData: 
    550                 log.debug("SessionHandlerMiddleware.__call__: found " 
    551                           "REMOTE_USER_DATA=%s, set from OpenID Relying Party " 
    552                           "signin",  
    553                           environ[ 
    554                               SessionHandlerMiddleware.USERDATA_ENVIRON_KEYNAME 
    555                           ]) 
    556                  
    557                 # eval is safe here because AuthKit cookie is signed and  
    558                 # AuthKit middleware checks for tampering 
    559                 if (SessionHandlerMiddleware.SM_URI_SESSION_KEYNAME not in  
    560                     session or  
    561                     SessionHandlerMiddleware.ID_SESSION_KEYNAME not in session): 
    562                      
    563                     axData = eval(remoteUserData) 
    564                     if (isinstance(axData, dict) and  
    565                         SessionHandlerMiddleware.AX_KEYNAME in axData): 
    566                          
    567                         ax = axData[SessionHandlerMiddleware.AX_KEYNAME] 
    568                          
    569                         # Save attributes keyed by attribute name 
    570                         session[ 
    571                             SessionHandlerMiddleware.AX_SESSION_KEYNAME 
    572                         ] = SessionHandlerMiddleware._parseOpenIdAX(ax) 
    573                          
    574                         log.debug("SessionHandlerMiddleware.__call__: updated " 
    575                                   "session with OpenID AX values: %r" %  
    576                                   session[ 
    577                                     SessionHandlerMiddleware.AX_SESSION_KEYNAME 
    578                                   ]) 
    579                              
    580                         # Save Session Manager specific attributes 
    581                         sessionManagerURI = ax.get( 
    582                                 SessionHandlerMiddleware.SM_URI_AX_KEYNAME) 
    583                              
    584                         session[SessionHandlerMiddleware.SM_URI_SESSION_KEYNAME 
    585                                 ] = sessionManagerURI 
    586      
    587                         sessionId = ax.get( 
    588                                 SessionHandlerMiddleware.SESSION_ID_AX_KEYNAME) 
    589                         session[SessionHandlerMiddleware.ID_SESSION_KEYNAME 
    590                                 ] = sessionId 
    591                                  
    592                         session.save() 
    593                          
    594                         log.debug("SessionHandlerMiddleware.__call__: updated " 
    595                                   "session " 
    596                                   "with sessionManagerURI=%s and " 
    597                                   "sessionId=%s",  
    598                                   sessionManagerURI,  
    599                                   sessionId) 
    600                      
    601                 # Reset cookie removing user data 
    602                 setUser = environ[ 
    603                     SessionHandlerMiddleware.AUTH_TKT_SET_USER_ENVIRON_KEYNAME] 
    604                 setUser( 
    605                     session[SessionHandlerMiddleware.USERNAME_SESSION_KEYNAME]) 
    606             else: 
    607                 log.debug("SessionHandlerMiddleware.__call__: REMOTE_USER_DATA " 
    608                           "is not set") 
    609  
    610             _start_response = start_response 
    611              
    612         return self._app(environ, _start_response) 
    613      
    614     @staticmethod                     
    615     def _parseOpenIdAX(ax): 
    616         """Return a dictionary of attribute exchange attributes parsed from the  
    617         OpenID Provider response set in the REMOTE_USER_DATA AuthKit environ 
    618         key 
    619          
    620         @param ax: dictionary of AX parameters - format of keys is e.g. 
    621         count.paramName, value.paramName.<n>, type.paramName 
    622         @type ax: dict 
    623         @return: dictionary of parameters keyed by parameter with values for 
    624         each parameter a tuple of count.paramName values 
    625         @rtype: dict 
    626         """ 
    627          
    628         # Copy Attributes into session 
    629         outputKeys = [k.replace('type.', '') for k in ax.keys() 
    630                       if k.startswith('type.')] 
    631          
    632         output = {} 
    633         for outputKey in outputKeys: 
    634             axCountKeyName = 'count.' + outputKey 
    635             axCount = int(ax[axCountKeyName]) 
    636              
    637             axValueKeyPrefix = 'value.%s.' % outputKey 
    638             output[outputKey] = tuple([v for k, v in ax.items()  
    639                                        if k.startswith(axValueKeyPrefix)]) 
    640              
    641             nVals = len(output[outputKey]) 
    642             if nVals != axCount: 
    643                 raise OpenIdAXConfigError('Got %d parameters for AX attribute ' 
    644                                           '"%s"; but "%s" AX key is set to %d' 
    645                                           % (nVals, 
    646                                              axCountKeyName, 
    647                                              axCountKeyName, 
    648                                              axCount)) 
    649                                               
    650         return output 
    651  
    652  
    653 from authkit.authenticate.multi import MultiHandler 
     375 
    654376 
    655377class AuthenticationMiddlewareConfigError(NDGSecurityMiddlewareConfigError): 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/wsgi/authz/__init__.py

    r6263 r6264  
    3131                                            SessionHandlerMiddleware) 
    3232 
     33from ndg.security.server.wsgi.authz.result_handler.basic import \ 
     34    PEPResultHandlerMiddleware 
     35     
    3336from ndg.security.common.authz.msi import (Policy, PIP, PIPBase,  
    3437                                           PIPAttributeQuery,  
    3538                                           PIPAttributeResponse, PDP, Request,  
    3639                                           Response, Resource, Subject) 
    37  
    38  
    39 class PEPResultHandlerMiddleware(SessionMiddlewareBase): 
    40     """This middleware is invoked if access is denied to a given resource.  It 
    41     is incorporated into the call stack by passing it in to a MultiHandler  
    42     instance.  The MultiHandler is configured in the AuthorizationMiddlewareBase  
    43     class below.  The MultiHandler is passed a checker method which determines 
    44     whether to allow access, or call this interface.   The checker is 
    45     implemented in the AuthorizationHandler.  See below ... 
    46      
    47     This class can be overridden to define custom behaviour for the access 
    48     denied response e.g. include an interface to enable users to register for 
    49     the dataset from which they have been denied access.  See  
    50     AuthorizationMiddlewareBase pepResultHandler keyword. 
    51      
    52     SessionMiddlewareBase base class defines user session key and  
    53     isAuthenticated property 
    54     """ 
    55      
    56     def __init__(self, app, global_conf, prefix='', **app_conf): 
    57         ''' 
    58         @type app: callable following WSGI interface 
    59         @param app: next middleware application in the chain       
    60         @type global_conf: dict         
    61         @param global_conf: PasteDeploy global configuration dictionary 
    62         @type prefix: basestring 
    63         @param prefix: prefix for configuration items 
    64         @type app_conf: dict         
    65         @param app_conf: PasteDeploy application specific configuration  
    66         dictionary 
    67         ''' 
    68         super(PEPResultHandlerMiddleware, self).__init__(app, 
    69                                                          global_conf, 
    70                                                          prefix=prefix, 
    71                                                          **app_conf) 
    72                 
    73     @NDGSecurityMiddlewareBase.initCall 
    74     def __call__(self, environ, start_response): 
    75          
    76         log.debug("PEPResultHandlerMiddleware.__call__ ...") 
    77          
    78         self.session = self.environ.get(self.sessionKey) 
    79         if not self.isAuthenticated: 
    80             # This check is included as a precaution: this condition should be 
    81             # caught be the AuthNRedirectHandlerMiddleware or PEPFilter 
    82             log.warning("PEPResultHandlerMiddleware: user is not " 
    83                         "authenticated - setting HTTP 401 response") 
    84             return self._setErrorResponse(code=UNAUTHORIZED) 
    85         else: 
    86             # Get response message from PDP recorded by PEP 
    87             pepCtx = self.session.get('pepCtx', {}) 
    88             pdpResponse = pepCtx.get('response') 
    89             msg = getattr(pdpResponse, 'message', '') 
    90                  
    91             response = ("Access is forbidden for this resource:%s" 
    92                         "Please check with your site administrator that you " 
    93                         "have the required access privileges." %  
    94                         msg.join(('\n\n',)*2)) 
    95  
    96             return self._setErrorResponse(code=FORBIDDEN, msg=response) 
    9740 
    9841 
     
    11760    MIDDLEWARE_ID = 'PEPFilter' 
    11861    POLICY_PARAM_PREFIX = 'policy.' 
     62     
    11963    SESSION_KEYNAME = 'sessionKey' 
     64 
     65    # Key names for PEP context information 
     66    PEPCTX_SESSION_KEYNAME = 'pepCtx' 
     67    PEPCTX_REQUEST_KEYNAME = 'request' 
     68    PEPCTX_RESPONSE_KEYNAME = 'response' 
     69    PEPCTX_TIMESTAMP_KEYNAME = 'timestamp' 
    12070    POLICY_FILEPATH_PARAMNAME = 'filePath' 
    12171     
     
    220170        # Record the result in the user's session to enable later  
    221171        # interrogation by the AuthZResultHandlerMiddleware 
    222         session['pepCtx'] = {'request': request, 'response': response, 
    223                              'timestamp': time()} 
    224         session.save() 
    225172         
    226173        if response.status == Response.DECISION_PERMIT: 
     
    244191            return self._setErrorResponse(code=triggerStatusCode) 
    245192 
     193    @classmethod 
     194    def setSession(cls, session, save=True): 
     195        session[cls.PEPCTX_SESSION_KEYNAME] = { 
     196            cls.PEPCTX_REQUEST_KEYNAME: request,  
     197            cls.PEPCTX_RESPONSE_KEYNAME: response, 
     198            cls.PEPCTX_TIMESTAMP_KEYNAME: time() 
     199        } 
     200         
     201        if save: 
     202            session.save() 
     203         
    246204    def _getMatchingTargets(self, resourceURI): 
    247205        """This method may only be called following __call__ as __call__ 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_test/ndg/security/test/unit/wsgi/authz/test_authz.py

    r6069 r6264  
    107107        self.startSiteAAttributeAuthority() 
    108108         
    109          
    110  
    111109    def test01CatchNoBeakerSessionFound(self): 
    112110         
     
    215213        print response 
    216214 
    217  
    218  
    219215         
    220216class TestAuthZMiddleware(object): 
Note: See TracChangeset for help on using the changeset viewer.