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

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

Added new access control interface and functionality to OpenID Provider to enable a custom context object to be passed between login and logout calls.

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.common.authz.msi import Policy
18from ndg.security.server.wsgi import NDGSecurityMiddlewareBase, \
19    NDGSecurityMiddlewareConfigError
20
21from ndg.security.server.wsgi import NDGSecurityMiddlewareBase, \
22    NDGSecurityMiddlewareConfigError
23
24from ndg.security.common.authz.msi import PDP, Request, Resource
25from ndg.security.common.wssecurity import WSSecurityConfig
26from ndg.security.common.sessionmanager import SessionManagerClient, \
27    SessionNotFound, SessionCertTimeError, SessionExpired, InvalidSession, \
28    AttributeRequestDenied                   
29   
30class SubjectRetrievalError(Exception):
31    """Generic exception class for errors related to information about the
32    subject"""
33   
34class InvalidAttributeCertificate(SubjectRetrievalError):
35    "The certificate containing authorisation roles is invalid"
36    def __init__(self, msg=None):
37        SubjectRetrievalError.__init__(self, msg or 
38                                       InvalidAttributeCertificate.__doc__)
39   
40class SessionExpiredMsg(SubjectRetrievalError):
41    'Session has expired.  Please re-login'
42    def __init__(self, msg=None):
43        SubjectRetrievalError.__init__(self, msg or SessionExpiredMsg.__doc__)
44
45class InvalidSessionMsg(SubjectRetrievalError):
46    'Session is invalid.  Please try re-login'
47    def __init__(self, msg=None):
48        SubjectRetrievalError.__init__(self, msg or 
49                                       InvalidSessionMsg.__doc__)
50
51class InitSessionCtxError(SubjectRetrievalError):
52    'A problem occurred initialising a session connection'
53    def __init__(self, msg=None):
54        SubjectRetrievalError.__init__(self, msg or 
55                                       InitSessionCtxError.__doc__)
56
57class AttributeCertificateRequestError(SubjectRetrievalError):
58    'A problem occurred requesting a certificate containing authorisation roles'
59    def __init__(self, msg=None):
60        SubjectRetrievalError.__init__(self,msg or 
61                                    AttributeCertificateRequestError.__doc__)
62
63class PEPMiddleware(NDGSecurityPathFilter):
64    triggerStatus = '403'
65    id = 'forbidden'
66   
67    propertyDefaults = {
68        'sessionKey': 'beaker.session'
69    }
70
71    _isAuthenticated = lambda self: \
72                            'username' in self.environ.get(self.sessionKey, ())
73    isAuthenticated = property(fget=_isAuthenticated,
74                               doc='boolean to indicate is user logged in')
75
76    def __init__(self, app, global_conf, prefix='', **app_conf):
77       
78        pdpCfg = PEPMiddleware._filterKeywords(app_conf, 'pdp.')
79        self.pdp = PDP(**pdpCfg)
80
81        self.wssecurityCfg = WSSecurityConfig()
82        wssePrefix = 'sessionManagerClient.wssecurity'
83        self.wssecurityCfg.update(app_conf, prefix=wssePrefix)
84       
85        PEPMiddleware._filterKeywords(app_conf, wssePrefix+'.')
86                 
87        self.sslCACertFilePathList = app_conf.pop(
88                            'sessionManagerClient.sslCACertFilePathList', [])
89       
90        super(PEPMiddleware, self).__init__(app,
91                                            global_conf,
92                                            prefix=prefix,
93                                            **app_conf)
94
95    @staticmethod
96    def _filterKeywords(conf, prefix):
97        filteredConf = {}
98        prefixLen = len(prefix)
99        for k, v in conf.items():
100            if k.startswith(prefix):
101                filteredConf[k[prefixLen:]] = conf.pop(k)
102               
103        return filteredConf
104               
105    @NDGSecurityPathFilter.initCall
106    def __call__(self, environ, start_response):
107        self.session = self.environ.get(self.sessionKey)
108        if self.isAuthenticated:
109            if self.isAuthorized():
110                # Return next layer in stack
111                return self._app(environ, start_response)
112            else:
113                return self.accessDeniedResponse()
114        else:
115            response = "Not authenticated"
116            start_response(PEPMiddleware.getStatusMessage(401),
117                           [('Content-type', 'text/plain') ,
118                            ('Content-length', str(len(response)))])
119            return response           
120
121    def _getAttributeCertificate(self, attributeAuthorityURI):
122        try:
123            # Create Session Manager client - if a file path was set, setting
124            # are read from a separate config file section otherwise, from the
125            # PDP config object
126            smClnt = SessionManagerClient(
127                            uri=self.session['sessionManagerURI'],
128                            sslCACertFilePathList=self.sslCACertFilePathList,
129                            cfg=self.wssecurityCfg)
130        except Exception, e:
131            log.error("Creating Session Manager client: %s" % e)
132            raise InitSessionCtxError()
133       
134         
135        try:
136            # Make request for attribute certificate
137            attCert = smClnt.getAttCert(
138                                attributeAuthorityURI=attributeAuthorityURI,
139                                sessID=self.session['sessionID'])
140            return attCert
141       
142        except AttributeRequestDenied, e:
143            log.error("Request for attribute certificate denied: %s" % e)
144            raise PDPUserAccessDenied()
145       
146        except SessionNotFound, e:
147            log.error("No session found: %s" % e)
148            raise PDPUserNotLoggedIn()
149
150        except SessionExpired, e:
151            log.error("Session expired: %s" % e)
152            raise InvalidSessionMsg()
153
154        except SessionCertTimeError, e:
155            log.error("Session cert. time error: %s" % e)
156            raise InvalidSessionMsg()
157           
158        except InvalidSession, e:
159            log.error("Invalid user session: %s" % e)
160            raise InvalidSessionMsg()
161
162        except Exception, e:
163            log.error("Request from Session Manager [%s] to Attribute "
164                      "Authority [%s] for attribute certificate: %s: %s" % 
165                      (self.session['sessionManagerURI'],
166                       attributeAuthorityURI,
167                       e.__class__, e))
168            raise AttributeCertificateRequestError()
169       
170       
171    def isAuthorized(self):
172        '''Check constraints on the requested URI and return boolean - access
173        allowed/denied'''
174        environ = self.environ
175       
176        # Make a request object to pass to the PDP
177        request = Request()
178        request.subject.attributes['userId'] = self.session['username']
179        request.resource = self.environ.get(
180            'ndg.security.server.wsgi.pep.resource') or Resource(self.pathInfo)
181       
182        # Look for matching targets to the given resource
183        matchingTargets = [target for target in self.pdp.policy.targets
184                           if target.regEx.match(request.resource.uri) \
185                           is not None]
186       
187        attributeAuthorityURIs = []
188        for matchingTarget in matchingTargets:
189           
190            # Make call to relevant Attribute Authority if not already
191            # requested
192            if matchingTarget.attributeAuthorityURI not in \
193               attributeAuthorityURIs:
194                attributeCertificate = self._getAttributeCertificate(
195                                        matchingTarget.attributeAuthorityURI)
196                attributeAuthorityURIs.append(
197                                        matchingTarget.attributeAuthorityURI)
198               
199            request.subject.attributes.update(
200                                        {'roles': attributeCertificate.roles})
201           
202        response = self.pdp.evaluate(request)
203        return status
204   
205    def accessDeniedResponse(self):
206        '''Break out of the WSGI stack and set a error message for the user
207        403 status is still set to enable programmatic handling of this
208        response'''
209        response = "Access Denied"
210        start_response(PEPMiddleware.getStatusMessage(403),
211                       [('Content-type', 'text/plain') ,
212                        ('Content-length', str(len(response)))])
213        return response           
214   
215    @classmethod
216    def checker(cls, environ, status, headers):
217        """Set the trigger for calling this middleware.  In this case, it's a
218        HTTP 403 Forbidden response detected in the middleware chain
219        """
220        log.debug("PEPMiddleware.checker received status %r, headers "
221                  "%r", status, headers)
222       
223        if status.startswith(cls.triggerStatus) or environ['PATH_INFO'] == '/test_securedURI':
224#            environ['ndg.security.server.wsgi.pep.resource'] = Resource(environ['PATH_INFO'])
225            log.debug("PEPMiddleware.checker returning True")
226            return True
227        else:
228            log.debug("PEPMiddleware.checker returning False")
229            return False
230
231from authkit.authenticate.multi import MultiHandler
232
233class AuthorizationMiddleware(NDGSecurityMiddlewareBase):
234    '''Handler to call Policy Decision Point middleware and intercept
235    authorisation requests.  Add THIS class to any middleware chain and NOT
236    PEPMiddleware which it wraps.
237    '''
238    resourceKeyName = 'ndg.security.server.wsgi.pep.resource'
239   
240    def __init__(self, app, global_conf, prefix='', **app_conf):
241                       
242        app = MultiHandler(app)
243                           
244        app.add_method(PEPMiddleware.id,
245                       PEPMiddleware.filter_app_factory,
246                       global_conf,
247                       prefix=prefix,
248                       **app_conf)
249       
250        app.add_checker(PEPMiddleware.id, PEPMiddleware.checker)
251               
252       
253        super(AuthorizationMiddleware, self).__init__(app,
254                                                      global_conf,
255                                                      prefix=prefix,
256                                                      **app_conf)
257       
Note: See TracBrowser for help on using the repository browser.