Changeset 7358


Ignore:
Timestamp:
24/08/10 15:34:07 (9 years ago)
Author:
pjkersha
Message:

Incomplete - task 2: XACML-Security Integration

  • added caching capability to Policy Information Point. This enables the PIP to retrieve previously cached assertions from an Attribute Authority optimising performance. Caching is done with beaker.session but instead of indexing based on a cookie, it's based on the subject Id i.e. for ESG, a user's OpenID.
Location:
TI12-security/trunk/NDGSecurity/python
Files:
5 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/NDGSecurity/python/ndg_security_common/ndg/security/common/credentialwallet.py

    r7357 r7358  
    2222from ConfigParser import ConfigParser 
    2323 
     24try: 
     25    from abc import ABCMeta, abstractmethod 
     26except ImportError: 
     27    # Allow for Python version < 2.6 
     28    ABCMeta = type 
     29    abstractmethod = lambda f: f 
     30     
    2431from ndg.saml.utils import SAMLDateTime 
    2532from ndg.saml.saml2.core import Assertion 
    2633 
     34from ndg.security.common.utils import TypedList 
    2735from ndg.security.common.utils.configfileparsers import (      
    2836                                                    CaseSensitiveConfigParser,) 
     
    3846    to enable writing to the log""" 
    3947 
    40            
    41 class _MetaCredentialWallet(type): 
    42     """Enable CredentialWallet to have read only class variables e.g. 
    43      
    44     print CredentialWallet.accessDenied  
    45      
    46     ... is allowed but, 
    47      
    48     CredentialWallet.accessDenied = None 
    49      
    50     ... raises - AttributeError: can't set attribute""" 
    51      
    52     def _getAccessDenied(cls): 
    53         '''accessDenied get method''' 
    54         return False 
    55      
    56     accessDenied = property(fget=_getAccessDenied) 
    57      
    58     def _getAccessGranted(cls): 
    59         '''accessGranted get method''' 
    60         return True 
    61      
    62     accessGranted = property(fget=_getAccessGranted) 
    63  
    6448 
    6549class CredentialContainer(object): 
    6650    """Container for cached credentials""" 
    6751    ID_ATTRNAME = 'id' 
    68     ITEM_ATTRNAME = 'credential' 
     52    ITEM_ATTRNAME = 'credentials' 
    6953    ISSUERNAME_ATTRNAME = 'issuerName' 
    7054    CREDENTIAL_TYPE_ATTRNAME = 'type' 
     
    8468         
    8569        self.__id = -1 
    86         self.__credential = None 
     70        self.__assertionsMap = None 
    8771        self.__issuerName = None 
    8872 
     
    11397                      "set to -1 for new credentials") 
    11498 
    115     def _getCredential(self): 
    116         return self.__credential 
    117  
    118     def _setCredential(self, value): 
     99    def _getCredentials(self): 
     100        return self.__assertionsMap 
     101 
     102    def _setCredentials(self, value): 
    119103        # Safeguard type attribute referencing for unpickling process - this 
    120104        # method may be called before type attribute has been set 
     
    123107                        None) 
    124108         
    125         if _type is not None and not isinstance(value, _type): 
    126             raise TypeError('Expecting %r type for "credential" attribute; ' 
    127                             'got %r' % type(value)) 
    128         self.__credential = value 
    129  
    130     credential = property(_getCredential,  
    131                           _setCredential,  
    132                           doc="Credential object") 
     109        if (_type is not None and  
     110            not isinstance(value, TypedList) and 
     111            value.elementType != _type): 
     112            raise TypeError('Expecting TypedList(%s) type for "credentials" ' 
     113                            'attribute; got %r' % (_type, type(value))) 
     114        self.__assertionsMap = value 
     115 
     116    credentials = property(_getCredentials, _setCredentials,  
     117                           doc="Credentials object") 
    133118 
    134119    def _getIssuerName(self): 
     
    140125    issuerName = property(_getIssuerName,  
    141126                          _setIssuerName,  
    142                           doc="Name of issuer of the credential") 
     127                          doc="Name of issuer of the credentials") 
    143128 
    144129    def __getstate__(self): 
     
    162147    """  
    163148    CONFIG_FILE_OPTNAMES = ("userId", ) 
    164     __slots__ = ("__credentials", "__userId") 
     149    __metaclass__ = ABCMeta 
     150    __slots__ = ("__userId", ) 
    165151     
    166152    def __init__(self): 
    167153        self.__userId = None 
    168         self.__credentials = {} 
    169154 
    170155    @classmethod 
     
    193178        raise NotImplementedError(CredentialWalletBase.parseConfig.__doc__) 
    194179 
    195     def addCredential(self,  
    196                       credential,  
    197                       issuerEndpoint=None, 
    198                       bUpdateCredentialRepository=True): 
     180    @abstractmethod 
     181    def addCredentials(self, key, credentials): 
    199182        """Add a new attribute certificate to the list of credentials held. 
    200183 
    201         @type credential: determined by derived class implementation e.g. 
    202         SAML assertion 
    203         @param credential: new attribute Certificate to be added 
    204         @type issuerEndpoint: basestring 
    205         @param issuerEndpoint: input the URI of issuing service from 
    206         which credential was retrieved.  This is added to a dict to enable  
    207         access to a given Attribute Certificate keyed by issuing service  
    208         URI. See the getCredential method. 
    209         @type bUpdateCredentialRepository: bool 
    210         @param bUpdateCredentialRepository: if set to True, and a repository  
    211         exists it will be updated with the new credentials also 
    212          
    213         @rtype: bool 
    214         @return: True if certificate was added otherwise False.  - If an 
    215         existing certificate from the same issuer has a later expiry it will 
    216         take precedence and the new input certificate is ignored.""" 
    217         raise NotImplementedError(CredentialWalletBase.addCredential.__doc__) 
    218              
     184        @type key: basestring 
     185        @param key: key to use to retrieve the credential 
     186        @type credentials: determined by derived class implementation e.g. 
     187        list of SAML assertions 
     188        @param credentials: new credentials to be added 
     189        """ 
     190        raise NotImplementedError(CredentialWalletBase.addCredentials.__doc__) 
     191             
     192    @abstractmethod 
    219193    def audit(self): 
    220194        """Check the credentials held in the wallet removing any that have 
     
    222196        raise NotImplementedError(CredentialWalletBase.audit.__doc__) 
    223197 
     198    @abstractmethod 
    224199    def updateCredentialRepository(self, auditCred=True): 
    225200        """Copy over non-persistent credentials held by wallet into the 
     
    232207                    CredentialWalletBase.updateCredentialRepository.__doc__) 
    233208         
    234     def _getCredentials(self): 
    235         """Get Property method.  Credentials dict is read-only but also see  
    236         addCredential method 
    237          
    238         @rtype: dict 
    239         @return: cached ACs indesed by issuing organisation name""" 
    240         return self.__credentials 
    241  
    242     # Publish attribute 
    243     credentials = property(fget=_getCredentials, 
    244                            doc="List of credentials linked to issuing " 
    245                                "authorities") 
     209    @abstractmethod 
     210    def retrieveCredentials(self, key): 
     211        """Retrieve Credentials corresponding to the given key 
     212         
     213        @rtype: list 
     214        @return: cached credentials indexed by key""" 
     215        return self.__credentials.get(issuer) 
    246216         
    247217    def _getUserId(self): 
     
    281251    CONFIG_FILE_OPTNAMES = CredentialWalletBase.CONFIG_FILE_OPTNAMES + ( 
    282252                           "clockSkewTolerance", ) 
    283     __slots__ = ("__clockSkewTolerance",) 
     253    __slots__ = ("__clockSkewTolerance", "__assertionsMap") 
    284254 
    285255    def __init__(self): 
    286256        super(SAMLAssertionWallet, self).__init__() 
    287257        self.__clockSkewTolerance = timedelta(seconds=0.) 
     258        self.__assertionsMap = {} 
    288259     
    289260    def _getClockSkewTolerance(self): 
     
    334305                 
    335306            setattr(self, optName, val) 
     307          
     308    def addCredentials(self, key, credentials, verifyCredentials=True): 
     309        """Add a new assertion to the list of assertion credentials held. 
     310 
     311        @type credentials: iterable 
     312        @param credential: list of SAML assertions for a given issuer 
     313        @type key: basestring 
     314        @param key: key by which these credentials should be referred to 
     315        @type verifyCredential: bool 
     316        @param verifyCredential: if set to True, test validity of credential 
     317        by calling isValidCredential method. 
     318        """         
     319        for credential in credentials: 
     320            if not isinstance(credential, Assertion): 
     321                raise TypeError("Input credentials must be %r type; got %r" % 
     322                                (Assertion, credential)) 
     323                 
     324            elif verifyCredentials and not self.isValidCredential(credential): 
     325                raise CredentialWalletError("Validity time error with " 
     326                                            "assertion %r" % credential) 
     327         
     328        # Check to see if there are existing credentials held under the same key 
     329        # If so, compare the expiry time.  The one with the latest expiry will  
     330        # be retained and the other ignored 
     331        bUpdateCred = True 
     332        if key in self.__assertionsMap: 
     333            # There is an existing certificate held with the same issuing 
     334            # host name as the new certificate 
     335            credentialOld = self.__assertionsMap[key].credential 
     336 
     337            # If the new certificate has an earlier expiry time then ignore it 
     338            bUpdateCred = (credential.conditions.notOnOrAfter >  
     339                           credentialOld.conditions.notOnOrAfter) 
     340             
     341        if bUpdateCred: 
     342            self.__assertionsMap[key] = credentials 
     343 
     344    def retrieveCredentials(self, key): 
     345        """Retrieve credentials for the given key 
     346         
     347        @param key: key index to credentials to retrieve 
     348        @type key: basestring 
     349        @rtype: iterable / None type if none found for key 
     350        @return: cached credentials indexed by input key 
     351        """ 
     352        return self.__assertionsMap.get(key) 
    336353                         
    337354    def audit(self): 
     
    341358        log.debug("SAMLAssertionWallet.audit ...") 
    342359         
    343         for issuerName, issuerEntry in self.credentials.items(): 
    344             if not self.isValidCredential(issuerEntry.credential): 
    345                 del self.credentials[issuerName] 
     360        for k, v in self.__assertionsMap.items(): 
     361            creds = [credential for credential in v 
     362                     if not self.isValidCredential(credential)] 
     363            if len(creds) > 0: 
     364                self.__assertionsMap[k] = TypedList(Assertion) 
     365                self.__assertionsMap[k].extend(creds) 
     366            else: 
     367                del self.__assertionsMap[k] 
    346368 
    347369    def isValidCredential(self, assertion): 
     
    371393        return True 
    372394     
     395    # Implement abstract method 
     396    updateCredentialRepository = lambda self: None 
     397     
    373398    def __getstate__(self): 
    374399        '''Enable pickling for use with beaker.session''' 
     
    390415    statements based on endpoint URI and issuer name 
    391416    """ 
    392     __slots__ = ("__credentialsKeyedByURI",) 
     417    __slots__ = ("__assertionsMapKeyedByURI",) 
    393418     
    394419    def __init__(self): 
    395420        super(SAMLAttributeWallet, self).__init__() 
    396         self.__credentialsKeyedByURI = {} 
    397  
    398     def _getCredentialsKeyedByURI(self): 
     421        self.__assertionsMapKeyedByURI = {} 
     422 
     423    def retrieveCredentialsByURI(self, issuerEndpoint): 
    399424        """Get Property method for credentials keyed by issuing service URI 
    400         Credentials dict is read-only but also see addCredential method 
     425        Credentials dict is read-only but also see addCredentials method 
    401426         
    402427        @rtype: dict 
    403428        @return: cached ACs indexed by issuing service URI""" 
    404         return self.__credentialsKeyedByURI 
    405      
    406     # Publish attribute 
    407     credentialsKeyedByURI = property(fget=_getCredentialsKeyedByURI, 
    408                                      doc="List of credentials " 
    409                                          "linked to issuing service URI") 
    410           
    411     def addCredential(self,  
    412                       credential,  
    413                       issuerEndpoint=None, 
    414                       verifyCredential=True): 
    415         """Add a new assertion to the list of assertion credentials held. 
    416  
    417         @type credential: SAML assertion 
    418         @param credential: new assertion to be added 
    419         @type issuerEndpoint: basestring 
    420         @param issuerEndpoint: input the issuing service URI from 
    421         which credential was retrieved.  This is added to a dict to enable  
    422         access to a given Attribute Certificate keyed by issuing service  
    423         URI. See the getCredential method. 
    424         @type verifyCredential: bool 
    425         @param verifyCredential: if set to True, test validity of credential 
    426         by calling isValidCredential method. 
    427          
    428         @rtype: bool 
    429         @return: True if credential was added otherwise False.  - If an 
    430         existing certificate from the same issuer has a later expiry it will 
    431         take precedence and the new input certificate is ignored.""" 
    432          
    433         # Check input 
    434         if not isinstance(credential, Assertion): 
    435             raise CredentialWalletError("Input credential must be an " 
    436                                         "%r type object" % Assertion)         
    437  
    438         if verifyCredential and not self.isValidCredential(credential): 
    439             raise CredentialWalletError("Validity time error with assertion %r" 
    440                                         % credential) 
    441          
    442         # Check to see if there is an existing Attribute Certificate held 
    443         # that was issued by the same host.  If so, compare the expiry time. 
    444         # The one with the latest expiry will be retained and the other 
    445         # ignored 
    446         bUpdateCred = True 
    447         if credential.issuer is None: 
    448             raise AttributeError("Adding SAML assertion to wallet: no issuer " 
    449                                  "set") 
    450              
    451         issuerName = credential.issuer.value 
    452          
    453         if issuerName in self.credentials: 
    454             # There is an existing certificate held with the same issuing 
    455             # host name as the new certificate 
    456             credentialOld = self.credentials[issuerName].credential 
    457  
    458             # If the new certificate has an earlier expiry time then ignore it 
    459             bUpdateCred = (credential.conditions.notOnOrAfter >  
    460                            credentialOld.conditions.notOnOrAfter) 
    461  
    462         if bUpdateCred: 
    463             thisCredential = CredentialContainer(Assertion) 
    464             thisCredential.credential = credential 
    465             thisCredential.issuerName = issuerName 
    466              
    467             self.credentials[issuerName] = thisCredential 
    468              
    469             if issuerEndpoint: 
    470                 self.credentialsKeyedByURI[ 
    471                     issuerEndpoint] = thisCredential 
    472                          
     429        return self.__assertionsMapKeyedByURI.get(issuerEndpoint) 
     430                
    473431    def audit(self): 
    474432        """Check the credentials held in the wallet removing any that have 
     
    477435        log.debug("SAMLAssertionWallet.audit ...") 
    478436         
    479         for issuerName, issuerEntry in self.credentials.items(): 
     437        for issuerName, issuerEntry in self.__assertionsMap.items(): 
    480438            if not self.isValidCredential(issuerEntry.credential): 
    481439                foundMatch = False 
    482440                endpoint = None 
    483                 for endpoint, v in self.credentialsKeyedByURI.items(): 
     441                for endpoint, v in self.__assertionsMapKeyedByURI.items(): 
    484442                    if v.issuerName.value == issuerName: 
    485443                        foundMatch = True 
     
    487445                 
    488446                if foundMatch: 
    489                     self.credentialsKeyedByURI.pop(endpoint, None) 
     447                    self.__assertionsMapKeyedByURI.pop(endpoint, None) 
    490448                     
    491                 del self.credentials[issuerName] 
     449                del self.__assertionsMap[issuerName] 
    492450     
    493451    def __getstate__(self): 
     
    511469    __slots__ = () 
    512470     
    513     def addCredential(self, assertion, verifyCredential=True): 
    514         """Add a new assertion to the list of assertion credentials held. 
    515  
    516         @type assertion: SAML assertion 
    517         @param assertion: new assertion to be added 
     471    def addCredentials(self, assertions, verifyCredential=True): 
     472        """Add new assertions from a given issuer 
     473 
     474        @type assertions: SAML assertion 
     475        @param assertions: new assertion to be added 
    518476        @type verifyCredential: bool 
    519477        @param verifyCredential: if set to True, test validity of credential 
     
    548506         
    549507        resourceId = assertion.authzDecisionStatements[0].resource         
    550         if resourceId in self.credentials: 
     508        if resourceId in self.__assertionsMap: 
    551509            # There is an existing certificate held with the same issuing 
    552510            # host name as the new certificate 
    553             assertionOld = self.credentials[resourceId].credential 
     511            assertionOld = self.__assertionsMap[resourceId].credential 
    554512 
    555513            # If the new certificate has an earlier expiry time then ignore it 
     
    564522                thisCredential.issuerName = assertion.issuer.value 
    565523                 
    566             self.credentials[resourceId] = thisCredential 
     524            self.__assertionsMap[resourceId] = thisCredential 
    567525                 
    568526    def __getstate__(self): 
     
    590548    credentials.  It enables retrieval of attribute certificates from a user's 
    591549    previous session(s)""" 
    592          
     550    __metaclass__ = ABCMeta 
     551     
     552    @abstractmethod 
    593553    def __init__(self, propFilePath=None, dbPPhrase=None, **prop): 
    594554        """Initialise Credential Repository abstract base class.  Derive from  
     
    613573            self.__init__.__doc__.replace('\n       ','')) 
    614574 
    615  
     575    @abstractmethod 
    616576    def addUser(self, userId, dn=None): 
    617577        """A new user to Credentials Repository 
     
    623583        raise NotImplementedError( 
    624584            self.addUser.__doc__.replace('\n       ','')) 
    625  
    626                              
     585            
     586    @abstractmethod 
    627587    def auditCredentials(self, userId=None, **attCertValidKeys): 
    628588        """Check the attribute certificates held in the repository and delete 
     
    638598            self.auditCredentials.__doc__.replace('\n       ','')) 
    639599 
    640  
    641     def getCredentials(self, userId): 
     600    @abstractmethod 
     601    def retrieveCredentials(self, userId): 
    642602        """Get the list of credentials for a given users DN 
    643603         
     
    645605        @param userId: users userId, name or X.509 cert. distinguished name 
    646606        @rtype: list  
    647         @return: list of Attribute Certificates""" 
     607        @return: list of credentials""" 
    648608        raise NotImplementedError( 
    649609            self.getCredentials.__doc__.replace('\n       ','')) 
    650610 
    651          
    652     def addCredentials(self, userId, attCertList): 
    653         """Add new attribute certificates for a user.  The user must have 
     611    @abstractmethod         
     612    def addCredentials(self, userId, credentialsList): 
     613        """Add credentials for a user.  The user must have 
    654614        been previously registered in the repository 
    655615 
    656616        @type userId: string 
    657617        @param userId: users userId, name or X.509 cert. distinguished name 
    658         @type attCertList: list 
    659         @param attCertList: list of attribute certificates""" 
     618        @type credentialsList: list 
     619        @param credentialsList: list of credentials 
     620        """ 
    660621        raise NotImplementedError( 
    661622            self.addCredentials.__doc__.replace('\n       ','')) 
     
    675636        """Null Credential Repository addUser placeholder""" 
    676637 
    677     def getCredentials(self, userId): 
     638    def retrieveCredentials(self, userId): 
    678639        """Null Credential Repository getCredentials placeholder""" 
    679640        return [] 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/xacml/pip/saml_pip.py

    r7357 r7358  
    1414from os import path 
    1515from ConfigParser import SafeConfigParser, ConfigParser 
     16import base64 
     17 
     18import beaker.session 
    1619 
    1720from ndg.xacml.core.attributedesignator import SubjectAttributeDesignator 
     
    2831                                            AttributeQuerySslSOAPBinding 
    2932                                             
    30 from ndg.security.common.utils import VettedDict 
    31  
     33from ndg.security.common.utils import VettedDict, str2Bool 
     34from ndg.security.common.credentialwallet import SAMLAssertionWallet 
     35 
     36 
     37class SessionCache(object):   
     38    """Class to cache previous attribute query results retrieved from  
     39    Attribute Authority callouts.  This is to optimise performance.  Session 
     40    caching is based on beaker.session 
     41     
     42    @ivar __session: wrapped beaker session instance 
     43    @type __session: beaker.session.Session 
     44    """ 
     45    __slots__ = ('__session', ) 
     46     
     47    def __init__(self, _id, data_dir=None): 
     48        """ 
     49        @param _id: unique identifier for session to be created, or one to reload 
     50        from store 
     51        @type _id: basestring 
     52        @param data_dir: directory for permanent storage of sessions. 
     53        Sessions are used as a means of optimisation caching Attribute Query  
     54        results to reduce the number of Attribute Authority web service calls. 
     55        If set to None, sessions are cached in memory only. 
     56        @type data_dir: None type / basestring 
     57        """                 
     58        # Expecting URIs for Ids, make them safe for storage by encoding first 
     59        encodedId = base64.b64encode(_id) 
     60         
     61        # The first argument is the request object, a dictionary-like object 
     62        # from which and to which cookie settings are made.  This can be ignored 
     63        # here as the cookie functionality is not being used. 
     64        self.__session = beaker.session.Session({}, id=encodedId,  
     65                                                data_dir=data_dir, 
     66                                                use_cookies=False) 
     67        if 'wallet' not in self.__session: 
     68            self.__session['wallet'] = SAMLAssertionWallet() 
     69     
     70    def add(self, assertions, issuerEndpoint): 
     71        """Add a SAML assertion containing attribute statement(s) from an 
     72        Attribute Authority 
     73         
     74        @type assertions: ndg.security.common.utils.TypedList 
     75        @param assertions: new SAML assertions to be added corresponding to the 
     76        issuerEndpoint 
     77        @type issuerEndpoint: basestring 
     78        @param issuerEndpoint: input the issuing service URI from 
     79        which assertions were retrieved.  This is added to a dict to enable  
     80        access to given Assertions keyed by issuing service URI. See the  
     81        retrieveAssertions method. 
     82        @raise KeyError: error with session object - no wallet key set 
     83        """ 
     84        self.__session['wallet'].addCredentials(issuerEndpoint, assertions) 
     85         
     86    def retrieve(self, issuerEndpoint): 
     87        '''Get the cached assertions for the given Attribute Authority issuer 
     88         
     89        @type issuerEndpoint: basestring 
     90        @param issuerEndpoint: input the issuing service URI from 
     91        which assertion was retrieved. 
     92        @return: SAML assertion response cached from a previous call to the  
     93        Attribute Authority with the given endpoint 
     94        @raise KeyError: error with session object - no wallet key set 
     95        ''' 
     96        wallet = self.__session['wallet'] 
     97        return wallet.retrieveCredentials(issuerEndpoint) 
     98             
     99    def __del__(self): 
     100        """Ensure session is saved when this object goes out of scope""" 
     101        if isinstance(self.__session, beaker.session.Session): 
     102            self.__session.save() 
     103         
    32104 
    33105class PIPException(Exception): 
     
    56128        AttributeQuerySslSOAPBinding.QUERY_ATTRIBUTES_ATTRNAME 
    57129    ) 
     130     
     131    # Special attribute setting for SAML Attribute Query attributes - see 
     132    # __setattr__ 
    58133    ATTRIBUTE_QUERY_ATTRNAME = 'attributeQuery' 
    59134    LEN_ATTRIBUTE_QUERY_ATTRNAME = len(ATTRIBUTE_QUERY_ATTRNAME) 
     
    72147        '__mappingFilePath',  
    73148        '__attributeId2AttributeAuthorityMap', 
    74         '__attributeQueryBinding' 
     149        '__attributeQueryBinding', 
     150        '__cacheSessions', 
     151        '__sessionCacheDataDir', 
     152        '__sessionCache' 
    75153    ) 
    76154     
    77     def __init__(self): 
    78         '''Initialise settings for connection to an Attribute Authority''' 
     155    def __init__(self, sessionCacheDataDir=None): 
     156        '''Initialise settings for connection to an Attribute Authority 
     157         
     158        @param sessionCacheDataDir: directory for permanent storage of sessions. 
     159        Sessions are used as a means of optimisation caching Attribute Query  
     160        results to reduce the number of Attribute Authority web service calls. 
     161        If set to None, sessions are cached in memory only. 
     162        @type sessionCacheDataDir: None type / basestring 
     163        ''' 
    79164        self.__subjectAttributeId = None 
    80165        self.__mappingFilePath = None 
     
    86171        self.__attributeQueryBinding = AttributeQuerySslSOAPBinding() 
    87172         
     173        self.__cacheSessions = True 
     174        self.__sessionCacheDataDir = sessionCacheDataDir 
     175        self.__sessionCache = None 
     176 
     177    def _getCacheSessions(self): 
     178        return self.__cacheSessions 
     179 
     180    def _setCacheSessions(self, value): 
     181        if isinstance(value, basestring): 
     182            self.__cacheSessions = str2Bool(value) 
     183        elif isinstance(value, bool): 
     184            self.__cacheSessions = value 
     185        else: 
     186            raise TypeError('Expecting string/bool type for "cacheSessions" ' 
     187                            'attribute; got %r' % type(value)) 
     188         
     189        self.__cacheSessions = value 
     190 
     191    cacheSessions = property(_getCacheSessions, _setCacheSessions,  
     192                             doc="Cache attribute query results to optimise " 
     193                                 "performance") 
     194 
     195    def _getSessionCacheDataDir(self): 
     196        return self.__sessionCacheDataDir 
     197 
     198    def _setSessionCacheDataDir(self, value): 
     199        if not isinstance(value, (basestring, type(None))): 
     200            raise TypeError('Expecting string/None type for ' 
     201                            '"sessionCacheDataDir"; got %r' % type(value)) 
     202             
     203        self.__sessionCacheDataDir = value 
     204 
     205    sessionCacheDataDir = property(_getSessionCacheDataDir,  
     206                                   _setSessionCacheDataDir,  
     207                                   doc="Data Directory for Session Cache.  " 
     208                                       "This setting will be ignored if " 
     209                                       '"cacheSessions" is set to False') 
     210     
    88211    def _get_subjectAttributeId(self): 
    89212        return self.__subjectAttributeId 
     
    207330                super(PIP, self).__setattr__(name, value) 
    208331                 
    209             setattr(self.__attributeQueryBinding, queryAttrName, value) 
     332            setattr(self.__attributeQueryBinding, queryAttrName, value)             
    210333        else: 
    211334            super(PIP, self).__setattr__(name, value) 
     
    249372                            (XacmlRequestCtx, type(context))) 
    250373         
    251         # TODO: Check for cached attributes for this subject (i.e. user)         
    252         # If none found send a query to the attribute authority 
    253  
    254374        # Look up mapping from request attribute ID to Attribute Authority to 
    255375        # query  
     
    270390         
    271391        # Get subject from the request context 
     392        subject = None 
    272393        subjectId = None 
    273394        for subject in context.subjects: 
     
    292413            xacmlCtxSubject = subject 
    293414             
    294         # Get the id of the attribute to be queried for and add it to the SAML 
    295         # query 
    296415        attributeFormat = attributeDesignator.dataType 
    297416        attributeId = attributeDesignator.attributeId 
    298          
    299         samlAttribute = SamlAttribute() 
    300         samlAttribute.name = attributeDesignator.attributeId 
    301         samlAttribute.nameFormat = attributeFormat 
    302         self.attributeQueryBinding.query.attributes.append(samlAttribute) 
    303          
    304         # Dispatch query 
    305         try: 
    306             self.attributeQueryBinding.subjectID = subjectId 
    307             self.attributeQueryBinding.subjectIdFormat = self.subjectAttributeId 
    308             response = self.attributeQueryBinding.send( 
     417             
     418        # Check for cached attributes for this subject (i.e. user)         
     419        # If none found send a query to the attribute authority 
     420        if self.cacheSessions: 
     421            sessionCache = SessionCache(subjectId, 
     422                                        data_dir=self.__sessionCacheDataDir) 
     423            assertions = sessionCache.retrieve(attributeAuthorityURI) 
     424        else: 
     425            assertions = None 
     426             
     427        if assertions is None: 
     428            # No cached assertions are available for this Attribute Authority, 
     429            # make a fresh call  
     430             
     431            # Get the id of the attribute to be queried for and add it to the  
     432            # SAML query 
     433             
     434            samlAttribute = SamlAttribute() 
     435            samlAttribute.name = attributeDesignator.attributeId 
     436            samlAttribute.nameFormat = attributeFormat 
     437            self.attributeQueryBinding.query.attributes.append(samlAttribute) 
     438             
     439            # Dispatch query 
     440            try: 
     441                self.attributeQueryBinding.subjectID = subjectId 
     442                self.attributeQueryBinding.subjectIdFormat = \ 
     443                                                    self.subjectAttributeId 
     444                response = self.attributeQueryBinding.send( 
    309445                                                    uri=attributeAuthorityURI) 
    310         except Exception: 
    311             log.exception('Error querying Attribute service %r with subject %r', 
    312                           attributeAuthorityURI, 
    313                           subjectId) 
    314             raise 
    315         finally: 
    316             # !Ensure relevant query attributes are reset ready for any  
    317             # subsequent query! 
    318             self.attributeQueryBinding.subjectID = '' 
    319             self.attributeQueryBinding.subjectIdFormat = '' 
    320             self.attributeQueryBinding.query.attributes = SamlTypedList( 
     446            except Exception: 
     447                log.exception('Error querying Attribute service %r with ' 
     448                              'subject %r', attributeAuthorityURI, subjectId) 
     449                raise 
     450            finally: 
     451                # !Ensure relevant query attributes are reset ready for any  
     452                # subsequent query! 
     453                self.attributeQueryBinding.subjectID = '' 
     454                self.attributeQueryBinding.subjectIdFormat = '' 
     455                self.attributeQueryBinding.query.attributes = SamlTypedList( 
    321456                                                                SamlAttribute) 
    322457         
     458            assertions = response.assertions 
     459            if self.cacheSessions: 
     460                sessionCache.add(assertions, attributeAuthorityURI) 
     461             
    323462        # Unpack SAML assertion attribute corresponding to the name  
    324463        # format specified and copy into XACML attributes       
     
    331470        xacmlAttrValClass = factory(attributeFormat) 
    332471         
    333         for assertion in response.assertions: 
     472        for assertion in assertions: 
    334473            for statement in assertion.attributeStatements: 
    335474                for attribute in statement.attributes: 
     
    349488            matchFound = attr.attributeId == attributeId 
    350489            if matchFound: 
     490                # Weed out duplicates 
    351491                newAttrVals = [attrVal  
    352492                               for attrVal in xacmlAttribute.attributeValues  
     
    360500        # Return the attributes to the caller to comply with the interface 
    361501        return xacmlAttribute.attributeValues 
    362         
  • TI12-security/trunk/NDGSecurity/python/ndg_security_test/ndg/security/test/unit/__init__.py

    r7350 r7358  
    210210         
    211211    def __del__(self): 
     212        self.stopAllServices() 
     213         
     214    def stopAllServices(self): 
    212215        """Stop any services started with the addService method""" 
    213216        if hasattr(self, 'services'): 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_test/ndg/security/test/unit/authz/xacml/saml_pip.cfg

    r7314 r7358  
    2020saml_pip.mappingFilePath = %(here)s/pip-mapping.txt 
    2121 
     22# Cache assertions retrieved from the Attribute Authority to optimise performance 
     23saml_pip.sessionCacheDataDir = %(here)s/query-results-cache 
     24 
    2225# The attribute ID of the subject value to extract from the XACML request 
    2326# context and pass in the SAML attribute query 
     
    2730saml_pip.attributeQuery.issuerFormat = urn:oasis:names:tc:SAML:1.1:nameid-format:x509SubjectName 
    2831 
    29 # These settings configure SSL mutual authentication for the query to the SAML Attribute Authority 
     32# These settings configure SSL mutual authentication for the query to the SAML  
     33# Attribute Authority 
    3034saml_pip.attributeQuery.sslCertFilePath = $NDGSEC_TEST_CONFIG_DIR/pki/localhost.crt 
    3135saml_pip.attributeQuery.sslPriKeyFilePath = $NDGSEC_TEST_CONFIG_DIR/pki/localhost.key 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_test/ndg/security/test/unit/authz/xacml/test_saml_pip.py

    r7339 r7358  
    1515from os import path 
    1616import unittest 
     17 
     18from urllib2 import URLError 
    1719 
    1820from ndg.xacml.core.attributedesignator import SubjectAttributeDesignator 
     
    4547                                    
    4648    attributeValueClassFactory = AttributeValueClassFactory() 
    47      
    48     def __init__(self, *arg, **kw): 
    49         BaseTestCase.__init__(self, *arg, **kw) 
    50         self.startSiteAAttributeAuthority(withSSL=True,  
    51                     port=self.__class__.SITEA_SSL_ATTRIBUTEAUTHORITY_PORTNUM) 
    52          
     49             
    5350    def test01CreateAndCheckAttributes(self): 
    5451        pip = PIP() 
     
    6057        except AttributeError: 
    6158            pass 
     59         
     60        setattr(pip, 'sessionCacheDataDir', 'My data dir') 
    6261 
    6362    def test02ReadMappingFile(self): 
     
    8685         
    8786        return ctx 
    88                       
    89     def test03Query(self): 
     87     
     88    def _createPIP(self):    
     89        """Create PIP from test attribute settings"""               
    9090        pip = PIP() 
    9191        pip.mappingFilePath = self.__class__.MAPPING_FILEPATH 
     
    102102             
    103103        pip.attributeQueryBinding.sslCACertDir = self.__class__.CACERT_DIR 
    104              
    105         # Make attribute designator - in practice this would be passed back from 
    106         # the PDP via the context handler 
     104         
     105        return pip 
     106 
     107    def _createSubjectAttributeDesignator(self): 
     108        '''Make attribute designator - in practice this would be passed back  
     109        from the PDP via the context handler 
     110        ''' 
    107111        designator = SubjectAttributeDesignator() 
    108112        designator.attributeId = self.__class__.NDGS_ATTR_ID 
     
    112116                                    'http://www.w3.org/2001/XMLSchema#string') 
    113117         
     118        return designator 
     119     
     120    def _initQuery(self): 
     121        '''Convenience method to set-up the parameters needed for a query''' 
     122        pip = self._createPIP() 
     123        designator = self._createSubjectAttributeDesignator() 
    114124        ctx = self._createXacmlRequestCtx() 
     125        return pip, designator, ctx 
     126     
     127    def test03Query(self): 
     128        self.startSiteAAttributeAuthority(withSSL=True,  
     129                    port=self.__class__.SITEA_SSL_ATTRIBUTEAUTHORITY_PORTNUM) 
     130         
     131        pip, designator, ctx = self._initQuery() 
     132         
     133        # Avoid caching to avoid impacting other tests in this class 
     134        pip.cacheSessions = False 
    115135         
    116136        attributeValues = pip.attributeQuery(ctx, designator) 
     
    118138        print("PIP retrieved attribute values %r" % attributeValues) 
    119139         
     140        self.stopAllServices() 
     141         
    120142    def test04InitFromConfigFile(self): 
    121143        # Initialise from settings in a config file 
    122144        pip = PIP.fromConfig(self.__class__.CONFIG_FILEPATH) 
    123145        self.assert_(pip.mappingFilePath) 
    124  
     146         
     147    def test05SessionCaching(self): 
     148        self.startSiteAAttributeAuthority(withSSL=True,  
     149                    port=self.__class__.SITEA_SSL_ATTRIBUTEAUTHORITY_PORTNUM) 
     150         
     151        pipA, designator, ctx = self._initQuery() 
     152        attributeValuesA = pipA.attributeQuery(ctx, designator) 
     153         
     154        pipB = self._createPIP() 
     155        pipB.cacheSessions = False 
     156         
     157        attributeValuesB = pipB.attributeQuery(ctx, designator) 
     158         
     159        self.stopAllServices() 
     160         
     161        attributeValuesA2 = pipA.attributeQuery(ctx, designator) 
     162        self.assert_(len(attributeValuesA2) > 0) 
     163         
     164        try: 
     165            attributeValuesB2 = pipB.attributeQuery(ctx, designator) 
     166            self.fail("Expected URLError exception for call with no-caching set") 
     167        except URLError, e: 
     168            print("Pass: expected %r error for call with no-caching set" % e) 
     169         
     170         
    125171         
    126172if __name__ == "__main__": 
Note: See TracChangeset for help on using the changeset viewer.