Changeset 5186 for TI12-security


Ignore:
Timestamp:
08/04/09 15:29:39 (11 years ago)
Author:
pjkersha
Message:

Integrated PDP, PIP and Policy objects into Authkit.authenticate.MultiHandler? checker wrapper class.

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

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/python/ndg.security.common/ndg/security/common/authz/msi.py

    r5182 r5186  
    387387    """Policy Decision Point""" 
    388388     
    389     def __init__(self, policyFilePath=Policy(), pip=None): 
     389    def __init__(self, policy, pip): 
    390390        """Read in a file which determines access policy""" 
    391         self.policy = Policy.Parse(policyFilePath) 
     391        self.policy = policy 
    392392        self.pip = pip 
    393393         
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/authz/__init__.py

    r5181 r5186  
    2121    NDGSecurityMiddlewareConfigError 
    2222 
    23 from ndg.security.common.authz.msi import PIP, PDP, Request, Response, \ 
     23from ndg.security.common.authz.msi import Policy, PIP, PDP, Request, Response,\ 
    2424    Resource, Subject 
    2525 
    26 class PEPMiddleware(NDGSecurityPathFilter): 
    27     triggerStatus = '403' 
    28     id = 'forbidden' 
     26class AuthZResultHandlerMiddleware(NDGSecurityMiddlewareBase): 
     27    """Simple interface to send a 401 response if no username is set in the 
     28    beaker.session 
    2929     
     30    TODO: possible refactor to incorporate 403 response and user role  
     31    registration interface""" 
    3032    propertyDefaults = { 
    3133        'sessionKey': 'beaker.session' 
     
    3941    def __init__(self, app, global_conf, prefix='', **app_conf): 
    4042         
    41         # Policy Enforcement Point 
    42         pipCfg = PEPMiddleware._filterKeywords(app_conf, 'pip.') 
     43        super(AuthZResultHandlerMiddleware, self).__init__(app, 
     44                                                           global_conf, 
     45                                                           prefix=prefix, 
     46                                                           **app_conf) 
     47 
     48                
     49    @NDGSecurityMiddlewareBase.initCall 
     50    def __call__(self, environ, start_response): 
     51        self.session = self.environ.get(self.sessionKey) 
     52        if not self.isAuthenticated: 
     53            response = "Not authenticated" 
     54            start_response(self.__class__.getStatusMessage(401), 
     55                           [('Content-type', 'text/plain') , 
     56                            ('Content-length', str(len(response)))]) 
     57            return response 
     58        else: 
     59            # TODO: refactor to include a call to another interface - possibly 
     60            # - another WSGI to set a user friendly output and include links 
     61            # to enable the user to register for new access privileges 
     62            response = ("Access is forbidden for this resource.  Please check " 
     63                        "with your site administrator that you have the " 
     64                        "required access privileges") 
     65            start_response(self.__class__.getStatusMessage(403), 
     66                           [('Content-type', 'text/plain') , 
     67                            ('Content-length', str(len(response)))]) 
     68            return response 
     69 
     70 
     71class AuthorizationHandler(object): 
     72    """Interface to authkit.authenticate.MultiHandler checker callable 
     73    """ 
     74    triggerStatus = '403' 
     75    id = 'AuthorizationHandler' 
     76     
     77    propertyDefaults = { 
     78        'sessionKey': 'beaker.session' 
     79    } 
     80 
     81    _isAuthenticated = lambda self: \ 
     82                            'username' in self.environ.get(self.sessionKey, ()) 
     83    isAuthenticated = property(fget=_isAuthenticated, 
     84                               doc='boolean to indicate is user logged in') 
     85 
     86    def __init__(self, **app_conf): 
     87         
     88        # Policy Information Point 
     89        pipCfg = AuthorizationHandler._filterKeywords(app_conf, 'pip.') 
    4390        pip = PIP(**pipCfg) 
    4491 
    4592        # Policy Decision Point 
    46         pdpCfg = PEPMiddleware._filterKeywords(app_conf, 'pdp.') 
    47         self.pdp = PDP(pip=pip, **pdpCfg) 
     93        policyCfg = AuthorizationHandler._filterKeywords(app_conf, 'policy.') 
     94        self.policy = Policy.Parse(policyCfg['filePath']) 
     95        self.pdp = PDP(self.policy, pip) 
    4896         
    49         super(PEPMiddleware, self).__init__(app, 
    50                                             global_conf, 
    51                                             prefix=prefix, 
    52                                             **app_conf) 
    53  
     97        self.sessionKey = app_conf.get('sessionKey',  
     98                        AuthorizationHandler.propertyDefaults['sessionKey']) 
     99     
     100    def __call__(self, environ, status, headers): 
     101        """ 
     102        @rtype: bool 
     103        @return: True if access should be forbidden - this injects the  
     104        access forbidden middleware into the chain to interrupt access; False 
     105        return status bypasses the access forbidden and enables normal  
     106        execution of the WSGI middleware chain to proceed i.e. invoke the 
     107        middleware which this code is securing 
     108        """ 
     109        self.environ = environ 
     110        session = environ[self.sessionKey] 
     111         
     112        # Check for a secured resource 
     113        resourceURI = environ['PATH_INFO'] 
     114        matchingTargets = [target for target in self.policy.targets  
     115                           if target.regEx.match(resourceURI) is not None] 
     116        if len(matchingTargets) == 0: 
     117            if status.startswith(AuthorizationHandler.triggerStatus): 
     118                log.debug("AuthorizationHandler found 403 status but no " 
     119                          "policy set for this URI : preventing access as a " 
     120                          "precaution") 
     121                return True 
     122            else: 
     123                # No match - it's publicly accessible 
     124                return False 
     125         
     126        if not self.isAuthenticated: 
     127            return True 
     128         
     129        # Make a request object to pass to the PDP 
     130        request = Request() 
     131        request.subject[Subject.USERID_NS] = session['username'] 
     132        request.subject[Subject.SESSIONID_NS] = session.get('sessionId') 
     133        request.subject[Subject.SESSIONMANAGERURI_NS] = session.get( 
     134                                                        'sessionManagerURI') 
     135        request.resource[Resource.URI_NS] = resourceURI 
     136             
     137        response = self.pdp.evaluate(request) 
     138        permit = response.status == Response.DECISION_PERMIT 
     139        if permit: 
     140            if status.startswith(AuthorizationHandler.triggerStatus): 
     141                log.debug("AuthorizationHandler found 403 status but policy " 
     142                          "permits access: preventing access as a precaution") 
     143                return True 
     144            else: 
     145                # Skip the access forbidden middleware and call the next next 
     146                # WSGI app 
     147                log.debug("AuthorizationHandler access granted for policy") 
     148                return False 
     149        else: 
     150            log.debug("AuthorizationHandler access denied for policy") 
     151            # True invokes the access forbidden middleware 
     152            return True 
     153         
    54154    @staticmethod 
    55155    def _filterKeywords(conf, prefix): 
     
    61161                 
    62162        return filteredConf 
    63                 
    64     @NDGSecurityPathFilter.initCall 
    65     def __call__(self, environ, start_response): 
    66         self.session = self.environ.get(self.sessionKey) 
    67         if self.isAuthenticated: 
    68             if self.isAuthorized(): 
    69                 # Return next layer in stack 
    70                 return self._app(environ, start_response) 
    71             else: 
    72                 return self.accessDeniedResponse() 
    73         else: 
    74             response = "Not authenticated" 
    75             start_response(PEPMiddleware.getStatusMessage(401), 
    76                            [('Content-type', 'text/plain') , 
    77                             ('Content-length', str(len(response)))]) 
    78             return response                     
    79          
    80     def isAuthorized(self): 
    81         '''Check constraints on the requested URI and return boolean - access 
    82         allowed/denied''' 
    83         environ = self.environ 
    84          
    85         # Make a request object to pass to the PDP 
    86         request = Request() 
    87         request.subject[Subject.USERID_NS] = self.session['username'] 
    88         request.subject[Subject.SESSIONID_NS] = self.session['sessionId'] 
    89         request.subject[Subject.SESSIONMANAGERURI_NS] = self.session[ 
    90                                                         'sessionManagerURI'] 
    91          
    92         resourceURI = self.environ.get('ndg.security.server.wsgi.pep.resource') 
    93         request.resource[Resource.URI_NS] = resourceURI or self.pathInfo 
    94              
    95         response = self.pdp.evaluate(request) 
    96         return response.status == Response.DECISION_PERMIT 
    97      
    98     def accessDeniedResponse(self): 
    99         '''Break out of the WSGI stack and set a error message for the user 
    100         403 status is still set to enable programmatic handling of this  
    101         response''' 
    102         response = "Access Denied" 
    103         self.start_response(PEPMiddleware.getStatusMessage(403), 
    104                             [('Content-type', 'text/plain') , 
    105                              ('Content-length', str(len(response)))]) 
    106         return response             
    107     
    108     @classmethod 
    109     def checker(cls, environ, status, headers): 
    110         """Set the trigger for calling this middleware.  In this case, it's a 
    111         HTTP 403 Forbidden response detected in the middleware chain 
    112         """ 
    113         log.debug("PEPMiddleware.checker received status %r, headers " 
    114                   "%r", status, headers) 
    115          
    116         if status.startswith(cls.triggerStatus) or environ['PATH_INFO'] == '/test_securedURI': 
    117 #            environ['ndg.security.server.wsgi.pep.resource'] = Resource(environ['PATH_INFO']) 
    118             log.debug("PEPMiddleware.checker returning True") 
    119             return True 
    120         else: 
    121             log.debug("PEPMiddleware.checker returning False") 
    122             return False 
     163 
    123164 
    124165from authkit.authenticate.multi import MultiHandler 
     
    129170    PEPMiddleware which it wraps. 
    130171    ''' 
    131     resourceKeyName = 'ndg.security.server.wsgi.pep.resource' 
    132      
    133172    def __init__(self, app, global_conf, prefix='', **app_conf): 
    134173                         
    135174        app = MultiHandler(app) 
    136175                            
    137         app.add_method(PEPMiddleware.id, 
    138                        PEPMiddleware.filter_app_factory, 
     176        app.add_method(AuthorizationHandler.id, 
     177                       AuthZResultHandlerMiddleware.filter_app_factory, 
    139178                       global_conf, 
    140179                       prefix=prefix, 
    141180                       **app_conf) 
    142181         
    143         app.add_checker(PEPMiddleware.id, PEPMiddleware.checker) 
    144                  
     182        authorizationHandler = AuthorizationHandler(**app_conf) 
     183        app.add_checker(AuthorizationHandler.id, authorizationHandler)                 
    145184         
    146185        super(AuthorizationMiddleware, self).__init__(app, 
  • TI12-security/trunk/python/ndg.security.test/ndg/security/test/integration/authz/policy.xml

    r5182 r5186  
    11<?xml version="1.0" encoding="UTF-8"?> 
    22<Policy PolicyId="AuthZTest" xmlns="urn:ndg:security:authz:1.0:policy"> 
    3     <Description>Restrict access to URI /test_securedURI</Description> 
     3    <Description>Restrict access for Authorization integration tests</Description> 
    44     
    55    <Target> 
     
    1212        </AttributeAuthority> 
    1313    </Target> 
     14    <Target> 
     15        <URIPattern>/test_accessDeniedToSecuredURI</URIPattern> 
     16        <Attributes> 
     17            <Attribute>urn:siteA:security:authz:1.0:attr:forbidden</Attribute> 
     18            <Attribute>urn:siteA:security:authz:1.0:attr:keepout</Attribute> 
     19        </Attributes> 
     20        <AttributeAuthority> 
     21            <uri>http://localhost:7443/AttributeAuthority</uri> 
     22        </AttributeAuthority> 
     23    </Target> 
    1424</Policy> 
  • TI12-security/trunk/python/ndg.security.test/ndg/security/test/integration/authz/securedapp.ini

    r5182 r5186  
    4343paste.filter_app_factory=ndg.security.server.wsgi.authz:AuthorizationMiddleware.filter_app_factory 
    4444prefix = authz. 
    45 pdp.policyFilePath = %(here)s/policy.xml 
     45policy.filePath = %(here)s/policy.xml 
    4646 
    4747# Settings for Policy Information Point used by the Policy Decision Point to 
  • TI12-security/trunk/python/ndg.security.test/ndg/security/test/integration/authz/securedapp.py

    r5154 r5186  
    2626"/test_401": "test_401", 
    2727"/test_403": "test_403", 
    28 "/test_securedURI": "test_securedURI" 
     28"/test_securedURI": "test_securedURI", 
     29"/test_accessDeniedToSecuredURI": "test_accessDeniedToSecuredURI" 
    2930    } 
    3031 
     
    5253    <head/> 
    5354    <body> 
    54         <p>Authenticated!</p> 
    55         <p><a href="/logout">logout</a></p> 
    56     </body> 
    57 </html>""" 
    58             start_response('200 OK',  
    59                            [('Content-type', 'text/html'), 
    60                             ('Content-length', str(len(response)))]) 
    61         else: 
    62             response = """ 
    63 <head/> 
    64 <body> 
    65     <h1>Authorisation integration tests:</h1> 
    66     <ul>%s</ul> 
    67 </body> 
    68 """ % '\n'.join(['<li><a href="%s">%s</a></li>' % (link, name)  
    69        for link,name in self.method.items() if name != 'default']) 
     55        <h1>Authorisation integration tests:</h1> 
     56        <ul>%s</ul> 
     57        <p>You are logged in.  <a href="/logout">Logout</a></p> 
     58    </body> 
     59</html> 
     60""" % '\n'.join(['<li><a href="%s">%s</a></li>' % (link, name)  
     61                 for link,name in self.method.items() if name != 'default']) 
     62         
     63            start_response('200 OK',  
     64                           [('Content-type', 'text/html'), 
     65                            ('Content-length', str(len(response)))]) 
     66        else: 
     67            response = """<html> 
     68    <head/> 
     69    <body> 
     70        <h1>Authorisation integration tests:</h1> 
     71        <ul>%s</ul> 
     72    </body> 
     73</html> 
     74""" % '\n'.join(['<li><a href="%s">%s</a></li>' % (link, name)  
     75                 for link,name in self.method.items() if name != 'default']) 
    7076 
    7177            start_response('200 OK',  
     
    8086    <body> 
    8187        <h1>Authenticated!</h1> 
    82         <p><a href="/logout">logout</a></p> 
    83     </body> 
    84 </html>""" 
     88        <ul>%s</ul> 
     89        <p>You are logged in.  <a href="/logout">Logout</a></p> 
     90    </body> 
     91</html> 
     92""" % '\n'.join(['<li><a href="%s">%s</a></li>' % (link, name)  
     93                 for link,name in self.method.items() if name != 'default']) 
     94 
    8595            start_response('200 OK',  
    8696                           [('Content-type', 'text/html'), 
     
    102112    <body> 
    103113        <h1>Authorised!</h1> 
    104         <p><a href="/logout">logout</a></p> 
    105     </body> 
    106 </html>""" 
     114        <ul>%s</ul> 
     115        <p>You are logged in.  <a href="/logout">Logout</a></p> 
     116    </body> 
     117</html> 
     118""" % '\n'.join(['<li><a href="%s">%s</a></li>' % (link, name)  
     119                 for link,name in self.method.items() if name != 'default']) 
     120 
    107121            start_response('200 OK',  
    108122                           [('Content-type', 'text/html'), 
     
    124138    <body> 
    125139        <h1>Authorised for path [%s]!</h1> 
    126         <p><a href="/logout">logout</a></p> 
    127     </body> 
    128 </html>""" % environ['PATH_INFO'] 
     140        <ul>%s</ul> 
     141        <p>You are logged in.  <a href="/logout">Logout</a></p> 
     142    </body> 
     143</html> 
     144""" % (environ['PATH_INFO'], 
     145       '\n'.join(['<li><a href="%s">%s</a></li>' % (link, name)  
     146                 for link,name in self.method.items() if name != 'default'])) 
     147 
     148 
    129149            start_response('200 OK',  
    130150                           [('Content-type', 'text/html'), 
     
    137157                            ('Content-length', str(len(response)))]) 
    138158        return response 
    139      
     159 
     160 
     161    def test_accessDeniedToSecuredURI(self, environ, start_response): 
     162        """To be secured, the Authorization middleware must have this URI in 
     163        its policy and the user must not have the required role as specified 
     164        in the policy.  See ndg.security.test.config.attributeauthority.sitea 
     165        for user role settings retrieved from the attribute authority""" 
     166        if 'REMOTE_USER' in environ: 
     167            response = """<html> 
     168    <head/> 
     169    <body> 
     170        <h1>Authorised for path [%s]!</h1> 
     171        <ul>%s</ul> 
     172        <p>You are logged in.  <a href="/logout">Logout</a></p> 
     173    </body> 
     174</html> 
     175""" % (environ['PATH_INFO'], 
     176       '\n'.join(['<li><a href="%s">%s</a></li>' % (link, name)  
     177                 for link,name in self.method.items() if name != 'default'])) 
     178 
     179 
     180            start_response('200 OK',  
     181                           [('Content-type', 'text/html'), 
     182                            ('Content-length', str(len(response)))]) 
     183        else: 
     184            response = ("Authorization middleware must have this URI in its " 
     185                        "policy in order to secure it!") 
     186            start_response('200 OK',  
     187                           [('Content-type', 'text/plain'), 
     188                            ('Content-length', str(len(response)))]) 
     189        return response 
     190    
    140191    @classmethod 
    141192    def app_factory(cls, globalConfig, **localConfig): 
Note: See TracChangeset for help on using the changeset viewer.