source: TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/wsgi/authz/__init__.py @ 6263

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/wsgi/authz/__init__.py@6263
Revision 6263, 34.7 KB checked in by pjkersha, 11 years ago (diff)

moved authz module content to authz package level init

Line 
1"""WSGI Policy Enforcement Point Package
2
3NERC DataGrid Project
4"""
5__author__ = "P J Kershaw"
6__date__ = "16/01/2009"
7__copyright__ = "(C) 2009 Science and Technology Facilities Council"
8__contact__ = "Philip.Kershaw@stfc.ac.uk"
9__revision__ = "$Id: $"
10__license__ = "BSD - see LICENSE file in top-level directory"
11import logging
12log = logging.getLogger(__name__)
13
14import warnings
15from time import time
16from urlparse import urlunsplit
17from httplib import UNAUTHORIZED, FORBIDDEN
18
19from ndg.security.common.utils.classfactory import importClass
20from ndg.security.common.X509 import X509Cert
21from ndg.security.common.saml_utils.bindings import AttributeQuerySslSOAPBinding
22
23from ndg.security.common.credentialwallet import (NDGCredentialWallet,
24                                                  SAMLCredentialWallet)
25from ndg.security.server.wsgi import (NDGSecurityMiddlewareBase, 
26                                      NDGSecurityMiddlewareConfigError)
27
28from ndg.security.server.wsgi import (NDGSecurityMiddlewareBase, 
29                                      NDGSecurityMiddlewareConfigError)
30from ndg.security.server.wsgi.authn import (SessionMiddlewareBase, 
31                                            SessionHandlerMiddleware)
32
33from ndg.security.common.authz.msi import (Policy, PIP, PIPBase, 
34                                           PIPAttributeQuery, 
35                                           PIPAttributeResponse, PDP, Request, 
36                                           Response, Resource, Subject)
37
38
39class PEPResultHandlerMiddleware(SessionMiddlewareBase):
40    """This middleware is invoked if access is denied to a given resource.  It
41    is incorporated into the call stack by passing it in to a MultiHandler
42    instance.  The MultiHandler is configured in the AuthorizationMiddlewareBase
43    class below.  The MultiHandler is passed a checker method which determines
44    whether to allow access, or call this interface.   The checker is
45    implemented in the AuthorizationHandler.  See below ...
46   
47    This class can be overridden to define custom behaviour for the access
48    denied response e.g. include an interface to enable users to register for
49    the dataset from which they have been denied access.  See
50    AuthorizationMiddlewareBase pepResultHandler keyword.
51   
52    SessionMiddlewareBase base class defines user session key and
53    isAuthenticated property
54    """
55   
56    def __init__(self, app, global_conf, prefix='', **app_conf):
57        '''
58        @type app: callable following WSGI interface
59        @param app: next middleware application in the chain     
60        @type global_conf: dict       
61        @param global_conf: PasteDeploy global configuration dictionary
62        @type prefix: basestring
63        @param prefix: prefix for configuration items
64        @type app_conf: dict       
65        @param app_conf: PasteDeploy application specific configuration
66        dictionary
67        '''
68        super(PEPResultHandlerMiddleware, self).__init__(app,
69                                                         global_conf,
70                                                         prefix=prefix,
71                                                         **app_conf)
72               
73    @NDGSecurityMiddlewareBase.initCall
74    def __call__(self, environ, start_response):
75       
76        log.debug("PEPResultHandlerMiddleware.__call__ ...")
77       
78        self.session = self.environ.get(self.sessionKey)
79        if not self.isAuthenticated:
80            # This check is included as a precaution: this condition should be
81            # caught be the AuthNRedirectHandlerMiddleware or PEPFilter
82            log.warning("PEPResultHandlerMiddleware: user is not "
83                        "authenticated - setting HTTP 401 response")
84            return self._setErrorResponse(code=UNAUTHORIZED)
85        else:
86            # Get response message from PDP recorded by PEP
87            pepCtx = self.session.get('pepCtx', {})
88            pdpResponse = pepCtx.get('response')
89            msg = getattr(pdpResponse, 'message', '')
90               
91            response = ("Access is forbidden for this resource:%s"
92                        "Please check with your site administrator that you "
93                        "have the required access privileges." % 
94                        msg.join(('\n\n',)*2))
95
96            return self._setErrorResponse(code=FORBIDDEN, msg=response)
97
98
99class PEPFilterError(Exception):
100    """Base class for PEPFilter exception types"""
101   
102class PEPFilterConfigError(PEPFilterError):
103    """Configuration related error for PEPFilter"""
104
105class PEPFilter(SessionMiddlewareBase):
106    """PEP (Policy Enforcement Point) WSGI Middleware.  The PEP enforces
107    access control decisions made by the PDP (Policy Decision Point).  In
108    this case, it follows the WSG middleware filter pattern and is configured
109    in a pipeline upstream of the application(s) which it protects.  if an
110    access denied decision is made, the PEP enforces this by returning a
111    403 Forbidden HTTP response without the application middleware executing
112   
113    SessionMiddlewareBase base class defines user session key and
114    isAuthenticated property
115    """
116    TRIGGER_HTTP_STATUS_CODE = str(FORBIDDEN)
117    MIDDLEWARE_ID = 'PEPFilter'
118    POLICY_PARAM_PREFIX = 'policy.'
119    SESSION_KEYNAME = 'sessionKey'
120    POLICY_FILEPATH_PARAMNAME = 'filePath'
121   
122    def __init__(self, app, global_conf, prefix='', **local_conf):
123        """Initialise the PIP (Policy Information Point) and PDP (Policy
124        Decision Point).  The PDP makes access control decisions based on
125        a given policy.  The PIP manages the retrieval of user credentials on
126        behalf of the PDP
127       
128        @type app: callable following WSGI interface
129        @param app: next middleware application in the chain     
130        @type global_conf: dict       
131        @param global_conf: PasteDeploy global configuration dictionary
132        @type prefix: basestring
133        @param prefix: prefix for configuration items
134        @type local_conf: dict       
135        @param local_conf: PasteDeploy application specific configuration
136        dictionary
137       
138        """       
139        # Initialise the PDP reading in the policy
140        policyCfg = PEPFilter._filterKeywords(local_conf, 
141                                              PEPFilter.POLICY_PARAM_PREFIX)
142        self.policyFilePath = policyCfg[PEPFilter.POLICY_FILEPATH_PARAMNAME]
143        policy = Policy.Parse(policyCfg[PEPFilter.POLICY_FILEPATH_PARAMNAME])
144       
145        # Initialise the Policy Information Point to None.  This object is
146        # created and set later.  See AuthorizationMiddlewareBase.
147        self.pdp = PDP(policy, None)
148       
149        self.sessionKey = local_conf.get(PEPFilter.SESSION_KEYNAME, 
150                                         PEPFilter.propertyDefaults[
151                                                    PEPFilter.SESSION_KEYNAME])
152       
153        super(PEPFilter, self).__init__(app,
154                                        global_conf,
155                                        prefix=prefix,
156                                        **local_conf)
157
158    @NDGSecurityMiddlewareBase.initCall
159    def __call__(self, environ, start_response):
160        """
161        @type environ: dict
162        @param environ: WSGI environment variables dictionary
163        @type start_response: function
164        @param start_response: standard WSGI start response function
165        @rtype: iterable
166        @return: response
167        """
168        session = environ.get(self.sessionKey)
169        if session is None:
170            raise PEPFilterConfigError('No beaker session key "%s" found in '
171                                       'environ' % self.sessionKey)
172           
173        queryString = environ.get('QUERY_STRING', '')
174        resourceURI = urlunsplit(('', '', self.pathInfo, queryString, ''))
175       
176        # Check for a secured resource
177        matchingTargets = self._getMatchingTargets(resourceURI)
178        targetMatch = len(matchingTargets) > 0
179        if not targetMatch:
180            log.debug("PEPFilter.__call__: granting access - no matching URI "
181                      "path target was found in the policy for URI path [%s]", 
182                      resourceURI)
183            return self._app(environ, start_response)
184
185        log.debug("PEPFilter.__call__: found matching target(s):\n\n %s\n"
186                  "\nfrom policy file [%s] for URI Path=[%s]\n",
187                  '\n'.join(["RegEx=%s" % t for t in matchingTargets]), 
188                  self.policyFilePath,
189                  resourceURI)
190       
191        if not self.isAuthenticated:
192            log.info("PEPFilter.__call__: user is not authenticated - setting "
193                     "HTTP 401 response ...")
194           
195            # Set a 401 response for an authentication handler to capture
196            return self._setErrorResponse(code=UNAUTHORIZED)
197       
198        log.debug("PEPFilter.__call__: creating request to call PDP to check "
199                  "user authorisation ...")
200       
201        # Make a request object to pass to the PDP
202        request = Request()
203        request.subject[Subject.USERID_NS] = session['username']
204       
205        # IdP Session Manager specific settings:
206        #
207        # The following won't be set if the IdP running the OpenID Provider
208        # hasn't also deployed a Session Manager.  In this case, the
209        # Attribute Authority will be queried directly from here without a
210        # remote Session Manager intermediary to cache credentials
211        request.subject[Subject.SESSIONID_NS] = session.get('sessionId')
212        request.subject[Subject.SESSIONMANAGERURI_NS] = session.get(
213                                                        'sessionManagerURI')
214        request.resource[Resource.URI_NS] = resourceURI
215
216       
217        # Call the PDP
218        response = self.pdp.evaluate(request)       
219       
220        # Record the result in the user's session to enable later
221        # interrogation by the AuthZResultHandlerMiddleware
222        session['pepCtx'] = {'request': request, 'response': response,
223                             'timestamp': time()}
224        session.save()
225       
226        if response.status == Response.DECISION_PERMIT:
227            log.info("PEPFilter.__call__: PDP granted access for URI path "
228                     "[%s] using policy [%s]", 
229                     resourceURI, 
230                     self.policyFilePath)
231           
232            return self._app(environ, start_response)
233        else:
234            log.info("PEPFilter.__call__: PDP returned a status of [%s] "
235                     "denying access for URI path [%s] using policy [%s]", 
236                     response.decisionValue2String[response.status],
237                     resourceURI,
238                     self.policyFilePath) 
239           
240            # Trigger AuthZResultHandlerMiddleware by setting a response
241            # with HTTP status code equal to the TRIGGER_HTTP_STATUS_CODE class
242            # attribute value
243            triggerStatusCode = int(PEPFilter.TRIGGER_HTTP_STATUS_CODE)
244            return self._setErrorResponse(code=triggerStatusCode)
245
246    def _getMatchingTargets(self, resourceURI):
247        """This method may only be called following __call__ as __call__
248        updates the pathInfo property
249       
250        @type resourceURI: basestring
251        @param resourceURI: the URI of the requested resource
252        @rtype: list
253        @return: return list of policy target objects matching the current
254        path
255        """
256        matchingTargets = [target for target in self.pdp.policy.targets
257                           if target.regEx.match(resourceURI) is not None]
258        return matchingTargets
259
260    def multiHandlerInterceptFactory(self):
261        """Return a checker function for use with AuthKit's MultiHandler.
262        MultiHandler can be used to catch HTTP 403 Forbidden responses set by
263        an application and call middleware (AuthZResultMiddleware) to handle
264        the access denied message.
265        """
266       
267        def multiHandlerIntercept(environ, status, headers):
268            """AuthKit MultiHandler checker function to intercept
269            unauthorised response status codes from applications to be
270            protected.  This function's definition is embedded into a
271            factory method so that this function has visibility to the
272            PEPFilter object's attributes if required.
273           
274            @type environ: dict
275            @param environ: WSGI environment dictionary
276            @type status: basestring
277            @param status: HTTP response code set by application middleware
278            that this intercept function is to protect
279            @type headers: list
280            @param headers: HTTP response header content"""
281           
282            if status.startswith(PEPFilter.TRIGGER_HTTP_STATUS_CODE):
283                log.debug("PEPFilter: found [%s] status for URI path [%s]: "
284                          "invoking access denied response",
285                          PEPFilter.TRIGGER_HTTP_STATUS_CODE,
286                          environ['PATH_INFO'])
287                return True
288            else:
289                # No match - it's publicly accessible
290                log.debug("PEPFilter: the return status [%s] for this URI "
291                          "path [%s] didn't match the trigger status [%s]",
292                          status,
293                          environ['PATH_INFO'],
294                          PEPFilter.TRIGGER_HTTP_STATUS_CODE)
295                return False
296       
297        return multiHandlerIntercept
298       
299    @staticmethod
300    def _filterKeywords(conf, prefix):
301        filteredConf = {}
302        prefixLen = len(prefix)
303        for k, v in conf.items():
304            if k.startswith(prefix):
305                filteredConf[k[prefixLen:]] = conf.pop(k)
306               
307        return filteredConf
308
309    def _getPDP(self):
310        if self._pdp is None:
311            raise TypeError("PDP object has not been initialised")
312        return self._pdp
313   
314    def _setPDP(self, pdp):
315        if not isinstance(pdp, (PDP, None.__class__)):
316            raise TypeError("Expecting %s or None type for pdp; got %r" %
317                            (PDP.__class__.__name__, pdp))
318        self._pdp = pdp
319
320    pdp = property(fget=_getPDP,
321                   fset=_setPDP,
322                   doc="Policy Decision Point object makes access control "
323                       "decisions on behalf of the PEP")
324
325   
326class NdgPIPMiddlewareError(Exception):
327    """Base class for Policy Information Point WSGI middleware exception types
328    """
329   
330class NdgPIPMiddlewareConfigError(NdgPIPMiddlewareError):
331    """Configuration related error for Policy Information Point WSGI middleware
332    """   
333   
334class NdgPIPMiddleware(PIP, NDGSecurityMiddlewareBase):
335    '''Extend Policy Information Point to enable caching of credentials in
336    a NDGCredentialWallet object held in beaker.session
337    '''
338    ENVIRON_KEYNAME = 'ndg.security.server.wsgi.authz.NdgPIPMiddleware'
339       
340    propertyDefaults = {
341        'sessionKey': 'beaker.session.ndg.security',
342    }
343    propertyDefaults.update(NDGSecurityMiddlewareBase.propertyDefaults)
344 
345    def __init__(self, app, global_conf, prefix='', **local_conf):
346        '''
347        @type app: callable following WSGI interface
348        @param app: next middleware application in the chain     
349        @type global_conf: dict       
350        @param global_conf: PasteDeploy global configuration dictionary
351        @type prefix: basestring
352        @param prefix: prefix for configuration items
353        @type local_conf: dict       
354        @param local_conf: PasteDeploy application specific configuration
355        dictionary
356        '''
357       
358        # Pre-process list items splitting as needed
359        if isinstance(local_conf.get('caCertFilePathList'), basestring):
360            local_conf[
361                'caCertFilePathList'] = NDGSecurityMiddlewareBase.parseListItem(
362                                            local_conf['caCertFilePathList'])
363           
364        if isinstance(local_conf.get('sslCACertFilePathList'), basestring):
365            local_conf[
366                'sslCACertFilePathList'
367                ] = NDGSecurityMiddlewareBase.parseListItem(
368                                        local_conf['sslCACertFilePathList'])
369           
370        PIP.__init__(self, prefix=prefix, **local_conf)
371       
372        for k in local_conf.keys():
373            if k.startswith(prefix):
374                del local_conf[k]
375               
376        NDGSecurityMiddlewareBase.__init__(self,
377                                           app,
378                                           global_conf,
379                                           prefix=prefix,
380                                           **local_conf)
381       
382    def __call__(self, environ, start_response):
383        """Take a copy of the session object so that it is in scope for
384        _getAttributeCertificate call and add this instance to the environ
385        so that the PEPFilter can retrieve it and pass on to the PDP
386       
387        @type environ: dict
388        @param environ: WSGI environment variables dictionary
389        @type start_response: function
390        @param start_response: standard WSGI start response function
391        @rtype: iterable
392        @return: response
393        """
394        self.session = environ.get(self.sessionKey)
395        if self.session is None:
396            raise NdgPIPMiddlewareConfigError('No beaker session key "%s" found '
397                                           'in environ' % self.sessionKey)
398        environ[NdgPIPMiddleware.ENVIRON_KEYNAME] = self
399       
400        return self._app(environ, start_response)
401               
402    def _getAttributeCertificate(self, attributeAuthorityURI, **kw):
403        '''Extend base class implementation to make use of the
404        NDGCredentialWallet Attribute Certificate cache held in the beaker
405        session.  If no suitable certificate is present invoke default behaviour
406        and retrieve an Attribute Certificate from the Attribute Authority or
407        Session Manager specified
408
409        @type attributeAuthorityURI: basestring
410        @param attributeAuthorityURI: URI to Attribute Authority service
411        @type username: basestring
412        @param username: subject user identifier - could be an OpenID       
413        @type sessionId: basestring
414        @param sessionId: Session Manager session handle
415        @type sessionManagerURI: basestring
416        @param sessionManagerURI: URI to remote session manager service
417        @rtype: ndg.security.common.AttCert.AttCert
418        @return: Attribute Certificate containing user roles
419        '''
420        # Check for a wallet in the current session - if not present, create
421        # one.  See ndg.security.server.wsgi.authn.SessionHandlerMiddleware
422        # for session keys.  The 'credentialWallet' key is deleted along with
423        # any other security keys when the user logs out
424        if not 'credentialWallet' in self.session:
425            log.debug("NdgPIPMiddleware._getAttributeCertificate: adding a "
426                      "Credential Wallet to user session [%s] ...",
427                      self.session['username'])
428           
429            self.session['credentialWallet'] = NDGCredentialWallet(
430                                            userId=self.session['username'])
431            self.session.save()
432           
433        # Take reference to wallet for efficiency
434        credentialWallet = self.session['credentialWallet']   
435       
436        # Check for existing credentials cached in wallet           
437        credentialItem = credentialWallet.credentialsKeyedByURI.get(
438                                                        attributeAuthorityURI)       
439        if credentialItem is not None:
440            log.debug("NdgPIPMiddleware._getAttributeCertificate: retrieved "
441                      "existing Attribute Certificate cached in Credential "
442                      "Wallet for user session [%s]",
443                      self.session['username'])
444
445            # Existing cached credential found - skip call to remote Session
446            # Manager / Attribute Authority and return this certificate instead
447            return credentialItem.credential
448        else:   
449            attrCert = PIP._getAttributeCertificate(self,
450                                                    attributeAuthorityURI,
451                                                    **kw)
452           
453            log.debug("NdgPIPMiddleware._getAttributeCertificate: updating "
454                      "Credential Wallet with retrieved Attribute "
455                      "Certificate for user session [%s]",
456                      self.session['username'])
457       
458            # Update the wallet with this Attribute Certificate so that it's
459            # cached for future calls
460            credentialWallet.addCredential(attrCert,
461                                attributeAuthorityURI=attributeAuthorityURI)
462           
463            return attrCert
464
465   
466class SamlPIPMiddlewareError(Exception):
467    """Base class for SAML based Policy Information Point WSGI middleware
468    exception types
469    """
470
471 
472class SamlPIPMiddlewareConfigError(NdgPIPMiddlewareError):
473    """Configuration related error for Policy Information Point WSGI middleware
474    """
475   
476
477class SamlPIPMiddleware(PIPBase, NDGSecurityMiddlewareBase):
478    '''Extend Policy Information Point to enable caching of SAML credentials in
479    a SAMLCredentialWallet object held in beaker.session
480    '''
481    ENVIRON_KEYNAME = 'ndg.security.server.wsgi.authz.SamlPIPMiddleware'
482       
483    propertyDefaults = {
484        'sessionKey': 'beaker.session.ndg.security',
485    }
486    propertyDefaults.update(NDGSecurityMiddlewareBase.propertyDefaults)
487 
488    CREDENTIAL_WALLET_SESSION_KEYNAME = \
489        SessionHandlerMiddleware.CREDENTIAL_WALLET_SESSION_KEYNAME
490    USERNAME_SESSION_KEYNAME = \
491        SessionHandlerMiddleware.USERNAME_SESSION_KEYNAME
492         
493    ATTRIBUTE_QUERY_ATTRNAME = 'attributeQuery'
494    LEN_ATTRIBUTE_QUERY_ATTRNAME = len(ATTRIBUTE_QUERY_ATTRNAME)
495         
496    def __init__(self, app, global_conf, prefix='', **local_conf):
497        '''
498        @type app: callable following WSGI interface
499        @param app: next middleware application in the chain     
500        @type global_conf: dict       
501        @param global_conf: PasteDeploy global configuration dictionary
502        @type prefix: basestring
503        @param prefix: prefix for configuration items
504        @type local_conf: dict       
505        @param local_conf: PasteDeploy application specific configuration
506        dictionary
507        '''
508        self.session = None
509        self.__attributeQueryBinding = AttributeQuerySslSOAPBinding()
510       
511        nameOffset = len(prefix)
512        for k in local_conf.keys():
513            if k.startswith(prefix):
514                val = local_conf.pop(k)
515                name = k[nameOffset:]
516                setattr(self, name, val)
517               
518        if not self.__attributeQueryBinding.issuerName:
519            issuerX509Cert = X509Cert.Read(
520                    self.__attributeQueryBinding.sslCtxProxy.sslCertFilePath)
521            self.__attributeQueryBinding.issuerName = str(issuerX509Cert.dn)
522               
523        NDGSecurityMiddlewareBase.__init__(self, app, {})
524           
525    def __setattr__(self, name, value):
526        """Enable setting of AttributeQuerySslSOAPBinding attributes from
527        names starting with attributeQuery.* / attributeQuery_*.  Addition for
528        setting these values from ini file
529        """
530
531        # Coerce into setting AttributeQuerySslSOAPBinding attributes -
532        # names must start with 'attributeQuery\W' e.g.
533        # attributeQuery.clockSkew or attributeQuery_issuerDN
534        if name.startswith(SamlPIPMiddleware.ATTRIBUTE_QUERY_ATTRNAME):
535            setattr(self.__attributeQueryBinding, 
536                    name[SamlPIPMiddleware.LEN_ATTRIBUTE_QUERY_ATTRNAME+1:], 
537                    value)
538        else:
539            super(SamlPIPMiddleware, self).__setattr__(name, value)   
540
541    @property
542    def attributeQueryBinding(self):
543        """SAML SOAP Attribute Query client binding object"""
544        return self.__attributeQueryBinding
545               
546    def __call__(self, environ, start_response):
547        """Take a copy of the session object so that it is in scope for
548        attributeQuery call and add this instance to the environ
549        so that the PEPFilter can retrieve it and pass on to the PDP
550       
551        @type environ: dict
552        @param environ: WSGI environment variables dictionary
553        @type start_response: function
554        @param start_response: standard WSGI start response function
555        @rtype: iterable
556        @return: response
557        """
558        self.session = environ.get(self.sessionKey)
559        if self.session is None:
560            raise SamlPIPMiddlewareConfigError('No beaker session key "%s" '
561                                               'found in environ' % 
562                                               self.sessionKey)
563        environ[SamlPIPMiddleware.ENVIRON_KEYNAME] = self
564       
565        return self._app(environ, start_response)
566   
567    def attributeQuery(self, attributeQuery):
568        """Query the Attribute Authority specified in the request to retrieve
569        the attributes if any corresponding to the subject
570       
571        @type attributeResponse: PIPAttributeQuery
572        @param attributeResponse:
573        @rtype: PIPAttributeResponse
574        @return: response containing the attributes retrieved from the
575        Attribute Authority"""
576        if not isinstance(attributeQuery, PIPAttributeQuery):
577            raise TypeError('Expecting %r type for input "attributeQuery"; '
578                            'got %r' % (AttributeQuery, type(attributeQuery)))
579                           
580        attributeAuthorityURI = attributeQuery[
581                                        PIPAttributeQuery.ATTRIBUTEAUTHORITY_NS]
582       
583        log.debug("SamlPIPMiddleware: received attribute query: %r", 
584                  attributeQuery)
585               
586        # Check for a wallet in the current session - if not present, create
587        # one.  See ndg.security.server.wsgi.authn.SessionHandlerMiddleware
588        # for session keys.  The 'credentialWallet' key is deleted along with
589        # any other security keys when the user logs out
590        credentialWalletKeyName = \
591                            SamlPIPMiddleware.CREDENTIAL_WALLET_SESSION_KEYNAME
592        usernameKeyName = SamlPIPMiddleware.USERNAME_SESSION_KEYNAME
593           
594        if not credentialWalletKeyName in self.session:
595            log.debug("SamlPIPMiddleware.attributeQuery: adding a "
596                      "Credential Wallet to user session [%s] ...",
597                      self.session[usernameKeyName])
598           
599            credentialWallet = SAMLCredentialWallet()
600            credentialWallet.userId = self.session[usernameKeyName]
601           
602            self.session[credentialWalletKeyName] = credentialWallet
603            self.session.save()
604        else:   
605            # Take reference to wallet for efficiency
606            credentialWallet = self.session[credentialWalletKeyName]   
607       
608        # Check for existing credentials cached in wallet           
609        credentialItem = credentialWallet.credentialsKeyedByURI.get(
610                                                    attributeAuthorityURI)
611        if credentialItem is None:
612            # No assertion is cached - make a fresh SAML Attribute Query
613            self.attributeQueryBinding.subjectID = credentialWallet.userId
614            response = self.attributeQueryBinding.send(
615                                                    uri=attributeAuthorityURI)
616            for assertion in response.assertions:
617                credentialWallet.addCredential(assertion)
618           
619            log.debug("SamlPIPMiddleware.attributeQuery: updating Credential "
620                      "Wallet with retrieved SAML Attribute Assertion "
621                      "for user session [%s]", self.session[usernameKeyName])
622        else:
623            log.debug("SamlPIPMiddleware.attributeQuery: retrieved existing "
624                      "SAML Attribute Assertion cached in Credential Wallet "
625                      "for user session [%s]", self.session[usernameKeyName])
626
627        attributeResponse = PIPAttributeResponse()
628        attributeResponse[Subject.ROLES_NS] = []
629       
630        # Unpack assertion attribute values and add to the response object
631        for credentialItem in credentialWallet.credentials.values():
632            for statement in credentialItem.credential.attributeStatements:
633                for attribute in statement.attributes:
634                    attributeResponse[Subject.ROLES_NS] += [
635                        attributeValue.value
636                        for attributeValue in attribute.attributeValues
637                        if attributeValue.value not in attributeResponse[
638                                                            Subject.ROLES_NS]
639                    ]
640       
641        log.debug("SamlPIPMiddleware.attributeQuery response: %r", 
642                  attributeResponse)
643       
644        return attributeResponse
645   
646           
647from authkit.authenticate.multi import MultiHandler
648
649class AuthorizationMiddlewareError(Exception):
650    """Base class for AuthorizationMiddlewareBase exceptions"""
651   
652class AuthorizationMiddlewareConfigError(Exception):
653    """AuthorizationMiddlewareBase configuration related exceptions"""
654 
655   
656class AuthorizationMiddlewareBase(NDGSecurityMiddlewareBase):
657    '''Virtual class - A base Handler to call Policy Enforcement Point
658    middleware to intercept requests and enforce access control decisions. 
659   
660    Extend THIS class adding the new type to any WSGI middleware chain ahead of
661    the application(s) which it is to protect.  To make an implementation for
662    this virtual class, set PIP_MIDDLEWARE_CLASS in the derived type to a
663    valid Policy Information Point Class.  Use in conjunction with
664    ndg.security.server.wsgi.authn.AuthenticationMiddleware
665    '''
666    PEP_PARAM_PREFIX = 'pep.filter.'
667    PIP_PARAM_PREFIX = 'pip.'
668    PEP_RESULT_HANDLER_PARAMNAME = "pepResultHandler"
669   
670       
671    class PIP_MIDDLEWARE_CLASS(object):
672        """Policy Information Point WSGI middleware abstract base,
673        implementations should retrieve user credentials to enable the PDP to
674        make access control decisions
675        """
676        def __init__(self, app, global_conf, prefix='', **local_conf): 
677            raise NotImplementedError(' '.join(
678                AuthorizationMiddlewareBase.PIP_MIDDLEWARE_CLASS.__doc__.split())
679            )
680   
681    def __init__(self, app, global_conf, prefix='', **app_conf):
682        """Set-up Policy Enforcement Point to enforce access control decisions
683        based on the URI path requested and/or the HTTP response code set by
684        application(s) to be protected.  An AuthKit MultiHandler is setup to
685        handle the latter.  PEPResultHandlerMiddleware handles the output
686        set following an access denied decision
687        @type app: callable following WSGI interface
688        @param app: next middleware application in the chain     
689        @type global_conf: dict       
690        @param global_conf: PasteDeploy global configuration dictionary
691        @type prefix: basestring
692        @param prefix: prefix for configuration items
693        @type app_conf: dict       
694        @param app_conf: PasteDeploy application specific configuration
695        dictionary
696        """
697        authzPrefix = prefix + AuthorizationMiddlewareBase.PEP_PARAM_PREFIX
698        pepFilter = PEPFilter(app,
699                              global_conf,
700                              prefix=authzPrefix,
701                              **app_conf)
702        pepInterceptFunc = pepFilter.multiHandlerInterceptFactory()
703       
704        # Slot in the Policy Information Point in the WSGI stack at this point
705        # so that it can take a copy of the beaker session object from environ
706        # ahead of the PDP's request to it for an Attribute Certificate
707        pipPrefix = AuthorizationMiddlewareBase.PIP_PARAM_PREFIX
708        pipFilter = self.__class__.PIP_MIDDLEWARE_CLASS(pepFilter,
709                                                        global_conf,
710                                                        prefix=pipPrefix,
711                                                        **app_conf)
712        pepFilter.pdp.pip = pipFilter
713       
714        app = MultiHandler(pipFilter)
715
716        pepResultHandlerClassName = app_conf.pop(
717                prefix+AuthorizationMiddlewareBase.PEP_RESULT_HANDLER_PARAMNAME, 
718                None)
719        if pepResultHandlerClassName is None:
720            pepResultHandler = PEPResultHandlerMiddleware
721        else:
722            pepResultHandler = importClass(pepResultHandlerClassName,
723                                        objectType=PEPResultHandlerMiddleware)
724           
725        app.add_method(PEPFilter.MIDDLEWARE_ID,
726                       pepResultHandler.filter_app_factory,
727                       global_conf,
728                       prefix=prefix,
729                       **app_conf)
730       
731        app.add_checker(PEPFilter.MIDDLEWARE_ID, pepInterceptFunc)               
732       
733        super(AuthorizationMiddlewareBase, self).__init__(app,
734                                                      global_conf,
735                                                      prefix=prefix,
736                                                      **app_conf)
737 
738
739class NDGAuthorizationMiddleware(AuthorizationMiddlewareBase):
740    """Implementation of AuthorizationMiddlewareBase using the NDG Policy
741    Information Point interface.  This retrieves attributes over the SOAP/WSDL
742    Attribute Authority interface
743    (ndg.security.server.wsgi.attributeauthority.AttributeAuthoritySOAPBindingMiddleware)
744    and caches NDG Attribute Certificates in an
745    ndg.security.common.credentialWallet.NDGCredentialWallet
746    """     
747    PIP_MIDDLEWARE_CLASS = NdgPIPMiddleware   
748
749
750class AuthorizationMiddleware(NDGAuthorizationMiddleware):
751    """Include this class for backwards compatibility - see warning message
752    in FUTURE_DEPRECATION_WARNING_MSG class variable"""
753    FUTURE_DEPRECATION_WARNING_MSG = (
754        "AuthorizationMiddleware will be deprecated in future releases.  "
755        "NDGAuthorizationMiddleware is a drop in replacement but should be "
756        "replaced with SAMLAuthorizationMiddleware instead")
757   
758    def __init__(self, *arg, **kw):
759        warnings.warn(AuthorizationMiddleware.FUTURE_DEPRECATION_WARNING_MSG,
760                      PendingDeprecationWarning)
761        log.warning(AuthorizationMiddleware.FUTURE_DEPRECATION_WARNING_MSG) 
762        super(AuthorizationMiddleware, self).__init__(*arg, **kw)
763
764
765class SAMLAuthorizationMiddleware(AuthorizationMiddlewareBase):
766    """Implementation of AuthorizationMiddlewareBase using the SAML Policy
767    Information Point interface.  This retrieves attributes over the SOAP/SAML
768    Attribute Authority interface
769    (ndg.security.server.wsgi.saml.SOAPAttributeInterfaceMiddleware) and caches
770    SAML Assertions in a
771    ndg.security.common.credentialWallet.SAMLCredentialWallet
772    """     
773    PIP_MIDDLEWARE_CLASS = SamlPIPMiddleware
774   
Note: See TracBrowser for help on using the repository browser.