Changeset 7357 for TI12-security


Ignore:
Timestamp:
23/08/10 16:32:14 (9 years ago)
Author:
pjkersha
Message:

Incomplete - task 2: XACML-Security Integration

  • implemented caching of authorisation decision statements in the PEP to cut down on calls to authorisation service.
Location:
TI12-security/trunk/NDGSecurity/python
Files:
6 added
10 edited

Legend:

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

    r6730 r7357  
    3232    """Generic Exception class for CredentialWallet module.  Overrides  
    3333    Exception to enable writing to the log""" 
    34     def __init__(self, *arg, **kw): 
    35         if len(arg) > 0: 
    36             log.error(arg[0]) 
    37              
    38         Exception.__init__(self, *arg, **kw) 
    3934 
    4035 
     
    4338    to enable writing to the log""" 
    4439 
    45  
    46 class CredentialWalletAttributeRequestDenied(CredentialWalletError):     
    47     """Handling exception where CredentialWallet is denied authorisation by an 
    48     Attribute Authority. 
    49    
    50     @type __extAttCertList: list 
    51     @ivar __extAttCertList: list of candidate Attribute Certificates that 
    52     could be used to try to get a mapped certificate from the target  
    53     Attribute Authority 
    54      
    55     @type __trustedHostInfo: dict 
    56     @ivar __trustedHostInfo: dictionary indexed by host name giving  
    57     details of Attribute Authority URI and roles for trusted hosts""" 
    58      
    59     def __init__(self, *args, **kw): 
    60         """Raise exception for attribute request denied with option to give 
    61         caller hint to certificates that could used to try to obtain a 
    62         mapped certificate 
    63          
    64         @type extAttCertList: list 
    65         @param extAttCertList: list of candidate Attribute Certificates that 
    66         could be used to try to get a mapped certificate from the target  
    67         Attribute Authority 
    68          
    69         @type trustedHostInfo: dict  
    70         @param trustedHostInfo: dictionary indexed by host name giving  
    71         details of Attribute Authority URI and roles for trusted hosts""" 
    72          
    73         self.__trustedHostInfo = kw.pop('trustedHostInfo', {}) 
    74         self.__extAttCertList = kw.pop('extAttCertList', []) 
    75              
    76         CredentialWalletError.__init__(self, *args, **kw) 
    77  
    78     def _getTrustedHostInfo(self): 
    79         """Get message text""" 
    80         return self.__trustedHostInfo 
    81  
    82     trustedHostInfo = property(fget=_getTrustedHostInfo,  
    83                                doc="URI and roles details for trusted hosts") 
    84         
    85     def _getExtAttCertList(self): 
    86         """Return list of candidate Attribute Certificates that could be used 
    87         to try to get a mapped certificate from the target Attribute Authority 
    88         """ 
    89         return self.__extAttCertList 
    90  
    91     extAttCertList = property(fget=_getExtAttCertList, 
    92                               doc="list of candidate Attribute Certificates " 
    93                               "that could be used to try to get a mapped " 
    94                               "certificate from the target Attribute " 
    95                               "Authority") 
    96  
    9740           
    9841class _MetaCredentialWallet(type): 
     
    11861     
    11962    accessGranted = property(fget=_getAccessGranted) 
     63 
    12064 
    12165class CredentialContainer(object): 
     
    12468    ITEM_ATTRNAME = 'credential' 
    12569    ISSUERNAME_ATTRNAME = 'issuerName' 
    126     ATTRIBUTE_AUTHORITY_URI_ATTRNAME = 'attributeAuthorityURI' 
    12770    CREDENTIAL_TYPE_ATTRNAME = 'type' 
    12871     
     
    13174        ITEM_ATTRNAME, 
    13275        ISSUERNAME_ATTRNAME, 
    133         ATTRIBUTE_AUTHORITY_URI_ATTRNAME, 
    13476        CREDENTIAL_TYPE_ATTRNAME 
    13577    ) 
    13678    __slots__ = tuple(["__%s" % n for n in __ATTRIBUTE_NAMES]) 
     79    del n 
    13780     
    13881    def __init__(self, _type=None): 
     
    14386        self.__credential = None 
    14487        self.__issuerName = None 
    145         self.__attributeAuthorityURI = None 
    14688 
    14789    def _getType(self): 
     
    199141                          _setIssuerName,  
    200142                          doc="Name of issuer of the credential") 
    201  
    202     def _getAttributeAuthorityURI(self): 
    203         return self.__attributeAuthorityURI 
    204  
    205     def _setAttributeAuthorityURI(self, value): 
    206         """String or None type are allowed - The URI may be set to None if 
    207         a local Attribute Authority instance is being invoked rather 
    208         one hosted via a remote URI 
    209         """ 
    210         if not isinstance(value, (basestring, type(None))): 
    211             raise TypeError('Expecting string or None type for ' 
    212                             '"attributeAuthorityURI"; got %r instead' %  
    213                             type(value)) 
    214         self.__attributeAuthorityURI = value 
    215  
    216     attributeAuthorityURI = property(_getAttributeAuthorityURI, 
    217                                      _setAttributeAuthorityURI,  
    218                                      doc="Attribute Authority Service URI") 
    219143 
    220144    def __getstate__(self): 
     
    235159 
    236160class CredentialWalletBase(object): 
    237     """Abstract base class for NDG and SAML Credential Wallet implementations 
     161    """Abstract base class for Credential Wallet implementations 
    238162    """  
    239163    CONFIG_FILE_OPTNAMES = ("userId", ) 
    240     __slots__ = ("__credentials", "__credentialsKeyedByURI", "__userId") 
     164    __slots__ = ("__credentials", "__userId") 
    241165     
    242166    def __init__(self): 
    243167        self.__userId = None 
    244168        self.__credentials = {} 
    245         self.__credentialsKeyedByURI = {} 
    246169 
    247170    @classmethod 
     
    250173        @type cfg: basestring /ConfigParser derived type 
    251174        @param cfg: configuration file path or ConfigParser type object 
    252         @rtype: ndg.security.common.credentialWallet.SAMLCredentialWallet 
     175        @rtype: ndg.security.common.credentialWallet.SAMLAssertionWallet 
    253176        @return: new instance of this class 
    254177        ''' 
     
    272195    def addCredential(self,  
    273196                      credential,  
    274                       attributeAuthorityURI=None, 
     197                      issuerEndpoint=None, 
    275198                      bUpdateCredentialRepository=True): 
    276199        """Add a new attribute certificate to the list of credentials held. 
     
    279202        SAML assertion 
    280203        @param credential: new attribute Certificate to be added 
    281         @type attributeAuthorityURI: basestring 
    282         @param attributeAuthorityURI: input the Attribute Authority URI from 
     204        @type issuerEndpoint: basestring 
     205        @param issuerEndpoint: input the URI of issuing service from 
    283206        which credential was retrieved.  This is added to a dict to enable  
    284         access to a given Attribute Certificate keyed by Attribute Authority  
     207        access to a given Attribute Certificate keyed by issuing service  
    285208        URI. See the getCredential method. 
    286209        @type bUpdateCredentialRepository: bool 
     
    321244                           doc="List of credentials linked to issuing " 
    322245                               "authorities") 
    323  
    324     def _getCredentialsKeyedByURI(self): 
    325         """Get Property method for credentials keyed by Attribute Authority URI 
    326         Credentials dict is read-only but also see addCredential method 
    327          
    328         @rtype: dict 
    329         @return: cached ACs indexed by issuing Attribute Authority""" 
    330         return self.__credentialsKeyedByURI 
    331      
    332     # Publish attribute 
    333     credentialsKeyedByURI = property(fget=_getCredentialsKeyedByURI, 
    334                                      doc="List of Attribute Certificates " 
    335                                          "linked to attribute authority URI") 
    336246         
    337247    def _getUserId(self): 
     
    366276 
    367277 
    368 class SAMLCredentialWallet(CredentialWalletBase): 
    369     """CredentialWallet for Earth System Grid supporting caching of SAML  
    370     Attribute Assertions 
     278class SAMLAssertionWallet(CredentialWalletBase): 
     279    """Wallet for Earth System Grid supporting caching of SAML Assertions 
    371280    """ 
    372281    CONFIG_FILE_OPTNAMES = CredentialWalletBase.CONFIG_FILE_OPTNAMES + ( 
    373282                           "clockSkewTolerance", ) 
    374283    __slots__ = ("__clockSkewTolerance",) 
    375      
    376     CREDENTIAL_REPOSITORY_NOT_SUPPORTED_MSG = ("SAMLCredentialWallet doesn't " 
    377                                                "support the " 
    378                                                "CredentialRepository " 
    379                                                "interface") 
    380284 
    381285    def __init__(self): 
    382         super(SAMLCredentialWallet, self).__init__() 
     286        super(SAMLAssertionWallet, self).__init__() 
    383287        self.__clockSkewTolerance = timedelta(seconds=0.) 
    384  
     288     
    385289    def _getClockSkewTolerance(self): 
    386290        return self.__clockSkewTolerance 
     
    430334                 
    431335            setattr(self, optName, val) 
    432  
    433     def addCredential(self,  
    434                       credential,  
    435                       attributeAuthorityURI=None, 
    436                       bUpdateCredentialRepository=False, 
    437                       verifyCredential=True): 
    438         """Add a new assertion to the list of assertion credentials held. 
    439  
    440         @type credential: SAML assertion 
    441         @param credential: new assertion to be added 
    442         @type attributeAuthorityURI: basestring 
    443         @param attributeAuthorityURI: input the Attribute Authority URI from 
    444         which credential was retrieved.  This is added to a dict to enable  
    445         access to a given Attribute Certificate keyed by Attribute Authority  
    446         URI. See the getCredential method. 
    447         @type bUpdateCredentialRepository: bool 
    448         @param bUpdateCredentialRepository: if set to True, and a repository  
    449         exists it will be updated with the new credentials also. Nb. a derived 
    450         class will need to be implemented to enable this capability - see 
    451         the updateCredentialRepository method. 
    452         @type verifyCredential: bool 
    453         @param verifyCredential: if set to True, test validity of credential 
    454         by calling isValidCredential method. 
    455          
    456         @rtype: bool 
    457         @return: True if credential was added otherwise False.  - If an 
    458         existing certificate from the same issuer has a later expiry it will 
    459         take precedence and the new input certificate is ignored.""" 
    460          
    461         # Check input 
    462         if not isinstance(credential, Assertion): 
    463             raise CredentialWalletError("Input credential must be an " 
    464                                         "%r type object" % Assertion)         
    465  
    466         if verifyCredential and not self.isValidCredential(credential): 
    467             raise CredentialWalletError("Validity time error with assertion %r" 
    468                                         % credential) 
    469          
    470         # Check to see if there is an existing Attribute Certificate held 
    471         # that was issued by the same host.  If so, compare the expiry time. 
    472         # The one with the latest expiry will be retained and the other 
    473         # ignored 
    474         bUpdateCred = True 
    475         if credential.issuer is None: 
    476             raise AttributeError("Adding SAML assertion to wallet: no issuer " 
    477                                  "set") 
    478              
    479         issuerName = credential.issuer.value 
    480          
    481         if issuerName in self.credentials: 
    482             # There is an existing certificate held with the same issuing 
    483             # host name as the new certificate 
    484             credentialOld = self.credentials[issuerName].credential 
    485  
    486             # If the new certificate has an earlier expiry time then ignore it 
    487             bUpdateCred = (credential.conditions.notOnOrAfter >  
    488                            credentialOld.conditions.notOnOrAfter) 
    489  
    490         if bUpdateCred: 
    491             thisCredential = CredentialContainer(Assertion) 
    492             thisCredential.credential = credential 
    493             thisCredential.issuerName = issuerName 
    494             thisCredential.attributeAuthorityURI = attributeAuthorityURI 
    495              
    496             self.credentials[issuerName] = thisCredential 
    497              
    498             if attributeAuthorityURI: 
    499                 self.credentialsKeyedByURI[ 
    500                     attributeAuthorityURI] = thisCredential  
    501  
    502             # Update the Credentials Repository - the permanent store of user 
    503             # authorisation credentials.  This allows credentials for previous 
    504             # sessions to be re-instated 
    505             if bUpdateCredentialRepository: 
    506                 self.updateCredentialRepository() 
    507  
    508         # Flag to caller to indicate whether the input certificate was added 
    509         # to the credentials or an exsiting certificate from the same issuer 
    510         # took precedence 
    511         return bUpdateCred 
    512336                         
    513337    def audit(self): 
     
    515339        expired or are otherwise invalid.""" 
    516340 
    517         log.debug("SAMLCredentialWallet.audit ...") 
     341        log.debug("SAMLAssertionWallet.audit ...") 
    518342         
    519343        for issuerName, issuerEntry in self.credentials.items(): 
    520344            if not self.isValidCredential(issuerEntry.credential): 
    521                 self.credentialsKeyedByURI.pop( 
    522                     issuerEntry.attributeAuthorityURI, 
    523                     None) 
    524                      
    525345                del self.credentials[issuerName] 
    526  
    527     def updateCredentialRepository(self, auditCred=True): 
    528         """No Credential Repository support is required""" 
    529         msg = SAMLCredentialWallet.CREDENTIAL_REPOSITORY_NOT_SUPPORTED_MSG 
    530         log.warning(msg) 
    531         warnings.warn(msg) 
    532346 
    533347    def isValidCredential(self, assertion): 
     
    559373    def __getstate__(self): 
    560374        '''Enable pickling for use with beaker.session''' 
    561         _dict = super(SAMLCredentialWallet, self).__getstate__() 
    562          
    563         for attrName in SAMLCredentialWallet.__slots__: 
     375        _dict = super(SAMLAssertionWallet, self).__getstate__() 
     376         
     377        for attrName in SAMLAssertionWallet.__slots__: 
    564378            # Ugly hack to allow for derived classes setting private member 
    565379            # variables 
    566380            if attrName.startswith('__'): 
    567                 attrName = "_SAMLCredentialWallet" + attrName 
     381                attrName = "_SAMLAssertionWallet" + attrName 
    568382                 
    569383            _dict[attrName] = getattr(self, attrName) 
    570384             
    571385        return _dict 
     386 
     387 
     388class SAMLAttributeWallet(SAMLAssertionWallet): 
     389    """Wallet with functionality for retrieving assertions containing attribute  
     390    statements based on endpoint URI and issuer name 
     391    """ 
     392    __slots__ = ("__credentialsKeyedByURI",) 
     393     
     394    def __init__(self): 
     395        super(SAMLAttributeWallet, self).__init__() 
     396        self.__credentialsKeyedByURI = {} 
     397 
     398    def _getCredentialsKeyedByURI(self): 
     399        """Get Property method for credentials keyed by issuing service URI 
     400        Credentials dict is read-only but also see addCredential method 
     401         
     402        @rtype: dict 
     403        @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                         
     473    def audit(self): 
     474        """Check the credentials held in the wallet removing any that have 
     475        expired or are otherwise invalid.""" 
     476 
     477        log.debug("SAMLAssertionWallet.audit ...") 
     478         
     479        for issuerName, issuerEntry in self.credentials.items(): 
     480            if not self.isValidCredential(issuerEntry.credential): 
     481                foundMatch = False 
     482                endpoint = None 
     483                for endpoint, v in self.credentialsKeyedByURI.items(): 
     484                    if v.issuerName.value == issuerName: 
     485                        foundMatch = True 
     486                        break 
     487                 
     488                if foundMatch: 
     489                    self.credentialsKeyedByURI.pop(endpoint, None) 
     490                     
     491                del self.credentials[issuerName] 
     492     
     493    def __getstate__(self): 
     494        '''Enable pickling for use with beaker.session''' 
     495        _dict = super(SAMLAttributeWallet, self).__getstate__() 
     496         
     497        for attrName in SAMLAttributeWallet.__slots__: 
     498            # Ugly hack to allow for derived classes setting private member 
     499            # variables 
     500            if attrName.startswith('__'): 
     501                attrName = "_SAMLAttributeWallet" + attrName 
     502                 
     503            _dict[attrName] = getattr(self, attrName) 
     504             
     505        return _dict                    
     506     
     507     
     508class SAMLAuthzDecisionWallet(SAMLAssertionWallet): 
     509    """Wallet with functionality for retrieving assertions containing  
     510    authorisation decision statements.  Credentials are keyed by resource Id""" 
     511    __slots__ = () 
     512     
     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 
     518        @type verifyCredential: bool 
     519        @param verifyCredential: if set to True, test validity of credential 
     520        by calling isValidCredential method. 
     521         
     522        @rtype: bool 
     523        @return: True if credential was added otherwise False.  - If an 
     524        existing certificate from the same issuer has a later expiry it will 
     525        take precedence and the new input certificate is ignored.""" 
     526         
     527        # Check input 
     528        if not isinstance(assertion, Assertion): 
     529            raise CredentialWalletError("Input assertion must be an " 
     530                                        "%r type object" % Assertion)         
     531 
     532        if verifyCredential and not self.isValidCredential(assertion): 
     533            raise CredentialWalletError("Validity time error with assertion %r" 
     534                                        % assertion) 
     535         
     536        # Check to see if there is an existing decision statement held for the 
     537        # same resource ID.  If so, compare the expiry time.  The one with the  
     538        # latest expiry will be retained and the other ignored 
     539        bUpdateCred = True 
     540         
     541        # Get the resource ID from the authorisation decision statement 
     542        # Nb. Assumes a SINGLE statement 
     543        if len(assertion.authzDecisionStatements) != 1: 
     544            raise CredentialWalletError("Expecting a single authorisation " 
     545                                        "decision statement passed with the " 
     546                                        "input assertion; found %r", 
     547                                        len(assertion.authzDecisionStatements)) 
     548         
     549        resourceId = assertion.authzDecisionStatements[0].resource         
     550        if resourceId in self.credentials: 
     551            # There is an existing certificate held with the same issuing 
     552            # host name as the new certificate 
     553            assertionOld = self.credentials[resourceId].credential 
     554 
     555            # If the new certificate has an earlier expiry time then ignore it 
     556            bUpdateCred = (assertion.conditions.notOnOrAfter >  
     557                           assertionOld.conditions.notOnOrAfter) 
     558 
     559        if bUpdateCred: 
     560            thisCredential = CredentialContainer(Assertion) 
     561            thisCredential.credential = assertion 
     562             
     563            if assertion.issuer.value: 
     564                thisCredential.issuerName = assertion.issuer.value 
     565                 
     566            self.credentials[resourceId] = thisCredential 
     567                 
     568    def __getstate__(self): 
     569        '''Enable pickling for use with beaker.session''' 
     570        _dict = super(SAMLAuthzDecisionWallet, self).__getstate__() 
     571         
     572        for attrName in SAMLAuthzDecisionWallet.__slots__: 
     573            # Ugly hack to allow for derived classes setting private member 
     574            # variables 
     575            if attrName.startswith('__'): 
     576                attrName = "_SAMLAuthzDecisionWallet" + attrName 
     577                 
     578            _dict[attrName] = getattr(self, attrName) 
     579             
     580        return _dict         
    572581         
    573582         
  • TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/wsgi/authz/__init__.py

    r7287 r7357  
    2222 
    2323from ndg.security.common.utils.classfactory import importClass 
    24 from ndg.security.common.credentialwallet import SAMLCredentialWallet 
     24from ndg.security.common.credentialwallet import SAMLAttributeWallet 
    2525from ndg.security.server.wsgi import NDGSecurityMiddlewareBase 
    2626from ndg.security.server.wsgi.authz.pep import SamlPepFilter 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/wsgi/authz/pep.py

    r7287 r7357  
    2121from ndg.security.server.wsgi.session import (SessionMiddlewareBase,  
    2222                                              SessionHandlerMiddleware) 
     23from ndg.security.common.credentialwallet import SAMLAuthzDecisionWallet 
     24from ndg.security.common.utils import str2Bool 
    2325 
    2426 
     
    4547    AUTHZ_DECISION_QUERY_PARAMS_PREFIX = 'authzDecisionQuery.' 
    4648    SESSION_KEY_PARAM_NAME = 'sessionKey' 
     49    CACHE_DECISIONS_PARAM_NAME = 'cacheDecisions' 
    4750     
    4851    CREDENTIAL_WALLET_SESSION_KEYNAME = \ 
     
    5356    PARAM_NAMES = ( 
    5457        'authzServiceURI', 
    55         'sessionKey' 
     58        'sessionKey', 
     59        'cacheDecisions' 
    5660    ) 
    5761    __slots__ = ( 
     
    7074        self.__authzServiceURI = None 
    7175        self.__sessionKey = None 
     76        self.__cacheDecisions = False 
    7277 
    7378    def _getClient(self): 
     
    116121                          doc="environ key name for Beaker session object") 
    117122 
     123    def _getCacheDecisions(self): 
     124        return self.__cacheDecisions 
     125 
     126    def _setCacheDecisions(self, value): 
     127        if isinstance(value, basestring): 
     128            self.__cacheDecisions = str2Bool(value) 
     129        elif isinstance(value, bool): 
     130            self.__cacheDecisions = value 
     131        else: 
     132            raise TypeError('Expecting bool/string type for "cacheDecisions" ' 
     133                            'attribute; got %r' % type(value)) 
     134         
     135    cacheDecisions = property(_getCacheDecisions, _setCacheDecisions,  
     136                              doc="Set to True to make the session cache " 
     137                                  "authorisation decisions returned from the " 
     138                                  "Authorisation Service") 
     139     
    118140    def initialise(self, prefix='', **kw): 
    119141        '''Initialise object from keyword settings 
     
    135157            value = kw.get(paramName) 
    136158            if value is None: 
    137                 raise SamlPepFilterConfigError('Missing option %r' %  
    138                                                    paramName) 
     159                raise SamlPepFilterConfigError('Missing option %r' % paramName) 
     160             
    139161            setattr(self, name, value) 
    140162                     
     
    177199         
    178200        request = webob.Request(environ) 
    179         self.client.resourceURI = request.url 
    180          
    181         # Nb. user may not be logged in hence REMOTE_USER is not set 
    182         self.client.subjectID = request.remote_user or '' 
    183          
    184         samlAuthzResponse = self.client.send(uri=self.__authzServiceURI) 
    185  
    186         # Record the result in the user's session to enable later  
    187         # interrogation by any result handler Middleware 
    188         self.setSession(self.client.query, samlAuthzResponse) 
     201         
     202        # Check for cached decision 
     203        if self.cacheDecisions: 
     204            cachedAssertion = self._retrieveAuthzDecision(request.url) 
     205        else: 
     206            cachedAssertion = None    
     207             
     208        if cachedAssertion is not None: 
     209            assertions = (cachedAssertion,) 
     210        else:  
     211            # No stored decision in cache, invoke the authorisation service    
     212            self.client.resourceURI = request.url 
     213             
     214            # Nb. user may not be logged in hence REMOTE_USER is not set 
     215            self.client.subjectID = request.remote_user or '' 
     216             
     217            samlAuthzResponse = self.client.send(uri=self.__authzServiceURI) 
     218            assertions = samlAuthzResponse.assertions 
     219             
     220            # Record the result in the user's session to enable later  
     221            # interrogation by any result handler Middleware 
     222            self.setSession(self.client.query, samlAuthzResponse) 
     223         
    189224         
    190225        # Set HTTP 403 Forbidden response if any of the decisions returned are 
     
    192227        failDecisions = (DecisionType.DENY, DecisionType.INDETERMINATE) 
    193228         
    194         for assertion in samlAuthzResponse.assertions: 
     229        # Review decision statement(s) in assertions and enforce the decision 
     230        assertion = None 
     231        for assertion in assertions: 
    195232            for authzDecisionStatement in assertion.authzDecisionStatements: 
    196233                if authzDecisionStatement.decision.value in failDecisions: 
     
    211248                    return response(environ, start_response) 
    212249 
     250        if assertion is None: 
     251            log.error("No assertions set in authorisation decision response " 
     252                      "from %r", self.authzServiceURI) 
     253             
     254            response.body = ('An error occurred retrieving an access decision ' 
     255                             'for %r for user %r' % ( 
     256                                             self.client.resourceURI, 
     257                                             self.client.subjectID)) 
     258            response.content_type = 'text/plain' 
     259            log.info(response.body) 
     260            return response(environ, start_response)      
     261                
     262        if self.cacheDecisions: 
     263            self._cacheAuthzDecision(assertion) 
     264             
    213265        # If got through to here then all is well, call next WSGI middleware/app 
    214266        return self._app(environ, start_response) 
    215267 
     268    def _retrieveAuthzDecision(self, resourceId): 
     269        """Return assertion containing authorisation decision for the given 
     270        resource ID. 
     271         
     272        @param resourceId: search for decisions for this resource Id 
     273        @type resourceId: basestring 
     274        @return: assertion containing authorisation decision for the given 
     275        resource ID or None if no wallet has been set or no assertion was  
     276        found matching the input resource Id 
     277        @rtype: ndg.saml.saml2.core.Assertion / None type 
     278        """ 
     279        # Get reference to wallet 
     280        walletKeyName = self.__class__.CREDENTIAL_WALLET_SESSION_KEYNAME 
     281        credWallet = self.session.get(walletKeyName) 
     282         
     283        # Wallet has a dictionary of credential objects keyed by resource ID 
     284        credentials = getattr(credWallet, 'credentials', {}) 
     285         
     286        # Retrieve assertion from Credential object 
     287        assertion = getattr(credentials.get(resourceId), 'credential', None) 
     288        return assertion 
     289         
     290         
     291    def _cacheAuthzDecision(self, assertion): 
     292        """Cache an authorisation decision from a response retrieved from the  
     293        authorisation service.  This is invoked only if cacheDecisions boolean 
     294        is set to True 
     295         
     296        @param assertion: SAML assertion containing authorisation decision 
     297        @type assertion: ndg.saml.saml2.core.Assertion 
     298        """ 
     299        walletKeyName = self.__class__.CREDENTIAL_WALLET_SESSION_KEYNAME 
     300        credWallet = self.session.get(walletKeyName) 
     301        if credWallet is None: 
     302            credWallet = SAMLAuthzDecisionWallet() 
     303         
     304        credWallet.addCredential(assertion) 
     305        self.session[walletKeyName] = credWallet 
     306        self.session.save() 
     307         
    216308    def setSession(self, request, response, save=True): 
    217309        """Set PEP context information in the Beaker session using standard key 
    218         names 
     310        names.  This is a snapshot of the last request and the response  
     311        received.  It can be used by downstream middleware to provide contextual 
     312        information about authorisation decisions 
    219313         
    220314        @param session: beaker session 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/xacml/ctx_handler/saml_ctx_handler.py

    r7350 r7357  
    232232            return None 
    233233        else: 
    234             return self.__issuerProxy.value 
     234            return self.__issuerProxy.format 
    235235 
    236236    def _setIssuerFormat(self, value): 
     
    461461        assertion.version = SAMLVersion(SAMLVersion.VERSION_20) 
    462462        assertion.id = str(uuid4()) 
     463         
     464        assertion.issuer = _saml.Issuer() 
     465        assertion.issuer.value = self.issuerName 
     466        assertion.issuer.format = self.issuerFormat 
    463467         
    464468        now = datetime.utcnow() 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/xacml/pip/saml_pip.py

    r7350 r7357  
    234234        @return: attribute values found for query subject or None if none  
    235235        could be found 
    236         @raise PIPConfigException: if attribute ID -> Attribute Authority mapping is 
    237         empty   
     236        @raise PIPConfigException: if attribute ID -> Attribute Authority  
     237        mapping is empty   
    238238        """ 
    239239         
     
    275275                if attribute.attributeId == self.subjectAttributeId: 
    276276                    if len(attribute.attributeValues) != 1: 
    277                         raise Exception("Expecting a single attribute value " 
    278                                         "for query subject ID") 
     277                        raise PIPRequestCtxException("Expecting a single " 
     278                                                     "attribute value " 
     279                                                     "for query subject ID") 
    279280                    subjectId = attribute.attributeValues[0].value 
    280281                    break 
     
    294295        # query 
    295296        attributeFormat = attributeDesignator.dataType 
     297        attributeId = attributeDesignator.attributeId 
     298         
    296299        samlAttribute = SamlAttribute() 
    297300        samlAttribute.name = attributeDesignator.attributeId 
     
    321324        # format specified and copy into XACML attributes       
    322325        xacmlAttribute = XacmlAttribute() 
    323         xacmlAttribute.attributeId = attributeDesignator.attributeId 
     326        xacmlAttribute.attributeId = attributeId 
    324327        xacmlAttribute.dataType = attributeFormat 
    325328         
     
    342345         
    343346        # Update the XACML request context subject with the new attributes 
    344         xacmlCtxSubject.attributes.append(xacmlAttribute) 
     347        matchFound = False 
     348        for attr in xacmlCtxSubject.attributes: 
     349            matchFound = attr.attributeId == attributeId 
     350            if matchFound: 
     351                newAttrVals = [attrVal  
     352                               for attrVal in xacmlAttribute.attributeValues  
     353                               if attrVal not in attr.attributeValues] 
     354                attr.attributeValues.extend(newAttrVals) 
     355                break 
     356             
     357        if not matchFound: 
     358            xacmlCtxSubject.attributes.append(xacmlAttribute) 
    345359         
    346360        # Return the attributes to the caller to comply with the interface 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_test/ndg/security/test/config/authorisationservice/policy.xml

    r7350 r7357  
    165165                            AttributeId="urn:siteA:security:authz:1.0:attr"  
    166166                            DataType="http://www.w3.org/2001/XMLSchema#string"/> 
    167                         <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">urn:siteA:security:authz:1.0:attr:admin</AttributeValue> 
     167                        <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#string">admin</AttributeValue> 
    168168                    </SubjectMatch> 
    169169                </Subject> 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_test/ndg/security/test/integration/__init__.py

    r7350 r7357  
    2323"/logout?ndg.security.logout.r=/test_logoutWithReturn2QueryArg":  
    2424    "test_logoutWithReturn2QueryArg", 
    25 "/test_logoutWithReturn2QueryArg": "test_logoutWithReturn2QueryArg" 
     25"/test_logoutViaHttpReferrer": "test_logoutViaHttpReferrer" 
    2626    } 
    2727    header = """        <h1>Authorisation Integration Tests:</h1> 
     
    8888</html> 
    8989""" % (AuthZTestApp.header, 
    90        '\n'.join(['<li><a href="%s">%s</a></li>' % (link, link)  
     90       '\n'.join(['<li><a href="%s">%s</a></li>' % (link, name)  
    9191                 for link,name in self.method.items() if name != 'default']) 
    9292       ) 
     
    220220        return response 
    221221     
     222    def test_logoutViaHttpReferrer(self, environ, start_response): 
     223        """Test logout - the middleware works out where to return to by checking 
     224        the HTTP_REFERER environ setting 
     225        """ 
     226        response = """<html> 
     227    <head/> 
     228    <body> 
     229        <h1>Logged Out</h1> 
     230        <p>Successfully redirected to specified return to HTTP_REFERER=%s  
     231        following logout.   
     232        <a href="/">Return to tests</a></p> 
     233    </body> 
     234</html> 
     235""" % environ['PATH_INFO'] 
     236 
     237        start_response('200 OK',  
     238                       [('Content-type', 'text/html'), 
     239                        ('Content-length', str(len(response)))]) 
     240        return response 
     241     
    222242    @classmethod 
    223243    def app_factory(cls, globalConfig, **localConfig): 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_test/ndg/security/test/integration/full_system/policy.xml

    r7350 r7357  
    3737        Policy element above 
    3838    --> 
     39    <Rule RuleId="Graphics and CSS" Effect="Permit"> 
     40        <!--  
     41            Public access for graphics and CSS content 
     42        --> 
     43        <Target> 
     44            <Resources> 
     45                <Resource> 
     46                    <ResourceMatch MatchId="urn:oasis:names:tc:xacml:2.0:function:anyURI-regexp-match"> 
     47                        <ResourceAttributeDesignator 
     48                            AttributeId="urn:oasis:names:tc:xacml:1.0:resource:resource-id" 
     49                            DataType="http://www.w3.org/2001/XMLSchema#anyURI"/> 
     50                        <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#anyURI">^http://localhost:7080/layout/</AttributeValue> 
     51                    </ResourceMatch> 
     52                </Resource> 
     53            </Resources> 
     54        </Target> 
     55    </Rule> 
     56 
    3957    <Rule RuleId="urn:ndg:security:public-uri" Effect="Permit"> 
    4058        <!--  
     
    5169                            AttributeId="urn:oasis:names:tc:xacml:1.0:resource:resource-id" 
    5270                            DataType="http://www.w3.org/2001/XMLSchema#anyURI"/> 
    53                         <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#anyURI">^http://localhost:7080/(test_401|test_403|test_logoutWithReturn2QueryArg)?$</AttributeValue> 
     71                        <AttributeValue DataType="http://www.w3.org/2001/XMLSchema#anyURI">^http://localhost:7080/(test_401|test_403|test_logoutViaHttpReferrer|test_logoutWithReturn2QueryArg)?$</AttributeValue> 
    5472                    </ResourceMatch> 
    5573                </Resource> 
  • TI12-security/trunk/NDGSecurity/python/ndg_security_test/ndg/security/test/integration/full_system/securedapp.ini

    r7350 r7357  
    9595pep.sessionKey = beaker.session.ndg.security 
    9696pep.authzServiceURI = https://localhost:7443/AuthorisationService 
     97pep.cacheDecisions = True 
    9798 
    9899# Settings for Policy Information Point used by the Policy Decision Point to 
Note: See TracChangeset for help on using the changeset viewer.