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

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

1.0.1 rc3

  • Fixed return in msi PIP._getAttributeCertificate
  • improved documentation for ndg.security.server.wsgi.authz
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 AuthZResultHandlerMiddleware(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(AuthZResultHandlerMiddleware, 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.  Please check "
66                        "with your site administrator that you have the "
67                        "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 AuthorizationHandler(object):
75    """Interface to authkit.authenticate.MultiHandler checker callable.
76    The checker returns True/False to indicate to the MultiHandler whether to
77    call the access denied middleware AuthZResultHandlerMiddleware.  To
78    return this bool, it evaluates the users credentials against the
79    constraints on the resource to be accessed by calling the NDG Policy
80    Decision Point
81    """
82    triggerStatus = '403'
83    id = 'AuthorizationHandler'
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_conf):
95       
96        # Policy Information Point
97        pipCfg = AuthorizationHandler._filterKeywords(app_conf, 'pip.')
98        pip = PIP(**pipCfg)
99
100        # Policy Decision Point
101        policyCfg = AuthorizationHandler._filterKeywords(app_conf, 'policy.')
102        self.policyFilePath = policyCfg['filePath']
103        self.policy = Policy.Parse(policyCfg['filePath'])
104        self.pdp = PDP(self.policy, pip)
105       
106        self.sessionKey = app_conf.get('sessionKey', 
107                        AuthorizationHandler.propertyDefaults['sessionKey'])
108   
109    def __call__(self, environ, status, headers):
110        """
111        @rtype: bool
112        @return: True if access should be forbidden - this injects the
113        access forbidden middleware into the chain to interrupt access; False
114        return status bypasses the access forbidden and enables normal
115        execution of the WSGI middleware chain to proceed i.e. invoke the
116        middleware which this code is securing
117        """
118        self.environ = environ
119        session = environ[self.sessionKey]
120       
121        # Check for a secured resource
122        resourceURI = environ['PATH_INFO']
123        matchingTargets = [target for target in self.policy.targets
124                           if target.regEx.match(resourceURI) is not None]
125        if len(matchingTargets) == 0:
126            if status.startswith(AuthorizationHandler.triggerStatus):
127                log.debug("AuthorizationHandler found 403 status but no "
128                          "policy set for this URI : preventing access as a "
129                          "precaution")
130                return True
131            else:
132                # No match - it's publicly accessible
133                log.debug("AuthorizationHandler: no match was found in the "
134                          "policy for uri [%s]", resourceURI)
135                return False
136
137        log.debug("AuthorizationHandler found matching target(s):\n\n "
138                  "%s\nfrom policy file [%s] for URI=[%s]" % 
139                  ('\n'.join(["RegEx=%s" % t for t in matchingTargets]), 
140                   self.policyFilePath,
141                   resourceURI))
142       
143        if not self.isAuthenticated:
144            log.debug("AuthorizationHandler: user is not authenticated")
145            return True
146       
147        # Make a request object to pass to the PDP
148        request = Request()
149        request.subject[Subject.USERID_NS] = session['username']
150       
151        # The following won't be set if the IdP running the OpenID Provider
152        # hasn't also deployed a Session Manager.  In this case, the
153        # Attribute Authority will be queried directly from here without a
154        # remote Session Manager intermediary to cache credentials
155        request.subject[Subject.SESSIONID_NS] = session.get('sessionId')
156        request.subject[Subject.SESSIONMANAGERURI_NS] = session.get(
157                                                        'sessionManagerURI')
158        request.resource[Resource.URI_NS] = resourceURI
159           
160        response = self.pdp.evaluate(request)
161        permit = response.status == Response.DECISION_PERMIT
162        if permit:
163            if status.startswith(AuthorizationHandler.triggerStatus):
164                log.debug("AuthorizationHandler found 403 status but policy "
165                          "permits access: preventing access as a precaution")
166                return True
167            else:
168                # Skip the access forbidden middleware and call the next next
169                # WSGI app
170                log.debug("AuthorizationHandler access granted to [%s] using "
171                          "policy [%s]" % (resourceURI, self.policyFilePath))
172                return False
173        else:
174            log.debug("AuthorizationHandler policy [%s] denied access for "
175                      "uri [%s]", self.policyFilePath, resourceURI)
176            # True invokes the access forbidden middleware
177            return True
178       
179    @staticmethod
180    def _filterKeywords(conf, prefix):
181        filteredConf = {}
182        prefixLen = len(prefix)
183        for k, v in conf.items():
184            if k.startswith(prefix):
185                filteredConf[k[prefixLen:]] = conf.pop(k)
186               
187        return filteredConf
188
189
190from authkit.authenticate.multi import MultiHandler
191
192class AuthorizationMiddleware(NDGSecurityMiddlewareBase):
193    '''Handler to call Policy Decision Point middleware and intercept
194    authorisation requests.  Add THIS class to any middleware chain and NOT
195    AuthZResultHandlerMiddleware and AuthorizationHandler which it wraps.
196    '''
197    def __init__(self, app, global_conf, prefix='', **app_conf):
198        """Set-up AuthKit MultiHandler with a WSGI interface to handle HTTP
199        403 access denied responses - AuthZResultHandlerMiddleware and a
200        checker which intercepts requests and makes access control decisions
201        using the Policy Decision Point (PDP)"""
202       
203        app = MultiHandler(app)
204                           
205        app.add_method(AuthorizationHandler.id,
206                       AuthZResultHandlerMiddleware.filter_app_factory,
207                       global_conf,
208                       prefix=prefix,
209                       **app_conf)
210       
211        authorizationHandler = AuthorizationHandler(**app_conf)
212        app.add_checker(AuthorizationHandler.id, authorizationHandler)               
213       
214        super(AuthorizationMiddleware, self).__init__(app,
215                                                      global_conf,
216                                                      prefix=prefix,
217                                                      **app_conf)
218       
Note: See TracBrowser for help on using the repository browser.