def __init__(self, app): ''' Add reference to next WSGI middleware/app and create a SAML authorisation decision query client interface ''' self._app = app self._client_binding = AuthzDecisionQuerySslSOAPBinding() self._client_query = AuthzDecisionQueryFactory.create() self.__session = None self.__authzServiceURI = None self.__sessionKey = None self.__cacheDecisions = False self.__localPdp = None self.__localPolicyFilePath = None self._ignore_file_list_pat = []
def test02_from_kw(self): authz_query = AuthzDecisionQueryFactory.from_kw( prefix='authz_q.', **self.config) self.assertEqual(authz_query.subject.nameID.format, self.config['authz_q.subject.nameID.format'], 'Parameter is %r, expected %r' % ( authz_query.subject.nameID.format, self.config['authz_q.subject.nameID.format'])) self.assertEqual(authz_query.issuer.value, self.config['authz_q.issuer.value'], 'Parameter is %r, expected %r' % ( authz_query.issuer.value, self.config['authz_q.issuer.value']))
def initialise(self, prefix='', **kw): '''Initialise object from keyword settings :type prefix: basestring :param prefix: prefix for configuration items :type kw: dict :param kw: configuration settings dictionary :raise SamlPepFilterConfigError: missing option setting(s) ''' # Parse other options for name in SamlPepFilter.PARAM_NAMES: paramName = prefix + name value = kw.get(paramName) if value is not None: setattr(self, name, value) # All but the local policy settings are manadatory elif name not in self.__class__.OPTIONAL_PARAM_NAMES: raise SamlPepFilterConfigError('Missing option %r' % paramName) # Parse authorisation decision query options - first the bindings i.e. # the connection specific settings query_binding_prefix = prefix + \ self.__class__.AUTHZ_DECISION_QUERY_BINDING_PARAMS_PREFIX self.client_binding.parseKeywords(prefix=query_binding_prefix, **kw) # ... next set constants to do with the authorisation decision queries # that will be made. Settings such as the resource URI and principle # (user being queried for) are set on a call by call basis query_prefix = prefix + \ self.__class__.AUTHZ_DECISION_QUERY_PARAMS_PREFIX self.client_query = AuthzDecisionQueryFactory.from_kw( prefix=query_prefix, **kw) # Initialise the local PDP if self.localPolicyFilePath: self.__localPdp = PDP.fromPolicySource(self.localPolicyFilePath, XacmlPolicyReaderFactory)
def enforce(self, environ, start_response): """Get access control decision from PDP(s) and enforce the decision :type environ: dict :param environ: WSGI environment variables dictionary :type start_response: function :param start_response: standard WSGI start response function :rtype: iterable :return: response """ request = webob.Request(environ) requestURI = request.url # Nb. user may not be logged in hence REMOTE_USER is not set remote_user = request.remote_user or '' # Apply local PDP if set if not self.is_applicable_request(requestURI): # The local PDP has returned a decision that the requested URI is # not applicable and so the authorisation service need not be # invoked. This step is an efficiency measure to avoid multiple # callouts to the authorisation service for resources which # obviously don't need any restrictions return self._app(environ, start_response) # Check for cached decision if self.cacheDecisions: assertions = self._retrieveCachedAssertions(requestURI) else: assertions = None noCachedAssertion = assertions is None or len(assertions) == 0 if noCachedAssertion: # No stored decision in cache, invoke the authorisation service # Make a new query object query = AuthzDecisionQueryFactory.create() # Copy constant settings. These constants were set at # initialisation query.subject.nameID.format = \ self.client_query.subject.nameID.format query.issuer.value = self.client_query.issuer.value query.issuer.format = self.client_query.issuer.format # Set dynamic settings particular to this individual request query.subject.nameID.value = remote_user query.resource = request.url try: samlAuthzResponse = self.client_binding.send( query, uri=self.authzServiceURI) except (UrlLib2SOAPClientError, URLError) as e: import traceback if isinstance(e, UrlLib2SOAPClientError): log.error( "Error, HTTP %s response from authorisation " "service %r requesting access to %r: %s", e.urllib2Response.code, self.authzServiceURI, requestURI, traceback.format_exc()) else: log.error( "Error, calling authorisation service %r " "requesting access to %r: %s", self.authzServiceURI, requestURI, traceback.format_exc()) response = webob.Response() response.status = httplib.FORBIDDEN response.body = ('An error occurred retrieving an access ' 'decision for %r for user %r' % (requestURI, remote_user)) response.content_type = 'text/plain' return response(environ, start_response) assertions = samlAuthzResponse.assertions # Record the result in the user's session to enable later # interrogation by any result handler Middleware self.save_result_ctx(query, samlAuthzResponse) # Set HTTP 403 Forbidden response if any of the decisions returned are # deny or indeterminate status failDecisions = ( DecisionType.DENY, #@UndefinedVariable DecisionType.INDETERMINATE) #@UndefinedVariable # Review decision statement(s) in assertions and enforce the decision assertion = None for assertion in assertions: for authzDecisionStatement in assertion.authzDecisionStatements: if authzDecisionStatement.decision.value in failDecisions: response = webob.Response() if not remote_user: # Access failed and the user is not logged in response.status = httplib.UNAUTHORIZED else: # The user is logged in but not authorised response.status = httplib.FORBIDDEN response.body = 'Access denied to %r for user %r' % ( requestURI, remote_user) response.content_type = 'text/plain' log.info(response.body) return response(environ, start_response) if assertion is None: log.error( "No assertions set in authorisation decision response " "from %r", self.authzServiceURI) response = webob.Response() response.status = httplib.FORBIDDEN response.body = ('An error occurred retrieving an access decision ' 'for %r for user %r' % (requestURI, remote_user)) response.content_type = 'text/plain' log.info(response.body) return response(environ, start_response) # Cache assertion if flag is set and it's one that's been freshly # obtained from an authorisation decision query rather than one # retrieved from the cache if self.cacheDecisions and noCachedAssertion: self._cacheAssertions(request.url, [assertion]) # If got through to here then all is well, call next WSGI middleware/app return self._app(environ, start_response)
def test01_create(self): authz_query = AuthzDecisionQueryFactory.create() self.assertIsNotNone(authz_query.subject, 'query subject is none') self.assertIsNotNone(authz_query.issuer, 'query issuer is none')
def enforce(self, environ, start_response): """Get access control decision from PDP(s) and enforce the decision :type environ: dict :param environ: WSGI environment variables dictionary :type start_response: function :param start_response: standard WSGI start response function :rtype: iterable :return: response """ request = webob.Request(environ) requestURI = request.url # Nb. user may not be logged in hence REMOTE_USER is not set remote_user = request.remote_user or '' # Apply local PDP if set if not self.is_applicable_request(requestURI): # The local PDP has returned a decision that the requested URI is # not applicable and so the authorisation service need not be # invoked. This step is an efficiency measure to avoid multiple # callouts to the authorisation service for resources which # obviously don't need any restrictions return self._app(environ, start_response) # Check for cached decision if self.cacheDecisions: assertions = self._retrieveCachedAssertions(requestURI) else: assertions = None noCachedAssertion = assertions is None or len(assertions) == 0 if noCachedAssertion: # No stored decision in cache, invoke the authorisation service # Make a new query object query = AuthzDecisionQueryFactory.create() # Copy constant settings. These constants were set at # initialisation query.subject.nameID.format = \ self.client_query.subject.nameID.format query.issuer.value = self.client_query.issuer.value query.issuer.format = self.client_query.issuer.format # Set dynamic settings particular to this individual request query.subject.nameID.value = remote_user query.resource = request.url try: samlAuthzResponse = self.client_binding.send(query, uri=self.authzServiceURI) except (UrlLib2SOAPClientError, URLError) as e: import traceback if isinstance(e, UrlLib2SOAPClientError): log.error("Error, HTTP %s response from authorisation " "service %r requesting access to %r: %s", e.urllib2Response.code, self.authzServiceURI, requestURI, traceback.format_exc()) else: log.error("Error, calling authorisation service %r " "requesting access to %r: %s", self.authzServiceURI, requestURI, traceback.format_exc()) response = webob.Response() response.status = httplib.FORBIDDEN response.body = ('An error occurred retrieving an access ' 'decision for %r for user %r' % (requestURI, remote_user)) response.content_type = 'text/plain' return response(environ, start_response) assertions = samlAuthzResponse.assertions # Record the result in the user's session to enable later # interrogation by any result handler Middleware self.save_result_ctx(query, samlAuthzResponse) # Set HTTP 403 Forbidden response if any of the decisions returned are # deny or indeterminate status failDecisions = (DecisionType.DENY, #@UndefinedVariable DecisionType.INDETERMINATE) #@UndefinedVariable # Review decision statement(s) in assertions and enforce the decision assertion = None for assertion in assertions: for authzDecisionStatement in assertion.authzDecisionStatements: if authzDecisionStatement.decision.value in failDecisions: response = webob.Response() if not remote_user: # Access failed and the user is not logged in response.status = httplib.UNAUTHORIZED else: # The user is logged in but not authorised response.status = httplib.FORBIDDEN response.body = 'Access denied to %r for user %r' % ( requestURI, remote_user) response.content_type = 'text/plain' log.info(response.body) return response(environ, start_response) if assertion is None: log.error("No assertions set in authorisation decision response " "from %r", self.authzServiceURI) response = webob.Response() response.status = httplib.FORBIDDEN response.body = ('An error occurred retrieving an access decision ' 'for %r for user %r' % (requestURI, remote_user)) response.content_type = 'text/plain' log.info(response.body) return response(environ, start_response) # Cache assertion if flag is set and it's one that's been freshly # obtained from an authorisation decision query rather than one # retrieved from the cache if self.cacheDecisions and noCachedAssertion: self._cacheAssertions(request.url, [assertion]) # If got through to here then all is well, call next WSGI middleware/app return self._app(environ, start_response)
def get_authz_decision(environ, url, remote_user): saml_trusted_ca_dir = environ.get('saml_trusted_ca_dir', '') authz_service_uri = environ.get('authz_service_uri', '') client_binding = AuthzDecisionQuerySslSOAPBinding() client_binding.sslCACertDir = saml_trusted_ca_dir client_binding.clockSkewTolerance = 1 # 1 second tolerance # Make a new query object query = AuthzDecisionQueryFactory.create() # Copy constant settings. These constants were set at # initialisation query.subject = Subject() query.subject.nameID = NameID() query.subject.nameID.format = 'urn:esg:openid' query.issuer = Issuer() query.issuer.format = Issuer.X509_SUBJECT query.issuer.value = 'O=NDG, OU=Security, CN=localhost' # Set dynamic settings particular to this individual request query.subject.nameID.value = remote_user query.resource = url try: saml_authz_response = client_binding.send(query, uri=authz_service_uri) except (UrlLib2SOAPClientError, URLError) as e: import traceback if isinstance(e, UrlLib2SOAPClientError): logger.error( "Error, HTTP %s response from authorisation " "service %r requesting access to %r: %s", e.urllib2Response.code, authz_service_uri, url, traceback.format_exc()) else: logger.error( "Error, calling authorisation service %r " "requesting access to %r: %s", authz_service_uri, url, traceback.format_exc()) assertions = saml_authz_response.assertions # Set HTTP 403 Forbidden response if any of the decisions returned are # deny or indeterminate status fail_decisions = ( DecisionType.DENY, #@UndefinedVariable DecisionType.INDETERMINATE) #@UndefinedVariable # Review decision statement(s) in assertions and enforce the decision assertion = None for assertion in assertions: for authz_decision_statement in assertion.authzDecisionStatements: assertion = authz_decision_statement.decision.value if assertion in fail_decisions: break if assertion is None: logger.error("No assertions set in authorisation decision response " "from {0}".format(authz_service_uri)) return assertion