Changeset 5279


Ignore:
Timestamp:
12/05/09 16:45:48 (10 years ago)
Author:
pjkersha
Message:

Major fix to authorisation middleware:

  • Apply request URI checking in WSGI middleware not in MultiHandler? checker function
  • MultiHandler? checker is still used but this performs the function of responding to HTTP 403 Forbidden responses from applications to be protected downstream in the WSGI stack
  • Refactored:
    • PEPFilter is a WSGI app to enforce access control decisions made by the PDP.
    • AuthZResultMiddleware -> PEPResultMiddleware
  • PEPResultMiddleware provides the response if access is denied. This can happen if a URI path matches a target in the policy or if an application downstream sets a 403 response.
Location:
TI12-security/trunk/python
Files:
3 edited

Legend:

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

    r5273 r5279  
    2929        try: 
    3030            client = WSGISessionManagerClient(environ=environ, 
    31                                     environKeyName=self.sessionManagerFilterID) 
     31                                environKeyName=self.sessionManagerFilterID) 
    3232            res = client.connect(username, passphrase=password) 
    3333 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/authz/__init__.py

    r5273 r5279  
    2424    Response, Resource, Subject 
    2525 
    26 class AuthZResultHandlerMiddleware(NDGSecurityMiddlewareBase): 
     26class PEPResultHandlerMiddleware(NDGSecurityMiddlewareBase): 
    2727    """This middleware is invoked if access is denied to a given resource.  It 
    2828    is incorporated into the call stack by passing it in to a MultiHandler  
     
    4545    def __init__(self, app, global_conf, prefix='', **app_conf): 
    4646         
    47         super(AuthZResultHandlerMiddleware, self).__init__(app, 
     47        super(PEPResultHandlerMiddleware, self).__init__(app, 
    4848                                                           global_conf, 
    4949                                                           prefix=prefix, 
     
    6363            # - another WSGI to set a user friendly output and include links 
    6464            # to enable the user to register for new access privileges 
    65             response = ("Access is forbidden for this resource.  Please check " 
    66                         "with your site administrator that you have the " 
    67                         "required access privileges") 
     65            response = ("Access is forbidden for this resource.\n\nPlease " 
     66                        "check with your site administrator that you have " 
     67                        "the required access privileges") 
    6868            start_response(self.__class__.getStatusMessage(403), 
    6969                           [('Content-type', 'text/plain') , 
     
    7272 
    7373 
    74 class AuthorizationHandler(object): 
    75     """Interface to authkit.authenticate.MultiHandler checker callable. 
    76     The checker returns True/False to indicate to the MultiHandler whether to 
    77     call the access denied middleware AuthZResultHandlerMiddleware.  To 
    78     return this bool, it evaluates the users credentials against the  
    79     constraints on the resource to be accessed by calling the NDG Policy 
    80     Decision Point 
     74class PEPFilter(NDGSecurityMiddlewareBase): 
     75    """PEP (Policy Enforcement Point) WSGI Middleware.  The PEP enforces 
     76    access control decisions made by the PDP (Policy Decision Point).  In  
     77    this case, it follows the WSG middleware filter pattern and is configured 
     78    in a pipeline upstream of the application(s) which it protects.  if an  
     79    access denied decision is made, the PEP enforces this by returning a  
     80    403 Forbidden HTTP response without the application middleware executing 
    8181    """ 
    8282    triggerStatus = '403' 
    83     id = 'AuthorizationHandler' 
     83    id = 'PEPFilter' 
    8484     
    8585    propertyDefaults = { 
     
    9292                               doc='boolean to indicate is user logged in') 
    9393 
    94     def __init__(self, **app_conf): 
    95          
    96         # Policy Information Point 
    97         pipCfg = AuthorizationHandler._filterKeywords(app_conf, 'pip.') 
     94    def __init__(self, app, global_conf, prefix='', **local_conf): 
     95        """Initialise the PIP (Policy Information Point) and PDP (Policy  
     96        Decision Point).  The PDP makes access control decisions based on 
     97        a given policy.  The PIP manages the retrieval of user credentials on  
     98        behalf of the PDP 
     99         
     100        """ 
     101        pipCfg = PEPFilter._filterKeywords(local_conf, 'pip.') 
    98102        pip = PIP(**pipCfg) 
    99103 
    100         # Policy Decision Point 
    101         policyCfg = AuthorizationHandler._filterKeywords(app_conf, 'policy.') 
     104        # Initialise the  reading in the policy 
     105        policyCfg = PEPFilter._filterKeywords(local_conf, 'policy.') 
    102106        self.policyFilePath = policyCfg['filePath'] 
    103107        self.policy = Policy.Parse(policyCfg['filePath']) 
    104108        self.pdp = PDP(self.policy, pip) 
    105109         
    106         self.sessionKey = app_conf.get('sessionKey',  
    107                         AuthorizationHandler.propertyDefaults['sessionKey']) 
    108      
    109     def __call__(self, environ, status, headers): 
    110         """ 
    111         @rtype: bool 
    112         @return: True if access should be forbidden - this injects the  
    113         access forbidden middleware into the chain to interrupt access; False 
    114         return status bypasses the access forbidden and enables normal  
    115         execution of the WSGI middleware chain to proceed i.e. invoke the 
    116         middleware which this code is securing 
    117         """ 
    118         self.environ = environ 
     110        self.sessionKey = local_conf.get('sessionKey',  
     111                                     PEPFilter.propertyDefaults['sessionKey']) 
     112         
     113        super(PEPFilter, self).__init__(app, 
     114                                        global_conf, 
     115                                        prefix=prefix, 
     116                                        **local_conf) 
     117         
     118    @NDGSecurityMiddlewareBase.initCall 
     119    def __call__(self, environ, start_response): 
    119120        session = environ[self.sessionKey] 
     121        resourceURI = self.pathInfo 
    120122         
    121123        # Check for a secured resource 
    122         resourceURI = environ['PATH_INFO'] 
    123         matchingTargets = [target for target in self.policy.targets  
    124                            if target.regEx.match(resourceURI) is not None] 
    125         if len(matchingTargets) == 0: 
    126             if status.startswith(AuthorizationHandler.triggerStatus): 
    127                 log.debug("AuthorizationHandler found 403 status but no " 
    128                           "policy set for this URI : preventing access as a " 
    129                           "precaution") 
    130                 return True 
    131             else: 
    132                 # No match - it's publicly accessible 
    133                 log.debug("AuthorizationHandler: no match was found in the " 
    134                           "policy for uri [%s]", resourceURI) 
    135                 return False 
    136  
    137         log.debug("AuthorizationHandler found matching target(s):\n\n " 
    138                   "%s\nfrom policy file [%s] for URI=[%s]" %  
    139                   ('\n'.join(["RegEx=%s" % t for t in matchingTargets]),  
    140                    self.policyFilePath, 
    141                    resourceURI)) 
     124        matchingTargets = self._getMatchingTargets() 
     125        targetMatch = len(matchingTargets) > 0 
     126        if not targetMatch: 
     127            log.debug("PEPFilter: no matching URI path target was found in " 
     128                      "the policy for URI path [%s]", resourceURI) 
     129            return self._app(environ, start_response)        
     130 
     131        log.info("PEPFilter found matching target(s):\n\n %s\n" 
     132                 "\nfrom policy file [%s] for URI Path=[%s]\n", 
     133                 '\n'.join(["RegEx=%s" % t for t in matchingTargets]),  
     134                 self.policyFilePath, 
     135                 resourceURI) 
    142136         
    143137        if not self.isAuthenticated: 
    144             log.debug("AuthorizationHandler: user is not authenticated") 
    145             return True 
     138            log.info("PEPFilter: user is not authenticated") 
     139            return self.authZResult(environ, start_response) 
    146140         
    147141        # Make a request object to pass to the PDP 
     
    149143        request.subject[Subject.USERID_NS] = session['username'] 
    150144         
     145        # IdP Session Manager specific settings: 
     146        # 
    151147        # The following won't be set if the IdP running the OpenID Provider 
    152148        # hasn't also deployed a Session Manager.  In this case, the 
     
    161157        permit = response.status == Response.DECISION_PERMIT 
    162158        if permit: 
    163             if status.startswith(AuthorizationHandler.triggerStatus): 
    164                 log.debug("AuthorizationHandler found 403 status but policy " 
    165                           "permits access: preventing access as a precaution") 
     159            log.info("PEPFilter: PDP using policy [%s] denied access for " 
     160                     "uri path [%s]", self.policyFilePath, resourceURI) 
     161             
     162            return self._app(environ, start_response) 
     163        else: 
     164            log.debug("PEPFilter: PDP using policy [%s] granted access for " 
     165                      "uri path [%s]", self.policyFilePath, resourceURI) 
     166            return self.authZResult(environ, start_response) 
     167 
     168    def _setAuthZResult(self, val): 
     169        if not isinstance(val, PEPResultHandlerMiddleware): 
     170            raise TypeError("Expecting AuthZResultMiddleware type; got %r" % 
     171                            val) 
     172        self._authZResult = val 
     173         
     174    def _getAuthZResult(self): 
     175        return getattr(self, '_authZResult', None) 
     176     
     177    authZResult = property(fget=_getAuthZResult, 
     178                           fset=_setAuthZResult, 
     179                           doc="middleware object to handle access denied " 
     180                               "response from PDP") 
     181     
     182    def _getMatchingTargets(self): 
     183        """This method may only be called following __call__ as __call__ 
     184        updates the pathInfo property 
     185         
     186        @rtype: list 
     187        @return: return list of policy target objects matching the current  
     188        path  
     189        """ 
     190        resourceURI = self.pathInfo 
     191        matchingTargets = [target for target in self.policy.targets  
     192                           if target.regEx.match(resourceURI) is not None] 
     193        return matchingTargets 
     194 
     195    def multiHandlerInterceptFactory(self): 
     196        """Return a checker function for use with AuthKit's MultiHandler. 
     197        MultiHandler can be used to catch HTTP 403 Forbidden responses set by 
     198        an application and call middleware (AuthZResultMiddleware) to handle 
     199        the access denied message. 
     200        """ 
     201         
     202        def multiHandlerIntercept(environ, status, headers): 
     203            """AuthKit MultiHandler checker function to intercept  
     204            unauthorised response status codes from applications to be  
     205            protected.  This function's definition is embedded into a 
     206            factory method so that this function has visibility to the  
     207            PEPFilter object's attributes if required. 
     208             
     209            @type environ: dict 
     210            @param environ: WSGI environment dictionary 
     211            @type status: basestring 
     212            @param status: HTTP response code set by application middleware 
     213            that this intercept function is to protect 
     214            @type headers: list 
     215            @param headers: HTTP response header content""" 
     216            if status.startswith(PEPFilter.triggerStatus): 
     217                log.info("Policy Enforcement Point found [%s] status for URI " 
     218                         "path [%s]: invoking access denied response", 
     219                         PEPFilter.triggerStatus, 
     220                         environ['PATH_INFO']) 
    166221                return True 
    167222            else: 
    168                 # Skip the access forbidden middleware and call the next next 
    169                 # WSGI app 
    170                 log.debug("AuthorizationHandler access granted to [%s] using " 
    171                           "policy [%s]" % (resourceURI, self.policyFilePath)) 
     223                # No match - it's publicly accessible 
     224                log.debug("Policy Enforcement Point: the return status [%s] " 
     225                          "for this URI path [%s] didn't match the trigger " 
     226                          "status [%s]", 
     227                          status, 
     228                          environ['PATH_INFO'], 
     229                          PEPFilter.triggerStatus) 
    172230                return False 
    173         else: 
    174             log.debug("AuthorizationHandler policy [%s] denied access for " 
    175                       "uri [%s]", self.policyFilePath, resourceURI) 
    176             # True invokes the access forbidden middleware 
    177             return True 
     231         
     232        return multiHandlerIntercept 
    178233         
    179234    @staticmethod 
     
    186241                 
    187242        return filteredConf 
    188  
     243             
    189244 
    190245from authkit.authenticate.multi import MultiHandler 
    191246 
    192247class AuthorizationMiddleware(NDGSecurityMiddlewareBase): 
    193     '''Handler to call Policy Decision Point middleware and intercept 
    194     authorisation requests.  Add THIS class to any middleware chain and NOT 
    195     AuthZResultHandlerMiddleware and AuthorizationHandler which it wraps. 
     248    '''Handler to call Policy Enforcement Point middleware to intercept  
     249    requests and enforce access control decisions.  Add THIS class to any  
     250    WSGI middleware chain ahead of the application(s) which it is to  
     251    protect.  Use in conjunction with  
     252    ndg.security.server.wsgi.authn.AuthenticationMiddleware 
    196253    ''' 
     254     
    197255    def __init__(self, app, global_conf, prefix='', **app_conf): 
    198         """Set-up AuthKit MultiHandler with a WSGI interface to handle HTTP 
    199         403 access denied responses - AuthZResultHandlerMiddleware and a  
    200         checker which intercepts requests and makes access control decisions 
    201         using the Policy Decision Point (PDP)""" 
    202          
    203         app = MultiHandler(app) 
     256        """Set-up Policy Enforcement Point to enforce access control decisions 
     257        based on the URI path requested and/or the HTTP response code set by 
     258        application(s) to be protected.  An AuthKit MultiHandler is setup to  
     259        handle the latter.  PEPResultHandlerMiddleware handles the output 
     260        set following an access denied decision""" 
     261         
     262        pepFilter = PEPFilter(app,global_conf,prefix=prefix+'pep.',**app_conf) 
     263        pepInterceptFunc = pepFilter.multiHandlerInterceptFactory() 
     264         
     265        app = MultiHandler(pepFilter) 
    204266                            
    205         app.add_method(AuthorizationHandler.id, 
    206                        AuthZResultHandlerMiddleware.filter_app_factory, 
     267        app.add_method(PEPFilter.id, 
     268                       PEPResultHandlerMiddleware.filter_app_factory, 
    207269                       global_conf, 
    208270                       prefix=prefix, 
    209271                       **app_conf) 
    210272         
    211         authorizationHandler = AuthorizationHandler(**app_conf) 
    212         app.add_checker(AuthorizationHandler.id, authorizationHandler)                 
     273        app.add_checker(PEPFilter.id, pepInterceptFunc)                 
     274        pepFilter.authZResult = app.binding[PEPFilter.id] 
    213275         
    214276        super(AuthorizationMiddleware, self).__init__(app, 
     
    216278                                                      prefix=prefix, 
    217279                                                      **app_conf) 
    218          
     280                 
  • TI12-security/trunk/python/ndg.security.test/ndg/security/test/integration/authz/policy.xml

    r5273 r5279  
    99        </Attributes> 
    1010        <AttributeAuthority> 
    11 <!--           
    1211            <uri>http://localhost:7443/AttributeAuthority</uri> 
    13 -->    
    14             <uri>https://ndg3beta.badc.rl.ac.uk/AttributeAuthority</uri> 
    1512        </AttributeAuthority> 
    1613    </Target> 
Note: See TracChangeset for help on using the changeset viewer.