source: TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/wsgi/authz/pep.py @ 7287

Subversion URL: http://proj.badc.rl.ac.uk/svn/ndg/TI12-security/trunk/NDGSecurity/python/ndg_security_server/ndg/security/server/wsgi/authz/pep.py@7287
Revision 7287, 9.0 KB checked in by pjkersha, 10 years ago (diff)

Incomplete - task 2: XACML-Security Integration

  • Working WSGI Authorisation filter with connection to SAML/XACML based Authorisation Service - unit tests: ndg.security.test.unit.wsgi.authz.test_authz
  • It may need some optimisation to avoid too many WS callouts to the Authorisation Service - perhaps add a local PDP to the authorisation filter to filter out some requests going over the wire e.g. requests for web page CSS or graphics content.
  • The XACML policy file has some big additions to it to support the various test conditions in ndg.security.test.unit.wsgi.authz.test_authz. These should be ported back to the ndg_xacml package unit tests.
  • Next major task: remove temp fix in XACML Context handler - instead of using hardwired roles for the user alter it so that the PDP makes a request back to the PIP (Policy Enforcement Point) to grab additional attributes. The PIP will call to Attibute Service(s) to pull any additional attributes needed/
Line 
1'''NDG Security Policy Enforcement Point Module
2
3__author__ = "P J Kershaw"
4__date__ = "11/07/10"
5__copyright__ = "(C) 2010 Science and Technology Facilities Council"
6__license__ = "BSD - see LICENSE file in top-level directory"
7__contact__ = "Philip.Kershaw@stfc.ac.uk"
8__revision__ = '$Id:$'
9'''
10import logging
11log = logging.getLogger(__name__)
12
13import httplib
14from time import time
15
16import webob
17
18from ndg.saml.saml2.core import DecisionType
19from ndg.saml.saml2.binding.soap.client.authzdecisionquery import \
20                                            AuthzDecisionQuerySslSOAPBinding
21from ndg.security.server.wsgi.session import (SessionMiddlewareBase, 
22                                              SessionHandlerMiddleware)
23
24
25class SamlPepFilterConfigError(Exception):
26    """Error with SAML PEP configuration settings"""
27   
28   
29class SamlPepFilter(SessionMiddlewareBase):
30    '''Policy Enforcement Point for ESG with SAML based Interface
31   
32    @requires: ndg.security.server.wsgi.session.SessionHandlerMiddleware
33    instance upstream in the WSGI stack.
34   
35    @cvar AUTHZ_DECISION_QUERY_PARAMS_PREFIX: prefix for SAML authorisation
36    decision query options in config file
37    @type AUTHZ_DECISION_QUERY_PARAMS_PREFIX: string
38   
39    @cvar PARAM_NAMES: list of config option names
40    @type PARAM_NAMES: tuple
41   
42    @ivar __client: SAML authorisation decision query client
43    @type __client: ndg.saml.saml2.binding.soap.client.authzdecisionquery.AuthzDecisionQuerySslSOAPBinding
44    '''
45    AUTHZ_DECISION_QUERY_PARAMS_PREFIX = 'authzDecisionQuery.'
46    SESSION_KEY_PARAM_NAME = 'sessionKey'
47   
48    CREDENTIAL_WALLET_SESSION_KEYNAME = \
49        SessionHandlerMiddleware.CREDENTIAL_WALLET_SESSION_KEYNAME
50    USERNAME_SESSION_KEYNAME = \
51        SessionHandlerMiddleware.USERNAME_SESSION_KEYNAME
52   
53    PARAM_NAMES = (
54        'authzServiceURI',
55        'sessionKey'
56    )
57    __slots__ = (
58        '_app', '__client', '__session',
59    ) + tuple(["__%s" % i for i in PARAM_NAMES])
60    del i
61   
62    def __init__(self, app):
63        '''
64        Add reference to next WSGI middleware/app and create a SAML
65        authorisation decision query client interface
66        '''
67        self._app = app
68        self.__client = AuthzDecisionQuerySslSOAPBinding()
69        self.__session = None
70        self.__authzServiceURI = None
71        self.__sessionKey = None
72
73    def _getClient(self):
74        return self.__client
75
76    def _setClient(self, value):
77        if not isinstance(value, basestring):
78            raise TypeError('Expecting string type for "client" attribute; '
79                            'got %r' % type(value))
80        self.__client = value
81
82    client = property(_getClient, _setClient, 
83                      doc="SAML authorisation decision query SOAP client")
84
85    def _getSession(self):
86        return self.__session
87
88    def _setSession(self, value):
89        self.__session = value
90
91    session = property(_getSession, _setSession, 
92                       doc="Beaker Security Session instance")
93
94    def _getAuthzServiceURI(self):
95        return self.__authzServiceURI
96
97    def _setAuthzServiceURI(self, value):
98        if not isinstance(value, basestring):
99            raise TypeError('Expecting string type for "authzServiceURI" '
100                            'attribute; got %r' % type(value))
101        self.__authzServiceURI = value
102
103    authzServiceURI = property(_getAuthzServiceURI, _setAuthzServiceURI, 
104                               doc="Authorisation Service URI")
105
106    def _getSessionKey(self):
107        return self.__sessionKey
108
109    def _setSessionKey(self, value):
110        if not isinstance(value, basestring):
111            raise TypeError('Expecting string type for "sessionKey" attribute; '
112                            'got %r' % type(value))
113        self.__sessionKey = value
114
115    sessionKey = property(_getSessionKey, _setSessionKey, 
116                          doc="environ key name for Beaker session object")
117
118    def initialise(self, prefix='', **kw):
119        '''Initialise object from keyword settings
120       
121        @type prefix: basestring
122        @param prefix: prefix for configuration items
123        @type kw: dict       
124        @param kw: configuration settings
125        dictionary
126        @raise SamlPepFilterConfigError: missing option setting(s)
127        '''
128        # Parse authorisation decision query options
129        queryPrefix = prefix + self.__class__.AUTHZ_DECISION_QUERY_PARAMS_PREFIX
130        self.client.parseKeywords(prefix=queryPrefix, **kw)
131           
132        # Parse other options
133        for name in SamlPepFilter.PARAM_NAMES:
134            paramName = prefix + name
135            value = kw.get(paramName)
136            if value is None:
137                raise SamlPepFilterConfigError('Missing option %r' % 
138                                                   paramName)
139            setattr(self, name, value)
140                   
141    @classmethod
142    def filter_app_factory(cls, app, global_conf, prefix='', **app_conf):
143        """Set-up using a Paste app factory pattern. 
144       
145        @type app: callable following WSGI interface
146        @param app: next middleware application in the chain     
147        @type global_conf: dict       
148        @param global_conf: PasteDeploy global configuration dictionary
149        @type prefix: basestring
150        @param prefix: prefix for configuration items
151        @type app_conf: dict       
152        @param app_conf: PasteDeploy application specific configuration
153        dictionary
154        """
155        app = cls(app)
156        app.initialise(prefix=prefix, **app_conf)
157       
158        return app
159               
160    def __call__(self, environ, start_response):
161        """Intercept request and call authorisation service to make an access
162        control decision
163       
164        @type environ: dict
165        @param environ: WSGI environment variables dictionary
166        @type start_response: function
167        @param start_response: standard WSGI start response function
168        @rtype: iterable
169        @return: response
170        """
171        # Get reference to session object - SessionHandler middleware must be in
172        # place upstream of this middleware in the WSGI stack
173        if self.sessionKey not in environ:
174            raise SamlPepFilterConfigError('No beaker session key "%s" found '
175                                           'in environ' % self.sessionKey)
176        self.session = environ[self.sessionKey]
177       
178        request = webob.Request(environ)
179        self.client.resourceURI = request.url
180       
181        # Nb. user may not be logged in hence REMOTE_USER is not set
182        self.client.subjectID = request.remote_user or ''
183       
184        samlAuthzResponse = self.client.send(uri=self.__authzServiceURI)
185
186        # Record the result in the user's session to enable later
187        # interrogation by any result handler Middleware
188        self.setSession(self.client.query, samlAuthzResponse)
189       
190        # Set HTTP 403 Forbidden response if any of the decisions returned are
191        # deny or indeterminate status
192        failDecisions = (DecisionType.DENY, DecisionType.INDETERMINATE)
193       
194        for assertion in samlAuthzResponse.assertions:
195            for authzDecisionStatement in assertion.authzDecisionStatements:
196                if authzDecisionStatement.decision.value in failDecisions:
197                    response = webob.Response()
198                   
199                    if not self.client.subjectID:
200                        # Access failed and the user is not logged in
201                        response.status = httplib.UNAUTHORIZED
202                    else:
203                        # The user is logged in but not authorised
204                        response.status = httplib.FORBIDDEN
205                       
206                    response.body = 'Access denied to %r for user %r' % (
207                                                     self.client.resourceURI,
208                                                     self.client.subjectID)
209                    response.content_type = 'text/plain'
210                    log.info(response.body)
211                    return response(environ, start_response)
212
213        # If got through to here then all is well, call next WSGI middleware/app
214        return self._app(environ, start_response)
215
216    def setSession(self, request, response, save=True):
217        """Set PEP context information in the Beaker session using standard key
218        names
219       
220        @param session: beaker session
221        @type session: beaker.session.SessionObject
222        @param request: authorisation decision query
223        @type request: ndg.saml.saml2.core.AuthzDecisionQuery
224        @param response: authorisation response
225        @type response: ndg.saml.saml2.core.Response
226        @param save: determines whether session is saved or not
227        @type save: bool
228        """
229        self.session[self.__class__.PEPCTX_SESSION_KEYNAME] = {
230            self.__class__.PEPCTX_REQUEST_SESSION_KEYNAME: request, 
231            self.__class__.PEPCTX_RESPONSE_SESSION_KEYNAME: response,
232            self.__class__.PEPCTX_TIMESTAMP_SESSION_KEYNAME: time()
233        }
234       
235        if save:
236            self.session.save()     
Note: See TracBrowser for help on using the repository browser.