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

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@6271
Revision 6271, 33.6 KB checked in by pjkersha, 10 years ago (diff)

Working Genshi PEP result handler plugin

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           
590            log.debug("SamlPIPMiddleware.attributeQuery: updating Credential "
591                      "Wallet with retrieved SAML Attribute Assertion "
592                      "for user session [%s]", self.session[usernameKeyName])
593        else:
594            log.debug("SamlPIPMiddleware.attributeQuery: retrieved existing "
595                      "SAML Attribute Assertion cached in Credential Wallet "
596                      "for user session [%s]", self.session[usernameKeyName])
597
598        attributeResponse = PIPAttributeResponse()
599        attributeResponse[Subject.ROLES_NS] = []
600       
601        # Unpack assertion attribute values and add to the response object
602        for credentialItem in credentialWallet.credentials.values():
603            for statement in credentialItem.credential.attributeStatements:
604                for attribute in statement.attributes:
605                    attributeResponse[Subject.ROLES_NS] += [
606                        attributeValue.value
607                        for attributeValue in attribute.attributeValues
608                        if attributeValue.value not in attributeResponse[
609                                                            Subject.ROLES_NS]
610                    ]
611       
612        log.debug("SamlPIPMiddleware.attributeQuery response: %r", 
613                  attributeResponse)
614       
615        return attributeResponse
616
617
618class AuthorizationMiddlewareError(Exception):
619    """Base class for AuthorizationMiddlewareBase exceptions"""
620   
621   
622class AuthorizationMiddlewareConfigError(Exception):
623    """AuthorizationMiddlewareBase configuration related exceptions"""
624 
625   
626class AuthorizationMiddlewareBase(NDGSecurityMiddlewareBase):
627    '''Virtual class - A base Handler to call Policy Enforcement Point
628    middleware to intercept requests and enforce access control decisions. 
629   
630    Extend THIS class adding the new type to any WSGI middleware chain ahead of
631    the application(s) which it is to protect.  To make an implementation for
632    this virtual class, set PIP_MIDDLEWARE_CLASS in the derived type to a
633    valid Policy Information Point Class.  Use in conjunction with
634    ndg.security.server.wsgi.authn.AuthenticationMiddleware
635    '''
636    PEP_PARAM_PREFIX = 'pep.filter.'
637    PIP_PARAM_PREFIX = 'pip.'
638    PEP_RESULT_HANDLER_PARAMNAME = "pepResultHandler"
639    PEP_RESULT_HANDLER_PARAM_PREFIX = PEP_RESULT_HANDLER_PARAMNAME + '.'
640    PEP_RESULT_HANDLER_STATIC_CONTENT_DIR_PARAMNAME = 'staticContentDir'
641   
642    class PIP_MIDDLEWARE_CLASS(object):
643        """Policy Information Point WSGI middleware abstract base,
644        implementations should retrieve user credentials to enable the PDP to
645        make access control decisions
646        """
647        def __init__(self, app, global_conf, prefix='', **local_conf): 
648            raise NotImplementedError(' '.join(
649                AuthorizationMiddlewareBase.PIP_MIDDLEWARE_CLASS.__doc__.split())
650            )
651   
652    def __init__(self, app, global_conf, prefix='', **app_conf):
653        """Set-up Policy Enforcement Point to enforce access control decisions
654        based on the URI path requested and/or the HTTP response code set by
655        application(s) to be protected.  An AuthKit MultiHandler is setup to
656        handle the latter.  PEPResultHandlerMiddleware handles the output
657        set following an access denied decision
658        @type app: callable following WSGI interface
659        @param app: next middleware application in the chain     
660        @type global_conf: dict       
661        @param global_conf: PasteDeploy global configuration dictionary
662        @type prefix: basestring
663        @param prefix: prefix for configuration items
664        @type app_conf: dict       
665        @param app_conf: PasteDeploy application specific configuration
666        dictionary
667        """
668        cls = AuthorizationMiddlewareBase
669       
670        # Allow for static content for use with PEP result handler middleware       
671        pepResultHandlerParamPrefix = prefix + \
672                                            cls.PEP_RESULT_HANDLER_PARAM_PREFIX
673        pepResultHandlerStaticContentDirParamName = \
674            pepResultHandlerParamPrefix + \
675            cls.PEP_RESULT_HANDLER_STATIC_CONTENT_DIR_PARAMNAME
676       
677        pepResultHandlerStaticContentDir = app_conf.get(
678                                    pepResultHandlerStaticContentDirParamName)
679        if pepResultHandlerStaticContentDir is not None:   
680            staticApp = StaticURLParser(pepResultHandlerStaticContentDir)
681            app = Cascade([app, staticApp], catch=(404,))
682
683        authzPrefix = prefix + cls.PEP_PARAM_PREFIX
684        pepFilter = PEPFilter(app,
685                              global_conf,
686                              prefix=authzPrefix,
687                              **app_conf)
688        pepInterceptFunc = pepFilter.multiHandlerInterceptFactory()
689       
690        # Slot in the Policy Information Point in the WSGI stack at this point
691        # so that it can take a copy of the beaker session object from environ
692        # ahead of the PDP's request to it for an Attribute Certificate
693        pipPrefix = cls.PIP_PARAM_PREFIX
694        pipFilter = self.__class__.PIP_MIDDLEWARE_CLASS(pepFilter,
695                                                        global_conf,
696                                                        prefix=pipPrefix,
697                                                        **app_conf)
698        pepFilter.pdp.pip = pipFilter
699       
700        app = MultiHandler(pipFilter)
701
702        pepResultHandlerClassName = app_conf.pop(
703                                        prefix+cls.PEP_RESULT_HANDLER_PARAMNAME, 
704                                        None)
705        if pepResultHandlerClassName is None:
706            pepResultHandler = PEPResultHandlerMiddleware
707        else:
708            pepResultHandler = importClass(pepResultHandlerClassName,
709                                    objectType=PEPResultHandlerMiddlewareBase)
710                               
711        app.add_method(PEPFilter.MIDDLEWARE_ID,
712                       pepResultHandler.filter_app_factory,
713                       global_conf,
714                       prefix=pepResultHandlerParamPrefix,
715                       **app_conf)
716       
717        app.add_checker(PEPFilter.MIDDLEWARE_ID, pepInterceptFunc)
718
719        super(AuthorizationMiddlewareBase, self).__init__(app, {})
720 
721
722class NDGAuthorizationMiddleware(AuthorizationMiddlewareBase):
723    """Implementation of AuthorizationMiddlewareBase using the NDG Policy
724    Information Point interface.  This retrieves attributes over the SOAP/WSDL
725    Attribute Authority interface
726    (ndg.security.server.wsgi.attributeauthority.AttributeAuthoritySOAPBindingMiddleware)
727    and caches NDG Attribute Certificates in an
728    ndg.security.common.credentialWallet.NDGCredentialWallet
729    """     
730    PIP_MIDDLEWARE_CLASS = NdgPIPMiddleware   
731
732
733class AuthorizationMiddleware(NDGAuthorizationMiddleware):
734    """Include this class for backwards compatibility - see warning message
735    in FUTURE_DEPRECATION_WARNING_MSG class variable"""
736    FUTURE_DEPRECATION_WARNING_MSG = (
737        "AuthorizationMiddleware will be deprecated in future releases.  "
738        "NDGAuthorizationMiddleware is a drop in replacement but should be "
739        "replaced with SAMLAuthorizationMiddleware instead")
740   
741    def __init__(self, *arg, **kw):
742        warnings.warn(AuthorizationMiddleware.FUTURE_DEPRECATION_WARNING_MSG,
743                      PendingDeprecationWarning)
744        log.warning(AuthorizationMiddleware.FUTURE_DEPRECATION_WARNING_MSG) 
745        super(AuthorizationMiddleware, self).__init__(*arg, **kw)
746
747
748class SAMLAuthorizationMiddleware(AuthorizationMiddlewareBase):
749    """Implementation of AuthorizationMiddlewareBase using the SAML Policy
750    Information Point interface.  This retrieves attributes over the SOAP/SAML
751    Attribute Authority interface
752    (ndg.security.server.wsgi.saml.SOAPAttributeInterfaceMiddleware) and caches
753    SAML Assertions in a
754    ndg.security.common.credentialWallet.SAMLCredentialWallet
755    """     
756    PIP_MIDDLEWARE_CLASS = SamlPIPMiddleware
757   
Note: See TracBrowser for help on using the repository browser.