Changeset 7287 for TI12-security/trunk


Ignore:
Timestamp:
06/08/10 09:49:47 (9 years ago)
Author:
pjkersha
Message:

Incomplete - task 2: XACML-Security Integration

  • Working WSGI Authorisation filter with connection to SAML/XACML based Authorisation Service - unit tests: ndg.security.test.unit.wsgi.authz.test_authz
  • It may need some optimisation to avoid too many WS callouts to the Authorisation Service - perhaps add a local PDP to the authorisation filter to filter out some requests going over the wire e.g. requests for web page CSS or graphics content.
  • The XACML policy file has some big additions to it to support the various test conditions in ndg.security.test.unit.wsgi.authz.test_authz. These should be ported back to the ndg_xacml package unit tests.
  • Next major task: remove temp fix in XACML Context handler - instead of using hardwired roles for the user alter it so that the PDP makes a request back to the PIP (Policy Enforcement Point) to grab additional attributes. The PIP will call to Attibute Service(s) to pull any additional attributes needed/
Location:
TI12-security/trunk/NDGSecurity/python
Files:
7 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/NDGSecurity/python/ndg_security_common/ndg/security/common/saml_utils/esg/xml/etree.py

    r7076 r7287  
    1111import logging 
    1212log = logging.getLogger(__name__) 
    13 import re 
    1413 
    1514from xml.etree import ElementTree 
    1615 
    1716from ndg.saml.xml import XMLTypeParseError, UnknownAttrProfile 
    18 from ndg.saml.xml.etree import (AttributeValueElementTreeBase, ResponseElementTree, 
    19                             QName) 
     17from ndg.saml.xml.etree import (AttributeValueElementTreeBase,  
     18                                ResponseElementTree, 
     19                                QName) 
    2020 
    2121from ndg.security.common.saml_utils.esg import XSGroupRoleAttributeValue 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/wsgi/authz/__init__.py

    r7155 r7287  
    1515from time import time 
    1616from urlparse import urlunsplit 
    17 from httplib import UNAUTHORIZED, FORBIDDEN 
     17import httplib 
    1818 
    1919from paste.cascade import Cascade 
     
    2222 
    2323from ndg.security.common.utils.classfactory import importClass 
    24 from ndg.security.common.X509 import X509Cert 
    25 from ndg.saml.saml2.binding.soap.client.attributequery import \ 
    26                                                 AttributeQuerySslSOAPBinding 
    27  
    2824from ndg.security.common.credentialwallet import SAMLCredentialWallet 
    29 from ndg.security.server.wsgi import (NDGSecurityMiddlewareBase,  
    30                                       NDGSecurityMiddlewareConfigError) 
    31  
    32 from ndg.security.server.wsgi import (NDGSecurityMiddlewareBase,  
    33                                       NDGSecurityMiddlewareConfigError) 
    34 from ndg.security.server.wsgi.session import (SessionMiddlewareBase,  
    35                                               SessionHandlerMiddleware) 
     25from ndg.security.server.wsgi import NDGSecurityMiddlewareBase 
     26from ndg.security.server.wsgi.authz.pep import SamlPepFilter 
    3627from ndg.security.server.wsgi.authz.result_handler import \ 
    3728    PEPResultHandlerMiddlewareBase 
    3829from ndg.security.server.wsgi.authz.result_handler.basic import \ 
    3930    PEPResultHandlerMiddleware 
    40      
    41 from ndg.security.common.authz.pip import (PIPBase, PIPAttributeQuery, 
    42                                            PIPAttributeResponse) 
    43  
    44 from ndg.security.common.authz import Subject 
    45 from ndg.security.common.authz.msi import (Policy, PDP, Request, Response,  
    46                                            Resource) 
    4731 
    4832 
    49 class PEPFilterError(Exception): 
    50     """Base class for PEPFilter exception types""" 
     33class Http403ForbiddenStatusHandler(object): 
     34    """Handler to catch HTTP 403 Forbidden responses.  It integrates with 
     35    AuthKit's MultiHandler.  This enables the given middleware to be substituted 
     36    into the WSGI stack should a 403 status be detected set from upstream 
     37    middleware. 
     38         
     39    @cvar TRIGGER_HTTP_STATUS_CODE: status code to catch - HTTP 403 Forbidden 
     40    @type TRIGGER_HTTP_STATUS_CODE: basestring 
     41    """ 
     42    TRIGGER_HTTP_STATUS_CODE = str(httplib.FORBIDDEN) 
    5143     
    52      
    53 class PEPFilterConfigError(PEPFilterError): 
    54     """Configuration related error for PEPFilter""" 
    55  
    56  
    57 class PEPFilter(SessionMiddlewareBase): 
    58     """PEP (Policy Enforcement Point) WSGI Middleware.  The PEP enforces 
    59     access control decisions made by the PDP (Policy Decision Point).  In  
    60     this case, it follows the WSG middleware filter pattern and is configured 
    61     in a pipeline upstream of the application(s) which it protects.  if an  
    62     access denied decision is made, the PEP enforces this by returning a  
    63     403 Forbidden HTTP response without the application middleware executing 
    64      
    65     SessionMiddlewareBase base class defines user session key and  
    66     isAuthenticated property 
    67     """ 
    68     TRIGGER_HTTP_STATUS_CODE = str(FORBIDDEN) 
    69     MIDDLEWARE_ID = 'PEPFilter' 
    70     POLICY_PARAM_PREFIX = 'policy.' 
    71      
    72     SESSION_KEYNAME = 'sessionKey' 
    73     POLICY_FILEPATH_PARAMNAME = 'filePath' 
    74      
    75     def __init__(self, app, global_conf, prefix='', **local_conf): 
    76         """Initialise the PIP (Policy Information Point) and PDP (Policy  
    77         Decision Point).  The PDP makes access control decisions based on 
    78         a given policy.  The PIP manages the retrieval of user credentials on  
    79         behalf of the PDP 
    80          
    81         @type app: callable following WSGI interface 
    82         @param app: next middleware application in the chain       
    83         @type global_conf: dict         
    84         @param global_conf: PasteDeploy global configuration dictionary 
    85         @type prefix: basestring 
    86         @param prefix: prefix for configuration items 
    87         @type local_conf: dict         
    88         @param local_conf: PasteDeploy application specific configuration  
    89         dictionary 
    90          
    91         """        
    92         # Initialise the PDP reading in the policy 
    93         policyCfg = PEPFilter._filterKeywords(local_conf,  
    94                                               PEPFilter.POLICY_PARAM_PREFIX) 
    95         self.policyFilePath = policyCfg[PEPFilter.POLICY_FILEPATH_PARAMNAME] 
    96         policy = Policy.Parse(policyCfg[PEPFilter.POLICY_FILEPATH_PARAMNAME]) 
    97          
    98         # Initialise the Policy Information Point to None.  This object is 
    99         # created and set later.  See AuthorizationMiddlewareBase. 
    100         self.pdp = PDP(policy, None) 
    101          
    102         self.sessionKey = local_conf.get(PEPFilter.SESSION_KEYNAME,  
    103                                          PEPFilter.propertyDefaults[ 
    104                                                     PEPFilter.SESSION_KEYNAME]) 
    105          
    106         super(PEPFilter, self).__init__(app, 
    107                                         global_conf, 
    108                                         prefix=prefix, 
    109                                         **local_conf) 
    110  
    111     @NDGSecurityMiddlewareBase.initCall 
    112     def __call__(self, environ, start_response): 
    113         """ 
    114         @type environ: dict 
    115         @param environ: WSGI environment variables dictionary 
    116         @type start_response: function 
    117         @param start_response: standard WSGI start response function 
    118         @rtype: iterable 
    119         @return: response 
    120         """ 
    121         session = environ.get(self.sessionKey) 
    122         if session is None: 
    123             raise PEPFilterConfigError('No beaker session key "%s" found in ' 
    124                                        'environ' % self.sessionKey) 
    125              
    126         queryString = environ.get('QUERY_STRING', '') 
    127         resourceURI = urlunsplit(('', '', self.pathInfo, queryString, '')) 
    128          
    129         # Check for a secured resource 
    130         matchingTargets = self._getMatchingTargets(resourceURI) 
    131         targetMatch = len(matchingTargets) > 0 
    132         if not targetMatch: 
    133             log.debug("PEPFilter.__call__: granting access - no matching URI " 
    134                       "path target was found in the policy for URI path [%s]",  
    135                       resourceURI) 
    136             return self._app(environ, start_response) 
    137  
    138         log.debug("PEPFilter.__call__: found matching target(s):\n\n %s\n" 
    139                   "\nfrom policy file [%s] for URI Path=[%s]\n", 
    140                   '\n'.join(["RegEx=%s" % t for t in matchingTargets]),  
    141                   self.policyFilePath, 
    142                   resourceURI) 
    143          
    144         if not self.isAuthenticated: 
    145             log.info("PEPFilter.__call__: user is not authenticated - setting " 
    146                      "HTTP 401 response ...") 
    147              
    148             # Set a 401 response for an authentication handler to capture 
    149             return self._setErrorResponse(code=UNAUTHORIZED) 
    150          
    151         log.debug("PEPFilter.__call__: creating request to call PDP to check " 
    152                   "user authorisation ...") 
    153          
    154         # Make a request object to pass to the PDP.   
    155         request = Request() 
    156         request.subject[Subject.USERID_NS] = session['username'] 
    157         request.resource[Resource.URI_NS] = resourceURI 
    158  
    159          
    160         # Call the PDP 
    161         response = self.pdp.evaluate(request)         
    162          
    163         # Record the result in the user's session to enable later  
    164         # interrogation by the AuthZResultHandlerMiddleware 
    165         PEPFilter.setSession(session, request, response) 
    166          
    167         if response.status == Response.DECISION_PERMIT: 
    168             log.info("PEPFilter.__call__: PDP granted access for URI path " 
    169                      "[%s] using policy [%s]",  
    170                      resourceURI,  
    171                      self.policyFilePath) 
    172              
    173             return self._app(environ, start_response) 
    174         else: 
    175             log.info("PEPFilter.__call__: PDP returned a status of [%s] " 
    176                      "denying access for URI path [%s] using policy [%s]",  
    177                      response.decisionValue2String[response.status], 
    178                      resourceURI, 
    179                      self.policyFilePath)  
    180              
    181             # Trigger AuthZResultHandlerMiddleware by setting a response  
    182             # with HTTP status code equal to the TRIGGER_HTTP_STATUS_CODE class 
    183             # attribute value 
    184             triggerStatusCode = int(PEPFilter.TRIGGER_HTTP_STATUS_CODE) 
    185             return self._setErrorResponse(code=triggerStatusCode) 
    186  
    18744    @classmethod 
    188     def setSession(cls, session, request, response, save=True): 
    189         """Set PEP context information in the Beaker session using standard key 
    190         names 
    191          
    192         @param session: beaker session 
    193         @type session: beaker.session.SessionObject 
    194         @param request: authorisation request 
    195         @type request: ndg.security.common.authz.msi.Request 
    196         @param response: authorisation response 
    197         @type response: ndg.security.common.authz.msi.Response 
    198         @param save: determines whether session is saved or not 
    199         @type save: bool 
    200         """ 
    201         session[cls.PEPCTX_SESSION_KEYNAME] = { 
    202             cls.PEPCTX_REQUEST_SESSION_KEYNAME: request,  
    203             cls.PEPCTX_RESPONSE_SESSION_KEYNAME: response, 
    204             cls.PEPCTX_TIMESTAMP_SESSION_KEYNAME: time() 
    205         } 
    206          
    207         if save: 
    208             session.save() 
    209          
    210     def _getMatchingTargets(self, resourceURI): 
    211         """This method may only be called following __call__ as __call__ 
    212         updates the pathInfo property 
    213          
    214         @type resourceURI: basestring 
    215         @param resourceURI: the URI of the requested resource 
    216         @rtype: list 
    217         @return: return list of policy target objects matching the current  
    218         path  
    219         """ 
    220         matchingTargets = [target for target in self.pdp.policy.targets  
    221                            if target.regEx.match(resourceURI) is not None] 
    222         return matchingTargets 
    223  
    224     def multiHandlerInterceptFactory(self): 
    225         """Return a checker function for use with AuthKit's MultiHandler. 
    226         MultiHandler can be used to catch HTTP 403 Forbidden responses set by 
    227         an application and call middleware (AuthZResultMiddleware) to handle 
    228         the access denied message. 
    229         """ 
    230          
    231         def multiHandlerIntercept(environ, status, headers): 
    232             """AuthKit MultiHandler checker function to intercept  
    233             unauthorised response status codes from applications to be  
    234             protected.  This function's definition is embedded into a 
    235             factory method so that this function has visibility to the  
    236             PEPFilter object's attributes if required. 
    237              
    238             @type environ: dict 
    239             @param environ: WSGI environment dictionary 
    240             @type status: basestring 
    241             @param status: HTTP response code set by application middleware 
    242             that this intercept function is to protect 
    243             @type headers: list 
    244             @param headers: HTTP response header content""" 
    245              
    246             if status.startswith(PEPFilter.TRIGGER_HTTP_STATUS_CODE): 
    247                 log.debug("PEPFilter: found [%s] status for URI path [%s]: " 
    248                           "invoking access denied response", 
    249                           PEPFilter.TRIGGER_HTTP_STATUS_CODE, 
    250                           environ['PATH_INFO']) 
    251                 return True 
    252             else: 
    253                 # No match - it's publicly accessible 
    254                 log.debug("PEPFilter: the return status [%s] for this URI " 
    255                           "path [%s] didn't match the trigger status [%s]", 
    256                           status, 
    257                           environ['PATH_INFO'], 
    258                           PEPFilter.TRIGGER_HTTP_STATUS_CODE) 
    259                 return False 
    260          
    261         return multiHandlerIntercept 
    262          
    263     @staticmethod 
    264     def _filterKeywords(conf, prefix): 
    265         filteredConf = {} 
    266         prefixLen = len(prefix) 
    267         for k, v in conf.items(): 
    268             if k.startswith(prefix): 
    269                 filteredConf[k[prefixLen:]] = conf.pop(k) 
    270                  
    271         return filteredConf 
    272  
    273     def _getPDP(self): 
    274         if self._pdp is None: 
    275             raise TypeError("PDP object has not been initialised") 
    276         return self._pdp 
    277      
    278     def _setPDP(self, pdp): 
    279         if not isinstance(pdp, (PDP, None.__class__)): 
    280             raise TypeError("Expecting %s or None type for pdp; got %r" % 
    281                             (PDP.__class__.__name__, pdp)) 
    282         self._pdp = pdp 
    283  
    284     pdp = property(fget=_getPDP, 
    285                    fset=_setPDP, 
    286                    doc="Policy Decision Point object makes access control " 
    287                        "decisions on behalf of the PEP") 
    288  
    289     
    290 class SamlPIPMiddlewareError(Exception): 
    291     """Base class for SAML based Policy Information Point WSGI middleware  
    292     exception types 
    293     """ 
    294  
    295    
    296 class SamlPIPMiddlewareConfigError(SamlPIPMiddlewareError): 
    297     """Configuration related error for SAML Policy Information Point WSGI  
    298     middleware 
    299     """ 
    300      
    301  
    302 class SamlPIPMiddleware(PIPBase, NDGSecurityMiddlewareBase): 
    303     '''Extend Policy Information Point to enable caching of SAML credentials in 
    304     a SAMLCredentialWallet object held in beaker.session 
    305     ''' 
    306     ENVIRON_KEYNAME = 'ndg.security.server.wsgi.authz.SamlPIPMiddleware' 
    307         
    308     propertyDefaults = { 
    309         'sessionKey': 'beaker.session.ndg.security', 
    310     } 
    311     propertyDefaults.update(NDGSecurityMiddlewareBase.propertyDefaults) 
    312    
    313     CREDENTIAL_WALLET_SESSION_KEYNAME = \ 
    314         SessionHandlerMiddleware.CREDENTIAL_WALLET_SESSION_KEYNAME 
    315     USERNAME_SESSION_KEYNAME = \ 
    316         SessionHandlerMiddleware.USERNAME_SESSION_KEYNAME 
    317           
    318     ATTRIBUTE_QUERY_ATTRNAME = 'attributeQuery' 
    319     LEN_ATTRIBUTE_QUERY_ATTRNAME = len(ATTRIBUTE_QUERY_ATTRNAME) 
    320            
    321     def __init__(self, app, global_conf, prefix='', **local_conf): 
    322         ''' 
    323         @type app: callable following WSGI interface 
    324         @param app: next middleware application in the chain       
    325         @type global_conf: dict         
    326         @param global_conf: PasteDeploy global configuration dictionary 
    327         @type prefix: basestring 
    328         @param prefix: prefix for configuration items 
    329         @type local_conf: dict         
    330         @param local_conf: PasteDeploy application specific configuration  
    331         dictionary 
    332         ''' 
    333         self.session = None 
    334         self.__attributeQueryBinding = AttributeQuerySslSOAPBinding() 
    335          
    336         nameOffset = len(prefix) 
    337         for k in local_conf.keys(): 
    338             if k.startswith(prefix): 
    339                 val = local_conf.pop(k) 
    340                 name = k[nameOffset:] 
    341                 setattr(self, name, val) 
    342                  
    343         if not self.__attributeQueryBinding.issuerName: 
    344             issuerX509Cert = X509Cert.Read( 
    345                     self.__attributeQueryBinding.sslCtxProxy.sslCertFilePath) 
    346             self.__attributeQueryBinding.issuerName = str(issuerX509Cert.dn) 
    347                  
    348         NDGSecurityMiddlewareBase.__init__(self, app, {}) 
    349              
    350     def __setattr__(self, name, value): 
    351         """Enable setting of AttributeQuerySslSOAPBinding attributes from 
    352         names starting with attributeQuery.* / attributeQuery_*.  Addition for 
    353         setting these values from ini file 
    354         """ 
    355  
    356         # Coerce into setting AttributeQuerySslSOAPBinding attributes -  
    357         # names must start with 'attributeQuery\W' e.g. 
    358         # attributeQuery.clockSkew or attributeQuery_issuerDN 
    359         if name.startswith(SamlPIPMiddleware.ATTRIBUTE_QUERY_ATTRNAME): 
    360             setattr(self.__attributeQueryBinding,  
    361                     name[SamlPIPMiddleware.LEN_ATTRIBUTE_QUERY_ATTRNAME+1:],  
    362                     value) 
    363         else: 
    364             super(SamlPIPMiddleware, self).__setattr__(name, value)     
    365  
    366     @property 
    367     def attributeQueryBinding(self): 
    368         """SAML SOAP Attribute Query client binding object""" 
    369         return self.__attributeQueryBinding 
    370                  
    371     def __call__(self, environ, start_response): 
    372         """Take a copy of the session object so that it is in scope for 
    373         attributeQuery call and add this instance to the environ 
    374         so that the PEPFilter can retrieve it and pass on to the PDP 
     45    def intercept(cls, environ, status, headers): 
     46        """Checker function for AuthKit Multihandler 
    37547         
    37648        @type environ: dict 
    377         @param environ: WSGI environment variables dictionary 
    378         @type start_response: function 
    379         @param start_response: standard WSGI start response function 
    380         @rtype: iterable 
    381         @return: response 
    382         """ 
    383         self.session = environ.get(self.sessionKey) 
    384         if self.session is None: 
    385             raise SamlPIPMiddlewareConfigError('No beaker session key "%s" ' 
    386                                                'found in environ' %  
    387                                                self.sessionKey) 
    388         environ[SamlPIPMiddleware.ENVIRON_KEYNAME] = self 
     49        @param environ: WSGI environment dictionary 
     50        @type status: basestring 
     51        @param status: HTTP response code set by application middleware 
     52        that this intercept function is to protect 
     53        @type headers: list 
     54        @param headers: HTTP response header content""" 
    38955         
    390         return self._app(environ, start_response) 
     56        if status.startswith(cls.TRIGGER_HTTP_STATUS_CODE): 
     57            log.debug("Found [%s] status for URI path [%s]: invoking access " 
     58                      "denied response", 
     59                      cls.TRIGGER_HTTP_STATUS_CODE, 
     60                      environ['PATH_INFO']) 
     61            return True 
     62        else: 
     63            # No match - it's publicly accessible 
     64            log.debug("The return status [%s] for this URI path [%s] didn't " 
     65                      "match the trigger status [%s]", 
     66                      status, 
     67                      environ['PATH_INFO'], 
     68                      cls.TRIGGER_HTTP_STATUS_CODE) 
     69            return False 
     70 
    39171     
    392     def attributeQuery(self, attributeQuery): 
    393         """Query the Attribute Authority specified in the request to retrieve 
    394         the attributes if any corresponding to the subject 
    395          
    396         @type attributeResponse: PIPAttributeQuery 
    397         @param attributeResponse:  
    398         @rtype: PIPAttributeResponse 
    399         @return: response containing the attributes retrieved from the 
    400         Attribute Authority""" 
    401         if not isinstance(attributeQuery, PIPAttributeQuery): 
    402             raise TypeError('Expecting %r type for input "attributeQuery"; ' 
    403                             'got %r' % (PIPAttributeQuery,  
    404                                         type(attributeQuery))) 
    405                              
    406         attributeAuthorityURI = attributeQuery[ 
    407                                         PIPAttributeQuery.ATTRIBUTEAUTHORITY_NS] 
    408          
    409         log.debug("SamlPIPMiddleware: received attribute query: %r",  
    410                   attributeQuery) 
    411                 
    412         # Check for a wallet in the current session - if not present, create 
    413         # one.  See ndg.security.server.wsgi.authn.SessionHandlerMiddleware 
    414         # for session keys.  The 'credentialWallet' key is deleted along with 
    415         # any other security keys when the user logs out 
    416         credentialWalletKeyName = \ 
    417                             SamlPIPMiddleware.CREDENTIAL_WALLET_SESSION_KEYNAME 
    418         usernameKeyName = SamlPIPMiddleware.USERNAME_SESSION_KEYNAME 
    419              
    420         if not credentialWalletKeyName in self.session: 
    421             log.debug("SamlPIPMiddleware.attributeQuery: adding a " 
    422                       "Credential Wallet to user session [%s] ...", 
    423                       self.session[usernameKeyName]) 
    424              
    425             credentialWallet = SAMLCredentialWallet() 
    426             credentialWallet.userId = self.session[usernameKeyName] 
    427              
    428             self.session[credentialWalletKeyName] = credentialWallet 
    429             self.session.save() 
    430         else:     
    431             # Take reference to wallet for efficiency 
    432             credentialWallet = self.session[credentialWalletKeyName]     
    433          
    434         # Check for existing credentials cached in wallet             
    435         credentialItem = credentialWallet.credentialsKeyedByURI.get( 
    436                                                     attributeAuthorityURI) 
    437         if credentialItem is None: 
    438             # No assertion is cached - make a fresh SAML Attribute Query 
    439             self.attributeQueryBinding.subjectID = credentialWallet.userId 
    440             response = self.attributeQueryBinding.send( 
    441                                                     uri=attributeAuthorityURI) 
    442             for assertion in response.assertions: 
    443                 credentialWallet.addCredential(assertion, 
    444                                    attributeAuthorityURI=attributeAuthorityURI, 
    445                                    verifyCredential=False) 
    446              
    447             log.debug("SamlPIPMiddleware.attributeQuery: updating Credential " 
    448                       "Wallet with retrieved SAML Attribute Assertion " 
    449                       "for user session [%s]", self.session[usernameKeyName]) 
    450         else: 
    451             log.debug("SamlPIPMiddleware.attributeQuery: retrieved existing " 
    452                       "SAML Attribute Assertion cached in Credential Wallet " 
    453                       "for user session [%s]", self.session[usernameKeyName]) 
    454  
    455         attributeResponse = PIPAttributeResponse() 
    456         attributeResponse[Subject.ROLES_NS] = [] 
    457          
    458         # Unpack assertion attribute values and add to the response object 
    459         for credentialItem in credentialWallet.credentials.values(): 
    460             for statement in credentialItem.credential.attributeStatements: 
    461                 for attribute in statement.attributes: 
    462                     attributeResponse[Subject.ROLES_NS] += [ 
    463                         attributeValue.value  
    464                         for attributeValue in attribute.attributeValues 
    465                         if attributeValue.value not in attributeResponse[ 
    466                                                             Subject.ROLES_NS] 
    467                     ] 
    468          
    469         log.debug("SamlPIPMiddleware.attributeQuery response: %r",  
    470                   attributeResponse) 
    471          
    472         return attributeResponse 
    473  
    474  
    475 class AuthorizationMiddlewareError(Exception): 
    476     """Base class for AuthorizationMiddlewareBase exceptions""" 
    477      
    478      
    479 class AuthorizationMiddlewareConfigError(Exception): 
    480     """AuthorizationMiddlewareBase configuration related exceptions""" 
     72class AuthorisationFilterConfigError(Exception): 
     73    """AuthorisationFilterBase configuration related exceptions""" 
    48174  
    48275    
    483 class AuthorizationMiddlewareBase(NDGSecurityMiddlewareBase): 
    484     '''Virtual class - A base Handler to call Policy Enforcement Point  
    485     middleware to intercept requests and enforce access control decisions.   
     76class AuthorisationFilter(object): 
     77    '''NDG Security Authorisation filter wraps the Policy Enforcement Point  
     78    (PEP) filter to intercept requests and enforce access control decisions and 
     79    result handler middleware which enables a customised response given an 
     80    authorisation denied decision from the PEP filter. 
     81    ''' 
     82    PEP_PARAM_PREFIX = 'pep.' 
     83    RESULT_HANDLER_PARAMNAME = "resultHandler" 
     84    RESULT_HANDLER_PARAM_PREFIX = RESULT_HANDLER_PARAMNAME + '.' 
     85    RESULT_HANDLER_STATIC_CONTENT_DIR_PARAMNAME = 'staticContentDir' 
    48686     
    487     Extend THIS class adding the new type to any WSGI middleware chain ahead of  
    488     the application(s) which it is to protect.  To make an implementation for  
    489     this virtual class, set PIP_MIDDLEWARE_CLASS in the derived type to a  
    490     valid Policy Information Point Class.  Use in conjunction with  
    491     ndg.security.server.wsgi.authn.AuthenticationMiddleware 
    492     ''' 
    493     PEP_PARAM_PREFIX = 'pep.filter.' 
    494     PIP_PARAM_PREFIX = 'pip.' 
    495     PEP_RESULT_HANDLER_PARAMNAME = "pepResultHandler" 
    496     PEP_RESULT_HANDLER_PARAM_PREFIX = PEP_RESULT_HANDLER_PARAMNAME + '.' 
    497     PEP_RESULT_HANDLER_STATIC_CONTENT_DIR_PARAMNAME = 'staticContentDir' 
    498      
    499     class PIP_MIDDLEWARE_CLASS(object): 
    500         """Policy Information Point WSGI middleware abstract base,  
    501         implementations should retrieve user credentials to enable the PDP to  
    502         make access control decisions 
    503         """ 
    504         def __init__(self, app, global_conf, prefix='', **local_conf):   
    505             raise NotImplementedError(' '.join( 
    506                 AuthorizationMiddlewareBase.PIP_MIDDLEWARE_CLASS.__doc__.split()) 
    507             ) 
    508      
    509     def __init__(self, app, global_conf, prefix='', **app_conf): 
     87    @classmethod 
     88    def filter_app_factory(cls, app, global_conf, prefix='', **app_conf): 
    51089        """Set-up Policy Enforcement Point to enforce access control decisions 
    51190        based on the URI path requested and/or the HTTP response code set by 
     
    523102        dictionary 
    524103        """ 
    525         cls = AuthorizationMiddlewareBase 
     104        # Allow for static content for use with PEP result handler middleware  
     105        resultHandlerParamPrefix = prefix + cls.RESULT_HANDLER_PARAM_PREFIX 
     106        resultHandlerStaticContentDirParamName = \ 
     107                                resultHandlerParamPrefix + \ 
     108                                cls.RESULT_HANDLER_STATIC_CONTENT_DIR_PARAMNAME 
    526109         
    527         # Allow for static content for use with PEP result handler middleware         
    528         pepResultHandlerParamPrefix = prefix + \ 
    529                                             cls.PEP_RESULT_HANDLER_PARAM_PREFIX 
    530         pepResultHandlerStaticContentDirParamName = \ 
    531             pepResultHandlerParamPrefix + \ 
    532             cls.PEP_RESULT_HANDLER_STATIC_CONTENT_DIR_PARAMNAME 
     110        resultHandlerStaticContentDir = app_conf.get( 
     111                                    resultHandlerStaticContentDirParamName) 
     112        if resultHandlerStaticContentDir is not None:     
     113            staticApp = StaticURLParser(resultHandlerStaticContentDir) 
     114            app = Cascade([app, staticApp], catch=(httplib.NOT_FOUND,)) 
     115 
     116        pepPrefix = prefix + cls.PEP_PARAM_PREFIX 
     117        pepFilter = SamlPepFilter.filter_app_factory(app,  
     118                                                     global_conf,  
     119                                                     prefix=pepPrefix,  
     120                                                     **app_conf) 
    533121         
    534         pepResultHandlerStaticContentDir = app_conf.get( 
    535                                     pepResultHandlerStaticContentDirParamName) 
    536         if pepResultHandlerStaticContentDir is not None:     
    537             staticApp = StaticURLParser(pepResultHandlerStaticContentDir) 
    538             app = Cascade([app, staticApp], catch=(404,)) 
     122        # Now add the multi-handler to enable result handler to be invoked on 
     123        # 403 forbidden status from upstream middleware  
     124        app = MultiHandler(pepFilter) 
    539125 
    540         authzPrefix = prefix + cls.PEP_PARAM_PREFIX 
    541         pepFilter = PEPFilter(app, 
    542                               global_conf, 
    543                               prefix=authzPrefix, 
    544                               **app_conf) 
    545         pepInterceptFunc = pepFilter.multiHandlerInterceptFactory() 
    546          
    547         # Slot in the Policy Information Point in the WSGI stack at this point 
    548         # so that it can take a copy of the beaker session object from environ 
    549         # ahead of the PDP's request to it for an Attribute Certificate 
    550         pipPrefix = cls.PIP_PARAM_PREFIX 
    551         pipFilter = self.__class__.PIP_MIDDLEWARE_CLASS(pepFilter, 
    552                                                         global_conf, 
    553                                                         prefix=pipPrefix, 
    554                                                         **app_conf) 
    555         pepFilter.pdp.pip = pipFilter 
    556          
    557         app = MultiHandler(pipFilter) 
    558  
    559         pepResultHandlerClassName = app_conf.pop( 
    560                                         prefix+cls.PEP_RESULT_HANDLER_PARAMNAME,  
    561                                         None) 
    562         if pepResultHandlerClassName is None: 
    563             pepResultHandler = PEPResultHandlerMiddleware 
     126        resultHandlerClassName = app_conf.pop( 
     127                                            prefix+cls.RESULT_HANDLER_PARAMNAME,  
     128                                            None) 
     129        if resultHandlerClassName is None: 
     130            resultHandler = PEPResultHandlerMiddleware 
    564131        else: 
    565             pepResultHandler = importClass(pepResultHandlerClassName, 
     132            resultHandler = importClass(resultHandlerClassName, 
    566133                                    objectType=PEPResultHandlerMiddlewareBase) 
    567134                                
    568         app.add_method(PEPFilter.MIDDLEWARE_ID, 
    569                        pepResultHandler.filter_app_factory, 
     135        app.add_method(resultHandler.__class__.__name__, 
     136                       resultHandler.filter_app_factory, 
    570137                       global_conf, 
    571                        prefix=pepResultHandlerParamPrefix, 
     138                       prefix=resultHandlerParamPrefix, 
    572139                       **app_conf) 
    573140         
    574         app.add_checker(PEPFilter.MIDDLEWARE_ID, pepInterceptFunc) 
    575  
    576         super(AuthorizationMiddlewareBase, self).__init__(app, {}) 
    577  
    578  
    579 class SAMLAuthorizationMiddleware(AuthorizationMiddlewareBase): 
    580     """Implementation of AuthorizationMiddlewareBase using the SAML Policy 
    581     Information Point interface.  This retrieves attributes over the SOAP/SAML 
    582     Attribute Authority interface  
    583     (ndg.security.server.wsgi.saml.attributeinterface.SOAPAttributeInterfaceMiddleware) and caches  
    584     SAML Assertions in a  
    585     ndg.security.common.credentialWallet.SAMLCredentialWallet 
    586     """       
    587     PIP_MIDDLEWARE_CLASS = SamlPIPMiddleware 
     141        app.add_checker(resultHandler.__class__.__name__,  
     142                        Http403ForbiddenStatusHandler.intercept) 
     143         
     144        return app 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/wsgi/authz/pep.py

    r7257 r7287  
    2323 
    2424 
    25 class SamlPepMiddlewareConfigError(Exception): 
     25class SamlPepFilterConfigError(Exception): 
    2626    """Error with SAML PEP configuration settings""" 
    2727     
    2828     
    29 class SamlPepMiddleware(SessionMiddlewareBase): 
     29class SamlPepFilter(SessionMiddlewareBase): 
    3030    '''Policy Enforcement Point for ESG with SAML based Interface 
    3131     
     
    124124        @param kw: configuration settings 
    125125        dictionary 
    126         @raise SamlPepMiddlewareConfigError: missing option setting(s) 
     126        @raise SamlPepFilterConfigError: missing option setting(s) 
    127127        ''' 
    128128        # Parse authorisation decision query options 
     
    131131             
    132132        # Parse other options 
    133         for name in SamlPepMiddleware.PARAM_NAMES: 
     133        for name in SamlPepFilter.PARAM_NAMES: 
    134134            paramName = prefix + name 
    135135            value = kw.get(paramName) 
    136136            if value is None: 
    137                 raise SamlPepMiddlewareConfigError('Missing option %r' %  
     137                raise SamlPepFilterConfigError('Missing option %r' %  
    138138                                                   paramName) 
    139139            setattr(self, name, value) 
     
    172172        # place upstream of this middleware in the WSGI stack 
    173173        if self.sessionKey not in environ: 
    174             raise SamlPepMiddlewareConfigError('No beaker session key "%s" ' 
    175                                                'found in environ' %  
    176                                                self.sessionKey) 
     174            raise SamlPepFilterConfigError('No beaker session key "%s" found ' 
     175                                           'in environ' % self.sessionKey) 
    177176        self.session = environ[self.sessionKey] 
    178177         
  • TI12-security/trunk/NDGSecurity/python/ndg_security_test/ndg/security/test/config/authorisationservice/policy.xml

    r7257 r7287  
    66    RuleCombiningAlgId="urn:oasis:names:tc:xacml:1.0:rule-combining-algorithm:permit-overrides"> 
    77    <Description> 
    8         NDG XACML example for unit tests: allow access for resource URIs  
    9         matching given regular expressions.  The subject must have at least one 
    10         of a set of named attributes allocated  
     8        Example for NDG Security unit tests: allow access for resource URIs  
     9        defined in the rules.  All other URIs are blocked from access 
     10         
     11        See ndg.security.test.unit.wsgi.authz.test_authz to see the various  
     12        rules tested out 
    1113    </Description> 
    1214     
     
    7880    <Rule RuleId="urn:ndg:security:secured-uri-rule" Effect="Permit"> 
    7981        <!--  
    80             Rule target(s) define which requests apply to the particular rule 
     82            Secure a URI path and all sub-paths using a regular expression to  
     83            define a URI pattern 
    8184        --> 
    8285        <Target> 
     
    99102             
    100103            The user must have at least one of the roles set - in this 
    101             case 'urn:siteA:security:authz:1.0:attr:staff' 
     104            case 'staff' 
    102105        --> 
    103106        <Condition> 
     
    118121            <Resources> 
    119122                <Resource> 
    120                     <ResourceMatch MatchId="urn:oasis:names:tc:xacml:2.0:function:anyURI-regexp-match"> 
     123                    <ResourceMatch MatchId="urn:oasis:names:tc:xacml:1.0:function:anyURI-equal"> 
    121124                        <ResourceAttributeDesignator 
    122                             AttributeId="urn:siteA:security:authz:1.0:attr:resourceURI" 
     125                            AttributeId="urn:oasis:names:tc:xacml:1.0:resource:resource-id" 
    123126                            DataType="http://www.w3.org/2001/XMLSchema#anyURI"/> 
    124                         <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#anyURI">^http://localhost/test_accessGrantedToSecuredURI</AttributeValue> 
     127                        <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#anyURI">http://localhost/test_accessGrantedToSecuredURI</AttributeValue> 
     128                    </ResourceMatch> 
     129                </Resource> 
     130            </Resources> 
     131        </Target> 
     132        <Condition> 
     133            <Apply FunctionId="urn:oasis:names:tc:xacml:1.0:function:string-at-least-one-member-of"> 
     134                <SubjectAttributeDesignator  
     135                    AttributeId="urn:ndg:security:authz:1.0:attr"  
     136                    DataType="http://www.w3.org/2001/XMLSchema#string"/> 
     137                <Apply FunctionId="urn:oasis:names:tc:xacml:1.0:function:string-bag"> 
     138                    <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">staff</AttributeValue> 
     139                    <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">administrator</AttributeValue> 
     140                </Apply> 
     141            </Apply> 
     142        </Condition> 
     143    </Rule> 
     144    <Rule RuleId="Access Granted to secured URI Rule modified for special admin query argument" Effect="Permit"> 
     145        <!--  
     146            This rule is a modified version of the above to allow for a real use 
     147            case where adding a special query argument grants extra privileges 
     148            associated with an administrator 
     149        --> 
     150        <Target> 
     151            <Resources> 
     152                <Resource> 
     153                    <ResourceMatch MatchId="urn:oasis:names:tc:xacml:1.0:function:anyURI-equal"> 
     154                        <ResourceAttributeDesignator 
     155                            AttributeId="urn:oasis:names:tc:xacml:1.0:resource:resource-id" 
     156                            DataType="http://www.w3.org/2001/XMLSchema#anyURI"/> 
     157                        <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#anyURI">http://localhost/test_accessGrantedToSecuredURI?admin=1</AttributeValue> 
    125158                    </ResourceMatch> 
    126159                </Resource> 
     
    128161            <Subjects> 
    129162                <Subject> 
    130                     <SubjectMatch> 
     163                    <SubjectMatch MatchId="urn:oasis:names:tc:xacml:1.0:function:string-equal"> 
    131164                        <SubjectAttributeDesignator  
    132165                            AttributeId="urn:ndg:security:authz:1.0:attr"  
    133166                            DataType="http://www.w3.org/2001/XMLSchema#string"/> 
    134                         <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">keepout</AttributeValue> 
     167                        <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">administrator</AttributeValue> 
    135168                    </SubjectMatch> 
    136169                </Subject> 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_test/ndg/security/test/unit/wsgi/authz/pep-result-handler-test.ini

    r7077 r7287  
    1919 
    2020[filter:AuthZFilter] 
    21 paste.filter_app_factory=ndg.security.server.wsgi.authz:SAMLAuthorizationMiddleware.filter_app_factory 
     21paste.filter_app_factory=ndg.security.server.wsgi.authz:AuthorisationFilter.filter_app_factory 
    2222prefix = authz. 
    23 policy.filePath = %(here)s/saml-policy.xml 
    2423 
    25 authz.pepResultHandler = ndg.security.server.wsgi.authz.result_handler.redirect.HTTPRedirectPEPResultHandlerMiddleware 
    26 authz.pepResultHandler.redirectURI = /nowhere 
     24# This result handler responds with a redirect request to the client if access 
     25# denied to the original requested URI 
     26authz.resultHandler = ndg.security.server.wsgi.authz.result_handler.redirect.HTTPRedirectPEPResultHandlerMiddleware 
     27authz.resultHandler.redirectURI = /test_accessGrantedToSecuredURI 
    2728 
    28 # Settings for Policy Information Point used by the Policy Decision Point to 
    29 # retrieve subject attributes from the Attribute Authority associated with the 
    30 # resource to be accessed 
     29# Settings for the Policy Enforcement Point  
     30authz.pep.sessionKey = beaker.session.ndg.security 
     31authz.pep.authzServiceURI = https://localhost:9443/authorisation-service 
    3132 
    3233# If omitted, DN of SSL Cert is used 
    33 pip.attributeQuery.issuerName =  
    34 pip.attributeQuery.clockSkewTolerance = 0. 
    35 pip.attributeQuery.queryAttributes.0 = urn:siteA:security:authz:1.0:attr, , http://www.w3.org/2001/XMLSchema#string 
    36 pip.attributeQuery.sslCACertDir=%(testConfigDir)s/ca 
    37 pip.attributeQuery.sslCertFilePath=%(testConfigDir)s/pki/test.crt 
    38 pip.attributeQuery.sslPriKeyFilePath=%(testConfigDir)s/pki/test.key 
     34authz.pep.authzDecisionQuery.issuerName = /O=NDG/OU=BADC/CN=test 
     35authz.pep.authzDecisionQuery.issuerFormat = urn:oasis:names:tc:SAML:1.1:nameid-format:x509SubjectName 
     36authz.pep.authzDecisionQuery.subjectIdFormat = urn:esg:openid 
     37authz.pep.authzDecisionQuery.clockSkewTolerance = 0. 
     38authz.pep.authzDecisionQuery.sslCACertDir=%(testConfigDir)s/ca 
     39authz.pep.authzDecisionQuery.sslCertFilePath=%(testConfigDir)s/pki/test.crt 
     40authz.pep.authzDecisionQuery.sslPriKeyFilePath=%(testConfigDir)s/pki/test.key 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_test/ndg/security/test/unit/wsgi/authz/saml-test.ini

    r7168 r7287  
    1313 
    1414[pipeline:main] 
    15 pipeline = AuthZFilter TestApp 
     15pipeline = PolicyEnforcementPointFilter TestApp 
    1616 
    1717[app:TestApp] 
    1818paste.app_factory = ndg.security.test.unit.wsgi.authz.test_authz:TestAuthZMiddleware 
    1919 
    20 [filter:AuthZFilter] 
    21 paste.filter_app_factory=ndg.security.server.wsgi.authz.pep:SamlPepMiddleware.filter_app_factory 
     20[filter:PolicyEnforcementPointFilter] 
     21paste.filter_app_factory=ndg.security.server.wsgi.authz.pep:SamlPepFilter.filter_app_factory 
    2222prefix = pep. 
    2323pep.sessionKey = beaker.session.ndg.security 
    2424pep.authzServiceURI = https://localhost:9443/authorisation-service 
    25  
    26 pep.pepResultHandler = ndg.security.test.unit.wsgi.authz.test_authz.RedirectFollowingAccessDenied 
    2725 
    2826# Settings for Policy Information Point used by the Policy Decision Point to 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_test/ndg/security/test/unit/wsgi/authz/test_authz.py

    r7257 r7287  
    2929from ndg.security.server.wsgi.authz.result_handler.redirect import \ 
    3030    HTTPRedirectPEPResultHandlerMiddleware 
    31 from ndg.security.server.wsgi.authz.pep import SamlPepMiddlewareConfigError 
     31from ndg.security.server.wsgi.authz.pep import SamlPepFilterConfigError 
    3232 
    3333 
     
    149149class TestAuthZMiddleware(object): 
    150150    '''Test Application for the Authentication handler to protect''' 
    151     response = "Test Authorization application" 
     151    RESPONSE = "Test Authorization application" 
    152152        
    153153    def __init__(self, app_conf, **local_conf): 
     
    177177        start_response(status, 
    178178                       [('Content-length',  
    179                          str(len(TestAuthZMiddleware.response))), 
     179                         str(len(TestAuthZMiddleware.RESPONSE))), 
    180180                        ('Content-type', 'text/plain')]) 
    181181         
    182         return [TestAuthZMiddleware.response] 
     182        return [TestAuthZMiddleware.RESPONSE + ' returned: ' + status] 
    183183 
    184184 
     
    188188    def save(self): 
    189189        pass 
    190   
    191      
    192 class SamlWSGIAuthZTestCase(BaseTestCase): 
     190 
     191 
     192class BaseAuthzFilterTestCase(BaseTestCase): 
     193    """Base class for NDG Security WSGI authorisation filters 
     194    """ 
    193195    INI_FILE = 'saml-test.ini' 
    194196    THIS_DIR = path.dirname(path.abspath(__file__)) 
     197    INI_FILEPATH = None # Set in __init__ to enable derived classes to alter 
    195198    SESSION_KEYNAME = 'beaker.session.ndg.security' 
    196199     
    197     def __init__(self, *args, **kwargs):        
     200    def __init__(self, *args, **kwargs):    
     201        """Test the authorisation filter using Paste fixture and set up  
     202        Authorisation and Attribute Services needed for making authorisation  
     203        decisions 
     204        """    
    198205        BaseTestCase.__init__(self, *args, **kwargs) 
    199206 
    200         wsgiapp = loadapp('config:'+SamlWSGIAuthZTestCase.INI_FILE,  
    201                           relative_to=SamlWSGIAuthZTestCase.THIS_DIR) 
     207        wsgiapp = loadapp('config:'+self.__class__.INI_FILE,  
     208                          relative_to=self.__class__.THIS_DIR) 
    202209        self.app = paste.fixture.TestApp(wsgiapp) 
    203210         
     211        self.__class__.INI_FILEPATH = os.path.join(self.__class__.THIS_DIR,  
     212                                                   self.__class__.INI_FILE) 
     213         
    204214        self.startSiteAAttributeAuthority(withSSL=True, 
    205             port=SamlWSGIAuthZTestCase.SITEA_SSL_ATTRIBUTEAUTHORITY_PORTNUM) 
    206          
    207         self.startAuthorisationService() 
     215            port=self.__class__.SITEA_SSL_ATTRIBUTEAUTHORITY_PORTNUM) 
     216         
     217        self.startAuthorisationService()   
     218           
     219           
     220class SamlPepFilterTestCase(BaseAuthzFilterTestCase): 
     221    """Test SAML based Policy Enforcement Filter.  This has a SAML authorisation 
     222    decision query interface to call to a remote authorisation service""" 
    208223 
    209224    def test01CatchNoBeakerSessionFound(self): 
     
    211226        # PEPFilterConfigError is raised if no beaker.session is set in  
    212227        # environ 
    213         self.assertRaises(SamlPepMiddlewareConfigError, self.app.get,  
     228        self.assertRaises(SamlPepFilterConfigError, self.app.get,  
    214229                          '/test_200') 
    215230        
     
    223238        response = self.app.get('/test_200', 
    224239                                extra_environ=extra_environ) 
     240        print response 
    225241 
    226242    def test03Catch401WithLoggedIn(self): 
     
    238254                                extra_environ=extra_environ, 
    239255                                status=401) 
     256        print response 
    240257 
    241258    def test04Catch403WithLoggedIn(self): 
     
    246263        extra_environ = { 
    247264            self.__class__.SESSION_KEYNAME: 
    248                 BeakerSessionStub(username=SamlWSGIAuthZTestCase.OPENID_URI), 
     265                BeakerSessionStub(username=SamlPepFilterTestCase.OPENID_URI), 
    249266            'REMOTE_USER': self.__class__.OPENID_URI 
    250267        } 
     
    252269                                extra_environ=extra_environ, 
    253270                                status=403) 
     271        print response 
    254272 
    255273    def test05Catch401WithNotLoggedInAndSecuredURI(self): 
    256         # AuthZ middleware grants access because the URI requested is has no 
     274        # AuthZ middleware grants access because the URI requested has no 
    257275        # subject restriction set in the policy rule 
    258276         
     
    263281                                extra_environ=extra_environ, 
    264282                                status=401) 
     283        print response 
    265284         
    266285    def test06AccessDeniedForSecuredURI(self): 
     
    270289        extra_environ = { 
    271290            self.__class__.SESSION_KEYNAME: 
    272                 BeakerSessionStub(username=SamlWSGIAuthZTestCase.OPENID_URI), 
     291                BeakerSessionStub(username=SamlPepFilterTestCase.OPENID_URI), 
    273292            'REMOTE_USER': self.__class__.OPENID_URI 
    274293        } 
     
    278297                                status=403) 
    279298        print response 
    280         self.assert_("Insufficient privileges to access the " 
    281                      "resource" in response) 
    282299 
    283300    def test07AccessGrantedForSecuredURI(self): 
     
    287304        extra_environ = { 
    288305            self.__class__.SESSION_KEYNAME: 
    289                 BeakerSessionStub(username=SamlWSGIAuthZTestCase.OPENID_URI), 
     306                BeakerSessionStub(username=SamlPepFilterTestCase.OPENID_URI), 
    290307            'REMOTE_USER': self.__class__.OPENID_URI 
    291308        } 
     
    294311                                extra_environ=extra_environ, 
    295312                                status=200) 
    296         self.assert_(TestAuthZMiddleware.response in response) 
    297         print response 
    298  
    299     def test08AccessDeniedForAdminQueryArg(self): 
     313        self.assert_(TestAuthZMiddleware.RESPONSE in response) 
     314        print response 
     315 
     316 
     317class PEPResultHandlerTestCase(BaseAuthzFilterTestCase): 
     318    """Test Authorisation Filter - this contains the PEP filter and a result 
     319    handler which enables customisation of behaviour on 403 Forbidden responses 
     320    """ 
     321    INI_FILE = 'pep-result-handler-test.ini' 
     322    AUTHZ_FILTER_SECTION = 'filter:AuthZFilter' 
     323    AUTHZ_RESULT_HANDLER_REDIRECT_URI_OPTNAME = 'authz.resultHandler.redirectURI' 
     324     
     325    def __init__(self, *arg, **kw): 
     326        BaseAuthzFilterTestCase.__init__(self, *arg, **kw) 
     327         
     328        cfgParser = SafeConfigParser() 
     329        cfgParser.read(self.__class__.INI_FILEPATH) 
     330         
     331        self.redirectURI = cfgParser.get(self.__class__.AUTHZ_FILTER_SECTION, 
     332                    self.__class__.AUTHZ_RESULT_HANDLER_REDIRECT_URI_OPTNAME) 
     333         
     334    def test01RedirectPEPResultHandlerMiddleware(self): 
     335        # User is logged in but doesn't have the required credentials for  
     336        extra_environ = { 
     337            self.__class__.SESSION_KEYNAME: 
     338                        BeakerSessionStub(username=self.__class__.OPENID_URI), 
     339            'REMOTE_USER': self.__class__.OPENID_URI 
     340        } 
     341         
     342        # Expecting result handler to be invoked overriding the 403 response 
     343        response = self.app.get('/test_accessDeniedToSecuredURI', 
     344                                extra_environ=extra_environ, 
     345                                status=302) 
     346        print("Result handler has intercepted the 403 Forbidden response " 
     347              "from the PEP and set this redirect response instead: %s" % 
     348              response) 
     349        self.assert_(response.header_dict.get('location') == self.redirectURI) 
     350 
     351    def test02RedirectFollowingAccessDeniedForAdminQueryArg(self): 
    300352         
    301353        # User is logged in but doesn't have the required credentials for  
     
    303355        extra_environ = { 
    304356            self.__class__.SESSION_KEYNAME: 
    305                 BeakerSessionStub(username=SamlWSGIAuthZTestCase.OPENID_URI), 
     357                BeakerSessionStub(username=SamlPepFilterTestCase.OPENID_URI), 
    306358            'REMOTE_USER': self.__class__.OPENID_URI 
    307359        } 
     
    312364        # into play the PEP result handler defined in this module, 
    313365        # RedirectFollowingAccessDenied.  This class reinvokes the request 
    314         # but without the admin query argument.  Access is then granted for 
    315         # the redirected request 
     366        # but without the admin query argument (see the ini file for what this 
     367        # location is.  Access is then granted because the user has access 
     368        # rights for the new location. 
    316369        response = self.app.get('/test_accessGrantedToSecuredURI', 
    317370                                params={'admin': 1}, 
    318371                                extra_environ=extra_environ, 
    319372                                status=302) 
    320         try: 
    321             redirectResponse = response.follow(extra_environ=extra_environ) 
    322         except paste.fixture.AppError, e: 
    323             self.failIf(TestAuthZMiddleware.response not in response) 
    324         print response 
    325  
    326  
    327 class PEPResultHandlerTestCase(BaseTestCase): 
    328     INI_FILE = 'pep-result-handler-test.ini' 
    329     THIS_DIR = os.path.dirname(os.path.abspath(__file__)) 
    330     INI_FILEPATH = path.join(THIS_DIR, INI_FILE) 
    331     SESSION_KEYNAME = 'beaker.session.ndg.security' 
    332      
    333     def __init__(self, *arg, **kw): 
    334         BaseTestCase.__init__(self, *arg, **kw) 
    335          
    336         here_dir = os.path.dirname(os.path.abspath(__file__)) 
    337         wsgiapp = loadapp('config:'+self.__class__.INI_FILE,  
    338                           relative_to=self.__class__.THIS_DIR) 
    339         self.app = paste.fixture.TestApp(wsgiapp) 
    340          
    341         cfg = SafeConfigParser(dict(here=self.__class__.THIS_DIR)) 
    342         cfg.read(self.__class__.INI_FILEPATH) 
    343         self.redirectURI = cfg.get('filter:AuthZFilter',  
    344                                    'authz.pepResultHandler.redirectURI') 
    345          
    346         self.startSiteAAttributeAuthority(withSSL=True, 
    347             port=SamlWSGIAuthZTestCase.SITEA_SSL_ATTRIBUTEAUTHORITY_PORTNUM) 
    348  
    349          
    350     def testRedirectPEPResultHandlerMiddleware(self): 
    351         # User is logged in but doesn't have the required credentials for  
    352         # access 
    353         raise NotImplementedError('TODO: fix recursion error') 
    354 #        extra_environ = { 
    355 #            self.__class__.SESSION_KEYNAME: 
    356 #                        BeakerSessionStub(username=self.__class__.OPENID_URI) 
    357 #        } 
    358 #         
    359 #        # Expecting redirect response to specified redirect URI 
    360 #        response = self.app.get('/test_accessDeniedToSecuredURI', 
    361 #                                extra_environ=extra_environ, 
    362 #                                status=302) 
    363 #        print(response) 
    364 #        self.assert_(response.header_dict.get('location') == self.redirectURI) 
     373 
     374        print("Redirect Handler has interrupted the 403 Denied response and " 
     375              "added this redirect response instead: %s" % response) 
     376         
     377        # Follow the redirect - the policy should allow access to the new  
     378        # location  
     379        redirectResponse = response.follow(extra_environ=extra_environ, 
     380                                           status=200) 
     381        print("Following the redirect to location %r gives this response: %s" % 
     382              (response.header_dict.get('location'), redirectResponse)) 
     383         
    365384         
    366385if __name__ == "__main__": 
Note: See TracChangeset for help on using the changeset viewer.