Changeset 6043


Ignore:
Timestamp:
24/11/09 14:14:53 (10 years ago)
Author:
pjkersha
Message:

Unit tested MSI PDP with per attribute entry attribute authority addresses.

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

    r6033 r6043  
    522522 
    523523 
     524class PIPBase(object): 
     525    """Policy Information Point base class.  PIP enables PDP to get user  
     526    attribute information in order to make access control decisions  
     527    """ 
     528    def __init__(self, prefix='', **cfg): 
     529        '''Initialise settings for connection to an Attribute Authority''' 
     530        raise NotImplementedError(PIPBase.__init__.__doc__) 
     531     
     532    def attributeQuery(self, attributeQuery): 
     533        """Query the Attribute Authority specified in the request to retrieve 
     534        the attributes if any corresponding to the subject 
     535         
     536        @type attributeResponse: PIPAttributeQuery 
     537        @param attributeResponse:  
     538        @rtype: PIPAttributeResponse 
     539        @return: response containing the attributes retrieved from the 
     540        Attribute Authority""" 
     541        raise NotImplementedError(PIPBase.attributeQuery.__doc__) 
     542     
     543 
    524544from ndg.security.common.wssecurity import WSSecurityConfig 
    525545 
    526  
    527 class PIP(object): 
     546class NdgPIP(PIPBase): 
    528547    """Policy Information Point - this implementation enables the PDP to  
    529548    retrieve attributes about the Subject""" 
     
    541560        ''' 
    542561        self.wssecurityCfg = WSSecurityConfig() 
    543         wssePrefix = prefix + PIP.wsseSectionName 
     562        wssePrefix = prefix + NdgPIP.wsseSectionName 
    544563        self.wssecurityCfg.update(cfg, prefix=wssePrefix) 
    545564                  
     
    551570        # Attribute Certificates retrieved 
    552571        self.caCertFilePathList = cfg.get(prefix + 'caCertFilePathList', []) 
    553  
    554572 
    555573    def attributeQuery(self, attributeQuery): 
     
    636654         
    637655        return attrCert 
    638      
    639656             
    640657    def _getAttributeCertificateFromSessionManager(self, 
     
    668685            log.error("Creating Session Manager client: %s" % e) 
    669686            raise InitSessionCtxError() 
    670          
    671           
     687              
    672688        try: 
    673689            # Make request for attribute certificate 
     
    703719                       e.__class__, e)) 
    704720            raise AttributeCertificateRequestError() 
    705  
    706721             
    707722    def _getAttributeCertificateFromAttributeAuthority(self, 
     
    752767                      e.__class__, e) 
    753768            raise AttributeCertificateRequestError() 
    754   
    755             
     769         
     770# Backwards compatibility 
     771PIP = NdgPIP 
     772 
     773           
    756774class PDP(object): 
    757775    """Policy Decision Point""" 
     
    763781 
    764782    def _getPolicy(self): 
    765         if self._policy is None: 
     783        if self.__policy is None: 
    766784            raise TypeError("Policy object has not been initialised") 
    767         return self._policy 
     785        return self.__policy 
    768786     
    769787    def _setPolicy(self, policy): 
     
    771789            raise TypeError("Expecting %s or None type for PDP policy; got %r"% 
    772790                            (Policy.__class__.__name__, policy)) 
    773         self._policy = policy 
     791        self.__policy = policy 
    774792 
    775793    policy = property(fget=_getPolicy, 
     
    779797 
    780798    def _getPIP(self): 
    781         if self._pip is None: 
     799        if self.__pip is None: 
    782800            raise TypeError("PIP object has not been initialised") 
    783801         
    784         return self._pip 
     802        return self.__pip 
    785803     
    786804    def _setPIP(self, pip): 
    787         if not isinstance(pip, (PIP, None.__class__)): 
     805        if not isinstance(pip, (PIPBase, None.__class__)): 
    788806            raise TypeError("Expecting %s or None type for PDP PIP; got %r"% 
    789                             (PIP.__class__.__name__, pip)) 
    790         self._pip = pip 
     807                            (PIPBase.__class__.__name__, pip)) 
     808        self.__pip = pip 
    791809 
    792810    pip = property(fget=_getPIP, 
     
    816834        status = [] 
    817835         
     836        # Make a query object for querying the Policy Information Point 
     837        attributeQuery = PIPAttributeQuery() 
     838        attributeQuery[PIPAttributeQuery.SUBJECT_NS] = request.subject 
     839         
     840        # Keep a cache of queried Attribute Authorities to avoid calling them  
     841        # multiple times 
     842        queriedAttributeAuthorityURIs = [] 
     843         
     844        # Iterate through the targets gathering user attributes from the 
     845        # relevant attribute authorities 
    818846        for matchingTarget in matchingTargets: 
    819847             
    820848            # Make call to the Policy Information Point to pull user 
    821             # attributes applicable to this resource 
    822             attributeQuery = PIPAttributeQuery() 
    823             attributeQuery[PIPAttributeQuery.SUBJECT_NS] = request.subject 
    824              
    825             attributeQuery[PIPAttributeQuery.ATTRIBUTEAUTHORITY_NS] = \ 
    826                                     matchingTarget.attributeAuthorityURI 
    827              
    828             # Exit from function returning indeterminate status if a  
    829             # problem occurs here 
    830             try: 
    831                 attributeResponse = self.pip.attributeQuery(attributeQuery) 
    832                  
    833             except SubjectRetrievalError, e: 
    834                 # i.e. a defined exception within the scope of this 
    835                 # module 
    836                 log.exception(e) 
    837                 return Response(Response.DECISION_INDETERMINATE, 
    838                                 message=str(e)) 
    839                  
    840             except Exception, e: 
    841                 log.exception(e) 
    842                 return Response(Response.DECISION_INDETERMINATE, 
    843                                 message="An internal error occurred") 
    844                              
    845             # Accumulate attributes retrieved from multiple attribute 
    846             # authorities 
    847             request.subject[Subject.ROLES_NS] += attributeResponse[ 
    848                                                         Subject.ROLES_NS] 
     849            # attributes applicable to this resource  
     850            for attribute in matchingTarget.attributes: 
     851                if (attribute.attributeAuthorityURI in  
     852                    queriedAttributeAuthorityURIs):  
     853                    continue 
     854                           
     855                attributeQuery[PIPAttributeQuery.ATTRIBUTEAUTHORITY_NS] = \ 
     856                                        attribute.attributeAuthorityURI 
     857             
     858                # Exit from function returning indeterminate status if a  
     859                # problem occurs here 
     860                try: 
     861                    attributeResponse = self.pip.attributeQuery(attributeQuery) 
     862                     
     863                except SubjectRetrievalError, e: 
     864                    # i.e. a defined exception within the scope of this 
     865                    # module 
     866                    log.exception(e) 
     867                    return Response(Response.DECISION_INDETERMINATE,  
     868                                    message=str(e)) 
     869                                 
     870                except Exception, e: 
     871                    log.exception(e) 
     872                    return Response(Response.DECISION_INDETERMINATE, 
     873                                    message="An internal error occurred") 
     874                                 
     875                # Accumulate attributes retrieved from multiple attribute 
     876                # authorities 
     877                request.subject[Subject.ROLES_NS] += attributeResponse[ 
     878                                                            Subject.ROLES_NS] 
    849879                
    850880            # Match the subject's attributes against the target 
     
    869899         
    870900    @staticmethod 
    871     def _match(resourceAttr, subjectRoleAttr): 
     901    def _match(resourceAttr, subjectAttr): 
    872902        """Helper method to iterate over user and resource attributes 
    873903        If one at least one match is found, a permit response is returned 
    874904        """ 
    875905        for attr in resourceAttr: 
    876             if attr in subjectRoleAttr: 
     906            if attr.name in subjectAttr: 
    877907                return Response.DECISION_PERMIT 
    878908             
  • TI12-security/trunk/python/ndg_security_common/ndg/security/common/credentialwallet.py

    r6040 r6043  
    19231923 
    19241924    def __setattr__(self, name, value): 
     1925        """Enable setting of SAML query attribute objects via a comma separated 
     1926        string suitable for use reading from an ini file.  Also enable direct 
     1927        setting of SSLContextProxy attributes as if they are attributes of 
     1928        this class again for convenience with ini file parsing. 
     1929        """ 
    19251930        try: 
    19261931            super(SamlCredentialWallet, self).__setattr__(name, value) 
  • TI12-security/trunk/python/ndg_security_common/ndg/security/common/utils/m2crypto.py

    r6040 r6043  
    291291            
    292292        return ctx 
    293      
     293  
     294    def copy(self, sslCtxProxy): 
     295        """Copy settings from another context object 
     296        """ 
     297        if not isinstance(sslCtxProxy, SSLContextProxy): 
     298            raise TypeError('Expecting %r for copy method input object; ' 
     299                            'got %r' % (SSLContextProxy, type(sslCtxProxy))) 
     300         
     301        for name in SSLContextProxy.OPTNAMES: 
     302            setattr(self, name, getattr(self, sslCtxProxy)) 
     303             
    294304    def createVerifySSLPeerCertCallback(self): 
    295305        """Create a callback function to enable the DN of the peer in an SSL 
  • TI12-security/trunk/python/ndg_security_server/ndg/security/server/wsgi/authz.py

    r6039 r6043  
    1717from ndg.security.common.utils.classfactory import importClass 
    1818from ndg.security.common.X509 import X500DN 
    19 from ndg.security.server.wsgi import NDGSecurityMiddlewareBase, \ 
    20     NDGSecurityMiddlewareConfigError 
    21  
    22 from ndg.security.server.wsgi import NDGSecurityMiddlewareBase, \ 
    23     NDGSecurityMiddlewareConfigError 
    24 from ndg.security.server.wsgi.authn import SessionMiddlewareBase 
    25  
    26 from ndg.security.common.authz.msi import Policy, PIP, PDP, Request, \ 
    27     Response, Resource, Subject 
     19from ndg.security.common.utils.m2crypto import SSLClientProxy 
     20 
     21from ndg.security.common.credentialwallet import (CredentialWallet, 
     22                                                  SamlCredentialWallet) 
     23from ndg.security.server.wsgi import (NDGSecurityMiddlewareBase,  
     24                                      NDGSecurityMiddlewareConfigError) 
     25 
     26from ndg.security.server.wsgi import (NDGSecurityMiddlewareBase,  
     27                                      NDGSecurityMiddlewareConfigError) 
     28from ndg.security.server.wsgi.authn import (SessionMiddlewareBase,  
     29                                            SessionHandlerMiddlewareBase) 
     30 
     31from ndg.security.common.authz.msi import (Policy, PIP, PIPBase,  
     32                                           PIPAttributeQuery,  
     33                                           PIPAttributeResponse, PDP, Request,  
     34                                           Response, Resource, Subject) 
     35 
    2836 
    2937class PEPResultHandlerMiddleware(SessionMiddlewareBase): 
     
    314322 
    315323    
    316 from ndg.security.common.authz.msi import PIP 
    317 from ndg.security.common.credentialwallet import CredentialWallet 
    318  
    319324class PIPMiddlewareError(Exception): 
    320325    """Base class for Policy Information Point WSGI middleware exception types 
     
    325330    """ 
    326331     
    327 class PIPMiddleware(PIP, NDGSecurityMiddlewareBase): 
     332class NdgPIPMiddleware(PIP, NDGSecurityMiddlewareBase): 
    328333    '''Extend Policy Information Point to enable caching of credentials in 
    329334    a CredentialWallet object held in beaker.session 
    330335    ''' 
    331     environKey = 'ndg.security.server.wsgi.authz.PIPMiddleware' 
     336    ENVIRON_KEYNAME = 'ndg.security.server.wsgi.authz.NdgPIPMiddleware' 
    332337        
    333338    propertyDefaults = { 
     
    387392        self.session = environ.get(self.sessionKey) 
    388393        if self.session is None: 
    389             raise PIPMiddlewareConfigError('No beaker session key "%s" found ' 
     394            raise NdgPIPMiddlewareConfigError('No beaker session key "%s" found ' 
    390395                                           'in environ' % self.sessionKey) 
    391         environ[PIPMiddleware.environKey] = self 
     396        environ[NdgPIPMiddleware.ENVIRON_KEYNAME] = self 
    392397         
    393398        return self._app(environ, start_response) 
     
    416421        # any other security keys when the user logs out 
    417422        if not 'credentialWallet' in self.session: 
    418             log.debug("PIPMiddleware._getAttributeCertificate: adding a " 
     423            log.debug("NdgPIPMiddleware._getAttributeCertificate: adding a " 
    419424                      "Credential Wallet to user session [%s] ...", 
    420425                      self.session['username']) 
     
    433438        attrCert = credentialItem.credential 
    434439        if attrCert is not None: 
    435             log.debug("PIPMiddleware._getAttributeCertificate: retrieved " 
     440            log.debug("NdgPIPMiddleware._getAttributeCertificate: retrieved " 
    436441                      "existing Attribute Certificate cached in Credential " 
    437442                      "Wallet for user session [%s]", 
     
    446451                                                    **kw) 
    447452             
    448             log.debug("PIPMiddleware._getAttributeCertificate: updating " 
     453            log.debug("NdgPIPMiddleware._getAttributeCertificate: updating " 
    449454                      "Credential Wallet with retrieved Attribute " 
    450455                      "Certificate for user session [%s]", 
     
    457462             
    458463            return attrCert 
    459  
     464     
     465 
     466class SamlPIPMiddleware(PIPBase, NDGSecurityMiddlewareBase): 
     467    '''Extend Policy Information Point to enable caching of SAML credentials in 
     468    a CredentialWallet object held in beaker.session 
     469    ''' 
     470    ENVIRON_KEYNAME = 'ndg.security.server.wsgi.authz.SamlPIPMiddleware' 
    460471        
     472    propertyDefaults = { 
     473        'sessionKey': 'beaker.session.ndg.security', 
     474    } 
     475    propertyDefaults.update(NDGSecurityMiddlewareBase.propertyDefaults) 
     476   
     477    CREDENTIAL_WALLET_SESSION_KEYNAME = \ 
     478        SessionHandlerMiddlewareBase.CREDENTIAL_WALLET_SESSION_KEYNAME 
     479    USERNAME_SESSION_KEYNAME = \ 
     480        SessionHandlerMiddlewareBase.USERNAME_SESSION_KEYNAME  
     481           
     482    def __init__(self, app, global_conf, prefix='', **local_conf): 
     483        ''' 
     484        @type app: callable following WSGI interface 
     485        @param app: next middleware application in the chain       
     486        @type global_conf: dict         
     487        @param global_conf: PasteDeploy global configuration dictionary 
     488        @type prefix: basestring 
     489        @param prefix: prefix for configuration items 
     490        @type local_conf: dict         
     491        @param local_conf: PasteDeploy application specific configuration  
     492        dictionary 
     493        ''' 
     494        # Hold SSL Attribute Authority connection settings in SSL Context Proxy 
     495        # object 
     496        self.__sslCtxProxy = SSLContextProxy() 
     497         
     498        nameOffset = len(prefix) 
     499        for k in local_conf.keys(): 
     500            if k.startswith(prefix): 
     501                val = local_conf.pop(k) 
     502                name = k[nameOffset:] 
     503                setattr(self.__sslCtxProxy, name, val) 
     504                 
     505        NDGSecurityMiddlewareBase.__init__(self, app, {}) 
     506      
     507    @property 
     508    def sslCtxProxy(self): 
     509        """SSL Context Proxy object used for setting up an SSL Context for 
     510        queries to the Attribute Authority 
     511        """ 
     512        return self.__sslCtxProxy 
     513     
     514             
     515    def __call__(self, environ, start_response): 
     516        """Take a copy of the session object so that it is in scope for 
     517        _getAttributeCertificate call and add this instance to the environ 
     518        so that the PEPFilter can retrieve it and pass on to the PDP 
     519         
     520        @type environ: dict 
     521        @param environ: WSGI environment variables dictionary 
     522        @type start_response: function 
     523        @param start_response: standard WSGI start response function 
     524        @rtype: iterable 
     525        @return: response 
     526        """ 
     527        self.session = environ.get(self.sessionKey) 
     528        if self.session is None: 
     529            raise SamlPIPMiddlewareConfigError('No beaker session key "%s" ' 
     530                                               'found in environ' %  
     531                                               self.sessionKey) 
     532        environ[SamlPIPMiddleware.ENVIRON_KEYNAME] = self 
     533         
     534        return self._app(environ, start_response) 
     535     
     536    def attributeQuery(self, attributeQuery): 
     537        """Query the Attribute Authority specified in the request to retrieve 
     538        the attributes if any corresponding to the subject 
     539         
     540        @type attributeResponse: PIPAttributeQuery 
     541        @param attributeResponse:  
     542        @rtype: PIPAttributeResponse 
     543        @return: response containing the attributes retrieved from the 
     544        Attribute Authority""" 
     545         
     546        attributeAuthorityURI = attributeQuery[ 
     547                                    PIPAttributeQuery.ATTRIBUTEAUTHORITY_NS] 
     548         
     549        log.debug("SamlPIPMiddleware: received attribute query: %r",  
     550                  attributeQuery) 
     551                
     552        # Check for a wallet in the current session - if not present, create 
     553        # one.  See ndg.security.server.wsgi.authn.SessionHandlerMiddleware 
     554        # for session keys.  The 'credentialWallet' key is deleted along with 
     555        # any other security keys when the user logs out 
     556        credentialWalletKeyName = \ 
     557            SamlPIPMiddleware.CREDENTIAL_WALLET_SESSION_KEYNAME 
     558        usernameKeyName = SamlPIPMiddleware.USERNAME_SESSION_KEYNAME 
     559             
     560        if not credentialWalletKeyName in self.session: 
     561            log.debug("SamlPIPMiddleware.attributeQuery: adding a " 
     562                      "Credential Wallet to user session [%s] ...", 
     563                      self.session[usernameKeyName]) 
     564             
     565            credentialWallet = SamlCredentialWallet() 
     566            credentialWallet.userId = self.session[usernameKeyName] 
     567            credentialWallet.sslCtxProxy.copy(self.sslCtxProxy) 
     568             
     569            self.session[credentialWalletKeyName] = credentialWallet 
     570            self.session.save() 
     571        else:     
     572            # Take reference to wallet for efficiency 
     573            credentialWallet = self.session[credentialWalletKeyName]     
     574         
     575        # Check for existing credentials cached in wallet             
     576        credentialItem = credentialWallet.credentialsKeyedByURI.get( 
     577                                                    attributeAuthorityURI) 
     578        if credentialItem is None: 
     579            # No assertion is cached - make a fresh query 
     580            credentialWallet.attributeAuthorityURI = attributeAuthorityURI 
     581            credentialWallet.attributeQuery() 
     582            credentialItem = credentialWallet.credentialsKeyedByURI.get( 
     583                                                    attributeAuthorityURI) 
     584             
     585            log.debug("SamlPIPMiddleware.attributeQuery: updating Credential " 
     586                      "Wallet with retrieved SAML Attribute Assertion " 
     587                      "for user session [%s]", 
     588                      self.session[usernameKeyName]) 
     589        else: 
     590            log.debug("SamlPIPMiddleware.attributeQuery: retrieved existing " 
     591                      "SAML Attribute Assertion cached in Credential Wallet " 
     592                      "for user session [%s]", 
     593                      self.session[usernameKeyName]) 
     594 
     595        attributeResponse = PIPAttributeResponse() 
     596        attributeResponse[Subject.ROLES_NS] = [ 
     597            attribute.value  
     598            for attribute in credentialItem.credential.attributes 
     599        ] 
     600         
     601        log.debug("SamlPIPMiddleware.attributeQuery response: %r",  
     602                  attributeResponse) 
     603         
     604        return attributeResponse 
     605     
     606            
    461607from authkit.authenticate.multi import MultiHandler 
    462608 
     
    505651        # ahead of the PDP's request to it for an Attribute Certificate 
    506652        pipPrefix = AuthorizationMiddleware.PIP_PARAM_PREFIX 
    507         pipFilter = PIPMiddleware(pepFilter, 
    508                                   global_conf, 
    509                                   prefix=pipPrefix, 
    510                                   **app_conf) 
     653        pipFilter = SamlPIPMiddleware(pepFilter, 
     654                                      global_conf, 
     655                                      prefix=pipPrefix, 
     656                                      **app_conf) 
    511657        pepFilter.pdp.pip = pipFilter 
    512658         
  • TI12-security/trunk/python/ndg_security_test/ndg/security/test/unit/authz/msi/test_msi.py

    r6022 r6043  
    1111from os import path 
    1212from ndg.security.test.unit import BaseTestCase 
    13 from ndg.security.common.authz.msi import Policy 
     13from ndg.security.common.authz.msi import (Policy, PDP, PIPBase, Subject, 
     14                                           Request, Resource, Response, 
     15                                           PIPAttributeQuery, 
     16                                           PIPAttributeResponse) 
    1417 
    15  
    16 class PolicyTestCase(BaseTestCase): 
     18class MsiBaseTestCase(BaseTestCase): 
     19    """Base class for passing common class variables between unit test classes 
     20    in this module""" 
    1721    THIS_DIR = path.dirname(__file__) 
    18     POLICY_1_0_FILENAME = 'policy-1.0.xml' 
    19     POLICY_1_0_FILEPATH = path.join(THIS_DIR, POLICY_1_0_FILENAME) 
    2022    POLICY_1_1_FILENAME = 'policy-1.1.xml' 
    2123    POLICY_1_1_FILEPATH = path.join(THIS_DIR, POLICY_1_1_FILENAME) 
     24     
     25     
     26class PolicyTestCase(MsiBaseTestCase): 
     27    """Unit tests for the MSI Policy""" 
     28    POLICY_1_0_FILENAME = 'policy-1.0.xml' 
     29    POLICY_1_0_FILEPATH = path.join(MsiBaseTestCase.THIS_DIR,  
     30                                    POLICY_1_0_FILENAME) 
    2231    ATTRIBUTE_AUTHORITY_URI = 'http://localhost:7443/AttributeAuthority' 
    2332     
     
    3645                       PolicyTestCase.ATTRIBUTE_AUTHORITY_URI) 
    3746         
    38     def test01ParseVersion1_1PolicyFile(self): 
     47    def test02ParseVersion1_1PolicyFile(self): 
    3948        policy = Policy.Parse(PolicyTestCase.POLICY_1_1_FILEPATH) 
    4049         
     
    4857                assert(attribute.name) 
    4958                assert(attribute.attributeAuthorityURI) 
     59 
     60 
     61class PIPPlaceholder(PIPBase): 
     62    """Policy Information Point for Testing the PDP""" 
     63    def __init__(self): 
     64        pass 
     65     
     66    def attributeQuery(self, attributeQuery): 
     67        subject = attributeQuery[PIPAttributeQuery.SUBJECT_NS] 
     68        username = subject[Subject.USERID_NS] 
     69         
     70        attributeResponse = PIPAttributeResponse() 
     71         
     72        if username == BaseTestCase.OPENID_URI: 
     73            attributeResponse[Subject.ROLES_NS] = BaseTestCase.ATTRIBUTE_VALUES 
     74             
     75        return attributeResponse 
     76 
     77     
     78class PDPTestCase(MsiBaseTestCase): 
     79    """Unit tests for the Policy Decision Point""" 
     80    PERMITTED_RESOURCE_URI = '/test_securedURI' 
     81    DENIED_RESOURCE_URI = '/test_accessDeniedToSecuredURI' 
     82     
     83    def setUp(self): 
     84        pip = PIPPlaceholder() 
     85        policy = Policy.Parse(PDPTestCase.POLICY_1_1_FILEPATH) 
     86        self.pdp = PDP(policy, pip) 
     87         
     88        # Make a request object to pass to the PDP 
     89        self.request = Request() 
     90        self.request.subject[Subject.USERID_NS] = PDPTestCase.OPENID_URI 
     91     
     92    def test01AccessPermitted(self): 
     93        self.request.resource[Resource.URI_NS 
     94                              ] = PDPTestCase.PERMITTED_RESOURCE_URI 
     95        response = self.pdp.evaluate(self.request) 
     96         
     97        self.assert_(response.status == Response.DECISION_PERMIT) 
     98 
     99    def test02AccessDenied(self): 
     100        self.request.resource[Resource.URI_NS] = PDPTestCase.DENIED_RESOURCE_URI       
     101        response = self.pdp.evaluate(self.request) 
     102         
     103        self.assert_(response.status == Response.DECISION_DENY) 
     104 
     105         
     106if __name__ == "__main__": 
     107    import unittest 
     108    unittest.main() 
Note: See TracChangeset for help on using the changeset viewer.