Changeset 5355


Ignore:
Timestamp:
02/06/09 15:42:32 (10 years ago)
Author:
pjkersha
Message:

Implemented a caching scheme for Attribute Certificates in the security filter deployed on the application middleware stack:

  • Credentials are already cached in the Session Manager but this resides on a separate WSGI stack so that in order to make a retrieval, a SOAP call is required
  • Caching is implemented on the Security filter by extending the Policy Information Point class (PIP) to make it a WSGI app - PIPMiddleware. This gives it visibility to the current beaker session. When PIPMiddleware makes a request to retrieve an Attribute Certificate it can query the certificate cache held in a CredentialWallet? tied to the beaker session.
  • The CredentialWallet? is pickleable so that beaker session can pickle its content and retrieve when the middleware comes back from being offline.
Location:
TI12-security/trunk/python
Files:
10 edited

Legend:

Unmodified
Added
Removed
  • TI12-security/trunk/python/Tests/urllib2secure_request/test_urllib2request.py

    r5350 r5355  
    3030            log.debug("Response:\n\n %s" % e.read()) 
    3131            #abort(401)  
    32             pass 
    3332 
    3433        elif e.code == 403: 
     
    4140            start_response("%d %s" % (e.code, e.msg), e.headers.dict.items()) 
    4241            return response 
    43                             
    4442        else: 
    4543            # Other error handling  - just calling raise here would probably  
  • TI12-security/trunk/python/buildout/ndgsecurity/buildout.cfg

    r5222 r5355  
    1010# BSD - See LICENSE file in top-level directory 
    1111[buildout] 
    12 parts = NDGSecurity 
     12parts = NDGSecurityModWSGI 
    1313 
    1414[NDGSecurity] 
    1515recipe = zc.recipe.egg 
     16 
    1617interpreter = py 
    1718eggs =  
     
    2021find-links = http://ndg.nerc.ac.uk/dist 
    2122 
     23[NDGSecurityModWSGI] 
     24recipe = collective.recipe.modwsgi 
     25config-file = ${buildout:directory}/production.ini 
     26eggs =  
     27        ndg_security 
     28        ndg_security_test 
     29find-links = http://ndg.nerc.ac.uk/dist 
     30 
  • TI12-security/trunk/python/ndg.security.common/ndg/security/common/authz/msi.py

    r5285 r5355  
    318318         
    319319        attributeCertificate = self._getAttributeCertificate( 
    320                                         attributeAuthorityURI, 
    321                                         username, 
    322                                         sessionId, 
    323                                         subject[Subject.SESSIONMANAGERURI_NS]) 
     320                    attributeAuthorityURI, 
     321                    username=username, 
     322                    sessionId=sessionId, 
     323                    sessionManagerURI=subject[Subject.SESSIONMANAGERURI_NS]) 
    324324 
    325325        attributeResponse = PIPAttributeResponse() 
     
    453453        '''Retrieve an Attribute Certificate direct from an Attribute 
    454454        Authority.  This method is invoked if no session ID or Session  
    455         MAnager endpoint where provided 
     455        Manager endpoint where provided 
    456456         
    457457        @type username: basestring 
     
    487487            raise PDPUserAccessDenied() 
    488488         
    489         # TODO: handle othe specific Exception types here for more fine 
     489        # TODO: handle other specific Exception types here for more fine 
    490490        # grained response info 
    491491 
     
    493493            log.error("Request to Attribute Authority [%s] for attribute " 
    494494                      "certificate: %s: %s", attributeAuthorityURI, 
    495                        e.__class__, e) 
     495                      e.__class__, e) 
    496496            raise AttributeCertificateRequestError() 
    497497 
     
    505505        self.policy = policy 
    506506        self.pip = pip 
    507          
     507 
     508    def _getPolicy(self): 
     509        if self._policy is None: 
     510            raise TypeError("Policy object has not been initialised") 
     511        return self._policy 
     512     
     513    def _setPolicy(self, policy): 
     514        if not isinstance(policy, (Policy, None.__class__)): 
     515            raise TypeError("Expecting %s or None type for PDP policy; got %r"% 
     516                            (Policy.__class__.__name__, policy)) 
     517        self._policy = policy 
     518 
     519    policy = property(fget=_getPolicy, 
     520                      fset=_setPolicy, 
     521                      doc="Policy type object used by the PDP to determine " 
     522                          "access for resources") 
     523 
     524    def _getPIP(self): 
     525        if self._pip is None: 
     526            raise TypeError("PIP object has not been initialised") 
     527         
     528        return self._pip 
     529     
     530    def _setPIP(self, pip): 
     531        if not isinstance(pip, (PIP, None.__class__)): 
     532            raise TypeError("Expecting %s or None type for PDP PIP; got %r"% 
     533                            (PIP.__class__.__name__, pip)) 
     534        self._pip = pip 
     535 
     536    pip = property(fget=_getPIP, 
     537                   fset=_setPIP, 
     538                   doc="Policy Information Point - PIP type object used by " 
     539                       "the PDP to retrieve user attributes") 
     540    
    508541    def evaluate(self, request): 
    509542        '''Make access control decision''' 
  • TI12-security/trunk/python/ndg.security.common/ndg/security/common/credentialwallet.py

    r5182 r5355  
    172172    @type attributeAuthorityURI: string 
    173173    @ivar attributeAuthorityURI: URI of Attribute Authority to make  
    174     requests to.  Setting this ALSO creates an AttributeAuthorityClient instance  
    175     _attributeAuthorityClnt.  - See attributeAuthorityURI property for 
     174    requests to.  Setting this ALSO creates an AttributeAuthorityClient  
     175    instance _attributeAuthorityClnt.  - See attributeAuthorityURI property for 
    176176    details. (property attribute) 
    177177     
     
    361361         
    362362        # Credentials are stored as a dictionary one element per attribute 
    363         # certicate held and indexed by certificate issuer name 
     363        # certificate held and indexed by certificate issuer name 
    364364        self._credentials = {} 
     365         
     366        # A second dictionary indexes by Attribute Authority URI: 
     367        self._credentialsKeyedByURI = {} 
    365368 
    366369 
     
    663666     
    664667    # Publish attribute 
    665     credentialsKeyedByURI = property(fget=_getCredentials, 
    666                            doc="List of Attribute Certificates linked to " 
    667                                "attribute authority URI") 
     668    credentialsKeyedByURI = property(fget=_getCredentialsKeyedByURI, 
     669                                     doc="List of Attribute Certificates " 
     670                                         "linked to attribute authority URI") 
    668671         
    669672    def _getCACertFilePathList(self): 
     
    932935                'id': -1,  
    933936                'attCert': attCert, 
     937                'issuerName': issuerName, 
    934938                'attributeAuthorityURI': attributeAuthorityURI 
    935939            } 
     
    12991303         
    13001304        @type attributeAuthorityURI: string 
    1301         @param attributeAuthorityURI: to call as a web service, specify the URI for the  
    1302         Attribute Authority. 
     1305        @param attributeAuthorityURI: to call as a web service, specify the URI 
     1306        for the Attribute Authority. 
    13031307         
    13041308        @type attributeAuthority: string 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/__init__.py

    r5343 r5355  
    9191        @type start_response: function 
    9292        @param start_response: standard WSGI start response function 
     93        @rtype: iterable 
     94        @return: response 
    9395        """ 
    9496        self._initCall(environ, start_response) 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/authn.py

    r5333 r5355  
    9494        'sessionKey': 'beaker.session.ndg.security' 
    9595    } 
    96     propertyDefaults.update(AuthNRedirectMiddleware.propertyDefaults) 
     96    propertyDefaults.update(NDGSecurityMiddlewareBase.propertyDefaults) 
    9797    return2URIArgName = 'ndg.security.r' 
    9898 
     
    122122 
    123123    def __init__(self, app, global_conf, **app_conf): 
     124        ''' 
     125        @type app: callable following WSGI interface 
     126        @param app: next middleware application in the chain       
     127        @type global_conf: dict         
     128        @param global_conf: PasteDeploy global configuration dictionary 
     129        @type prefix: basestring 
     130        @param prefix: prefix for configuration items 
     131        @type app_conf: dict         
     132        @param app_conf: PasteDeploy application specific configuration  
     133        dictionary 
     134        ''' 
    124135        self._redirectURI = None 
    125136        super(AuthNRedirectInitiatorMiddleware, self).__init__(app,  
     
    258269 
    259270    def __init__(self, app, global_conf, prefix='', **app_conf): 
     271        ''' 
     272        @type app: callable following WSGI interface 
     273        @param app: next middleware application in the chain       
     274        @type global_conf: dict         
     275        @param global_conf: PasteDeploy global configuration dictionary 
     276        @type prefix: basestring 
     277        @param prefix: prefix for configuration items 
     278        @type app_conf: dict         
     279        @param app_conf: PasteDeploy application specific configuration  
     280        dictionary 
     281        ''' 
    260282        super(SessionHandlerMiddleware, self).__init__(app,  
    261283                                                       global_conf, 
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/authz.py

    r5330 r5355  
    1414from urlparse import urlunsplit 
    1515 
    16 from ndg.security.server.wsgi import NDGSecurityPathFilter 
     16from ndg.security.common.utils.classfactory import importClass 
    1717from ndg.security.common.X509 import X500DN 
    1818from ndg.security.server.wsgi import NDGSecurityMiddlewareBase, \ 
     
    4848 
    4949    def __init__(self, app, global_conf, prefix='', **app_conf): 
    50          
     50        ''' 
     51        @type app: callable following WSGI interface 
     52        @param app: next middleware application in the chain       
     53        @type global_conf: dict         
     54        @param global_conf: PasteDeploy global configuration dictionary 
     55        @type prefix: basestring 
     56        @param prefix: prefix for configuration items 
     57        @type app_conf: dict         
     58        @param app_conf: PasteDeploy application specific configuration  
     59        dictionary 
     60        ''' 
    5161        super(PEPResultHandlerMiddleware, self).__init__(app, 
    5262                                                         global_conf, 
     
    8090            return self._setErrorResponse(code=403, msg=response) 
    8191 
     92 
    8293class PEPFilterError(Exception): 
    8394    """Base class for PEPFilter exception types""" 
     
    112123        behalf of the PDP 
    113124         
    114         """ 
    115         pipCfg = PEPFilter._filterKeywords(local_conf, 'pip.') 
    116         pip = PIP(**pipCfg) 
    117  
    118         # Initialise the  reading in the policy 
     125        @type app: callable following WSGI interface 
     126        @param app: next middleware application in the chain       
     127        @type global_conf: dict         
     128        @param global_conf: PasteDeploy global configuration dictionary 
     129        @type prefix: basestring 
     130        @param prefix: prefix for configuration items 
     131        @type local_conf: dict         
     132        @param local_conf: PasteDeploy application specific configuration  
     133        dictionary 
     134         
     135        """        
     136        # Initialise the PDP reading in the policy 
    119137        policyCfg = PEPFilter._filterKeywords(local_conf, 'policy.') 
    120138        self.policyFilePath = policyCfg['filePath'] 
    121139        self.policy = Policy.Parse(policyCfg['filePath']) 
    122         self.pdp = PDP(self.policy, pip) 
     140         
     141        # Initialise the Policy Information Point to None.  This object is 
     142        # created and set later.  See AuthorizationMiddleware. 
     143        self.pdp = PDP(self.policy, None) 
    123144         
    124145        self.sessionKey = local_conf.get('sessionKey',  
     
    129150                                        prefix=prefix, 
    130151                                        **local_conf) 
     152 
    131153         
    132154    @NDGSecurityMiddlewareBase.initCall 
    133155    def __call__(self, environ, start_response): 
     156        """ 
     157        @type environ: dict 
     158        @param environ: WSGI environment variables dictionary 
     159        @type start_response: function 
     160        @param start_response: standard WSGI start response function 
     161        @rtype: iterable 
     162        @return: response 
     163        """ 
    134164         
    135165        log.debug("PEPFilter.__call__ ...") 
     
    272302                 
    273303        return filteredConf 
    274              
    275  
     304 
     305    
     306from ndg.security.common.authz.msi import PIP 
     307from ndg.security.common.credentialwallet import CredentialWallet 
     308 
     309class PIPMiddlewareError(Exception): 
     310    """Base class for Policy Information Point WSGI middleware exception types 
     311    """ 
     312     
     313class PIPMiddlewareConfigError(PIPMiddlewareError): 
     314    """Configuration related error for Policy Information Point WSGI middleware 
     315    """ 
     316     
     317class PIPMiddleware(PIP, NDGSecurityMiddlewareBase): 
     318    '''Extend Policy Information Point to enable caching of credentials in 
     319    a CredentialWallet object held in beaker.session 
     320    ''' 
     321    environKey = 'ndg.security.server.wsgi.authz.PIPMiddleware' 
     322        
     323    propertyDefaults = { 
     324        'sessionKey': 'beaker.session.ndg.security', 
     325    } 
     326    propertyDefaults.update(NDGSecurityMiddlewareBase.propertyDefaults) 
     327   
     328    def __init__(self, app, global_conf, prefix='', **local_conf): 
     329        ''' 
     330        @type app: callable following WSGI interface 
     331        @param app: next middleware application in the chain       
     332        @type global_conf: dict         
     333        @param global_conf: PasteDeploy global configuration dictionary 
     334        @type prefix: basestring 
     335        @param prefix: prefix for configuration items 
     336        @type local_conf: dict         
     337        @param local_conf: PasteDeploy application specific configuration  
     338        dictionary 
     339        ''' 
     340        PIP.__init__(self, prefix=prefix, **local_conf) 
     341         
     342        for k in local_conf.keys(): 
     343            if k.startswith(prefix): 
     344                del local_conf[k] 
     345                 
     346        NDGSecurityMiddlewareBase.__init__(self, 
     347                                           app, 
     348                                           global_conf, 
     349                                           prefix=prefix, 
     350                                           **local_conf) 
     351         
     352    def __call__(self, environ, start_response): 
     353        """Take a copy of the session object so that it is in scope for 
     354        _getAttributeCertificate call and add this instance to the environ 
     355        so that the PEPFilter can retrieve it and pass on to the PDP 
     356         
     357        @type environ: dict 
     358        @param environ: WSGI environment variables dictionary 
     359        @type start_response: function 
     360        @param start_response: standard WSGI start response function 
     361        @rtype: iterable 
     362        @return: response 
     363        """ 
     364        self.session = environ.get(self.sessionKey) 
     365        if self.session is None: 
     366            raise PIPMiddlewareConfigError('No beaker session key "%s" found ' 
     367                                           'in environ' % self.sessionKey) 
     368        environ[PIPMiddleware.environKey] = self 
     369         
     370        return self._app(environ, start_response) 
     371     
     372             
     373    def _getAttributeCertificate(self, attributeAuthorityURI, **kw): 
     374        '''Extend base class implementation to make use of the CredentialWallet 
     375        Attribute Certificate cache held in the beaker session.  If no suitable 
     376        certificate is present invoke default behaviour and retrieve an  
     377        Attribute Certificate from the Attribute Authority or Session Manager 
     378        specified 
     379 
     380        @type attributeAuthorityURI: basestring 
     381        @param attributeAuthorityURI: URI to Attribute Authority service 
     382        @type username: basestring 
     383        @param username: subject user identifier - could be an OpenID         
     384        @type sessionId: basestring 
     385        @param sessionId: Session Manager session handle 
     386        @type sessionManagerURI: basestring 
     387        @param sessionManagerURI: URI to remote session manager service 
     388        @rtype: ndg.security.common.AttCert.AttCert 
     389        @return: Attribute Certificate containing user roles 
     390        ''' 
     391        # Check for a wallet in the current session - if not present, create 
     392        # one 
     393        if not 'credentialWallet' in self.session: 
     394            log.debug("PIPMiddleware._getAttributeCertificate: adding a " 
     395                      "Credential Wallet to user session [%s] ...", 
     396                      self.session['username']) 
     397             
     398            self.session['credentialWallet'] = CredentialWallet( 
     399                                            userId=self.session['username']) 
     400            self.session.save() 
     401             
     402        # Take reference to wallet for efficiency 
     403        credentialWallet = self.session['credentialWallet']     
     404         
     405        # Check for existing credentials cached in wallet             
     406        credential = credentialWallet.credentialsKeyedByURI.get( 
     407                                                    attributeAuthorityURI, {}) 
     408         
     409        attrCert = credential.get('attCert') 
     410        if attrCert is not None: 
     411            log.debug("PIPMiddleware._getAttributeCertificate: existing " 
     412                      "Attribute Certificate cached in Credential Wallet for " 
     413                      "user session [%s]", 
     414                      self.session['username']) 
     415 
     416            # Existing cached credential found - skip call to remote Session 
     417            # Manager / Attribute Authority and return this certificate instead 
     418            return attrCert 
     419        else:    
     420            attrCert = PIP._getAttributeCertificate(self, 
     421                                                    attributeAuthorityURI, 
     422                                                    **kw) 
     423             
     424            log.debug("PIPMiddleware._getAttributeCertificate: updating " 
     425                      "Credential Wallet with retrieved Attribute " 
     426                      "Certificate for user session [%s]", 
     427                      self.session['username']) 
     428         
     429            # Update the wallet with this Attribute Certificate so that it's  
     430            # cached for future calls 
     431            credentialWallet.addCredential(attrCert, 
     432                                attributeAuthorityURI=attributeAuthorityURI) 
     433             
     434            return attrCert 
     435 
     436        
    276437from authkit.authenticate.multi import MultiHandler 
    277 from ndg.security.common.utils.classfactory import importClass 
    278438 
    279439class AuthorizationMiddlewareError(Exception): 
     
    296456        application(s) to be protected.  An AuthKit MultiHandler is setup to  
    297457        handle the latter.  PEPResultHandlerMiddleware handles the output 
    298         set following an access denied decision""" 
     458        set following an access denied decision 
     459        @type app: callable following WSGI interface 
     460        @param app: next middleware application in the chain       
     461        @type global_conf: dict         
     462        @param global_conf: PasteDeploy global configuration dictionary 
     463        @type prefix: basestring 
     464        @param prefix: prefix for configuration items 
     465        @type app_conf: dict         
     466        @param app_conf: PasteDeploy application specific configuration  
     467        dictionary 
     468        """ 
    299469         
    300470        pepFilter = PEPFilter(app, 
     
    304474        pepInterceptFunc = pepFilter.multiHandlerInterceptFactory() 
    305475         
    306         app = MultiHandler(pepFilter) 
    307          
     476        pipApp = PIPMiddleware(pepFilter, 
     477                               global_conf, 
     478                               prefix='pip.', 
     479                               **app_conf) 
     480        pepFilter.pdp.pip = pipApp 
     481         
     482#        app = MultiHandler(pepFilter) 
     483        app = MultiHandler(pipApp) 
     484 
    308485        pepResultHandlerClassName = app_conf.pop(prefix+"pepResultHandler",  
    309486                                                 None)  
  • TI12-security/trunk/python/ndg.security.server/ndg/security/server/wsgi/openid/relyingparty/__init__.py

    r5333 r5355  
    137137        @type start_response: function 
    138138        @param start_response: standard WSGI start response function 
     139        @rtype: iterable 
     140        @return: response 
    139141        ''' 
    140142        session = environ[self.sessionKey] 
  • TI12-security/trunk/python/ndg.security.test/ndg/security/test/integration/authz/securedapp.ini

    r5315 r5355  
    6767 
    6868# 
    69 # WS-Security Settings for call to Session Manager 
     69# WS-Security Settings for call to Session Manager / Attribute Authority to 
     70# retrieve user attributes 
    7071 
    7172# Signature of an outbound message 
  • TI12-security/trunk/python/ndg.security.test/ndg/security/test/unit/attributeauthority/test_attributeauthority.py

    r5290 r5355  
    1717import re 
    1818import logging 
    19 logging.basicConfig() 
     19logging.basicConfig(level=logging.DEBUG) 
    2020 
    2121from os.path import expandvars as xpdVars 
Note: See TracChangeset for help on using the changeset viewer.