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

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

Changes for addition of AuthzDecisionQuery? WSGI interface (Authorisation service)

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