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

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

Added additional debug logging and improved error handling

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, Response,\
24    Resource, Subject
25
26class AuthZResultHandlerMiddleware(NDGSecurityMiddlewareBase):
27    """Simple interface to send a 401 response if no username is set in the
28    beaker.session
29   
30    TODO: possible refactor to incorporate 403 response and user role
31    registration interface"""
32    propertyDefaults = {
33        'sessionKey': 'beaker.session.ndg.security'
34    }
35
36    _isAuthenticated = lambda self: \
37                            'username' in self.environ.get(self.sessionKey, ())
38    isAuthenticated = property(fget=_isAuthenticated,
39                               doc='boolean to indicate is user logged in')
40
41    def __init__(self, app, global_conf, prefix='', **app_conf):
42       
43        super(AuthZResultHandlerMiddleware, self).__init__(app,
44                                                           global_conf,
45                                                           prefix=prefix,
46                                                           **app_conf)
47
48               
49    @NDGSecurityMiddlewareBase.initCall
50    def __call__(self, environ, start_response):
51        self.session = self.environ.get(self.sessionKey)
52        if not self.isAuthenticated:
53            response = "Not authenticated"
54            start_response(self.__class__.getStatusMessage(401),
55                           [('Content-type', 'text/plain') ,
56                            ('Content-length', str(len(response)))])
57            return response
58        else:
59            # TODO: refactor to include a call to another interface - possibly
60            # - another WSGI to set a user friendly output and include links
61            # to enable the user to register for new access privileges
62            response = ("Access is forbidden for this resource.  Please check "
63                        "with your site administrator that you have the "
64                        "required access privileges")
65            start_response(self.__class__.getStatusMessage(403),
66                           [('Content-type', 'text/plain') ,
67                            ('Content-length', str(len(response)))])
68            return response
69
70
71class AuthorizationHandler(object):
72    """Interface to authkit.authenticate.MultiHandler checker callable
73    """
74    triggerStatus = '403'
75    id = 'AuthorizationHandler'
76   
77    propertyDefaults = {
78        'sessionKey': 'beaker.session.ndg.security'
79    }
80
81    _isAuthenticated = lambda self: \
82                            'username' in self.environ.get(self.sessionKey,())
83    isAuthenticated = property(fget=_isAuthenticated,
84                               doc='boolean to indicate is user logged in')
85
86    def __init__(self, **app_conf):
87       
88        # Policy Information Point
89        pipCfg = AuthorizationHandler._filterKeywords(app_conf, 'pip.')
90        pip = PIP(**pipCfg)
91
92        # Policy Decision Point
93        policyCfg = AuthorizationHandler._filterKeywords(app_conf, 'policy.')
94        self.policyFilePath = policyCfg['filePath']
95        self.policy = Policy.Parse(policyCfg['filePath'])
96        self.pdp = PDP(self.policy, pip)
97       
98        self.sessionKey = app_conf.get('sessionKey', 
99                        AuthorizationHandler.propertyDefaults['sessionKey'])
100   
101    def __call__(self, environ, status, headers):
102        """
103        @rtype: bool
104        @return: True if access should be forbidden - this injects the
105        access forbidden middleware into the chain to interrupt access; False
106        return status bypasses the access forbidden and enables normal
107        execution of the WSGI middleware chain to proceed i.e. invoke the
108        middleware which this code is securing
109        """
110        self.environ = environ
111        session = environ[self.sessionKey]
112       
113        # Check for a secured resource
114        resourceURI = environ['PATH_INFO']
115        matchingTargets = [target for target in self.policy.targets
116                           if target.regEx.match(resourceURI) is not None]
117        if len(matchingTargets) == 0:
118            if status.startswith(AuthorizationHandler.triggerStatus):
119                log.debug("AuthorizationHandler found 403 status but no "
120                          "policy set for this URI : preventing access as a "
121                          "precaution")
122                return True
123            else:
124                # No match - it's publicly accessible
125                log.debug("AuthorizationHandler: no match was found in the "
126                          "policy for uri [%s]", resourceURI)
127                return False
128
129        log.debug("AuthorizationHandler found matching target(s):\n\n "
130                  "%s\nfrom policy file [%s] for URI=[%s]" % 
131                  ('\n'.join(["RegEx=%s" % t for t in matchingTargets]), 
132                   self.policyFilePath,
133                   resourceURI))
134       
135        if not self.isAuthenticated:
136            log.debug("AuthorizationHandler: user is not authenticated")
137            return True
138       
139        # Make a request object to pass to the PDP
140        request = Request()
141        request.subject[Subject.USERID_NS] = session['username']
142       
143        # The following won't be set if the IdP running the OpenID Provider
144        # hasn't also deployed a Session Manager.  In this case, the
145        # Attribute Authority will be queried directly from here without a
146        # remote Session Manager intermediary to cache credentials
147        request.subject[Subject.SESSIONID_NS] = session.get('sessionId')
148        request.subject[Subject.SESSIONMANAGERURI_NS] = session.get(
149                                                        'sessionManagerURI')
150        request.resource[Resource.URI_NS] = resourceURI
151           
152        response = self.pdp.evaluate(request)
153        permit = response.status == Response.DECISION_PERMIT
154        if permit:
155            if status.startswith(AuthorizationHandler.triggerStatus):
156                log.debug("AuthorizationHandler found 403 status but policy "
157                          "permits access: preventing access as a precaution")
158                return True
159            else:
160                # Skip the access forbidden middleware and call the next next
161                # WSGI app
162                log.debug("AuthorizationHandler access granted to [%s] using "
163                          "policy [%s]" % (resourceURI, self.policyFilePath))
164                return False
165        else:
166            log.debug("AuthorizationHandler policy [%s] denied access for "
167                      "uri [%s]", self.policyFilePath, resourceURI)
168            # True invokes the access forbidden middleware
169            return True
170       
171    @staticmethod
172    def _filterKeywords(conf, prefix):
173        filteredConf = {}
174        prefixLen = len(prefix)
175        for k, v in conf.items():
176            if k.startswith(prefix):
177                filteredConf[k[prefixLen:]] = conf.pop(k)
178               
179        return filteredConf
180
181
182from authkit.authenticate.multi import MultiHandler
183
184class AuthorizationMiddleware(NDGSecurityMiddlewareBase):
185    '''Handler to call Policy Decision Point middleware and intercept
186    authorisation requests.  Add THIS class to any middleware chain and NOT
187    PEPMiddleware which it wraps.
188    '''
189    def __init__(self, app, global_conf, prefix='', **app_conf):
190                       
191        app = MultiHandler(app)
192                           
193        app.add_method(AuthorizationHandler.id,
194                       AuthZResultHandlerMiddleware.filter_app_factory,
195                       global_conf,
196                       prefix=prefix,
197                       **app_conf)
198       
199        authorizationHandler = AuthorizationHandler(**app_conf)
200        app.add_checker(AuthorizationHandler.id, authorizationHandler)               
201       
202        super(AuthorizationMiddleware, self).__init__(app,
203                                                      global_conf,
204                                                      prefix=prefix,
205                                                      **app_conf)
206       
Note: See TracBrowser for help on using the repository browser.