Changeset 7257 for TI12-security


Ignore:
Timestamp:
29/07/10 21:32:16 (9 years ago)
Author:
pjkersha
Message:

Incomplete - task 2: XACML-Security Integration

  • cleaning out more old modules containing retired NDG2 security functionality
  • progress with ndg.security.test.unit.wsgi.authz.test_authz unit tests integrating SAML/XACML authorisation service to WSGI filter SAML PEP
Location:
TI12-security/trunk/NDGSecurity/python
Files:
1 added
3 deleted
4 edited
1 moved

Legend:

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

    r7168 r7257  
     1'''NDG Security Policy Enforcement Point Module 
     2 
     3__author__ = "P J Kershaw" 
     4__date__ = "11/07/10" 
     5__copyright__ = "(C) 2010 Science and Technology Facilities Council" 
     6__license__ = "BSD - see LICENSE file in top-level directory" 
     7__contact__ = "Philip.Kershaw@stfc.ac.uk" 
     8__revision__ = '$Id:$' 
    19''' 
    2 Created on 11 Jul 2010 
    3  
    4 @author: pjkersha 
    5 ''' 
    6 from webob import Request 
    7  
     10import logging 
     11log = logging.getLogger(__name__) 
     12 
     13import httplib 
     14from time import time 
     15 
     16import webob 
     17 
     18from ndg.saml.saml2.core import DecisionType 
    819from ndg.saml.saml2.binding.soap.client.authzdecisionquery import \ 
    920                                            AuthzDecisionQuerySslSOAPBinding 
    10 from ndg.security.server.wsgi.session import SessionHandlerMiddleware 
     21from ndg.security.server.wsgi.session import (SessionMiddlewareBase,  
     22                                              SessionHandlerMiddleware) 
    1123 
    1224 
     
    1527     
    1628     
    17 class SamlPepMiddleware(object): 
     29class SamlPepMiddleware(SessionMiddlewareBase): 
    1830    '''Policy Enforcement Point for ESG with SAML based Interface 
    1931     
     
    165177        self.session = environ[self.sessionKey] 
    166178         
    167         request = Request(environ) 
    168         self.__client.resourceURI = request.url 
    169         self.__client.subjectID = request.remote_user or '' 
    170          
    171         self.__client.send(uri=self.__authzServiceURI) 
    172          
     179        request = webob.Request(environ) 
     180        self.client.resourceURI = request.url 
     181         
     182        # Nb. user may not be logged in hence REMOTE_USER is not set 
     183        self.client.subjectID = request.remote_user or '' 
     184         
     185        samlAuthzResponse = self.client.send(uri=self.__authzServiceURI) 
     186 
     187        # Record the result in the user's session to enable later  
     188        # interrogation by any result handler Middleware 
     189        self.setSession(self.client.query, samlAuthzResponse) 
     190         
     191        # Set HTTP 403 Forbidden response if any of the decisions returned are 
     192        # deny or indeterminate status 
     193        failDecisions = (DecisionType.DENY, DecisionType.INDETERMINATE) 
     194         
     195        for assertion in samlAuthzResponse.assertions: 
     196            for authzDecisionStatement in assertion.authzDecisionStatements: 
     197                if authzDecisionStatement.decision.value in failDecisions: 
     198                    response = webob.Response() 
     199                     
     200                    if not self.client.subjectID: 
     201                        # Access failed and the user is not logged in 
     202                        response.status = httplib.UNAUTHORIZED 
     203                    else: 
     204                        # The user is logged in but not authorised 
     205                        response.status = httplib.FORBIDDEN 
     206                         
     207                    response.body = 'Access denied to %r for user %r' % ( 
     208                                                     self.client.resourceURI, 
     209                                                     self.client.subjectID) 
     210                    response.content_type = 'text/plain' 
     211                    log.info(response.body) 
     212                    return response(environ, start_response) 
     213 
     214        # If got through to here then all is well, call next WSGI middleware/app 
    173215        return self._app(environ, start_response) 
    174216 
    175     def _createAuthzDecisionQuery(self, 
    176                                   resourceURI,  
    177                             subject): 
    178         """Create SAML authorisation decision query object ready for dispatch 
     217    def setSession(self, request, response, save=True): 
     218        """Set PEP context information in the Beaker session using standard key 
     219        names 
     220         
     221        @param session: beaker session 
     222        @type session: beaker.session.SessionObject 
     223        @param request: authorisation decision query 
     224        @type request: ndg.saml.saml2.core.AuthzDecisionQuery 
     225        @param response: authorisation response 
     226        @type response: ndg.saml.saml2.core.Response 
     227        @param save: determines whether session is saved or not 
     228        @type save: bool 
    179229        """ 
    180 #        query = AuthzDecisionQuery() 
    181 #        query.version = SAMLVersion(SAMLVersion.VERSION_20) 
    182 #        query.id = str(uuid4()) 
    183 #        query.issueInstant = datetime.utcnow() 
    184 #         
    185 #        query.issuer = Issuer() 
    186 #        query.issuer.format = Issuer.X509_SUBJECT 
    187 #        query.issuer.value = issuer 
    188 #                         
    189 #        query.subject = Subject()   
    190 #        query.subject.nameID = NameID() 
    191 #        query.subject.nameID.format = "urn:ndg:saml:test:openid" 
    192 #        query.subject.nameID.value = subject 
    193 #    
    194 #        query.resource = resource 
    195 #                  
    196 #        query.actions.append(Action()) 
    197 #        query.actions[0].namespace = actionNs 
    198 #        query.actions[0].value = action     
    199 # 
    200 #        return query         
     230        self.session[self.__class__.PEPCTX_SESSION_KEYNAME] = { 
     231            self.__class__.PEPCTX_REQUEST_SESSION_KEYNAME: request,  
     232            self.__class__.PEPCTX_RESPONSE_SESSION_KEYNAME: response, 
     233            self.__class__.PEPCTX_TIMESTAMP_SESSION_KEYNAME: time() 
     234        } 
     235         
     236        if save: 
     237            self.session.save()      
  • TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/wsgi/authz/service.py

    r7168 r7257  
    1212log = logging.getLogger(__name__) 
    1313 
    14 from datetime import datetime, timedelta 
    15 from uuid import uuid4 
    16  
    17 # SAML authorisation decision query interface 
    18 from ndg.saml.saml2.core import (Response, AuthzDecisionStatement, Assertion, 
    19                                  Action, DecisionType, SAMLVersion, Issuer,  
    20                                  Status, StatusCode, StatusMessage, NameID,  
    21                                  Subject, Conditions) 
     14from ndg.xacml.core.context.pdp import PDP 
     15from ndg.xacml.parsers.etree.factory import ReaderFactory as \ 
     16    XacmlEtreePolicyReaderFactory 
    2217 
    2318from ndg.security.common.authz.pip.esginterface import PIP 
    24  
    25 # XACML Policy Decision Point 
    26 from ndg.xacml.parsers.etree.factory import ReaderFactory 
    27 from ndg.xacml.core.attribute import Attribute 
    28 from ndg.xacml.core.attributevalue import AttributeValueClassFactory 
    29 from ndg.xacml.core.context.pdpinterface import PDPInterface 
    30 from ndg.xacml.core.context.pdp import PDP 
    31 from ndg.xacml.core.context.request import Request 
    32 from ndg.xacml.core.context.resource import Resource 
    33 from ndg.xacml.core import context 
    34  
    35  
    36 class AuthzServiceMiddlewareError(Exception): 
     19from ndg.security.server.xacml.ctx_handler import saml_ctx_handler 
     20 
     21 
     22class AuthorisationServiceMiddlewareError(Exception): 
    3723    """Authorisation Service generic exception type""" 
    3824     
    3925 
    40 class AuthzServiceMiddlewareConfigError(AuthzServiceMiddlewareError): 
     26class AuthorisationServiceMiddlewareConfigError( 
     27                                        AuthorisationServiceMiddlewareError): 
    4128    """Authorisation Service configuration error""" 
    4229     
    4330     
    44 class AuthzServiceMiddleware(object): 
     31class AuthorisationServiceMiddleware(object): 
    4532    '''WSGI to add an NDG Security Authorization Service in the environ. 
     33     
     34    @cvar PIP_CFG_PREFIX: prefix for Policy Information Point related parameters 
     35    @type PIP_CFG_PREFIX: string 
    4636    ''' 
     37    DEFAULT_PARAM_PREFIX = 'authorisationService.' 
    4738    DEFAULT_QUERY_IFACE_KEYNAME = \ 
    4839                        'ndg.security.server.wsgi.authzservice.queryInterface' 
    4940     
    5041    ENVIRON_KEYNAME_QUERY_IFACE_OPTNAME = 'queryInterfaceKeyName' 
    51     ASSERTION_LIFETIME_OPTNAME = 'assertionLifetime' 
     42     
     43    XACML_CTX_HANDLER_PARAM_PREFIX = 'xacmlContext.' 
    5244     
    5345    # For loop based assignment where possible of config options in initialise() 
    5446    AUTHZ_SRVC_OPTION_DEFAULTS = { 
    5547        ENVIRON_KEYNAME_QUERY_IFACE_OPTNAME: DEFAULT_QUERY_IFACE_KEYNAME, 
    56         ASSERTION_LIFETIME_OPTNAME: 60*60*8, # 8 hours as default 
    5748    } 
    5849     
     
    6152     
    6253    __slots__ = ( 
    63         '__policyFilePath', 
     54        '__xacmlCtxHandler', 
    6455        '__queryInterface',  
    6556        '__' + ENVIRON_KEYNAME_QUERY_IFACE_OPTNAME, 
    66         '__' + ASSERTION_LIFETIME_OPTNAME, 
    67         '__pdp',  
    6857        '_app', 
    6958    ) 
    7059         
    7160    def __init__(self, app): 
    72         '''Set-up an Authorization Service instance 
     61        '''Set-up an Authorisation Service instance 
     62         
     63        @param app: next app/middleware in WSGI stack 
     64        @type app: callable 
    7365        ''' 
    7466        self._app = app 
    75         self.__policyFilePath = None 
    76         self.__pdp = None 
     67        self.__xacmlCtxHandler = saml_ctx_handler.SamlCtxHandler() 
    7768        self.__queryInterface = None 
    7869        self.__queryInterfaceKeyName = None 
    79         self.__assertionLifetime = 0. 
    80          
    81     def initialise(self, global_conf, prefix='authorizationservice.', 
    82                    **app_conf): 
    83         """Set-up Authorization Service middleware using a Paste app factory  
    84         pattern.  Overloaded base class method to enable custom settings from  
    85         app_conf 
    86          
    87         @type app: callable following WSGI interface 
    88         @param app: next middleware application in the chain       
    89         @type global_conf: dict         
    90         @param global_conf: PasteDeploy global configuration dictionary 
     70         
     71    def initialise(self, prefix=DEFAULT_PARAM_PREFIX, **app_conf): 
     72        """Set-up Authorization Service middleware from keyword settings 
     73         
    9174        @type prefix: basestring 
    9275        @param prefix: prefix for configuration items 
     
    9578        dictionary 
    9679        """ 
    97         cls = AuthzServiceMiddleware 
     80        cls = AuthorisationServiceMiddleware 
    9881         
    9982        # Loop based assignment where possible 
     
    10386         
    10487        self.queryInterface = self.createQueryInterface()     
    105  
    106         # Initialise the Authorisation Policy 
     88         
     89        # Initialise the Policy Information Point  
     90        pipCfgPrefix = prefix + cls.PIP_CFG_PREFIX 
     91        pip = PIP.fromConfig(app_conf, prefix=pipCfgPrefix) 
     92         
    10793        policyFilePathOptName = prefix + cls.POLICY_FILEPATH_OPTNAME 
    10894        policyFilePath = app_conf.get(policyFilePathOptName) 
    10995        if policyFilePath is None: 
    110             raise AuthzServiceMiddlewareConfigError('No policy file path set - ' 
    111                                                     '"policyFilePath" option ' 
    112                                                     'name') 
    113          
    114         self.policyFilePath = policyFilePath  
    115          
    116         # Initialise the Policy Information Point  
    117         pipCfgPrefix = prefix + cls.PIP_CFG_PREFIX 
    118         pip = PIP.fromConfig(app_conf, prefix=pipCfgPrefix) 
    119              
    120         # Initialise the PDP reading in the policy         
    121         self.pdp = PDP.fromPolicySource(self.policyFilePath, ReaderFactory) 
     96            raise AuthorisationServiceMiddlewareConfigError("No XACML policy " 
     97                                                            "file set") 
     98             
     99        # Initialise the XACML Context handler 
     100        ctxHandlerPrefix = prefix + \ 
     101                                self.__class__.XACML_CTX_HANDLER_PARAM_PREFIX 
     102        lenCtxHandlerPrefix = len(ctxHandlerPrefix) 
     103                                 
     104        for optName, value in app_conf.items(): 
     105            if optName.startswith(ctxHandlerPrefix): 
     106                attrName = optName[lenCtxHandlerPrefix:] 
     107                setattr(self.__xacmlCtxHandler, attrName, value) 
     108                 
     109        # Read XACML policy into PDP 
     110        self.__xacmlCtxHandler.pdp = PDP.fromPolicySource(policyFilePath, 
     111                                                XacmlEtreePolicyReaderFactory) 
    122112         
    123113    @classmethod 
     
    137127        ''' 
    138128        app = cls(app) 
    139         app.initialise(global_conf, **app_conf) 
     129        app.initialise(**app_conf) 
    140130         
    141131        return app 
     
    154144        return self._app(environ, start_response) 
    155145 
    156     _getPolicyFilePath = lambda self: self.__policyFilePath 
    157      
    158     def _setPolicyFilePath(self, value): 
    159         if not isinstance(value, basestring): 
    160             raise TypeError('Expecting string type for "policyFilePath" ' 
    161                             'attribute; got %r' % type(value)) 
    162         self.__policyFilePath = value 
    163  
    164     policyFilePath = property(_getPolicyFilePath,  
    165                               _setPolicyFilePath,  
    166                               doc="Policy file path") 
    167  
    168     def _getIssuerFormat(self): 
    169         if self.__issuerProxy is None: 
    170             return None 
    171         else: 
    172             return self.__issuerProxy.value 
    173  
    174     def _setIssuerFormat(self, value): 
    175         if self.__issuerProxy is None: 
    176             self.__issuerProxy = Issuer() 
    177              
    178         self.__issuerProxy.format = value 
    179  
    180     issuerFormat = property(_getIssuerFormat, _setIssuerFormat,  
    181                             doc="Issuer format") 
    182 # 
    183 #    def _getIssuerName(self): 
    184 #        if self.__issuerProxy is None: 
    185 #            return None 
    186 #        else: 
    187 #            return self.__issuerProxy.value 
    188 # 
    189 #    def _setIssuerName(self, value): 
    190 #        if self.__issuerProxy is None: 
    191 #            self.__issuerProxy = Issuer() 
    192 #             
    193 #        self.__issuerProxy.value = value 
    194 # 
    195 #    issuerName = property(_getIssuerName, _setIssuerName,  
    196 #                          doc="Name of issuer of SAML Authorisation Query " 
    197 #                              "Response") 
    198      
    199     _getAssertionLifetime = lambda self: self.__assertionLifetime 
    200      
    201     def _setAssertionLifetime(self, value): 
    202         if isinstance(value, (int, float, basestring)): 
    203             self.__assertionLifetime = float(value) 
    204         else: 
    205             raise TypeError('Expecting int, float or string type for ' 
    206                             '"assertionLifetime" attribute; got %s instead' %  
    207                             type(value)) 
    208  
    209     assertionLifetime = property(fget=_getAssertionLifetime, 
    210                                  fset=_setAssertionLifetime, 
    211                                  doc="lifetime of assertion in seconds used to " 
    212                                      "set assertion conditions notOnOrAfter " 
    213                                      "time") 
    214           
    215     def _getPdp(self): 
    216         return self.__pdp 
    217  
    218     def _setPdp(self, value): 
    219         if not isinstance(value, PDPInterface): 
    220             raise TypeError('Expecting %r type for "pdp" attribute; got %r ' 
    221                             'instead' % (PDPInterface, value)) 
    222              
    223         self.__pdp = value 
    224  
    225     pdp = property(_getPdp, _setPdp, None, "Policy Decision Point") 
    226146 
    227147    def _get_queryInterfaceKeyName(self): 
     
    269189        """ 
    270190         
    271         # Nest function within AuthzServiceMiddleware method so that self is 
    272         # in its scope 
     191        # Nest function within AuthorisationServiceMiddleware method so that  
     192        # self is in its scope 
    273193        def getAuthzDecision(authzDecisionQuery, samlResponse): 
    274194            """Authorisation decision function accepts a SAML AuthzDecisionQuery 
    275             and calls the Policy Decision Point returning a response 
    276              
    277             @type authzDecisionQuery: saml.saml2.core.AuthzDecisionQuery 
     195            and calls the XACML context handler returning a response.  The 
     196            context handler is an interface to the the XACML Policy Decision  
     197            Point, XACML polic(y|ies) and Policy Information Point. 
     198             
     199            @type authzDecisionQuery: ndg.saml.saml2.core.AuthzDecisionQuery 
    278200            @param authzDecisionQuery: WSGI environment variables dictionary 
    279             @rtype: saml.saml2.core.Response 
     201            @rtype: ndg.saml.saml2.core.Response 
    280202            @return: SAML response containing Authorisation Decision Statement 
    281             """         
    282             request = self._createXacmlRequestCtx(authzDecisionQuery) 
    283              
    284             # Call the PDP 
    285             xacmlResponse = self.pdp.evaluate(request) 
    286              
    287             # Create the SAML Response 
    288             self._createSAMLResponseAssertion(authzDecisionQuery, samlResponse) 
    289             authzDecisionStatement = samlResponse.assertions[0 
    290                                                     ].authzDecisionStatements[0] 
    291              
    292             if (xacmlResponse.results[0].decision ==  
    293                 context.result.Decision.PERMIT): 
    294                 log.info("AuthzServiceMiddleware.__call__: PDP granted " 
    295                          "access for URI path [%s] using policy [%s]",  
    296                          authzDecisionQuery.resource,  
    297                          self.policyFilePath) 
    298                  
    299                 authzDecisionStatement.decision = DecisionType.PERMIT 
    300              
    301             elif (xacmlResponse.results[0].decision ==  
    302                   context.result.Decision.INDETERMINATE): 
    303                 log.info("AuthzServiceMiddleware.__call__: PDP returned a " 
    304                          "status of [%s] denying access for URI path [%s] " 
    305                          "using policy [%s]",  
    306                          context.result.Decision.INDETERMINATE, 
    307                          authzDecisionQuery.resource, 
    308                          self.policyFilePath)  
    309                  
    310                 authzDecisionStatement.decision = DecisionType.INDETERMINATE 
    311                    
    312             else: 
    313                 log.info("AuthzServiceMiddleware.__call__: PDP returned a " 
    314                          "status of [%s] denying access for URI path [%s] " 
    315                          "using policy [%s]",  
    316                          context.result.Decision.DENY, 
    317                          authzDecisionQuery.resource, 
    318                          self.policyFilePath)  
    319                  
    320                 authzDecisionStatement.decision = DecisionType.DENY 
    321      
    322             return samlResponse 
    323          
     203            """ 
     204            # Create special request object which enables the context handler 
     205            # to formulate a response from the query and the existing response 
     206            # object initialised by this object 
     207            request = saml_ctx_handler.SamlPEPRequest() 
     208            request.authzDecisionQuery = authzDecisionQuery 
     209            request.response = samlResponse 
     210             
     211            response = self.__xacmlCtxHandler.handlePEPRequest(request) 
     212             
     213            return response 
     214             
    324215        return getAuthzDecision 
    325      
    326     def _createXacmlRequestCtx(self, authzDecisionQuery): 
    327         """Translate SAML authorisation decision query into a XACML request 
    328         context 
    329         """ 
    330         request = context.request.Request() 
    331         subject = context.subject.Subject() 
    332          
    333         attributeValueFactory = AttributeValueClassFactory() 
    334          
    335         openidSubjectAttribute = Attribute() 
    336         roleAttribute = Attribute() 
    337          
    338         openidSubjectAttribute.attributeId = \ 
    339                                         authzDecisionQuery.subject.nameID.format 
    340                                          
    341         AnyUriAttributeValue = attributeValueFactory( 
    342                                     'http://www.w3.org/2001/XMLSchema#anyURI') 
    343          
    344         openidSubjectAttribute.dataType = AnyUriAttributeValue.IDENTIFIER 
    345          
    346         openidSubjectAttribute.attributeValues.append(AnyUriAttributeValue()) 
    347         openidSubjectAttribute.attributeValues[-1].value = \ 
    348                                     authzDecisionQuery.subject.nameID.value 
    349          
    350         subject.attributes.append(openidSubjectAttribute) 
    351  
    352         StringAttributeValue = attributeValueFactory( 
    353                                     'http://www.w3.org/2001/XMLSchema#string') 
    354  
    355         # TODO: get attributes - replace hard coded values 
    356         roleAttribute.attributeId = "urn:ndg:security:authz:1.0:attr" 
    357         roleAttribute.dataType = StringAttributeValue.IDENTIFIER 
    358          
    359         roleAttribute.attributeValues.append(StringAttributeValue()) 
    360         roleAttribute.attributeValues[-1].value = 'staff'  
    361          
    362         subject.attributes.append(roleAttribute) 
    363                                    
    364         request.subjects.append(subject) 
    365          
    366         resource = Resource() 
    367         resourceAttribute = Attribute() 
    368         resource.attributes.append(resourceAttribute) 
    369          
    370         resourceAttribute.attributeId = \ 
    371                             "urn:oasis:names:tc:xacml:1.0:resource:resource-id" 
    372                              
    373         resourceAttribute.dataType = AnyUriAttributeValue.IDENTIFIER 
    374         resourceAttribute.attributeValues.append(AnyUriAttributeValue()) 
    375         resourceAttribute.attributeValues[-1].value = \ 
    376                                                     authzDecisionQuery.resource 
    377  
    378         request.resources.append(resource) 
    379          
    380         request.action = context.action.Action() 
    381         actionAttribute = Attribute() 
    382         request.action.attributes.append(actionAttribute) 
    383          
    384         actionAttribute.attributeId = \ 
    385                                 "urn:oasis:names:tc:xacml:1.0:action:action-id" 
    386         actionAttribute.dataType = StringAttributeValue.IDENTIFIER 
    387         actionAttribute.attributeValues.append(StringAttributeValue()) 
    388         actionAttribute.attributeValues[-1].value = authzDecisionQuery.actions[0 
    389                                                                         ].value 
    390          
    391         return request 
    392      
    393     def _createSAMLResponseAssertion(self, authzDecisionQuery, response): 
    394         """Helper method to add an assertion containing an Authorisation 
    395         Decision Statement to the SAML response 
    396          
    397         @param authzDecisionQuery: SAML Authorisation Decision Query 
    398         @type authzDecisionQuery: ndg.saml.saml2.core.AuthzDecisionQuery 
    399         @param response: SAML response 
    400         @type response: ndg.saml.saml2.core.Response 
    401         """ 
    402 #        response = Response() 
    403 #         
    404         now = datetime.utcnow() 
    405         response.issueInstant = now 
    406 #         
    407 #        # Make up a request ID that this response is responding to 
    408 #        response.inResponseTo = authzDecisionQuery.id 
    409 #        response.id = str(uuid4()) 
    410 #        response.version = SAMLVersion(SAMLVersion.VERSION_20) 
    411 #             
    412 #        response.issuer = Issuer() 
    413 #        response.issuer.format = self.issuerFormat 
    414 #        response.issuer.value = self.issuerName 
    415 # 
    416 #        response.status = Status() 
    417 #        response.status.statusCode = StatusCode() 
    418 #        response.status.statusMessage = StatusMessage()         
    419 #         
    420 #        response.status.statusCode.value = StatusCode.SUCCESS_URI 
    421 #        response.status.statusMessage.value = ("Response created " 
    422 #                                               "successfully") 
    423          
    424         assertion = Assertion() 
    425         response.assertions.append(assertion) 
    426             
    427         assertion.version = SAMLVersion(SAMLVersion.VERSION_20) 
    428         assertion.id = str(uuid4()) 
    429         assertion.issueInstant = now 
    430          
    431         # Add a conditions statement for a validity of 8 hours 
    432         assertion.conditions = Conditions() 
    433         assertion.conditions.notBefore = now 
    434         assertion.conditions.notOnOrAfter = now + timedelta( 
    435                                                 seconds=self.assertionLifetime) 
    436                 
    437         assertion.subject = Subject() 
    438         assertion.subject.nameID = NameID() 
    439         assertion.subject.nameID.format = \ 
    440             authzDecisionQuery.subject.nameID.format 
    441         assertion.subject.nameID.value = \ 
    442             authzDecisionQuery.subject.nameID.value 
    443          
    444         authzDecisionStatement = AuthzDecisionStatement() 
    445         assertion.authzDecisionStatements.append(authzDecisionStatement) 
    446                      
    447         authzDecisionStatement.resource = authzDecisionQuery.resource 
    448          
    449         for action in authzDecisionQuery.actions: 
    450             authzDecisionStatement.actions.append(Action()) 
    451             authzDecisionStatement.actions[-1].namespace = action.namespace 
    452             authzDecisionStatement.actions[-1].value = action.value 
    453  
    454         return response 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/xacml/ctx_handler/saml_ctx_handler.py

    r7077 r7257  
    1 ''' 
    2 Created on 14 May 2010 
    3  
    4 @author: pjkersha 
    5 ''' 
    6 from ndg.xacml.core.context.handler import CtxHandlerInterface 
    7  
    8  
    9 class SamlCtxHandler(CtxHandlerInterface): 
     1"""XACML Context handler translates to and from SAML Authorisation Decision 
     2Query / Response 
     3 
     4""" 
     5__author__ = "P J Kershaw" 
     6__date__ = "14/05/10" 
     7__copyright__ = "(C) 2010 Science and Technology Facilities Council" 
     8__license__ = "BSD - see LICENSE file in top-level directory" 
     9__contact__ = "Philip.Kershaw@stfc.ac.uk" 
     10__revision__ = '$Id$' 
     11import logging 
     12log = logging.getLogger(__name__) 
     13 
     14from datetime import datetime, timedelta 
     15from uuid import uuid4 
     16 
     17from ndg.saml.saml2 import core as _saml 
     18from ndg.saml.common import SAMLVersion 
     19 
     20from ndg.xacml.core import context as _xacmlContext 
     21from ndg.xacml.core.attribute import Attribute as XacmlAttribute 
     22from ndg.xacml.core.attributevalue import AttributeValueClassFactory as \ 
     23    XacmlAttributeValueClassFactory 
     24from ndg.xacml.parsers.etree.factory import ReaderFactory 
     25 
     26 
     27class SamlPEPRequest(object): 
     28    """Helper class for SamlCtxHandler.handlePEPRequest""" 
     29    __slots__ = ('__authzDecisionQuery', '__response', '__policyFilePath') 
     30     
     31    def __init__(self): 
     32        self.__authzDecisionQuery = None 
     33        self.__response = None 
     34     
     35    def _getAuthzDecisionQuery(self): 
     36        return self.__authzDecisionQuery 
     37 
     38    def _setAuthzDecisionQuery(self, value): 
     39        if not isinstance(value, _saml.AuthzDecisionQuery): 
     40            raise TypeError('Expecting %r type for "response" attribute, got %r' 
     41                            % (_saml.Response, type(value))) 
     42        self.__authzDecisionQuery = value 
     43         
     44    authzDecisionQuery = property(_getAuthzDecisionQuery,  
     45                                  _setAuthzDecisionQuery,  
     46                                  doc="SAML Authorisation Decision Query") 
     47 
     48    def _getResponse(self): 
     49        return self.__response 
     50 
     51    def _setResponse(self, value): 
     52        if not isinstance(value, _saml.Response): 
     53            raise TypeError('Expecting %r type for "response" attribute, got %r' 
     54                            % (_saml.Response, type(value))) 
     55        self.__response = value 
     56 
     57    response = property(_getResponse, _setResponse, doc="SAML Response") 
     58    
     59         
     60class SamlCtxHandler(_xacmlContext.handler.CtxHandlerBase): 
    1061    """XACML Context handler for accepting SAML 2.0 based authorisation 
    1162    decision queries and interfacing to a PEP with SAML based Attribute Query 
    1263    Interface 
    1364    """ 
    14     def pepQuery(self, request, designator): 
     65    __slots__ = ( 
     66        '__policyFilePath', 
     67        '__issuerProxy',  
     68        '__assertionLifetime', 
     69    ) 
     70     
     71    def __init__(self): 
     72        super(SamlCtxHandler, self).__init__() 
     73         
     74        # Proxy object for SAML Response Issuer attributes.  By generating a  
     75        # proxy the Response objects inherent attribute validation can be  
     76        # applied to Issuer related config parameters before they're assigned to 
     77        # the response issuer object generated in the authorisation decision  
     78        # query response 
     79        self.__issuerProxy = _saml.Issuer() 
     80        self.__assertionLifetime = 0. 
     81 
     82    def _getIssuerFormat(self): 
     83        if self.__issuerProxy is None: 
     84            return None 
     85        else: 
     86            return self.__issuerProxy.value 
     87 
     88    def _setIssuerFormat(self, value): 
     89        if self.__issuerProxy is None: 
     90            self.__issuerProxy = _saml.Issuer() 
     91             
     92        self.__issuerProxy.format = value 
     93 
     94    issuerFormat = property(_getIssuerFormat, _setIssuerFormat,  
     95                            doc="Issuer format") 
     96 
     97    def _getIssuerName(self): 
     98        if self.__issuerProxy is None: 
     99            return None 
     100        else: 
     101            return self.__issuerProxy.value 
     102 
     103    def _setIssuerName(self, value): 
     104        if self.__issuerProxy is None: 
     105            self.__issuerProxy = _saml.Issuer() 
     106             
     107        self.__issuerProxy.value = value 
     108 
     109    issuerName = property(_getIssuerName, _setIssuerName,  
     110                          doc="Name of issuer of SAML Authorisation Query " 
     111                              "Response") 
     112     
     113    _getAssertionLifetime = lambda self: self.__assertionLifetime 
     114     
     115    def _setAssertionLifetime(self, value): 
     116        if isinstance(value, (int, float, long, basestring)): 
     117            self.__assertionLifetime = float(value) 
     118        else: 
     119            raise TypeError('Expecting int, long, float or string type for ' 
     120                            '"assertionLifetime" attribute; got %s instead' %  
     121                            type(value)) 
     122 
     123    assertionLifetime = property(fget=_getAssertionLifetime, 
     124                                 fset=_setAssertionLifetime, 
     125                                 doc="lifetime of assertion in seconds used to " 
     126                                     "set assertion conditions notOnOrAfter " 
     127                                     "time") 
     128         
     129    def handlePEPRequest(self, pepRequest): 
     130        """Handle request from Policy Enforcement Point 
     131         
     132        @param pepRequest: request containing a SAML authorisation decision 
     133        query and optionally an initialised SAML response object 
     134        @type pepRequest: ndg.security.server.xacml.saml_ctx_handler.SamlPEPRequest 
     135        @return: SAML authorisation decision response 
     136        @rtype: ndg.saml.saml2.core.Response 
     137        """ 
     138        samlAuthzDecisionQuery = pepRequest.authzDecisionQuery 
     139         
     140        xacmlRequest = self._createXacmlRequestCtx(samlAuthzDecisionQuery) 
     141         
     142        # Call the PDP 
     143        xacmlResponse = self.pdp.evaluate(xacmlRequest) 
     144         
     145        # Create the SAML Response 
     146        samlResponse = self._createSAMLResponseAssertion(samlAuthzDecisionQuery, 
     147                                                         pepRequest.response) 
     148         
     149        samlAuthzDecisionStatement = samlResponse.assertions[0 
     150                                                ].authzDecisionStatements[0] 
     151         
     152        # Convert the decision status 
     153        if (xacmlResponse.results[0].decision ==  
     154            _xacmlContext.result.Decision.PERMIT): 
     155            log.info("PDP granted access for URI path [%s]",  
     156                     samlAuthzDecisionQuery.resource) 
     157             
     158            samlAuthzDecisionStatement.decision = _saml.DecisionType.PERMIT 
     159         
     160        elif (xacmlResponse.results[0].decision ==  
     161              _xacmlContext.result.Decision.INDETERMINATE): 
     162            log.info("PDP returned a status of [%s] denying access for URI " 
     163                     "path [%s]", _xacmlContext.result.Decision.INDETERMINATE, 
     164                     samlAuthzDecisionQuery.resource)  
     165             
     166            samlAuthzDecisionStatement.decision = \ 
     167                                                _saml.DecisionType.INDETERMINATE 
     168        else: 
     169            log.info("PDP returned a status of [%s] denying access for URI " 
     170                     "path [%s]", _xacmlContext.result.Decision.DENY, 
     171                     samlAuthzDecisionQuery.resource)  
     172             
     173            samlAuthzDecisionStatement.decision = _saml.DecisionType.DENY 
     174 
     175        return samlResponse 
     176         
     177    def pipQuery(self, request, designator): 
    15178        """Query a Policy Information Point to retrieve the attribute values 
    16179        corresponding to the specified input designator.  Optionally, update the 
     
    19182        """ 
    20183        return [] 
     184     
     185    def _createXacmlRequestCtx(self, samlAuthzDecisionQuery): 
     186        """Translate SAML authorisation decision query into a XACML request 
     187        context 
     188        """ 
     189        xacmlRequest = _xacmlContext.request.Request() 
     190        xacmlSubject = _xacmlContext.subject.Subject() 
     191         
     192        xacmlAttributeValueFactory = XacmlAttributeValueClassFactory() 
     193         
     194        openidSubjectAttribute = XacmlAttribute() 
     195        roleAttribute = XacmlAttribute() 
     196         
     197        openidSubjectAttribute.attributeId = \ 
     198                                samlAuthzDecisionQuery.subject.nameID.format 
     199                                         
     200        XacmlAnyUriAttributeValue = xacmlAttributeValueFactory( 
     201                                    'http://www.w3.org/2001/XMLSchema#anyURI') 
     202         
     203        openidSubjectAttribute.dataType = XacmlAnyUriAttributeValue.IDENTIFIER 
     204         
     205        openidSubjectAttribute.attributeValues.append( 
     206                                                    XacmlAnyUriAttributeValue()) 
     207        openidSubjectAttribute.attributeValues[-1].value = \ 
     208                                samlAuthzDecisionQuery.subject.nameID.value 
     209         
     210        xacmlSubject.attributes.append(openidSubjectAttribute) 
     211 
     212        XacmlStringAttributeValue = xacmlAttributeValueFactory( 
     213                                    'http://www.w3.org/2001/XMLSchema#string') 
     214 
     215        # TODO: get attributes - replace hard coded values 
     216        roleAttribute.attributeId = "urn:ndg:security:authz:1.0:attr" 
     217        roleAttribute.dataType = XacmlStringAttributeValue.IDENTIFIER 
     218         
     219        roleAttribute.attributeValues.append(XacmlStringAttributeValue()) 
     220        roleAttribute.attributeValues[-1].value = 'staff'  
     221         
     222        xacmlSubject.attributes.append(roleAttribute) 
     223                                   
     224        xacmlRequest.subjects.append(xacmlSubject) 
     225         
     226        resource = _xacmlContext.resource.Resource() 
     227        resourceAttribute = XacmlAttribute() 
     228        resource.attributes.append(resourceAttribute) 
     229         
     230        resourceAttribute.attributeId = \ 
     231                            "urn:oasis:names:tc:xacml:1.0:resource:resource-id" 
     232                             
     233        resourceAttribute.dataType = XacmlAnyUriAttributeValue.IDENTIFIER 
     234        resourceAttribute.attributeValues.append(XacmlAnyUriAttributeValue()) 
     235        resourceAttribute.attributeValues[-1].value = \ 
     236                                                samlAuthzDecisionQuery.resource 
     237 
     238        xacmlRequest.resources.append(resource) 
     239         
     240        xacmlRequest.action = _xacmlContext.action.Action() 
     241         
     242        for action in samlAuthzDecisionQuery.actions: 
     243            xacmlActionAttribute = XacmlAttribute() 
     244            xacmlRequest.action.attributes.append(xacmlActionAttribute) 
     245             
     246            xacmlActionAttribute.attributeId = \ 
     247                                "urn:oasis:names:tc:xacml:1.0:action:action-id" 
     248            xacmlActionAttribute.dataType = XacmlStringAttributeValue.IDENTIFIER 
     249            xacmlActionAttribute.attributeValues.append( 
     250                                                    XacmlStringAttributeValue()) 
     251            xacmlActionAttribute.attributeValues[-1].value = action.value 
     252         
     253        return xacmlRequest 
     254     
     255    def _createSAMLResponseAssertion(self, authzDecisionQuery, response): 
     256        """Helper method to add an assertion containing an Authorisation 
     257        Decision Statement to the SAML response 
     258         
     259        @param authzDecisionQuery: SAML Authorisation Decision Query 
     260        @type authzDecisionQuery: ndg.saml.saml2.core.AuthzDecisionQuery 
     261        @param response: SAML response 
     262        @type response: ndg.saml.saml2.core.Response 
     263        """ 
     264         
     265        # Check for a response set, if none present create one. 
     266        if response is None: 
     267            response = _saml.Response() 
     268             
     269            now = datetime.utcnow() 
     270            response.issueInstant = now 
     271             
     272            # Make up a request ID that this response is responding to 
     273            response.inResponseTo = authzDecisionQuery.id 
     274            response.id = str(uuid4()) 
     275            response.version = SAMLVersion(SAMLVersion.VERSION_20) 
     276                 
     277            response.issuer = _saml.Issuer() 
     278            response.issuer.format = self.issuerFormat 
     279            response.issuer.value = self.issuerName 
     280     
     281            response.status = _saml.Status() 
     282            response.status.statusCode = _saml.StatusCode() 
     283            response.status.statusMessage = _saml.StatusMessage()         
     284             
     285            response.status.statusCode.value = _saml.StatusCode.SUCCESS_URI 
     286            response.status.statusMessage.value = ("Response created " 
     287                                                   "successfully") 
     288         
     289        assertion = _saml.Assertion() 
     290        response.assertions.append(assertion) 
     291            
     292        assertion.version = SAMLVersion(SAMLVersion.VERSION_20) 
     293        assertion.id = str(uuid4()) 
     294         
     295        now = datetime.utcnow() 
     296        assertion.issueInstant = now 
     297         
     298        # Add a conditions statement for a validity of 8 hours 
     299        assertion.conditions = _saml.Conditions() 
     300        assertion.conditions.notBefore = now 
     301        assertion.conditions.notOnOrAfter = now + timedelta( 
     302                                                seconds=self.assertionLifetime) 
     303                
     304        assertion.subject = _saml.Subject() 
     305        assertion.subject.nameID = _saml.NameID() 
     306        assertion.subject.nameID.format = \ 
     307            authzDecisionQuery.subject.nameID.format 
     308        assertion.subject.nameID.value = \ 
     309            authzDecisionQuery.subject.nameID.value 
     310         
     311        authzDecisionStatement = _saml.AuthzDecisionStatement() 
     312        assertion.authzDecisionStatements.append(authzDecisionStatement) 
     313                     
     314        authzDecisionStatement.resource = authzDecisionQuery.resource 
     315         
     316        for action in authzDecisionQuery.actions: 
     317            authzDecisionStatement.actions.append(_saml.Action()) 
     318            authzDecisionStatement.actions[-1].namespace = action.namespace 
     319            authzDecisionStatement.actions[-1].value = action.value 
     320 
     321        return response 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_test/ndg/security/test/config/authorisationservice/authorisation-service.ini

    r7168 r7257  
    3737# This filter is a container for a binding to a SOAP based interface to the 
    3838# Attribute Authority 
    39 paste.filter_app_factory = ndg.saml.test.binding.soap.test_authzservice:TestAuthorisationServiceMiddleware 
    40 queryInterfaceKeyName = AUTHZ_DECISION_QUERY_FUNC 
     39paste.filter_app_factory = ndg.security.server.wsgi.authz.service:AuthorisationServiceMiddleware.filter_app_factory 
     40prefix = authz. 
     41authz.queryInterfaceKeyName = AUTHZ_DECISION_QUERY_FUNC 
     42authz.policyFilePath = %(here)s/policy.xml 
     43authz.xacmlContext.assertionLifetime = 86400 
    4144 
    4245# Logging configuration 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_test/ndg/security/test/unit/wsgi/authz/test_authz.py

    r7168 r7257  
    4141from ndg.saml.xml.etree import (AuthzDecisionQueryElementTree,  
    4242                                ResponseElementTree) 
     43 
    4344 
    4445class TestAuthorisationServiceMiddleware(object): 
     
    124125        return authzDecisionQuery 
    125126 
     127 
    126128class RedirectFollowingAccessDenied(PEPResultHandlerMiddleware): 
    127129     
     
    141143        else: 
    142144            return super(RedirectFollowingAccessDenied, self).__call__( 
    143                                                             environ, 
    144                                                             start_response) 
    145  
    146          
     145                                                                environ, 
     146                                                                start_response) 
     147 
     148 
    147149class TestAuthZMiddleware(object): 
    148150    '''Test Application for the Authentication handler to protect''' 
     
    177179                         str(len(TestAuthZMiddleware.response))), 
    178180                        ('Content-type', 'text/plain')]) 
     181         
    179182        return [TestAuthZMiddleware.response] 
    180183 
     
    195198        BaseTestCase.__init__(self, *args, **kwargs) 
    196199 
    197          
    198200        wsgiapp = loadapp('config:'+SamlWSGIAuthZTestCase.INI_FILE,  
    199201                          relative_to=SamlWSGIAuthZTestCase.THIS_DIR) 
     
    238240 
    239241    def test04Catch403WithLoggedIn(self): 
    240          
    241242        # Check that the application being secured can raise a HTTP 403 
    242243        # response and that this respected by the Authorization middleware 
     
    245246        extra_environ = { 
    246247            self.__class__.SESSION_KEYNAME: 
    247                 BeakerSessionStub(username=SamlWSGIAuthZTestCase.OPENID_URI) 
     248                BeakerSessionStub(username=SamlWSGIAuthZTestCase.OPENID_URI), 
     249            'REMOTE_USER': self.__class__.OPENID_URI 
    248250        } 
    249251        response = self.app.get('/test_403',  
     
    252254 
    253255    def test05Catch401WithNotLoggedInAndSecuredURI(self): 
    254          
    255         # AuthZ middleware grants access because the URI requested is not  
    256         # targeted in the policy 
     256        # AuthZ middleware grants access because the URI requested is has no 
     257        # subject restriction set in the policy rule 
    257258         
    258259        # AuthZ middleware checks for username key in session set by AuthN 
     
    269270        extra_environ = { 
    270271            self.__class__.SESSION_KEYNAME: 
    271                 BeakerSessionStub(username=SamlWSGIAuthZTestCase.OPENID_URI) 
     272                BeakerSessionStub(username=SamlWSGIAuthZTestCase.OPENID_URI), 
     273            'REMOTE_USER': self.__class__.OPENID_URI 
    272274        } 
    273275         
     
    285287        extra_environ = { 
    286288            self.__class__.SESSION_KEYNAME: 
    287                 BeakerSessionStub(username=SamlWSGIAuthZTestCase.OPENID_URI) 
     289                BeakerSessionStub(username=SamlWSGIAuthZTestCase.OPENID_URI), 
     290            'REMOTE_USER': self.__class__.OPENID_URI 
    288291        } 
    289292         
     
    300303        extra_environ = { 
    301304            self.__class__.SESSION_KEYNAME: 
    302                 BeakerSessionStub(username=SamlWSGIAuthZTestCase.OPENID_URI) 
     305                BeakerSessionStub(username=SamlWSGIAuthZTestCase.OPENID_URI), 
     306            'REMOTE_USER': self.__class__.OPENID_URI 
    303307        } 
    304308         
     
    347351        # User is logged in but doesn't have the required credentials for  
    348352        # access 
    349         extra_environ = { 
    350             self.__class__.SESSION_KEYNAME: 
    351                         BeakerSessionStub(username=self.__class__.OPENID_URI) 
    352         } 
    353          
    354         # Expecting redirect response to specified redirect URI 
    355         response = self.app.get('/test_accessDeniedToSecuredURI', 
    356                                 extra_environ=extra_environ, 
    357                                 status=302) 
    358         print(response) 
    359         self.assert_(response.header_dict.get('location') == self.redirectURI) 
     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) 
    360365         
    361366if __name__ == "__main__": 
Note: See TracChangeset for help on using the changeset viewer.