def test_request_full_signed(self): """Test full SAML Request/Response flow, fully signed""" http_request = self.factory.get("/") http_request.user = get_anonymous_user() middleware = SessionMiddleware(dummy_get_response) middleware.process_request(http_request) http_request.session.save() # First create an AuthNRequest request_proc = RequestProcessor(self.source, http_request, "test_state") request = request_proc.build_auth_n() # To get an assertion we need a parsed request (parsed by provider) parsed_request = AuthNRequestParser(self.provider).parse( b64encode(request.encode()).decode(), "test_state") # Now create a response and convert it to string (provider) response_proc = AssertionProcessor(self.provider, http_request, parsed_request) response = response_proc.build_response() # Now parse the response (source) http_request.POST = QueryDict(mutable=True) http_request.POST["SAMLResponse"] = b64encode( response.encode()).decode() response_parser = ResponseProcessor(self.source) response_parser.parse(http_request)
def test_request_id_invalid(self): """Test generated AuthNRequest with invalid request ID""" http_request = self.factory.get("/") http_request.user = get_anonymous_user() middleware = SessionMiddleware(dummy_get_response) middleware.process_request(http_request) http_request.session.save() # First create an AuthNRequest request_proc = RequestProcessor(self.source, http_request, "test_state") request = request_proc.build_auth_n() # change the request ID http_request.session[SESSION_REQUEST_ID] = "test" http_request.session.save() # To get an assertion we need a parsed request (parsed by provider) parsed_request = AuthNRequestParser(self.provider).parse( b64encode(request.encode()).decode(), "test_state") # Now create a response and convert it to string (provider) response_proc = AssertionProcessor(self.provider, http_request, parsed_request) response = response_proc.build_response() # Now parse the response (source) http_request.POST = QueryDict(mutable=True) http_request.POST["SAMLResponse"] = b64encode( response.encode()).decode() response_parser = ResponseProcessor(self.source) with self.assertRaises(MismatchedRequestID): response_parser.parse(http_request)
def test_request_attributes_invalid(self): """Test full SAML Request/Response flow, fully signed""" user = create_test_admin_user() http_request = get_request("/", user=user) # First create an AuthNRequest request_proc = RequestProcessor(self.source, http_request, "test_state") request = request_proc.build_auth_n() # Create invalid PropertyMapping scope = SAMLPropertyMapping.objects.create(name="test", saml_name="test", expression="q") self.provider.property_mappings.add(scope) # To get an assertion we need a parsed request (parsed by provider) parsed_request = AuthNRequestParser(self.provider).parse( b64encode(request.encode()).decode(), "test_state") # Now create a response and convert it to string (provider) response_proc = AssertionProcessor(self.provider, http_request, parsed_request) self.assertIn(user.username, response_proc.build_response()) events = Event.objects.filter(action=EventAction.CONFIGURATION_ERROR, ) self.assertTrue(events.exists()) self.assertEqual( events.first().context["message"], "Failed to evaluate property-mapping: name 'q' is not defined", )
def test_response_schema(self): """Test generated AuthNRequest against Schema""" http_request = self.factory.get("/") http_request.user = get_anonymous_user() middleware = SessionMiddleware(dummy_get_response) middleware.process_request(http_request) http_request.session.save() # First create an AuthNRequest request_proc = RequestProcessor(self.source, http_request, "test_state") request = request_proc.build_auth_n() # To get an assertion we need a parsed request (parsed by provider) parsed_request = AuthNRequestParser(self.provider).parse( b64encode(request.encode()).decode(), "test_state") # Now create a response and convert it to string (provider) response_proc = AssertionProcessor(self.provider, http_request, parsed_request) response = response_proc.build_response() metadata = etree.fromstring(response) # nosec schema = etree.XMLSchema( etree.parse("xml/saml-schema-protocol-2.0.xsd")) self.assertTrue(schema.validate(metadata))
def get(self, request: HttpRequest, source_slug: str) -> HttpResponse: """Replies with an XHTML SSO Request.""" source: SAMLSource = get_object_or_404(SAMLSource, slug=source_slug) if not source.enabled: raise Http404 relay_state = request.GET.get("next", "") auth_n_req = RequestProcessor(source, request, relay_state) # If the source is configured for Redirect bindings, we can just redirect there if source.binding_type == SAMLBindingTypes.REDIRECT: # Parse the initial SSO URL sso_url = urlparse(source.sso_url) # Parse the querystring into a dict... url_kwargs = dict(parse_qsl(sso_url.query)) # ... and update it with the SAML args url_kwargs.update(auth_n_req.build_auth_n_detached()) # Encode it back into a string res = ParseResult( scheme=sso_url.scheme, netloc=sso_url.netloc, path=sso_url.path, params=sso_url.params, query=urlencode(url_kwargs), fragment=sso_url.fragment, ) # and merge it back into a URL final_url = urlunparse(res) return redirect(final_url) # As POST Binding we show a form try: saml_request = nice64(auth_n_req.build_auth_n()) except InternalError as exc: LOGGER.warning(str(exc)) return bad_request_message(request, str(exc)) injected_stages = [] plan_kwargs = { PLAN_CONTEXT_TITLE: _("Redirecting to %(app)s..." % {"app": source.name}), PLAN_CONTEXT_CONSENT_TITLE: _("Redirecting to %(app)s..." % {"app": source.name}), PLAN_CONTEXT_ATTRS: { "SAMLRequest": saml_request, "RelayState": relay_state, }, PLAN_CONTEXT_URL: source.sso_url, } # For just POST we add a consent stage, # otherwise we default to POST_AUTO, with direct redirect if source.binding_type == SAMLBindingTypes.POST: injected_stages.append(in_memory_stage(ConsentStageView)) plan_kwargs[ PLAN_CONTEXT_CONSENT_HEADER] = f"Continue to {source.name}" injected_stages.append(in_memory_stage(AutosubmitStageView)) return self.handle_login_flow( source, *injected_stages, **plan_kwargs, )
def test_signed_valid(self): """Test generated AuthNRequest with valid signature""" http_request = get_request("/") # First create an AuthNRequest request_proc = RequestProcessor(self.source, http_request, "test_state") request = request_proc.build_auth_n() # Now we check the ID and signature parsed_request = AuthNRequestParser(self.provider).parse( b64encode(request.encode()).decode(), "test_state") self.assertEqual(parsed_request.id, request_proc.request_id) self.assertEqual(parsed_request.relay_state, "test_state")
def test_request_schema(self): """Test generated AuthNRequest against Schema""" http_request = get_request("/") # First create an AuthNRequest request_proc = RequestProcessor(self.source, http_request, "test_state") request = request_proc.build_auth_n() metadata = etree.fromstring(request) # nosec schema = etree.XMLSchema( etree.parse("xml/saml-schema-protocol-2.0.xsd")) # nosec self.assertTrue(schema.validate(metadata))
def test_signed_valid(self): """Test generated AuthNRequest with valid signature""" http_request = self.factory.get("/") middleware = SessionMiddleware(dummy_get_response) middleware.process_request(http_request) http_request.session.save() # First create an AuthNRequest request_proc = RequestProcessor(self.source, http_request, "test_state") request = request_proc.build_auth_n() # Now we check the ID and signature parsed_request = AuthNRequestParser(self.provider).parse( b64encode(request.encode()).decode(), "test_state") self.assertEqual(parsed_request.id, request_proc.request_id) self.assertEqual(parsed_request.relay_state, "test_state")
def test_request_attributes(self): """Test full SAML Request/Response flow, fully signed""" user = create_test_admin_user() http_request = get_request("/", user=user) # First create an AuthNRequest request_proc = RequestProcessor(self.source, http_request, "test_state") request = request_proc.build_auth_n() # To get an assertion we need a parsed request (parsed by provider) parsed_request = AuthNRequestParser(self.provider).parse( b64encode(request.encode()).decode(), "test_state") # Now create a response and convert it to string (provider) response_proc = AssertionProcessor(self.provider, http_request, parsed_request) self.assertIn(user.username, response_proc.build_response())
def test_signed_valid_detached(self): """Test generated AuthNRequest with valid signature (detached)""" http_request = get_request("/") # First create an AuthNRequest request_proc = RequestProcessor(self.source, http_request, "test_state") params = request_proc.build_auth_n_detached() # Now we check the ID and signature parsed_request = AuthNRequestParser(self.provider).parse_detached( params["SAMLRequest"], params["RelayState"], params["Signature"], params["SigAlg"], ) self.assertEqual(parsed_request.id, request_proc.request_id) self.assertEqual(parsed_request.relay_state, "test_state")
def test_request_schema(self): """Test generated AuthNRequest against Schema""" http_request = self.factory.get("/") middleware = SessionMiddleware(dummy_get_response) middleware.process_request(http_request) http_request.session.save() # First create an AuthNRequest request_proc = RequestProcessor(self.source, http_request, "test_state") request = request_proc.build_auth_n() metadata = etree.fromstring(request) # nosec schema = etree.XMLSchema( etree.parse("xml/saml-schema-protocol-2.0.xsd")) # nosec self.assertTrue(schema.validate(metadata))
def get(self, request: HttpRequest, source_slug: str) -> HttpResponse: """Replies with an XHTML SSO Request.""" source: SAMLSource = get_object_or_404(SAMLSource, slug=source_slug) if not source.enabled: raise Http404 relay_state = request.GET.get("next", "") auth_n_req = RequestProcessor(source, request, relay_state) # If the source is configured for Redirect bindings, we can just redirect there if source.binding_type == SAMLBindingTypes.REDIRECT: url_args = urlencode(auth_n_req.build_auth_n_detached()) return redirect(f"{source.sso_url}?{url_args}") # As POST Binding we show a form saml_request = nice64(auth_n_req.build_auth_n()) injected_stages = [] plan_kwargs = { PLAN_CONTEXT_TITLE: _("Redirecting to %(app)s..." % {"app": source.name}), PLAN_CONTEXT_CONSENT_TITLE: _( "Redirecting to %(app)s..." % {"app": source.name} ), PLAN_CONTEXT_ATTRS: { "SAMLRequest": saml_request, "RelayState": relay_state, }, PLAN_CONTEXT_URL: source.sso_url, } # For just POST we add a consent stage, # otherwise we default to POST_AUTO, with direct redirect if source.binding_type == SAMLBindingTypes.POST: injected_stages.append(in_memory_stage(ConsentStageView)) plan_kwargs[PLAN_CONTEXT_CONSENT_HEADER] = f"Continue to {source.name}" injected_stages.append(in_memory_stage(AutosubmitStageView)) return self.handle_login_flow( source, *injected_stages, **plan_kwargs, )