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

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@6512
Revision 6512, 33.8 KB checked in by pjkersha, 11 years ago (diff)

Patches to CredentialWallet?, SAML interfaces and authz middleware for WPS testing.

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