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

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

Tested Policy with regex target URIs

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'
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'
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                return False
126
127        log.debug("AuthorizationHandler found matching target(s):\n\n "
128                  "%s\nfrom policy file [%s] for URI=[%s]" % 
129                  ('\n'.join(["RegEx=%s" % t for t in matchingTargets]), 
130                   self.policyFilePath,
131                   resourceURI))
132       
133        if not self.isAuthenticated:
134            return True
135       
136        # Make a request object to pass to the PDP
137        request = Request()
138        request.subject[Subject.USERID_NS] = session['username']
139        request.subject[Subject.SESSIONID_NS] = session.get('sessionId')
140        request.subject[Subject.SESSIONMANAGERURI_NS] = session.get(
141                                                        'sessionManagerURI')
142        request.resource[Resource.URI_NS] = resourceURI
143           
144        response = self.pdp.evaluate(request)
145        permit = response.status == Response.DECISION_PERMIT
146        if permit:
147            if status.startswith(AuthorizationHandler.triggerStatus):
148                log.debug("AuthorizationHandler found 403 status but policy "
149                          "permits access: preventing access as a precaution")
150                return True
151            else:
152                # Skip the access forbidden middleware and call the next next
153                # WSGI app
154                log.debug("AuthorizationHandler access granted to [%s] using "
155                          "policy [%s]" % (resourceURI, self.policyFilePath))
156                return False
157        else:
158            log.debug("AuthorizationHandler access denied for policy")
159            # True invokes the access forbidden middleware
160            return True
161       
162    @staticmethod
163    def _filterKeywords(conf, prefix):
164        filteredConf = {}
165        prefixLen = len(prefix)
166        for k, v in conf.items():
167            if k.startswith(prefix):
168                filteredConf[k[prefixLen:]] = conf.pop(k)
169               
170        return filteredConf
171
172
173from authkit.authenticate.multi import MultiHandler
174
175class AuthorizationMiddleware(NDGSecurityMiddlewareBase):
176    '''Handler to call Policy Decision Point middleware and intercept
177    authorisation requests.  Add THIS class to any middleware chain and NOT
178    PEPMiddleware which it wraps.
179    '''
180    def __init__(self, app, global_conf, prefix='', **app_conf):
181                       
182        app = MultiHandler(app)
183                           
184        app.add_method(AuthorizationHandler.id,
185                       AuthZResultHandlerMiddleware.filter_app_factory,
186                       global_conf,
187                       prefix=prefix,
188                       **app_conf)
189       
190        authorizationHandler = AuthorizationHandler(**app_conf)
191        app.add_checker(AuthorizationHandler.id, authorizationHandler)               
192       
193        super(AuthorizationMiddleware, self).__init__(app,
194                                                      global_conf,
195                                                      prefix=prefix,
196                                                      **app_conf)
197       
Note: See TracBrowser for help on using the repository browser.