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

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