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

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

Major fix to authorisation middleware:

  • Apply request URI checking in WSGI middleware not in MultiHandler? checker function
  • MultiHandler? checker is still used but this performs the function of responding to HTTP 403 Forbidden responses from applications to be protected downstream in the WSGI stack
  • Refactored:
    • PEPFilter is a WSGI app to enforce access control decisions made by the PDP.
    • AuthZResultMiddleware -> PEPResultMiddleware
  • PEPResultMiddleware provides the response if access is denied. This can happen if a URI path matches a target in the policy or if an application downstream sets a 403 response.
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-levle directory"
11import logging
12log = logging.getLogger(__name__)
13import httplib
14
15from ndg.security.server.wsgi import NDGSecurityPathFilter
16from ndg.security.common.X509 import X500DN
17from ndg.security.server.wsgi import NDGSecurityMiddlewareBase, \
18    NDGSecurityMiddlewareConfigError
19
20from ndg.security.server.wsgi import NDGSecurityMiddlewareBase, \
21    NDGSecurityMiddlewareConfigError
22
23from ndg.security.common.authz.msi import Policy, PIP, PDP, Request, \
24    Response, Resource, Subject
25
26class PEPResultHandlerMiddleware(NDGSecurityMiddlewareBase):
27    """This middleware is invoked if access is denied to a given resource.  It
28    is incorporated into the call stack by passing it in to a MultiHandler
29    instance.  The MultiHandler is configured in the AuthorizationMiddleware
30    class below.  The MultiHandler is passed a checker method which determines
31    whether to allow access, or call this interface.   The checker is
32    implemented in the AuthorizationHandler.  See below ...
33   
34    TODO: possible refactor to incorporate user role registration interface.
35    For ESG collaboration, the scenario following access denied is to g"""
36    propertyDefaults = {
37        'sessionKey': 'beaker.session.ndg.security'
38    }
39
40    _isAuthenticated = lambda self: \
41                            'username' in self.environ.get(self.sessionKey,())
42    isAuthenticated = property(fget=_isAuthenticated,
43                               doc='boolean to indicate is user logged in')
44
45    def __init__(self, app, global_conf, prefix='', **app_conf):
46       
47        super(PEPResultHandlerMiddleware, self).__init__(app,
48                                                           global_conf,
49                                                           prefix=prefix,
50                                                           **app_conf)
51               
52    @NDGSecurityMiddlewareBase.initCall
53    def __call__(self, environ, start_response):
54        self.session = self.environ.get(self.sessionKey)
55        if not self.isAuthenticated:
56            response = "Not authenticated"
57            start_response(self.__class__.getStatusMessage(401),
58                           [('Content-type', 'text/plain') ,
59                            ('Content-length', str(len(response)))])
60            return response
61        else:
62            # TODO: refactor to include a call to another interface - possibly
63            # - another WSGI to set a user friendly output and include links
64            # to enable the user to register for new access privileges
65            response = ("Access is forbidden for this resource.\n\nPlease "
66                        "check with your site administrator that you have "
67                        "the required access privileges")
68            start_response(self.__class__.getStatusMessage(403),
69                           [('Content-type', 'text/plain') ,
70                            ('Content-length', str(len(response)))])
71            return response
72
73
74class PEPFilter(NDGSecurityMiddlewareBase):
75    """PEP (Policy Enforcement Point) WSGI Middleware.  The PEP enforces
76    access control decisions made by the PDP (Policy Decision Point).  In
77    this case, it follows the WSG middleware filter pattern and is configured
78    in a pipeline upstream of the application(s) which it protects.  if an
79    access denied decision is made, the PEP enforces this by returning a
80    403 Forbidden HTTP response without the application middleware executing
81    """
82    triggerStatus = '403'
83    id = 'PEPFilter'
84   
85    propertyDefaults = {
86        'sessionKey': 'beaker.session.ndg.security'
87    }
88
89    _isAuthenticated = lambda self: \
90                            'username' in self.environ.get(self.sessionKey,())
91    isAuthenticated = property(fget=_isAuthenticated,
92                               doc='boolean to indicate is user logged in')
93
94    def __init__(self, app, global_conf, prefix='', **local_conf):
95        """Initialise the PIP (Policy Information Point) and PDP (Policy
96        Decision Point).  The PDP makes access control decisions based on
97        a given policy.  The PIP manages the retrieval of user credentials on
98        behalf of the PDP
99       
100        """
101        pipCfg = PEPFilter._filterKeywords(local_conf, 'pip.')
102        pip = PIP(**pipCfg)
103
104        # Initialise the  reading in the policy
105        policyCfg = PEPFilter._filterKeywords(local_conf, 'policy.')
106        self.policyFilePath = policyCfg['filePath']
107        self.policy = Policy.Parse(policyCfg['filePath'])
108        self.pdp = PDP(self.policy, pip)
109       
110        self.sessionKey = local_conf.get('sessionKey', 
111                                     PEPFilter.propertyDefaults['sessionKey'])
112       
113        super(PEPFilter, self).__init__(app,
114                                        global_conf,
115                                        prefix=prefix,
116                                        **local_conf)
117       
118    @NDGSecurityMiddlewareBase.initCall
119    def __call__(self, environ, start_response):
120        session = environ[self.sessionKey]
121        resourceURI = self.pathInfo
122       
123        # Check for a secured resource
124        matchingTargets = self._getMatchingTargets()
125        targetMatch = len(matchingTargets) > 0
126        if not targetMatch:
127            log.debug("PEPFilter: no matching URI path target was found in "
128                      "the policy for URI path [%s]", resourceURI)
129            return self._app(environ, start_response)       
130
131        log.info("PEPFilter found matching target(s):\n\n %s\n"
132                 "\nfrom policy file [%s] for URI Path=[%s]\n",
133                 '\n'.join(["RegEx=%s" % t for t in matchingTargets]), 
134                 self.policyFilePath,
135                 resourceURI)
136       
137        if not self.isAuthenticated:
138            log.info("PEPFilter: user is not authenticated")
139            return self.authZResult(environ, start_response)
140       
141        # Make a request object to pass to the PDP
142        request = Request()
143        request.subject[Subject.USERID_NS] = session['username']
144       
145        # IdP Session Manager specific settings:
146        #
147        # The following won't be set if the IdP running the OpenID Provider
148        # hasn't also deployed a Session Manager.  In this case, the
149        # Attribute Authority will be queried directly from here without a
150        # remote Session Manager intermediary to cache credentials
151        request.subject[Subject.SESSIONID_NS] = session.get('sessionId')
152        request.subject[Subject.SESSIONMANAGERURI_NS] = session.get(
153                                                        'sessionManagerURI')
154        request.resource[Resource.URI_NS] = resourceURI
155           
156        response = self.pdp.evaluate(request)
157        permit = response.status == Response.DECISION_PERMIT
158        if permit:
159            log.info("PEPFilter: PDP using policy [%s] denied access for "
160                     "uri path [%s]", self.policyFilePath, resourceURI)
161           
162            return self._app(environ, start_response)
163        else:
164            log.debug("PEPFilter: PDP using policy [%s] granted access for "
165                      "uri path [%s]", self.policyFilePath, resourceURI)
166            return self.authZResult(environ, start_response)
167
168    def _setAuthZResult(self, val):
169        if not isinstance(val, PEPResultHandlerMiddleware):
170            raise TypeError("Expecting AuthZResultMiddleware type; got %r" %
171                            val)
172        self._authZResult = val
173       
174    def _getAuthZResult(self):
175        return getattr(self, '_authZResult', None)
176   
177    authZResult = property(fget=_getAuthZResult,
178                           fset=_setAuthZResult,
179                           doc="middleware object to handle access denied "
180                               "response from PDP")
181   
182    def _getMatchingTargets(self):
183        """This method may only be called following __call__ as __call__
184        updates the pathInfo property
185       
186        @rtype: list
187        @return: return list of policy target objects matching the current
188        path
189        """
190        resourceURI = self.pathInfo
191        matchingTargets = [target for target in self.policy.targets
192                           if target.regEx.match(resourceURI) is not None]
193        return matchingTargets
194
195    def multiHandlerInterceptFactory(self):
196        """Return a checker function for use with AuthKit's MultiHandler.
197        MultiHandler can be used to catch HTTP 403 Forbidden responses set by
198        an application and call middleware (AuthZResultMiddleware) to handle
199        the access denied message.
200        """
201       
202        def multiHandlerIntercept(environ, status, headers):
203            """AuthKit MultiHandler checker function to intercept
204            unauthorised response status codes from applications to be
205            protected.  This function's definition is embedded into a
206            factory method so that this function has visibility to the
207            PEPFilter object's attributes if required.
208           
209            @type environ: dict
210            @param environ: WSGI environment dictionary
211            @type status: basestring
212            @param status: HTTP response code set by application middleware
213            that this intercept function is to protect
214            @type headers: list
215            @param headers: HTTP response header content"""
216            if status.startswith(PEPFilter.triggerStatus):
217                log.info("Policy Enforcement Point found [%s] status for URI "
218                         "path [%s]: invoking access denied response",
219                         PEPFilter.triggerStatus,
220                         environ['PATH_INFO'])
221                return True
222            else:
223                # No match - it's publicly accessible
224                log.debug("Policy Enforcement Point: the return status [%s] "
225                          "for this URI path [%s] didn't match the trigger "
226                          "status [%s]",
227                          status,
228                          environ['PATH_INFO'],
229                          PEPFilter.triggerStatus)
230                return False
231       
232        return multiHandlerIntercept
233       
234    @staticmethod
235    def _filterKeywords(conf, prefix):
236        filteredConf = {}
237        prefixLen = len(prefix)
238        for k, v in conf.items():
239            if k.startswith(prefix):
240                filteredConf[k[prefixLen:]] = conf.pop(k)
241               
242        return filteredConf
243           
244
245from authkit.authenticate.multi import MultiHandler
246
247class AuthorizationMiddleware(NDGSecurityMiddlewareBase):
248    '''Handler to call Policy Enforcement Point middleware to intercept
249    requests and enforce access control decisions.  Add THIS class to any
250    WSGI middleware chain ahead of the application(s) which it is to
251    protect.  Use in conjunction with
252    ndg.security.server.wsgi.authn.AuthenticationMiddleware
253    '''
254   
255    def __init__(self, app, global_conf, prefix='', **app_conf):
256        """Set-up Policy Enforcement Point to enforce access control decisions
257        based on the URI path requested and/or the HTTP response code set by
258        application(s) to be protected.  An AuthKit MultiHandler is setup to
259        handle the latter.  PEPResultHandlerMiddleware handles the output
260        set following an access denied decision"""
261       
262        pepFilter = PEPFilter(app,global_conf,prefix=prefix+'pep.',**app_conf)
263        pepInterceptFunc = pepFilter.multiHandlerInterceptFactory()
264       
265        app = MultiHandler(pepFilter)
266                           
267        app.add_method(PEPFilter.id,
268                       PEPResultHandlerMiddleware.filter_app_factory,
269                       global_conf,
270                       prefix=prefix,
271                       **app_conf)
272       
273        app.add_checker(PEPFilter.id, pepInterceptFunc)               
274        pepFilter.authZResult = app.binding[PEPFilter.id]
275       
276        super(AuthorizationMiddleware, self).__init__(app,
277                                                      global_conf,
278                                                      prefix=prefix,
279                                                      **app_conf)
280               
Note: See TracBrowser for help on using the repository browser.