def test_saml_mirror_frontend_with_saml_backend_with_multiple_target_providers( self, satosa_config_dict, idp_conf, saml_mirror_frontend_config, saml_backend_config): idp_conf2 = copy.deepcopy(idp_conf) idp_conf2["entityid"] = "https://idp2.example.com" satosa_config_dict["FRONTEND_MODULES"] = [saml_mirror_frontend_config] saml_backend_config["config"]["sp_config"]["metadata"] = { "inline": [ create_metadata_from_config_dict(idp_conf), create_metadata_from_config_dict(idp_conf2) ] } satosa_config_dict["BACKEND_MODULES"] = [saml_backend_config] satosa_config = SATOSAConfig(satosa_config_dict) frontend_metadata, backend_metadata = create_entity_descriptors( satosa_config) assert len(frontend_metadata) == 1 assert len(frontend_metadata[saml_mirror_frontend_config["name"]]) == 2 entity_descriptors = frontend_metadata[ saml_mirror_frontend_config["name"]] for target_entity_id in [idp_conf["entityid"], idp_conf2["entityid"]]: encoded_target_entity_id = urlsafe_b64encode( target_entity_id.encode("utf-8")).decode("utf-8") self.assert_single_sign_on_endpoints_for_saml_mirror_frontend( entity_descriptors, encoded_target_entity_id, saml_mirror_frontend_config, [saml_backend_config["name"]]) assert len(backend_metadata) == 1 self.assert_assertion_consumer_service_endpoints_for_saml_backend( backend_metadata[saml_backend_config["name"]][0], saml_backend_config)
def setup_for_authn_req(self, context, idp_conf, sp_conf, nameid_format=None, relay_state="relay_state", internal_attributes=INTERNAL_ATTRIBUTES, extra_config={}): config = {"idp_config": idp_conf, "endpoints": ENDPOINTS} config.update(extra_config) sp_metadata_str = create_metadata_from_config_dict(sp_conf) idp_conf["metadata"]["inline"] = [sp_metadata_str] base_url = self.construct_base_url_from_entity_id(idp_conf["entityid"]) samlfrontend = SAMLFrontend(lambda ctx, internal_req: (ctx, internal_req), internal_attributes, config, base_url, "saml_frontend") samlfrontend.register_endpoints(["saml"]) idp_metadata_str = create_metadata_from_config_dict(samlfrontend.idp_config) sp_conf["metadata"]["inline"].append(idp_metadata_str) fakesp = FakeSP(SPConfig().load(sp_conf, metadata_construction=False)) destination, auth_req = fakesp.make_auth_req(samlfrontend.idp_config["entityid"], nameid_format, relay_state) context.request = auth_req tmp_dict = {} for val in context.request: if isinstance(context.request[val], list): tmp_dict[val] = context.request[val][0] else: tmp_dict[val] = context.request[val] context.request = tmp_dict return samlfrontend
def setup_for_authn_req(self, idp_conf, sp_conf, nameid_format): base = self.construct_base_url_from_entity_id(idp_conf["entityid"]) config = {"idp_config": idp_conf, "endpoints": ENDPOINTS, "base": base, "state_id": "state_id"} sp_metadata_str = create_metadata_from_config_dict(sp_conf) idp_conf["metadata"]["inline"] = [sp_metadata_str] samlfrontend = SamlFrontend(lambda context, internal_req: (context, internal_req), INTERNAL_ATTRIBUTES, config) samlfrontend.register_endpoints(["saml"]) idp_metadata_str = create_metadata_from_config_dict(samlfrontend.config) sp_conf["metadata"]["inline"].append(idp_metadata_str) fakesp = FakeSP(None, config=SPConfig().load(sp_conf, metadata_construction=False)) context = Context() context.state = State() context.request = parse.parse_qs( urlparse(fakesp.make_auth_req(samlfrontend.config["entityid"], nameid_format)).query) tmp_dict = {} for val in context.request: if isinstance(context.request[val], list): tmp_dict[val] = context.request[val][0] else: tmp_dict[val] = context.request[val] context.request = tmp_dict return context, samlfrontend
def test_saml_mirror_frontend_with_saml_backend_with_multiple_target_providers(self, satosa_config_dict, idp_conf, saml_mirror_frontend_config, saml_backend_config): idp_conf2 = copy.deepcopy(idp_conf) idp_conf2["entityid"] = "https://idp2.example.com" satosa_config_dict["FRONTEND_MODULES"] = [saml_mirror_frontend_config] saml_backend_config["config"]["sp_config"]["metadata"] = {"inline": [create_metadata_from_config_dict(idp_conf), create_metadata_from_config_dict( idp_conf2)]} satosa_config_dict["BACKEND_MODULES"] = [saml_backend_config] satosa_config = SATOSAConfig(satosa_config_dict) frontend_metadata, backend_metadata = create_entity_descriptors(satosa_config) assert len(frontend_metadata) == 1 assert len(frontend_metadata[saml_mirror_frontend_config["name"]]) == 2 entity_descriptors = frontend_metadata[saml_mirror_frontend_config["name"]] for target_entity_id in [idp_conf["entityid"], idp_conf2["entityid"]]: encoded_target_entity_id = urlsafe_b64encode(target_entity_id.encode("utf-8")).decode("utf-8") self.assert_single_sign_on_endpoints_for_saml_mirror_frontend(entity_descriptors, encoded_target_entity_id, saml_mirror_frontend_config, [saml_backend_config["name"]]) assert len(backend_metadata) == 1 self.assert_assertion_consumer_service_endpoints_for_saml_backend( backend_metadata[saml_backend_config["name"]][0], saml_backend_config)
def setup_for_authn_req(self, context, idp_conf, sp_conf, nameid_format=None, relay_state="relay_state", internal_attributes=INTERNAL_ATTRIBUTES, extra_config={}, subject=None): config = {"idp_config": idp_conf, "endpoints": ENDPOINTS} config.update(extra_config) sp_metadata_str = create_metadata_from_config_dict(sp_conf) idp_conf["metadata"]["inline"] = [sp_metadata_str] base_url = self.construct_base_url_from_entity_id(idp_conf["entityid"]) samlfrontend = SAMLFrontend(lambda ctx, internal_req: (ctx, internal_req), internal_attributes, config, base_url, "saml_frontend") samlfrontend.register_endpoints(["saml"]) idp_metadata_str = create_metadata_from_config_dict(samlfrontend.idp_config) sp_conf["metadata"]["inline"].append(idp_metadata_str) fakesp = FakeSP(SPConfig().load(sp_conf, metadata_construction=False)) destination, auth_req = fakesp.make_auth_req( samlfrontend.idp_config["entityid"], nameid_format, relay_state, subject=subject, ) context.request = auth_req tmp_dict = {} for val in context.request: if isinstance(context.request[val], list): tmp_dict[val] = context.request[val][0] else: tmp_dict[val] = context.request[val] context.request = tmp_dict return samlfrontend
def setup_test_config(sp_conf, idp_conf): idp_metadata_str = create_metadata_from_config_dict(idp_conf) sp_conf["metadata"]["inline"].append(idp_metadata_str) idp2_config = idp_conf.copy() idp2_config["entityid"] = "just_an_extra_idp" idp_metadata_str2 = create_metadata_from_config_dict(idp2_config) sp_conf["metadata"]["inline"].append(idp_metadata_str2) sp_metadata_str = create_metadata_from_config_dict(sp_conf) idp_conf["metadata"]["inline"] = [sp_metadata_str]
def setup_test_config(sp_conf, idp_conf): idp_metadata_str = create_metadata_from_config_dict(idp_conf) sp_conf["metadata"]["inline"].append(idp_metadata_str) idp2_config = idp_conf.copy() idp2_config["entityid"] = "just_an_extra_idp" idp_metadata_str2 = create_metadata_from_config_dict(idp2_config) sp_conf["metadata"]["inline"].append(idp_metadata_str2) sp_metadata_str = create_metadata_from_config_dict(sp_conf) idp_conf["metadata"]["inline"] = [sp_metadata_str]
def test_get_metadata_desc_with_logo_without_lang(self, sp_conf, idp_conf): # add logo without 'lang' idp_conf["service"]["idp"]["ui_info"]["logo"] = [{"text": "https://idp.example.com/static/logo.png", "width": "120", "height": "60"}] sp_conf["metadata"]["inline"] = [create_metadata_from_config_dict(idp_conf)] # instantiate new backend, with a single backing IdP samlbackend = SAMLBackend(None, INTERNAL_ATTRIBUTES, {"sp_config": sp_conf}, "base_url", "saml_backend") entity_descriptions = samlbackend.get_metadata_desc() assert len(entity_descriptions) == 1 idp_desc = entity_descriptions[0].to_dict() assert idp_desc["entityid"] == urlsafe_b64encode(idp_conf["entityid"].encode("utf-8")).decode("utf-8") assert idp_desc["contact_person"] == idp_conf["contact_person"] assert idp_desc["organization"]["name"][0] == tuple(idp_conf["organization"]["name"][0]) assert idp_desc["organization"]["display_name"][0] == tuple(idp_conf["organization"]["display_name"][0]) assert idp_desc["organization"]["url"][0] == tuple(idp_conf["organization"]["url"][0]) expected_ui_info = idp_conf["service"]["idp"]["ui_info"] ui_info = idp_desc["service"]["idp"]["ui_info"] assert ui_info["display_name"] == expected_ui_info["display_name"] assert ui_info["description"] == expected_ui_info["description"] assert ui_info["logo"] == expected_ui_info["logo"]
def test_use_of_disco_or_redirect_to_idp_when_using_mdq_and_forceauthn_is_set_1( self, context, sp_conf, idp_conf ): sp_conf["metadata"]["inline"] = [create_metadata_from_config_dict(idp_conf)] sp_conf["metadata"]["mdq"] = ["https://mdq.example.com"] context.decorate(Context.KEY_FORCE_AUTHN, "1") context.state[Context.KEY_MEMORIZED_IDP] = idp_conf["entityid"] backend_conf = { SAMLBackend.KEY_SP_CONFIG: sp_conf, SAMLBackend.KEY_DISCO_SRV: DISCOSRV_URL, SAMLBackend.KEY_MEMORIZE_IDP: True, SAMLBackend.KEY_MIRROR_FORCE_AUTHN: True, } samlbackend = SAMLBackend( None, INTERNAL_ATTRIBUTES, backend_conf, "base_url", "saml_backend" ) resp = samlbackend.start_auth(context, InternalData()) assert_redirect_to_discovery_server(resp, sp_conf, DISCOSRV_URL) backend_conf[SAMLBackend.KEY_USE_MEMORIZED_IDP_WHEN_FORCE_AUTHN] = True samlbackend = SAMLBackend( None, INTERNAL_ATTRIBUTES, backend_conf, "base_url", "saml_backend" ) resp = samlbackend.start_auth(context, InternalData()) assert_redirect_to_idp(resp, idp_conf)
def test_respect_sp_entity_categories(self, context, entity_category, entity_category_module, expected_attributes, idp_conf, sp_conf, internal_response): idp_metadata_str = create_metadata_from_config_dict(idp_conf) idp_conf["service"]["idp"]["policy"]["default"]["entity_categories"] = [entity_category_module] if all(entity_category): # don't insert empty entity category sp_conf["entity_category"] = entity_category if entity_category == [COCO]: sp_conf["service"]["sp"]["required_attributes"] = expected_attributes expected_attributes_in_all_entity_categories = list( itertools.chain(swamid.RELEASE[""], edugain.RELEASE[COCO], refeds.RELEASE[RESEARCH_AND_SCHOLARSHIP], swamid.RELEASE[(RESEARCH_AND_EDUCATION, EU)], swamid.RELEASE[(RESEARCH_AND_EDUCATION, HEI)], swamid.RELEASE[(RESEARCH_AND_EDUCATION, NREN)], swamid.RELEASE[SFS_1993_1153])) attribute_mapping = {} for expected_attribute in expected_attributes_in_all_entity_categories: attribute_mapping[expected_attribute.lower()] = {"saml": [expected_attribute]} internal_attributes = dict(attributes=attribute_mapping) samlfrontend = self.setup_for_authn_req(context, idp_conf, sp_conf, internal_attributes=internal_attributes) user_attributes = {k: "foo" for k in expected_attributes_in_all_entity_categories} internal_response.attributes = AttributeMapper(internal_attributes).to_internal("saml", user_attributes) internal_response.requester = sp_conf["entityid"] resp = self.get_auth_response(samlfrontend, context, internal_response, sp_conf, idp_metadata_str) assert Counter(resp.ava.keys()) == Counter(expected_attributes)
def test_get_metadata_desc(self, sp_conf, idp_conf): sp_conf["metadata"]["inline"] = [ create_metadata_from_config_dict(idp_conf) ] # instantiate new backend, with a single backing IdP samlbackend = SAMLBackend(None, INTERNAL_ATTRIBUTES, {"sp_config": sp_conf}, "base_url", "saml_backend") entity_descriptions = samlbackend.get_metadata_desc() assert len(entity_descriptions) == 1 idp_desc = entity_descriptions[0].to_dict() assert idp_desc["entityid"] == urlsafe_b64encode( idp_conf["entityid"].encode("utf-8")).decode("utf-8") assert idp_desc["contact_person"] == idp_conf["contact_person"] assert idp_desc["organization"]["name"][0] == tuple( idp_conf["organization"]["name"][0]) assert idp_desc["organization"]["display_name"][0] == tuple( idp_conf["organization"]["display_name"][0]) assert idp_desc["organization"]["url"][0] == tuple( idp_conf["organization"]["url"][0]) expected_ui_info = idp_conf["service"]["idp"]["ui_info"] ui_info = idp_desc["service"]["idp"]["ui_info"] assert ui_info["display_name"] == expected_ui_info["display_name"] assert ui_info["description"] == expected_ui_info["description"] assert ui_info["logo"] == expected_ui_info["logo"]
def test_create_mirrored_metadata_does_not_contain_target_contact_info(self, satosa_config_dict, idp_conf, saml_mirror_frontend_config, saml_backend_config): satosa_config_dict["FRONTEND_MODULES"] = [saml_mirror_frontend_config] saml_backend_config["config"]["sp_config"]["metadata"] = { "inline": [create_metadata_from_config_dict(idp_conf)]} satosa_config_dict["BACKEND_MODULES"] = [saml_backend_config] satosa_config = SATOSAConfig(satosa_config_dict) frontend_metadata, backend_metadata = create_entity_descriptors(satosa_config) assert len(frontend_metadata) == 1 entity_descriptors = frontend_metadata[saml_mirror_frontend_config["name"]] metadata = InMemoryMetaData(None, str(entity_descriptors[0])) metadata.load() entity_info = list(metadata.values())[0] expected_entity_info = saml_mirror_frontend_config["config"]["idp_config"] assert len(entity_info["contact_person"]) == len(expected_entity_info["contact_person"]) for i, contact in enumerate(expected_entity_info["contact_person"]): assert entity_info["contact_person"][i]["contact_type"] == contact["contact_type"] assert entity_info["contact_person"][i]["email_address"][0]["text"] == contact["email_address"][0] assert entity_info["contact_person"][i]["given_name"]["text"] == contact["given_name"] assert entity_info["contact_person"][i]["sur_name"]["text"] == contact["sur_name"] expected_org_info = expected_entity_info["organization"] assert entity_info["organization"]["organization_display_name"][0]["text"] == \ expected_org_info["display_name"][0][0] assert entity_info["organization"]["organization_name"][0]["text"] == expected_org_info["name"][0][0] assert entity_info["organization"]["organization_url"][0]["text"] == expected_org_info["url"][0][0]
def test_create_mirrored_metadata_does_not_contain_target_contact_info(self, satosa_config_dict, idp_conf, saml_mirror_frontend_config, saml_backend_config): satosa_config_dict["FRONTEND_MODULES"] = [saml_mirror_frontend_config] saml_backend_config["config"]["sp_config"]["metadata"] = { "inline": [create_metadata_from_config_dict(idp_conf)]} satosa_config_dict["BACKEND_MODULES"] = [saml_backend_config] satosa_config = SATOSAConfig(satosa_config_dict) frontend_metadata, backend_metadata = create_entity_descriptors(satosa_config) assert len(frontend_metadata) == 1 entity_descriptors = frontend_metadata[saml_mirror_frontend_config["name"]] metadata = InMemoryMetaData(None, str(entity_descriptors[0])) metadata.load() entity_info = list(metadata.values())[0] expected_entity_info = saml_mirror_frontend_config["config"]["idp_config"] assert len(entity_info["contact_person"]) == len(expected_entity_info["contact_person"]) for i, contact in enumerate(expected_entity_info["contact_person"]): assert entity_info["contact_person"][i]["contact_type"] == contact["contact_type"] assert entity_info["contact_person"][i]["email_address"][0]["text"] == contact["email_address"][0] assert entity_info["contact_person"][i]["given_name"]["text"] == contact["given_name"] assert entity_info["contact_person"][i]["sur_name"]["text"] == contact["sur_name"] expected_org_info = expected_entity_info["organization"] assert entity_info["organization"]["organization_display_name"][0]["text"] == \ expected_org_info["display_name"][0][0] assert entity_info["organization"]["organization_name"][0]["text"] == expected_org_info["name"][0][0] assert entity_info["organization"]["organization_url"][0]["text"] == expected_org_info["url"][0][0]
def test_saml_mirror_frontend_with_multiple_backends(self, satosa_config_dict, idp_conf, saml_mirror_frontend_config, saml_backend_config, oidc_backend_config): satosa_config_dict["FRONTEND_MODULES"] = [saml_mirror_frontend_config] saml_backend_config["config"]["sp_config"]["metadata"] = { "inline": [create_metadata_from_config_dict(idp_conf)]} satosa_config_dict["BACKEND_MODULES"] = [saml_backend_config, oidc_backend_config] satosa_config = SATOSAConfig(satosa_config_dict) frontend_metadata, backend_metadata = create_entity_descriptors(satosa_config) assert len(frontend_metadata) == 1 assert len(frontend_metadata[saml_mirror_frontend_config["name"]]) == 2 params = zip([idp_conf["entityid"], oidc_backend_config["config"]["provider_metadata"]["issuer"]], [saml_backend_config["name"], oidc_backend_config["name"]]) entity_descriptors = frontend_metadata[saml_mirror_frontend_config["name"]] for target_entity_id, backend_name in params: encoded_target_entity_id = urlsafe_b64encode(target_entity_id.encode("utf-8")).decode("utf-8") self.assert_single_sign_on_endpoints_for_saml_mirror_frontend(entity_descriptors, encoded_target_entity_id, saml_mirror_frontend_config, [backend_name]) # only the SAML backend produces SAML metadata assert len(backend_metadata) self.assert_assertion_consumer_service_endpoints_for_saml_backend( backend_metadata[saml_backend_config["name"]][0], saml_backend_config)
def test_redirect_to_idp_if_only_one_idp_in_metadata(self, context, sp_conf, idp_conf): sp_conf["metadata"]["inline"] = [create_metadata_from_config_dict(idp_conf)] # instantiate new backend, without any discovery service configured samlbackend = SAMLBackend(None, INTERNAL_ATTRIBUTES, {"sp_config": sp_conf}, "base_url", "saml_backend") resp = samlbackend.start_auth(context, InternalData()) self.assert_redirect_to_idp(resp, idp_conf)
def test_saml_mirror_frontend_with_multiple_backends( self, satosa_config_dict, idp_conf, saml_mirror_frontend_config, saml_backend_config, oidc_backend_config): satosa_config_dict["FRONTEND_MODULES"] = [saml_mirror_frontend_config] saml_backend_config["config"]["sp_config"]["metadata"] = { "inline": [create_metadata_from_config_dict(idp_conf)] } satosa_config_dict["BACKEND_MODULES"] = [ saml_backend_config, oidc_backend_config ] satosa_config = SATOSAConfig(satosa_config_dict) frontend_metadata, backend_metadata = create_entity_descriptors( satosa_config) assert len(frontend_metadata) == 1 assert len(frontend_metadata[saml_mirror_frontend_config["name"]]) == 2 params = zip([ idp_conf["entityid"], oidc_backend_config["config"]["provider_metadata"]["issuer"] ], [saml_backend_config["name"], oidc_backend_config["name"]]) entity_descriptors = frontend_metadata[ saml_mirror_frontend_config["name"]] for target_entity_id, backend_name in params: encoded_target_entity_id = urlsafe_b64encode( target_entity_id.encode("utf-8")).decode("utf-8") self.assert_single_sign_on_endpoints_for_saml_mirror_frontend( entity_descriptors, encoded_target_entity_id, saml_mirror_frontend_config, [backend_name]) # only the SAML backend produces SAML metadata assert len(backend_metadata) self.assert_assertion_consumer_service_endpoints_for_saml_backend( backend_metadata[saml_backend_config["name"]][0], saml_backend_config)
def test_respect_sp_entity_categories(self, context, entity_category, entity_category_module, expected_attributes, idp_conf, sp_conf, internal_response): idp_metadata_str = create_metadata_from_config_dict(idp_conf) idp_conf["service"]["idp"]["policy"]["default"]["entity_categories"] = [entity_category_module] if all(entity_category): # don't insert empty entity category sp_conf["entity_category"] = entity_category if entity_category == [COCO]: sp_conf["service"]["sp"]["required_attributes"] = expected_attributes expected_attributes_in_all_entity_categories = list( itertools.chain(swamid.RELEASE[""], edugain.RELEASE[COCO], refeds.RELEASE[RESEARCH_AND_SCHOLARSHIP], swamid.RELEASE[(RESEARCH_AND_EDUCATION, EU)], swamid.RELEASE[(RESEARCH_AND_EDUCATION, HEI)], swamid.RELEASE[(RESEARCH_AND_EDUCATION, NREN)], swamid.RELEASE[SFS_1993_1153])) attribute_mapping = {} for expected_attribute in expected_attributes_in_all_entity_categories: attribute_mapping[expected_attribute.lower()] = {"saml": [expected_attribute]} internal_attributes = dict(attributes=attribute_mapping) samlfrontend = self.setup_for_authn_req(context, idp_conf, sp_conf, internal_attributes=internal_attributes) user_attributes = {k: "foo" for k in expected_attributes_in_all_entity_categories} internal_response.attributes = AttributeMapper(internal_attributes).to_internal("saml", user_attributes) internal_response.requester = sp_conf["entityid"] resp = self.get_auth_response(samlfrontend, context, internal_response, sp_conf, idp_metadata_str) assert Counter(resp.ava.keys()) == Counter(expected_attributes)
def test_redirect_to_idp_if_only_one_idp_in_metadata(self, context, sp_conf, idp_conf): sp_conf["metadata"]["inline"] = [create_metadata_from_config_dict(idp_conf)] # instantiate new backend, without any discovery service configured samlbackend = SAMLBackend(None, INTERNAL_ATTRIBUTES, {"sp_config": sp_conf}, "base_url", "saml_backend") resp = samlbackend.start_auth(context, InternalData()) assert_redirect_to_idp(resp, idp_conf)
def test_get_metadata_desc_with_logo_without_lang(self, sp_conf, idp_conf): # add logo without 'lang' idp_conf["service"]["idp"]["ui_info"]["logo"] = [{"text": "https://idp.example.com/static/logo.png", "width": "120", "height": "60"}] sp_conf["metadata"]["inline"] = [create_metadata_from_config_dict(idp_conf)] # instantiate new backend, with a single backing IdP samlbackend = SAMLBackend(None, INTERNAL_ATTRIBUTES, {"sp_config": sp_conf}, "base_url", "saml_backend") entity_descriptions = samlbackend.get_metadata_desc() assert len(entity_descriptions) == 1 idp_desc = entity_descriptions[0].to_dict() assert idp_desc["entityid"] == urlsafe_b64encode(idp_conf["entityid"].encode("utf-8")).decode("utf-8") assert idp_desc["contact_person"] == idp_conf["contact_person"] assert idp_desc["organization"]["name"][0] == tuple(idp_conf["organization"]["name"][0]) assert idp_desc["organization"]["display_name"][0] == tuple(idp_conf["organization"]["display_name"][0]) assert idp_desc["organization"]["url"][0] == tuple(idp_conf["organization"]["url"][0]) expected_ui_info = idp_conf["service"]["idp"]["ui_info"] ui_info = idp_desc["service"]["idp"]["ui_info"] assert ui_info["display_name"] == expected_ui_info["display_name"] assert ui_info["description"] == expected_ui_info["description"] assert ui_info["logo"] == expected_ui_info["logo"]
def test_always_redirect_to_discovery_service_if_using_mdq(self, context, sp_conf, idp_conf): # one IdP in the metadata, but MDQ also configured so should always redirect to the discovery service sp_conf["metadata"]["inline"] = [create_metadata_from_config_dict(idp_conf)] sp_conf["metadata"]["mdq"] = ["https://mdq.example.com"] samlbackend = SAMLBackend(None, INTERNAL_ATTRIBUTES, {"sp_config": sp_conf, "disco_srv": DISCOSRV_URL,}, "base_url", "saml_backend") resp = samlbackend.start_auth(context, InternalData()) self.assert_redirect_to_discovery_server(resp, sp_conf)
def test_co_static_attributes(self, frontend, context, internal_response, idp_conf, sp_conf): # Use the frontend and context fixtures to dynamically create the # proxy IdP server that would be created during a flow. idp_server = frontend._create_co_virtual_idp(context) # Use the context fixture to find the CO name and the backend name # and then use those to dynamically update the ipd_conf fixture. co_name = frontend._get_co_name(context) backend_name = context.target_backend idp_conf = frontend._add_endpoints_to_config(idp_conf, co_name, backend_name) idp_conf = frontend._add_entity_id(idp_conf, co_name) # Use a utility function to serialize the idp_conf IdP configuration # fixture to a string and then dynamically update the sp_conf # SP configuration fixture with the metadata. idp_metadata_str = create_metadata_from_config_dict(idp_conf) sp_conf["metadata"]["inline"].append(idp_metadata_str) sp_config = SPConfig().load(sp_conf, metadata_construction=False) # Use the updated sp_config fixture to generate a fake SP and then # use the fake SP to generate an authentication request aimed at the # proxy CO virtual IdP. fakesp = FakeSP(sp_config) destination, auth_req = fakesp.make_auth_req( idp_server.config.entityid, nameid_format=None, relay_state="relay_state", subject=None, ) # Update the context with the authentication request. context.request = auth_req # Create the response arguments necessary for the IdP to respond to # the authentication request, update the request state and with it # the context, and then use the frontend fixture and the # internal_response fixture to handle the authentication response # and generate a response from the proxy IdP to the SP. resp_args = { "name_id_policy": NameIDPolicy(format=NAMEID_FORMAT_TRANSIENT), "in_response_to": None, "destination": sp_config.endpoint( "assertion_consumer_service", binding=BINDING_HTTP_REDIRECT )[0], "sp_entity_id": sp_conf["entityid"], "binding": BINDING_HTTP_REDIRECT } request_state = frontend._create_state_data(context, resp_args, "") context.state[frontend.name] = request_state frontend.handle_authn_response(context, internal_response) # Verify that the frontend added the CO static SAML attributes to the # internal response. for attr, value in self.CO_STATIC_SAML_ATTRIBUTES.items(): assert internal_response.attributes[attr] == value
def test_acr_mapping_per_idp_in_authn_response(self, context, idp_conf, sp_conf, internal_response): expected_loa = "LoA1" loa = {"": "http://eidas.europa.eu/LoA/low", idp_conf["entityid"]: expected_loa} samlfrontend = self.setup_for_authn_req(context, idp_conf, sp_conf, extra_config={"acr_mapping": loa}) idp_metadata_str = create_metadata_from_config_dict(samlfrontend.idp_config) resp = self.get_auth_response(samlfrontend, context, internal_response, sp_conf, idp_metadata_str) assert len(resp.assertion.authn_statement) == 1 authn_context_class_ref = resp.assertion.authn_statement[0].authn_context.authn_context_class_ref assert authn_context_class_ref.text == expected_loa
def test_acr_mapping_per_idp_in_authn_response(self, context, idp_conf, sp_conf, internal_response): expected_loa = "LoA1" loa = {"": "http://eidas.europa.eu/LoA/low", idp_conf["entityid"]: expected_loa} samlfrontend = self.setup_for_authn_req(context, idp_conf, sp_conf, extra_config={"acr_mapping": loa}) idp_metadata_str = create_metadata_from_config_dict(samlfrontend.idp_config) resp = self.get_auth_response(samlfrontend, context, internal_response, sp_conf, idp_metadata_str) assert len(resp.assertion.authn_statement) == 1 authn_context_class_ref = resp.assertion.authn_statement[0].authn_context.authn_context_class_ref assert authn_context_class_ref.text == expected_loa
def test_default_redirect_to_discovery_service_if_using_mdq( self, context, sp_conf, idp_conf ): # one IdP in the metadata, but MDQ also configured so should always redirect to the discovery service sp_conf["metadata"]["inline"] = [create_metadata_from_config_dict(idp_conf)] sp_conf["metadata"]["mdq"] = ["https://mdq.example.com"] samlbackend = SAMLBackend(None, INTERNAL_ATTRIBUTES, {"sp_config": sp_conf, "disco_srv": DISCOSRV_URL,}, "base_url", "saml_backend") resp = samlbackend.start_auth(context, InternalData()) assert_redirect_to_discovery_server(resp, sp_conf, DISCOSRV_URL)
def test_acr_mapping_per_idp_in_authn_response(self, idp_conf, sp_conf): expected_loa = "LoA1" loa = { "": "http://eidas.europa.eu/LoA/low", idp_conf["entityid"]: expected_loa } base = self.construct_base_url_from_entity_id(idp_conf["entityid"]) conf = { "idp_config": idp_conf, "endpoints": ENDPOINTS, "base": base, "state_id": "state_id", "acr_mapping": loa } samlfrontend = SamlFrontend(None, INTERNAL_ATTRIBUTES, conf) samlfrontend.register_endpoints(["foo"]) idp_metadata_str = create_metadata_from_config_dict( samlfrontend.config) sp_conf["metadata"]["inline"].append(idp_metadata_str) fakesp = FakeSP(None, config=SPConfig().load(sp_conf, metadata_construction=False)) auth_info = AuthenticationInformation(PASSWORD, "2015-09-30T12:21:37Z", idp_conf["entityid"]) internal_response = InternalResponse(auth_info=auth_info) context = Context() context.state = State() resp_args = { "name_id_policy": NameIDPolicy(format=NAMEID_FORMAT_TRANSIENT), "in_response_to": None, "destination": "", "sp_entity_id": None, "binding": BINDING_HTTP_REDIRECT } request_state = samlfrontend.save_state(context, resp_args, "") context.state.add(conf["state_id"], request_state) resp = samlfrontend.handle_authn_response(context, internal_response) resp_dict = parse_qs(urlparse(resp.message).query) resp = fakesp.parse_authn_request_response( resp_dict['SAMLResponse'][0], BINDING_HTTP_REDIRECT) assert len(resp.assertion.authn_statement) == 1 authn_context_class_ref = resp.assertion.authn_statement[ 0].authn_context.authn_context_class_ref assert authn_context_class_ref.text == expected_loa
def setup_for_authn_req(self, idp_conf, sp_conf, nameid_format): base = self.construct_base_url_from_entity_id(idp_conf["entityid"]) config = { "idp_config": idp_conf, "endpoints": ENDPOINTS, "base": base, "state_id": "state_id" } sp_metadata_str = create_metadata_from_config_dict(sp_conf) idp_conf["metadata"]["inline"] = [sp_metadata_str] samlfrontend = SamlFrontend( lambda context, internal_req: (context, internal_req), INTERNAL_ATTRIBUTES, config) samlfrontend.register_endpoints(["saml"]) idp_metadata_str = create_metadata_from_config_dict( samlfrontend.config) sp_conf["metadata"]["inline"].append(idp_metadata_str) fakesp = FakeSP(None, config=SPConfig().load(sp_conf, metadata_construction=False)) context = Context() context.state = State() context.request = parse.parse_qs( urlparse( fakesp.make_auth_req(samlfrontend.config["entityid"], nameid_format)).query) tmp_dict = {} for val in context.request: if isinstance(context.request[val], list): tmp_dict[val] = context.request[val][0] else: tmp_dict[val] = context.request[val] context.request = tmp_dict return context, samlfrontend
def frontend(self, idp_conf, sp_conf): """ This fixture is an instance of the SAMLVirtualCoFrontend with an IdP configuration that includes SAML metadata for the test SP configured by the sp_conf fixture so that we can test a SAML Response sent from the IdP. """ # Use a utility function to serialize the sp_conf fixture as # a string and then dynamically add it as the metadata available # as part of the idp_conf fixture. sp_metadata_str = create_metadata_from_config_dict(sp_conf) idp_conf["metadata"]["inline"] = [sp_metadata_str] # Dynamically add configuration details for the CO including static # SAML attributes so their presence in a SAML Response can be tested. collab_org = { "encodeable_name": self.CO, "co_static_saml_attributes": self.CO_STATIC_SAML_ATTRIBUTES, "co_attribute_scope": self.CO_SCOPE } # Use the dynamically updated idp_conf fixture, the configured # endpoints, and the collaborative organization configuration to # create the configuration for the frontend. conf = { "idp_config": idp_conf, "endpoints": ENDPOINTS, "collaborative_organizations": [collab_org] } # Use a richer set of internal attributes than what is provided # for the parent class so that we can test for the static SAML # attributes about the CO being asserted. internal_attributes = INTERNAL_ATTRIBUTES internal_attributes["attributes"][self.CO_O] = {"saml": ["o"]} internal_attributes["attributes"][self.CO_C] = {"saml": ["c"]} internal_attributes["attributes"][self.CO_CO] = {"saml": ["co"]} internal_attributes["attributes"][self.CO_NOREDUORGACRONYM] = ({ "saml": ["norEduOrgAcronym"] }) # Create, register the endpoints, and then return the frontend # instance. frontend = SAMLVirtualCoFrontend(lambda ctx, req: None, internal_attributes, conf, BASE_URL, "saml_virtual_co_frontend") frontend.register_endpoints([self.BACKEND]) return frontend
def test_redirect_to_idp_if_only_one_idp_in_metadata(self, sp_conf, idp_conf): sp_conf["metadata"]["inline"] = [create_metadata_from_config_dict(idp_conf)] samlbackend = SamlBackend(None, INTERNAL_ATTRIBUTES, {"config": sp_conf, "state_id": "saml_backend_test_id"}) state = State() state.add("test", "state") context = Context() context.state = state internal_req = InternalRequest(UserIdHashType.transient, None) resp = samlbackend.start_auth(context, internal_req) assert resp.status == "303 See Other" parsed = urlparse(resp.message) assert "{parsed.scheme}://{parsed.netloc}{parsed.path}".format( parsed=parsed) == \ idp_conf["service"]["idp"]["endpoints"]["single_sign_on_service"][0][0] assert "SAMLRequest" in parse_qs(parsed.query)
def test_custom_attribute_release_with_less_attributes_than_entity_category(self, context, idp_conf, sp_conf, internal_response): idp_metadata_str = create_metadata_from_config_dict(idp_conf) idp_conf["service"]["idp"]["policy"]["default"]["entity_categories"] = ["swamid"] sp_conf["entity_category"] = [SFS_1993_1153] expected_attributes = swamid.RELEASE[SFS_1993_1153] attribute_mapping = {} for expected_attribute in expected_attributes: attribute_mapping[expected_attribute.lower()] = {"saml": [expected_attribute]} internal_attributes = dict(attributes=attribute_mapping) user_attributes = {k: "foo" for k in expected_attributes} internal_response.attributes = AttributeMapper(internal_attributes).to_internal("saml", user_attributes) custom_attributes = {idp_conf["entityid"]: {sp_conf["entityid"]: {"exclude": ["norEduPersonNIN"]}}} samlfrontend = self.setup_for_authn_req(context, idp_conf, sp_conf, internal_attributes=internal_attributes, extra_config=dict(custom_attribute_release=custom_attributes)) resp = self.get_auth_response(samlfrontend, context, internal_response, sp_conf, idp_metadata_str) assert len(resp.ava.keys()) == 0
def test_custom_attribute_release_with_less_attributes_than_entity_category(self, context, idp_conf, sp_conf, internal_response): idp_metadata_str = create_metadata_from_config_dict(idp_conf) idp_conf["service"]["idp"]["policy"]["default"]["entity_categories"] = ["swamid"] sp_conf["entity_category"] = [SFS_1993_1153] expected_attributes = swamid.RELEASE[SFS_1993_1153] attribute_mapping = {} for expected_attribute in expected_attributes: attribute_mapping[expected_attribute.lower()] = {"saml": [expected_attribute]} internal_attributes = dict(attributes=attribute_mapping) user_attributes = {k: "foo" for k in expected_attributes} internal_response.attributes = AttributeMapper(internal_attributes).to_internal("saml", user_attributes) custom_attributes = {idp_conf["entityid"]: {sp_conf["entityid"]: {"exclude": ["norEduPersonNIN"]}}} samlfrontend = self.setup_for_authn_req(context, idp_conf, sp_conf, internal_attributes=internal_attributes, extra_config=dict(custom_attribute_release=custom_attributes)) resp = self.get_auth_response(samlfrontend, context, internal_response, sp_conf, idp_metadata_str) assert len(resp.ava.keys()) == 0
def test_acr_mapping_per_idp_in_authn_response(self, idp_conf, sp_conf): expected_loa = "LoA1" loa = {"": "http://eidas.europa.eu/LoA/low", idp_conf["entityid"]: expected_loa} base = self.construct_base_url_from_entity_id(idp_conf["entityid"]) conf = {"idp_config": idp_conf, "endpoints": ENDPOINTS, "base": base, "state_id": "state_id", "acr_mapping": loa} samlfrontend = SamlFrontend(None, INTERNAL_ATTRIBUTES, conf) samlfrontend.register_endpoints(["foo"]) idp_metadata_str = create_metadata_from_config_dict(samlfrontend.config) sp_conf["metadata"]["inline"].append(idp_metadata_str) fakesp = FakeSP(None, config=SPConfig().load(sp_conf, metadata_construction=False)) auth_info = AuthenticationInformation(PASSWORD, "2015-09-30T12:21:37Z", idp_conf["entityid"]) internal_response = InternalResponse(auth_info=auth_info) context = Context() context.state = State() resp_args = { "name_id_policy": NameIDPolicy(format=NAMEID_FORMAT_TRANSIENT), "in_response_to": None, "destination": "", "sp_entity_id": None, "binding": BINDING_HTTP_REDIRECT } request_state = samlfrontend.save_state(context, resp_args, "") context.state.add(conf["state_id"], request_state) resp = samlfrontend.handle_authn_response(context, internal_response) resp_dict = parse_qs(urlparse(resp.message).query) resp = fakesp.parse_authn_request_response(resp_dict['SAMLResponse'][0], BINDING_HTTP_REDIRECT) assert len(resp.assertion.authn_statement) == 1 authn_context_class_ref = resp.assertion.authn_statement[ 0].authn_context.authn_context_class_ref assert authn_context_class_ref.text == expected_loa
def test_get_metadata_desc(self, sp_conf, idp_conf): sp_conf["metadata"]["inline"] = [create_metadata_from_config_dict(idp_conf)] # instantiate new backend, with a single backing IdP samlbackend = SAMLBackend(None, INTERNAL_ATTRIBUTES, {"sp_config": sp_conf}, "base_url", "saml_backend") entity_descriptions = samlbackend.get_metadata_desc() assert len(entity_descriptions) == 1 idp_desc = entity_descriptions[0].to_dict() assert idp_desc["entityid"] == urlsafe_b64encode(idp_conf["entityid"].encode("utf-8")).decode("utf-8") assert idp_desc["contact_person"] == idp_conf["contact_person"] assert idp_desc["organization"]["name"][0] == tuple(idp_conf["organization"]["name"][0]) assert idp_desc["organization"]["display_name"][0] == tuple(idp_conf["organization"]["display_name"][0]) assert idp_desc["organization"]["url"][0] == tuple(idp_conf["organization"]["url"][0]) expected_ui_info = idp_conf["service"]["idp"]["ui_info"] ui_info = idp_desc["service"]["idp"]["ui_info"] assert ui_info["display_name"] == expected_ui_info["display_name"] assert ui_info["description"] == expected_ui_info["description"] assert ui_info["logo"] == expected_ui_info["logo"]
def test_respect_sp_entity_categories(self, entity_category, expected_attributes, idp_conf, sp_conf): base = self.construct_base_url_from_entity_id(idp_conf["entityid"]) conf = {"idp_config": idp_conf, "endpoints": ENDPOINTS, "base": base, "state_id": "state_id"} internal_attributes = {attr_name: {"saml": [attr_name.lower()]} for attr_name in expected_attributes} samlfrontend = SamlFrontend(None, dict(attributes=internal_attributes), conf) samlfrontend.register_endpoints(["foo"]) idp_metadata_str = create_metadata_from_config_dict(samlfrontend.idp_config) sp_conf["metadata"]["inline"].append(idp_metadata_str) sp_conf["entity_category"] = entity_category fakesp = FakeSP(None, config=SPConfig().load(sp_conf, metadata_construction=False)) auth_info = AuthenticationInformation(PASSWORD, "2015-09-30T12:21:37Z", idp_conf["entityid"]) internal_response = InternalResponse(auth_info=auth_info) user_attributes = {k: "foo" for k in expected_attributes} user_attributes.update({k: "bar" for k in ["extra", "more", "stuff"]}) internal_response.add_attributes(user_attributes) context = Context() context.state = State() resp_args = { "name_id_policy": NameIDPolicy(format=NAMEID_FORMAT_TRANSIENT), "in_response_to": None, "destination": "", "sp_entity_id": None, "binding": BINDING_HTTP_REDIRECT } request_state = samlfrontend.save_state(context, resp_args, "") context.state.add(conf["state_id"], request_state) resp = samlfrontend.handle_authn_response(context, internal_response) resp_dict = parse_qs(urlparse(resp.message).query) resp = fakesp.parse_authn_request_response(resp_dict['SAMLResponse'][0], BINDING_HTTP_REDIRECT) assert Counter(resp.ava.keys()) == Counter(expected_attributes)
def test_redirect_to_idp_if_only_one_idp_in_metadata( self, sp_conf, idp_conf): sp_conf["metadata"]["inline"] = [ create_metadata_from_config_dict(idp_conf) ] samlbackend = SamlBackend(None, INTERNAL_ATTRIBUTES, { "config": sp_conf, "state_id": "saml_backend_test_id" }) state = State() state.add("test", "state") context = Context() context.state = state internal_req = InternalRequest(UserIdHashType.transient, None) resp = samlbackend.start_auth(context, internal_req) assert resp.status == "303 See Other" parsed = urlparse(resp.message) assert "{parsed.scheme}://{parsed.netloc}{parsed.path}".format( parsed=parsed) == \ idp_conf["service"]["idp"]["endpoints"]["single_sign_on_service"][0][0] assert "SAMLRequest" in parse_qs(parsed.query)