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

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

1.0.1 rc2

Added capability for Policy Information Point to query an Attribute Authority directly without a remote Session Manager intermediary to cache credentials. This is the use case for ESG based IdP connecting to NDG services.

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 access denied for policy")
167            # True invokes the access forbidden middleware
168            return True
169       
170    @staticmethod
171    def _filterKeywords(conf, prefix):
172        filteredConf = {}
173        prefixLen = len(prefix)
174        for k, v in conf.items():
175            if k.startswith(prefix):
176                filteredConf[k[prefixLen:]] = conf.pop(k)
177               
178        return filteredConf
179
180
181from authkit.authenticate.multi import MultiHandler
182
183class AuthorizationMiddleware(NDGSecurityMiddlewareBase):
184    '''Handler to call Policy Decision Point middleware and intercept
185    authorisation requests.  Add THIS class to any middleware chain and NOT
186    PEPMiddleware which it wraps.
187    '''
188    def __init__(self, app, global_conf, prefix='', **app_conf):
189                       
190        app = MultiHandler(app)
191                           
192        app.add_method(AuthorizationHandler.id,
193                       AuthZResultHandlerMiddleware.filter_app_factory,
194                       global_conf,
195                       prefix=prefix,
196                       **app_conf)
197       
198        authorizationHandler = AuthorizationHandler(**app_conf)
199        app.add_checker(AuthorizationHandler.id, authorizationHandler)               
200       
201        super(AuthorizationMiddleware, self).__init__(app,
202                                                      global_conf,
203                                                      prefix=prefix,
204                                                      **app_conf)
205       
Note: See TracBrowser for help on using the repository browser.