def setup_class(self): server = Server("idp_conf") name_id = server.ident.transient_nameid("urn:mace:example.com:saml:roland:sp", "id12") self._resp_ = server.create_authn_response( IDENTITY, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=name_id, ) self._sign_resp_ = server.create_authn_response( IDENTITY, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=name_id, sign_assertion=True, ) self._resp_authn = server.create_authn_response( IDENTITY, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=name_id, authn=AUTHN, ) conf = config.SPConfig() conf.load_file("server_conf") self.conf = conf
def test_enc1(): server = Server("idp_conf") name_id = server.ident.transient_nameid( "urn:mace:example.com:saml:roland:sp", "id12") resp_ = server.create_authn_response( IDENTITY, "id12", "http://lingon.catalogix.se:8087/", "urn:mace:example.com:saml:roland:sp", name_id=name_id) statement = pre_encrypt_assertion(resp_) tmpl = "enc_tmpl.xml" # tmpl_file = open(tmpl, "w") # tmpl_file.write("%s" % pre_encryption_part()) # tmpl_file.close() data = "pre_enc.xml" # data_file = open(data, "w") # data_file.write("%s" % statement) # data_file.close() key_type = "des-192" com_list = [xmlsec_path, "encrypt", "--pubkey-cert-pem", "pubkey.pem", "--session-key", key_type, "--xml-data", data, "--node-xpath", ASSERT_XPATH] crypto = CryptoBackendXmlSec1(xmlsec_path) (_stdout, _stderr, output) = crypto._run_xmlsec( com_list, [tmpl], exception=EncryptError, validate_output=False) print output assert _stderr == "" assert _stdout == ""
def test_slo_soap(self): soon = time_util.in_a_while(days=1) sinfo = { "name_id": nid, "issuer": "urn:mace:example.com:saml:roland:idp", "not_on_or_after": soon, "user": { "givenName": "Leo", "surName": "Laport", } } sp = client.Saml2Client(config_file="server_conf") sp.users.add_information_about_person(sinfo) req_id, logout_request = sp.create_logout_request( name_id=nid, destination="http://localhost:8088/slo", issuer_entity_id="urn:mace:example.com:saml:roland:idp", reason="I'm tired of this") #_ = s_utils.deflate_and_base64_encode("%s" % (logout_request,)) saml_soap = make_soap_enveloped_saml_thingy(logout_request) self.server.ident.close() idp = Server("idp_soap_conf") request = idp.parse_logout_request(saml_soap) idp.ident.close() assert request
def setup_class(self): server = Server(dotname("idp_conf")) name_id = server.ident.transient_nameid( "urn:mace:example.com:saml:roland:sp","id12") self._resp_ = server.create_authn_response( IDENTITY, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=name_id, authn=AUTHN) self._sign_resp_ = server.create_authn_response( IDENTITY, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=name_id, sign_assertion=True, authn=AUTHN) self._resp_authn = server.create_authn_response( IDENTITY, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=name_id, authn=AUTHN) self.conf = config_factory("sp", dotname("server_conf")) self.conf.only_use_keys_in_metadata = False self.ar = authn_response(self.conf, "http://lingon.catalogix.se:8087/")
def setup_class(self): server = Server("idp_conf") name_id = server.ident.transient_nameid( "urn:mace:example.com:saml:roland:sp", "id12") self._resp_ = server.do_response( "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id {"eduPersonEntitlement":"Jeter"}, name_id = name_id ) self._sign_resp_ = server.do_response( "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id {"eduPersonEntitlement":"Jeter"}, name_id = name_id, sign=True ) self._resp_authn = server.do_response( "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id {"eduPersonEntitlement":"Jeter"}, name_id = name_id, authn=(saml.AUTHN_PASSWORD, "http://www.example.com/login") ) conf = config.SPConfig() conf.load_file("server_conf") self.conf = conf
def test_create_artifact_resolve(): b64art = create_artifact(SP, "aabbccddeeffgghhiijj", 1) artifact = base64.b64decode(b64art) #assert artifact[:2] == '\x00\x04' #assert int(artifact[2:4]) == 0 # s = sha1(SP) assert artifact[4:24] == s.digest() idp = Server(config_file="idp_all_conf") typecode = artifact[:2] assert typecode == ARTIFACT_TYPECODE destination = idp.artifact2destination(b64art, "spsso") msg = idp.create_artifact_resolve(b64art, destination, sid()) print msg args = idp.use_soap(msg, destination, None, False) sp = Saml2Client(config_file="servera_conf") ar = sp.parse_artifact_resolve(args["data"]) print ar assert ar.artifact.text == b64art
class TestSP(): def setup_class(self): self.sp = make_plugin("rem", saml_conf="server_conf") self.server = Server(config_file="idp_conf") def teardown_class(self): self.server.close() def test_setup(self): assert self.sp def test_identify(self): # Create a SAMLResponse ava = {"givenName": ["Derek"], "surName": ["Jeter"], "mail": ["*****@*****.**"], "title": ["The man"]} resp_str = "%s" % self.server.create_authn_response( ava, "id1", "http://lingon.catalogix.se:8087/", "urn:mace:example.com:saml:roland:sp", trans_name_policy, "*****@*****.**", authn=AUTHN) resp_str = base64.encodestring(resp_str) self.sp.outstanding_queries = {"id1": "http://www.example.com/service"} session_info = self.sp._eval_authn_response( {}, {"SAMLResponse": [resp_str]}) assert len(session_info) > 1 assert session_info["came_from"] == 'http://www.example.com/service' assert session_info["ava"] == {'givenName': ['Derek'], 'mail': ['*****@*****.**'], 'sn': ['Jeter'], 'title': ['The man']}
class TestServer2(): def setup_class(self): self.server = Server("restrictive_idp_conf") def teardown_class(self): self.server.close() def test_do_attribute_reponse(self): aa_policy = self.server.config.getattr("policy", "idp") print(aa_policy.__dict__) response = self.server.create_attribute_response( IDENTITY.copy(), "aaa", "http://example.com/sp/", "http://www.example.com/roland/sp") assert response is not None assert response.destination == "http://example.com/sp/" assert response.in_response_to == "aaa" assert response.version == "2.0" assert response.issuer.text == "urn:mace:example.com:saml:roland:idpr" assert response.status.status_code.value == samlp.STATUS_SUCCESS assert response.assertion assertion = response.assertion assert assertion.version == "2.0" subject = assertion.subject #assert subject.name_id.format == saml.NAMEID_FORMAT_TRANSIENT assert subject.subject_confirmation subject_conf = subject.subject_confirmation[0] assert subject_conf.subject_confirmation_data.in_response_to == "aaa"
def setup_class(self): server = Server("idp_conf") name_id = server.ident.transient_nameid( "urn:mace:example.com:saml:roland:sp","id12") policy = server.conf.getattr("policy", "idp") self._resp_ = server.create_response( "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id IDENTITY, name_id = name_id, policy=policy) self._sign_resp_ = server.create_response( "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id IDENTITY, name_id = name_id, sign_assertion=True, policy=policy) self._resp_authn = server.create_response( "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id IDENTITY, name_id = name_id, authn=(saml.AUTHN_PASSWORD, "http://www.example.com/login"), policy=policy) self.conf = config_factory("sp", "server_conf") self.conf.only_use_keys_in_metadata = False self.ar = authn_response(self.conf, "http://lingon.catalogix.se:8087/")
def test_1(self): server = Server("idp_slo_redirect_conf") request = _logout_request("sp_slo_redirect_conf") print request bindings = [BINDING_HTTP_REDIRECT] (resp, headers, message) = server.logout_response(request, bindings) assert resp == '302 Found' assert len(headers) == 1 assert headers[0][0] == "Location" assert message == ['']
def test_reshuffle_response(): server = Server("idp_conf") name_id = server.ident.transient_nameid("urn:mace:example.com:saml:roland:sp", "id12") resp_ = server.create_authn_response( IDENTITY, "id12", "http://lingon.catalogix.se:8087/", "urn:mace:example.com:saml:roland:sp", name_id=name_id ) resp2 = pre_encrypt_assertion(resp_) print resp2 assert resp2.encrypted_assertion.extension_elements
def test_enc2(): crypto = CryptoBackendXmlSec1(xmlsec_path) server = Server("idp_conf") name_id = server.ident.transient_nameid("urn:mace:example.com:saml:roland:sp", "id12") resp_ = server.create_authn_response( IDENTITY, "id12", "http://lingon.catalogix.se:8087/", "urn:mace:example.com:saml:roland:sp", name_id=name_id ) enc_resp = crypto.encrypt_assertion(resp_, full_path("pubkey.pem"), pre_encryption_part()) print enc_resp assert enc_resp
def test_encrypted_response_9(self): _server = Server("idp_conf_sp_no_encrypt") _resp = _server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=True, encrypt_assertion_self_contained=True, #encrypted_advice_attributes=True, pefim=True, ) self.verify_assertion(_resp.assertion.advice.assertion) _resp = _server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=False, encrypt_assertion_self_contained=True, #encrypted_advice_attributes=True, pefim=True ) self.verify_assertion(_resp.assertion.advice.assertion) _resp = _server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=True, encrypt_assertion_self_contained=True, encrypted_advice_attributes=False, ) self.verify_assertion([_resp.assertion])
class TestSP(): def setup_class(self): self.sp = make_plugin("rem", saml_conf="server_conf") self.server = Server(config_file="idp_conf") def test_setup(self): assert self.sp def test_identify(self): # Create a SAMLResponse ava = { "givenName": ["Derek"], "surname": ["Jeter"], "mail": ["*****@*****.**"]} resp_str = "\n".join(self.server.authn_response(ava, "id1", "http://lingon.catalogix.se:8087/", "urn:mace:example.com:saml:roland:sp", samlp.NameIDPolicy(format=saml.NAMEID_FORMAT_TRANSIENT, allow_create="true"), "*****@*****.**")) resp_str = base64.encodestring(resp_str) self.sp.outstanding_queries = {"id1":"http://www.example.com/service"} session_info = self.sp._eval_authn_response({},{"SAMLResponse":resp_str}) assert len(session_info) > 1 assert session_info["came_from"] == 'http://www.example.com/service' assert session_info["ava"] == {'givenName': ['Derek'], 'mail': ['*****@*****.**'], 'sn': ['Jeter']}
def create_authn_response(session_id, identity=dict(), sign=True): config = IdPConfig() config.load(idp_config) idp_server = Server(config=config) idp_server.ident = Identifier(auth.AuthDictCache(dict(), '_ident')) authn_response = str(idp_server.authn_response( identity=identity, in_response_to=session_id, destination='https://foo.example.com/sp/acs', sp_entity_id='https://foo.example.com/sp/metadata', name_id_policy=None, userid='Irrelevent', sign=sign, instance=True)) response = samlp.response_from_string(authn_response) return response.assertion[0].subject.name_id.text, authn_response
def test_1(self): server = Server("idp_slo_redirect_conf") req_id, request = _logout_request("sp_slo_redirect_conf") print(request) bindings = [BINDING_HTTP_REDIRECT] response = server.create_logout_response(request, bindings) binding, destination = server.pick_binding("single_logout_service", bindings, "spsso", request) http_args = server.apply_binding(binding, "%s" % response, destination, "relay_state", response=True) assert len(http_args) == 4 assert http_args["headers"][0][0] == "Location" assert http_args["data"] == []
def test_authn_response_0(self): self.server = Server("idp_conf") conf = config.SPConfig() conf.load_file("server_conf") self.client = client.Saml2Client(conf) ava = {"givenName": ["Derek"], "surName": ["Jeter"], "mail": ["*****@*****.**"], "title": "The man"} npolicy = samlp.NameIDPolicy(format=saml.NAMEID_FORMAT_TRANSIENT, allow_create="true") resp_str = "%s" % self.server.create_authn_response( ava, "id1", "http://*****:*****@example.com", authn=AUTHN) response = samlp.response_from_string(resp_str) print((response.keyswv())) assert _eq(response.keyswv(), ['status', 'destination', 'assertion', 'in_response_to', 'issue_instant', 'version', 'issuer', 'id']) print((response.assertion[0].keyswv())) assert len(response.assertion) == 1 assert _eq(response.assertion[0].keyswv(), ['attribute_statement', 'issue_instant', 'version', 'subject', 'conditions', 'id', 'issuer', 'authn_statement']) assertion = response.assertion[0] assert len(assertion.attribute_statement) == 1 astate = assertion.attribute_statement[0] print(astate) assert len(astate.attribute) == 4
class SamlServer(object): """ SAML Wrapper around pysaml2. Implements SAML2 Identity Provider functionality for Flask. """ def __init__(self, config, attribute_map=None): """Initialize SAML Identity Provider. Args: config (dict): Identity Provider config info in dict form attribute_map (dict): Mapping of attribute keys to user data """ self._config = IdPConfig() self._config.load(config) self._server = Server(config=self._config) self.attribute_map = {} if attribute_map is not None: self.attribute_map = attribute_map def handle_authn_request(self, request, login_form_cb): """Handles authentication request. TODO: create default login_form_cb, with unstyled login form? Args: request (Request): Flask request object for this HTTP transaction. login_form_cb (function): Function that displays login form with username and password fields. Takes a single parameter which is the service_provider_id so the form may be styled accordingly. """ if 'SAMLRequest' in request.values: details = self._server.parse_authn_request(request.details, BINDING_HTTP_REDIRECT) # TODO: check session for already authenticated user # and send authn_response immediately. # TODO: otherwise render login form login_form_cb(service_provider_id) else: pass # TODO: bad request? def get_service_provider_id(self, request): # TODO: pull service_provider_id from session pass def authn_response(self, userid): service_provider_id = get_service_provider_id() # TODO: send authn_response pass def get_metadata(self): """Returns SAML Identity Provider Metadata""" edesc = entity_descriptor(self._config, 24) if self._config.key_file: edesc = sign_entity_descriptor(edesc, 24, None, security_context(self._config)) response = make_response(str(edesc)) response.headers['Content-type'] = 'text/xml; charset=utf-8' return response
def setup_class(self): self.server = Server("idp_conf") conf = config.SPConfig() conf.load_file("server_conf") self.client = client.Saml2Client(conf) self.name_id = self.server.ident.transient_nameid( "urn:mace:example.com:saml:roland:sp", "id12") self.ava = {"givenName": ["Derek"], "sn": ["Jeter"], "mail": ["*****@*****.**"], "title": "The man"}
def test_request_response(): sp = Saml2Client(config_file="servera_conf") idp = Server(config_file="idp_all_conf") binding, destination = sp.pick_binding("name_id_mapping_service", entity_id=idp.config.entityid) policy = NameIDPolicy(format=NAMEID_FORMAT_TRANSIENT, sp_name_qualifier="urn:mace:swamid:junk", allow_create="true") nameid = NameID(format=NAMEID_FORMAT_TRANSIENT, text="foobar") nmr = sp.create_name_id_mapping_request(policy, nameid, destination) print nmr args = sp.use_soap(nmr, destination) # ------- IDP ------------ req = idp.parse_name_id_mapping_request(args["data"], binding) in_response_to = req.message.id name_id = NameID(format=NAMEID_FORMAT_PERSISTENT, text="foobar") idp_response = idp.create_name_id_mapping_response(name_id, in_response_to=in_response_to) print idp_response ht_args = sp.use_soap(idp_response) # ------- SP ------------ _resp = sp.parse_name_id_mapping_request_response(ht_args["data"], binding) print _resp.response r_name_id = _resp.response.name_id assert r_name_id.format == NAMEID_FORMAT_PERSISTENT assert r_name_id.text == "foobar"
def test_flow(): sp = Saml2Client(config_file="servera_conf") idp1 = Server(config_file="idp_conf_mdb") idp2 = Server(config_file="idp_conf_mdb") # clean out database idp1.ident.mdb.db.drop() # -- dummy request --- req_id, orig_req = sp.create_authn_request(idp1.config.entityid) # == Create an AuthnRequest response rinfo = idp1.response_args(orig_req, [BINDING_HTTP_POST]) # name_id = idp1.ident.transient_nameid("id12", rinfo["sp_entity_id"]) resp = idp1.create_authn_response( { "eduPersonEntitlement": "Short stop", "surName": "Jeter", "givenName": "Derek", "mail": "*****@*****.**", "title": "The man", }, userid="jeter", authn=AUTHN, **rinfo ) # What's stored away is the assertion a_info = idp2.session_db.get_assertion(resp.assertion.id) # Make sure what I got back from MongoDB is the same as I put in assert a_info["assertion"] == resp.assertion # By subject nid = resp.assertion.subject.name_id _assertion = idp2.session_db.get_assertions_by_subject(nid) assert len(_assertion) == 1 assert _assertion[0] == resp.assertion nids = idp2.ident.find_nameid("jeter") assert len(nids) == 1
def test_flow(): sp = Saml2Client(config_file="servera_conf") idp = Server(config_file="idp_all_conf") binding, destination = sp.pick_binding("manage_name_id_service", entity_id=idp.config.entityid) nameid = NameID(format=NAMEID_FORMAT_TRANSIENT, text="foobar") newid = NewID(text="Barfoo") mid, midq = sp.create_manage_name_id_request(destination, name_id=nameid, new_id=newid) print midq rargs = sp.apply_binding(binding, "%s" % midq, destination, "") # --------- @IDP -------------- _req = idp.parse_manage_name_id_request(rargs["data"], binding) print _req.message mnir = idp.create_manage_name_id_response(_req.message, [binding]) if binding != BINDING_SOAP: binding, destination = idp.pick_binding("manage_name_id_service", entity_id=sp.config.entityid) else: destination = "" respargs = idp.apply_binding(binding, "%s" % mnir, destination, "") print respargs # ---------- @SP --------------- _response = sp.parse_manage_name_id_request_response(respargs["data"], binding) print _response.response assert _response.response.id == mnir.id
def auth_response(identity, in_response_to, sp_conf): """Generates a fresh signed authentication response""" sp_entity_id = sp_conf.entityid idp_entity_id = sp_conf.idps().keys()[0] acs = sp_conf.endpoint('assertion_consumer_service')[0] issuer = saml.Issuer(text=idp_entity_id, format=saml.NAMEID_FORMAT_ENTITY) response = response_factory(issuer=issuer, in_response_to=in_response_to, destination=acs, status=success_status_factory()) idp_conf = IdPConfig() name_form = "urn:oasis:names:tc:SAML:2.0:attrname-format:uri" idp_conf.load({ 'entityid': idp_entity_id, 'xmlsec_binary': sp_conf.xmlsec_binary, 'attribute_map_dir': os.path.join(BASEDIR, 'attribute-maps'), 'service': { 'idp': { 'endpoints': tuple(), 'policy': { 'default': { "lifetime": {"minutes": 15}, "attribute_restrictions": None, "name_form": name_form, } } }, }, 'key_file': os.path.join(BASEDIR, 'idpcert.key'), 'cert_file': os.path.join(BASEDIR, 'idpcert.pem'), 'metadata': { 'local': [os.path.join(BASEDIR, 'sp_metadata.xml')], }, }) server = Server("", idp_conf) server.ident = Identifier(FakeDb()) userid = 'irrelevant' response = server.authn_response(identity, in_response_to, acs, sp_entity_id, None, userid) return '\n'.join(response)
def __init__(self, environ, start_response, conf, cache, incoming): """ Constructor for the class. :param environ: WSGI environ :param start_response: WSGI start response function :param conf: The SAML configuration :param cache: Cache with active sessions """ service.Service.__init__(self, environ, start_response) self.response_bindings = None self.idp = Server(config=conf, cache=cache) self.incoming = incoming
def test_handle_logout_soap(): sp = Saml2Client(config_file="servera_conf") idp = Server(config_file="idp_all_conf") policy = NameIDPolicy(format=NAMEID_FORMAT_TRANSIENT, sp_name_qualifier=sp.config.entityid, allow_create="true") name_id = idp.ident.construct_nameid("subject", name_id_policy=policy) binding, destination = idp.pick_binding("single_logout_service", [BINDING_SOAP], entity_id=sp.config.entityid) rid, request = idp.create_logout_request(destination, idp.config.entityid, name_id=name_id) args = idp.apply_binding(BINDING_SOAP, "%s" % request, destination) # register the user session_info = { "name_id": name_id, "issuer": idp.config.entityid, "not_on_or_after": in_a_while(minutes=15), "ava": { "givenName": "Anders", "surName": "Andersson", "mail": "*****@*****.**" } } sp.users.add_information_about_person(session_info) reply_args = sp.handle_logout_request(args["data"], name_id, binding, sign=False) print(reply_args) assert reply_args["method"] == "POST" assert reply_args["headers"] == [('content-type', 'application/soap+xml')] #if __name__ == "__main__": # test_handle_logout_soap()
def test_encrypted_response_6(self): _server = Server("idp_conf_verify_cert") cert_str_advice, cert_key_str_advice = generate_cert() cert_str_assertion, cert_key_str_assertion = generate_cert() _resp = _server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=True, encrypt_assertion_self_contained=True, #encrypted_advice_attributes=True, pefim=True, encrypt_cert_advice=cert_str_advice, encrypt_cert_assertion=cert_str_assertion ) sresponse = response_from_string(_resp) assert sresponse.signature is None _, key_file = make_temp("%s" % cert_key_str_assertion, decode=False) decr_text_1 = _server.sec.decrypt(_resp, key_file) _, key_file = make_temp("%s" % cert_key_str_advice, decode=False) decr_text_2 = _server.sec.decrypt(decr_text_1, key_file) resp = samlp.response_from_string(decr_text_2) resp.assertion = extension_elements_to_elements(resp.encrypted_assertion[0].extension_elements, [saml, samlp]) self.verify_advice_assertion(resp, decr_text_2)
def test_basic(): sp = Saml2Client(config_file="servera_conf") idp = Server(config_file="idp_all_conf") # -------- @SP ------------ binding, destination = sp.pick_binding("manage_name_id_service", entity_id=idp.config.entityid) nameid = NameID(format=NAMEID_FORMAT_TRANSIENT, text="foobar") newid = NewID(text="Barfoo") mid, mreq = sp.create_manage_name_id_request(destination, name_id=nameid, new_id=newid) print mreq rargs = sp.apply_binding(binding, "%s" % mreq, destination, "") # --------- @IDP -------------- _req = idp.parse_manage_name_id_request(rargs["data"], binding) print _req.message assert mid == _req.message.id
def __init__(self, config, attribute_map=None): """Initialize SAML Identity Provider. Args: config (dict): Identity Provider config info in dict form attribute_map (dict): Mapping of attribute keys to user data """ self._config = IdPConfig() self._config.load(config) self._server = Server(config=self._config) self.attribute_map = {} if attribute_map is not None: self.attribute_map = attribute_map
def create_logout_response(subject_id, destination, issuer_entity_id, req_entity_id, sign=True): config = IdPConfig() config.load(idp_config) idp_server = Server(config=config) # construct a request logout_request = create_logout_request( subject_id=subject_id, destination=destination, issuer_entity_id=issuer_entity_id, req_entity_id=req_entity_id) #idp_server.ident = Identifier(auth.AuthDictCache(dict(), '_ident')) resp, headers, message = idp_server.logout_response( request=logout_request, bindings=[BINDING_HTTP_REDIRECT], sign=sign) location = dict(headers).get('Location') url = urlparse.urlparse(location) params = urlparse.parse_qs(url.query) logout_response_xml = decode_base64_and_inflate(params['SAMLResponse'][0]) response = samlp.logout_response_from_string(logout_response_xml) return response.in_response_to, logout_response_xml
def test_unsolicited_response(self): """ """ self.server = Server("idp_conf") conf = config.SPConfig() conf.load_file("server_conf") self.client = Saml2Client(conf) for subject in self.client.users.subjects(): self.client.users.remove_person(subject) IDP = "urn:mace:example.com:saml:roland:idp" ava = { "givenName": ["Derek"], "surName": ["Jeter"], "mail": ["*****@*****.**"]} resp_str = "%s" % self.server.create_authn_response( identity=ava, in_response_to="id1", destination="http://lingon.catalogix.se:8087/", sp_entity_id="urn:mace:example.com:saml:roland:sp", name_id_policy=samlp.NameIDPolicy( format=saml.NAMEID_FORMAT_PERSISTENT), userid="*****@*****.**") resp_str = base64.encodestring(resp_str) self.client.allow_unsolicited = True authn_response = self.client.authn_request_response( {"SAMLResponse":resp_str}, ()) assert authn_response is not None assert authn_response.issuer() == IDP assert authn_response.response.assertion[0].issuer.text == IDP session_info = authn_response.session_info() print session_info assert session_info["ava"] == {'mail': ['*****@*****.**'], 'givenName': ['Derek'], 'surName': ['Jeter']} assert session_info["issuer"] == IDP assert session_info["came_from"] == "" response = samlp.response_from_string(authn_response.xmlstr) assert response.destination == "http://lingon.catalogix.se:8087/" # One person in the cache assert len(self.client.users.subjects()) == 1
def setup_class(self): self.server = Server("restrictive_idp_conf")
def setup_class(self): self.sp = make_plugin("rem", saml_conf="server_conf") # Explicitly allow unsigned responses for this test self.sp.saml_client.want_response_signed = False self.server = Server(config_file="idp_conf")
class TestServer1(): def setup_class(self): self.server = Server("idp_conf") conf = config.SPConfig() conf.load_file("server_conf") self.client = client.Saml2Client(conf) self.name_id = self.server.ident.transient_nameid( "urn:mace:example.com:saml:roland:sp", "id12") self.ava = { "givenName": ["Derek"], "surName": ["Jeter"], "mail": ["*****@*****.**"], "title": "The man" } def teardown_class(self): self.server.close() def verify_assertion(self, assertion): assert assertion assert assertion[0].attribute_statement ava = ava = get_ava(assertion[0]) assert ava ==\ {'mail': ['*****@*****.**'], 'givenName': ['Derek'], 'surName': ['Jeter'], 'title': ['The man']} def verify_encrypted_assertion(self, assertion, decr_text): self.verify_assertion(assertion) assert assertion[0].signature is None assert 'EncryptedAssertion><encas1:Assertion xmlns:encas0="http://www.w3.org/2001/XMLSchema-instance" ' \ 'xmlns:encas1="urn:oasis:names:tc:SAML:2.0:assertion"' in decr_text def verify_advice_assertion(self, resp, decr_text): assert resp.assertion[0].signature is None assert resp.assertion[0].advice.encrypted_assertion[ 0].extension_elements assertion = extension_elements_to_elements( resp.assertion[0].advice.encrypted_assertion[0].extension_elements, [saml, samlp]) self.verify_encrypted_assertion(assertion, decr_text) def test_issuer(self): issuer = self.server._issuer() assert isinstance(issuer, saml.Issuer) assert _eq(issuer.keyswv(), ["text", "format"]) assert issuer.format == saml.NAMEID_FORMAT_ENTITY assert issuer.text == self.server.config.entityid def test_assertion(self): assertion = s_utils.assertion_factory( subject=factory(saml.Subject, text="_aaa", name_id=factory( saml.NameID, format=saml.NAMEID_FORMAT_TRANSIENT)), attribute_statement=do_attribute_statement({ ("", "", "surName"): ("Jeter", ""), ("", "", "givenName"): ("Derek", ""), }), issuer=self.server._issuer(), ) assert _eq(assertion.keyswv(), [ 'attribute_statement', 'issuer', 'id', 'subject', 'issue_instant', 'version' ]) assert assertion.version == "2.0" assert assertion.issuer.text == "urn:mace:example.com:saml:roland:idp" # assert assertion.attribute_statement attribute_statement = assertion.attribute_statement assert len(attribute_statement.attribute) == 2 attr0 = attribute_statement.attribute[0] attr1 = attribute_statement.attribute[1] if attr0.attribute_value[0].text == "Derek": assert attr0.friendly_name == "givenName" assert attr1.friendly_name == "surName" assert attr1.attribute_value[0].text == "Jeter" else: assert attr1.friendly_name == "givenName" assert attr1.attribute_value[0].text == "Derek" assert attr0.friendly_name == "surName" assert attr0.attribute_value[0].text == "Jeter" # subject = assertion.subject assert _eq(subject.keyswv(), ["text", "name_id"]) assert subject.text == "_aaa" assert subject.name_id.format == saml.NAMEID_FORMAT_TRANSIENT def test_response(self): response = sigver.response_factory( in_response_to="_012345", destination="https:#www.example.com", status=s_utils.success_status_factory(), assertion=s_utils.assertion_factory( subject=factory(saml.Subject, text="_aaa", name_id=saml.NAMEID_FORMAT_TRANSIENT), attribute_statement=do_attribute_statement({ ("", "", "surName"): ("Jeter", ""), ("", "", "givenName"): ("Derek", ""), }), issuer=self.server._issuer(), ), issuer=self.server._issuer(), ) print(response.keyswv()) assert _eq(response.keyswv(), [ 'destination', 'assertion', 'status', 'in_response_to', 'issue_instant', 'version', 'issuer', 'id' ]) assert response.version == "2.0" assert response.issuer.text == "urn:mace:example.com:saml:roland:idp" assert response.destination == "https:#www.example.com" assert response.in_response_to == "_012345" # status = response.status print(status) assert status.status_code.value == samlp.STATUS_SUCCESS def test_parse_faulty_request(self): req_id, authn_request = self.client.create_authn_request( destination="http://www.example.com", id="id1") # should raise an error because faulty spentityid binding = BINDING_HTTP_REDIRECT htargs = self.client.apply_binding(binding, "%s" % authn_request, "http://www.example.com", "abcd") _dict = parse_qs(htargs["headers"][0][1].split('?')[1]) print(_dict) raises(OtherError, self.server.parse_authn_request, _dict["SAMLRequest"][0], binding) def test_parse_faulty_request_to_err_status(self): req_id, authn_request = self.client.create_authn_request( destination="http://www.example.com") binding = BINDING_HTTP_REDIRECT htargs = self.client.apply_binding(binding, "%s" % authn_request, "http://www.example.com", "abcd") _dict = parse_qs(htargs["headers"][0][1].split('?')[1]) print(_dict) try: self.server.parse_authn_request(_dict["SAMLRequest"][0], binding) status = None except OtherError as oe: print(oe.args) status = s_utils.error_status_factory(oe) assert status print(status) assert _eq(status.keyswv(), ["status_code", "status_message"]) assert status.status_message.text == 'Not destined for me!' status_code = status.status_code assert _eq(status_code.keyswv(), ["status_code", "value"]) assert status_code.value == samlp.STATUS_RESPONDER assert status_code.status_code.value == samlp.STATUS_UNKNOWN_PRINCIPAL def test_parse_ok_request(self): req_id, authn_request = self.client.create_authn_request( message_id="id1", destination="http://*****:*****@nyy.mlb.com", "title": "The man" }, "id12", # in_response_to "http://*****:*****@nyy.mlb.com"], "title": "The man" } npolicy = samlp.NameIDPolicy(format=saml.NAMEID_FORMAT_TRANSIENT, allow_create="true") resp_str = "%s" % self.server.create_authn_response( ava, "id1", "http://*****:*****@example.com", authn=AUTHN) response = samlp.response_from_string(resp_str) print(response.keyswv()) assert _eq(response.keyswv(), [ 'status', 'destination', 'assertion', 'in_response_to', 'issue_instant', 'version', 'issuer', 'id' ]) print(response.assertion[0].keyswv()) assert len(response.assertion) == 1 assert _eq(response.assertion[0].keyswv(), [ 'attribute_statement', 'issue_instant', 'version', 'subject', 'conditions', 'id', 'issuer', 'authn_statement' ]) assertion = response.assertion[0] assert len(assertion.attribute_statement) == 1 astate = assertion.attribute_statement[0] print(astate) assert len(astate.attribute) == 4 def test_signed_response(self): name_id = self.server.ident.transient_nameid( "urn:mace:example.com:saml:roland:sp", "id12") ava = { "givenName": ["Derek"], "surName": ["Jeter"], "mail": ["*****@*****.**"], "title": "The man" } signed_resp = self.server.create_authn_response( ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=name_id, sign_assertion=True) print(signed_resp) assert signed_resp sresponse = response_from_string(signed_resp) # It's the assertions that are signed not the response per se assert len(sresponse.assertion) == 1 assertion = sresponse.assertion[0] # Since the reponse is created dynamically I don't know the signature # value. Just that there should be one assert assertion.signature.signature_value.text != "" def test_signed_response_1(self): signed_resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=True, sign_assertion=True, ) sresponse = response_from_string(signed_resp) valid = self.server.sec.verify_signature( signed_resp, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:protocol:Response', node_id=sresponse.id, id_attr="") assert valid valid = self.server.sec.verify_signature( signed_resp, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:assertion:Assertion', node_id=sresponse.assertion[0].id, id_attr="") assert valid self.verify_assertion(sresponse.assertion) def test_signed_response_2(self): signed_resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=True, sign_assertion=False, ) sresponse = response_from_string(signed_resp) valid = self.server.sec.verify_signature( signed_resp, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:protocol:Response', node_id=sresponse.id, id_attr="") assert valid assert sresponse.assertion[0].signature == None def test_signed_response_3(self): signed_resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=True, ) sresponse = response_from_string(signed_resp) assert sresponse.signature == None valid = self.server.sec.verify_signature( signed_resp, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:assertion:Assertion', node_id=sresponse.assertion[0].id, id_attr="") assert valid self.verify_assertion(sresponse.assertion) def test_encrypted_signed_response_1(self): cert_str, cert_key_str = generate_cert() signed_resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=True, sign_assertion=True, encrypt_assertion=False, encrypt_assertion_self_contained=True, pefim=True, encrypt_cert_advice=cert_str, ) sresponse = response_from_string(signed_resp) valid = self.server.sec.verify_signature( signed_resp, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:protocol:Response', node_id=sresponse.id, id_attr="") assert valid valid = self.server.sec.verify_signature( signed_resp, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:assertion:Assertion', node_id=sresponse.assertion[0].id, id_attr="") assert valid _, key_file = make_temp(cert_key_str, decode=False) decr_text = self.server.sec.decrypt(signed_resp, key_file) resp = samlp.response_from_string(decr_text) assert resp.assertion[0].advice.encrypted_assertion[ 0].extension_elements assertion = extension_elements_to_elements( resp.assertion[0].advice.encrypted_assertion[0].extension_elements, [saml, samlp]) self.verify_assertion(assertion) #PEFIM never signs assertions. assert assertion[0].signature is None #valid = self.server.sec.verify_signature(decr_text, # self.server.config.cert_file, # node_name='urn:oasis:names:tc:SAML:2.0:assertion:Assertion', # node_id=assertion[0].id, # id_attr="") assert valid def test_encrypted_signed_response_2(self): cert_str, cert_key_str = generate_cert() signed_resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=True, sign_assertion=False, encrypt_assertion=True, encrypt_assertion_self_contained=True, ) sresponse = response_from_string(signed_resp) valid = self.server.sec.verify_signature( signed_resp, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:protocol:Response', node_id=sresponse.id, id_attr="") assert valid decr_text_old = copy.deepcopy("%s" % signed_resp) decr_text = self.server.sec.decrypt( signed_resp, self.client.config.encryption_keypairs[0]["key_file"]) assert decr_text == decr_text_old decr_text = self.server.sec.decrypt( signed_resp, self.client.config.encryption_keypairs[1]["key_file"]) assert decr_text != decr_text_old resp = samlp.response_from_string(decr_text) resp.assertion = extension_elements_to_elements( resp.encrypted_assertion[0].extension_elements, [saml, samlp]) assert resp.assertion[0].signature == None self.verify_assertion(resp.assertion) def test_encrypted_signed_response_3(self): cert_str, cert_key_str = generate_cert() signed_resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=True, sign_assertion=True, encrypt_assertion=True, encrypt_assertion_self_contained=False, encrypt_cert_assertion=cert_str, ) sresponse = response_from_string(signed_resp) valid = self.server.sec.verify_signature( signed_resp, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:protocol:Response', node_id=sresponse.id, id_attr="") assert valid _, key_file = make_temp(cert_key_str, decode=False) decr_text = self.server.sec.decrypt(signed_resp, key_file) resp = samlp.response_from_string(decr_text) resp.assertion = extension_elements_to_elements( resp.encrypted_assertion[0].extension_elements, [saml, samlp]) valid = self.server.sec.verify_signature( decr_text, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:assertion:Assertion', node_id=resp.assertion[0].id, id_attr="") assert valid self.verify_assertion(resp.assertion) assert 'xmlns:encas' not in decr_text def test_encrypted_signed_response_4(self): cert_str, cert_key_str = generate_cert() signed_resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=True, sign_assertion=True, encrypt_assertion=True, encrypt_assertion_self_contained=True, pefim=True, encrypt_cert_advice=cert_str, ) sresponse = response_from_string(signed_resp) valid = self.server.sec.verify_signature( signed_resp, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:protocol:Response', node_id=sresponse.id, id_attr="") assert valid decr_text = self.server.sec.decrypt( signed_resp, self.client.config.encryption_keypairs[1]["key_file"]) resp = samlp.response_from_string(decr_text) resp.assertion = extension_elements_to_elements( resp.encrypted_assertion[0].extension_elements, [saml, samlp]) valid = self.server.sec.verify_signature( decr_text, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:assertion:Assertion', node_id=resp.assertion[0].id, id_attr="") assert valid _, key_file = make_temp(cert_key_str, decode=False) decr_text = self.server.sec.decrypt(decr_text, key_file) resp = samlp.response_from_string(decr_text) assertion = extension_elements_to_elements( resp.encrypted_assertion[0].extension_elements, [saml, samlp]) assertion = \ extension_elements_to_elements(assertion[0].advice.encrypted_assertion[0].extension_elements,[saml, samlp]) self.verify_assertion(assertion) #PEFIM never signs assertion in advice assert assertion[0].signature is None #valid = self.server.sec.verify_signature(decr_text, # self.server.config.cert_file, # node_name='urn:oasis:names:tc:SAML:2.0:assertion:Assertion', # node_id=assertion[0].id, # id_attr="") assert valid def test_encrypted_response_1(self): cert_str_advice, cert_key_str_advice = generate_cert() _resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=False, encrypt_assertion_self_contained=True, pefim=True, encrypt_cert_advice=cert_str_advice, ) _resp = "%s" % _resp sresponse = response_from_string(_resp) assert sresponse.signature is None _, key_file = make_temp(cert_key_str_advice, decode=False) decr_text = self.server.sec.decrypt(_resp, key_file) resp = samlp.response_from_string(decr_text) self.verify_advice_assertion(resp, decr_text) def test_encrypted_response_2(self): cert_str_advice, cert_key_str_advice = generate_cert() _resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=True, encrypt_assertion_self_contained=True, pefim=True, encrypt_cert_advice=cert_str_advice, ) sresponse = response_from_string(_resp) assert sresponse.signature is None decr_text_1 = self.server.sec.decrypt( _resp, self.client.config.encryption_keypairs[1]["key_file"]) _, key_file = make_temp(cert_key_str_advice, decode=False) decr_text_2 = self.server.sec.decrypt(decr_text_1, key_file) resp = samlp.response_from_string(decr_text_2) resp.assertion = extension_elements_to_elements( resp.encrypted_assertion[0].extension_elements, [saml, samlp]) self.verify_advice_assertion(resp, decr_text_2) def test_encrypted_response_3(self): cert_str_assertion, cert_key_str_assertion = generate_cert() _resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=True, encrypt_assertion_self_contained=True, encrypted_advice_attributes=False, encrypt_cert_assertion=cert_str_assertion) sresponse = response_from_string(_resp) assert sresponse.signature is None _, key_file = make_temp(cert_key_str_assertion, decode=False) decr_text = self.server.sec.decrypt(_resp, key_file) resp = samlp.response_from_string(decr_text) assert resp.encrypted_assertion[0].extension_elements assertion = extension_elements_to_elements( resp.encrypted_assertion[0].extension_elements, [saml, samlp]) self.verify_encrypted_assertion(assertion, decr_text) def test_encrypted_response_4(self): _resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=True, encrypt_assertion_self_contained=True, encrypted_advice_attributes=False, ) sresponse = response_from_string(_resp) assert sresponse.signature is None decr_text = self.server.sec.decrypt( _resp, self.client.config.encryption_keypairs[1]["key_file"]) resp = samlp.response_from_string(decr_text) assert resp.encrypted_assertion[0].extension_elements assertion = extension_elements_to_elements( resp.encrypted_assertion[0].extension_elements, [saml, samlp]) self.verify_encrypted_assertion(assertion, decr_text) def test_encrypted_response_5(self): _resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=False, encrypt_assertion_self_contained=True, pefim=True) _resp = "%s" % _resp sresponse = response_from_string(_resp) assert sresponse.signature is None decr_text = self.server.sec.decrypt( _resp, self.client.config.encryption_keypairs[1]["key_file"]) resp = samlp.response_from_string(decr_text) self.verify_advice_assertion(resp, decr_text) def test_encrypted_response_6(self): _server = Server("idp_conf_verify_cert") cert_str_advice, cert_key_str_advice = generate_cert() cert_str_assertion, cert_key_str_assertion = generate_cert() _resp = _server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=True, encrypt_assertion_self_contained=True, pefim=True, encrypt_cert_advice=cert_str_advice, encrypt_cert_assertion=cert_str_assertion) sresponse = response_from_string(_resp) assert sresponse.signature is None _, key_file = make_temp(cert_key_str_assertion, decode=False) decr_text_1 = _server.sec.decrypt(_resp, key_file) _, key_file = make_temp(cert_key_str_advice, decode=False) decr_text_2 = _server.sec.decrypt(decr_text_1, key_file) resp = samlp.response_from_string(decr_text_2) resp.assertion = extension_elements_to_elements( resp.encrypted_assertion[0].extension_elements, [saml, samlp]) self.verify_advice_assertion(resp, decr_text_2) def test_encrypted_response_7(self): _resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=True, encrypt_assertion_self_contained=True, pefim=True) sresponse = response_from_string(_resp) assert sresponse.signature is None decr_text_1 = self.server.sec.decrypt( _resp, self.client.config.encryption_keypairs[1]["key_file"]) decr_text_2 = self.server.sec.decrypt( decr_text_1, self.client.config.encryption_keypairs[1]["key_file"]) resp = samlp.response_from_string(decr_text_2) resp.assertion = extension_elements_to_elements( resp.encrypted_assertion[0].extension_elements, [saml, samlp]) self.verify_advice_assertion(resp, decr_text_2) def test_encrypted_response_8(self): try: _resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=True, encrypt_assertion_self_contained=True, pefim=True, encrypt_cert_advice="whatever", encrypt_cert_assertion="whatever") assert False, "Must throw an exception" except EncryptError as ex: pass except Exception as ex: assert False, "Wrong exception!" try: _resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=False, encrypt_assertion_self_contained=True, pefim=True, encrypt_cert_advice="whatever", ) assert False, "Must throw an exception" except EncryptError as ex: pass except Exception as ex: assert False, "Wrong exception!" try: _resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=True, encrypt_assertion_self_contained=True, encrypted_advice_attributes=False, encrypt_cert_assertion="whatever") assert False, "Must throw an exception" except EncryptError as ex: pass except Exception as ex: assert False, "Wrong exception!" _server = Server("idp_conf_verify_cert") try: _resp = _server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=True, encrypt_assertion_self_contained=True, pefim=True, encrypt_cert_advice="whatever", encrypt_cert_assertion="whatever") assert False, "Must throw an exception" except CertificateError as ex: pass except Exception as ex: assert False, "Wrong exception!" try: _resp = _server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=False, encrypt_assertion_self_contained=True, pefim=True, encrypt_cert_advice="whatever", ) assert False, "Must throw an exception" except CertificateError as ex: pass except Exception as ex: assert False, "Wrong exception!" try: _resp = _server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=True, encrypt_assertion_self_contained=True, encrypted_advice_attributes=False, encrypt_cert_assertion="whatever") assert False, "Must throw an exception" except CertificateError as ex: pass except Exception as ex: assert False, "Wrong exception!" def test_encrypted_response_9(self): _server = Server("idp_conf_sp_no_encrypt") _resp = _server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=True, encrypt_assertion_self_contained=True, pefim=True, ) self.verify_assertion(_resp.assertion.advice.assertion) _resp = _server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=False, encrypt_assertion_self_contained=True, pefim=True) self.verify_assertion(_resp.assertion.advice.assertion) _resp = _server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=True, encrypt_assertion_self_contained=True, encrypted_advice_attributes=False, ) self.verify_assertion([_resp.assertion]) def test_slo_http_post(self): soon = time_util.in_a_while(days=1) sinfo = { "name_id": nid, "issuer": "urn:mace:example.com:saml:roland:idp", "not_on_or_after": soon, "user": { "givenName": "Leo", "surName": "Laport", } } self.client.users.add_information_about_person(sinfo) req_id, logout_request = self.client.create_logout_request( destination="http://localhost:8088/slop", name_id=nid, issuer_entity_id="urn:mace:example.com:saml:roland:idp", reason="I'm tired of this") intermed = base64.b64encode(str(logout_request).encode('utf-8')) #saml_soap = make_soap_enveloped_saml_thingy(logout_request) request = self.server.parse_logout_request(intermed, BINDING_HTTP_POST) assert request def test_slo_soap(self): soon = time_util.in_a_while(days=1) sinfo = { "name_id": nid, "issuer": "urn:mace:example.com:saml:roland:idp", "not_on_or_after": soon, "user": { "givenName": "Leo", "surName": "Laport", } } sp = client.Saml2Client(config_file="server_conf") sp.users.add_information_about_person(sinfo) req_id, logout_request = sp.create_logout_request( name_id=nid, destination="http://localhost:8088/slo", issuer_entity_id="urn:mace:example.com:saml:roland:idp", reason="I'm tired of this") #_ = s_utils.deflate_and_base64_encode("%s" % (logout_request,)) saml_soap = make_soap_enveloped_saml_thingy(logout_request) self.server.ident.close() with closing(Server("idp_soap_conf")) as idp: request = idp.parse_logout_request(saml_soap) idp.ident.close() assert request
def setup_class(self): self.server = Server("idp_conf") conf = config.SPConfig() conf.load_file("server_conf") self.client = Saml2Client(conf)
class TestSignedResponse(): def setup_class(self): self.server = Server("idp_conf") sign_alg = Mock() sign_alg.return_value = ds.SIG_RSA_SHA512 digest_alg = Mock() digest_alg.return_value = ds.DIGEST_SHA512 self.restet_default = ds.DefaultSignature ds.DefaultSignature = MagicMock() ds.DefaultSignature().get_sign_alg = sign_alg ds.DefaultSignature().get_digest_alg = digest_alg conf = config.SPConfig() conf.load_file("server_conf") self.client = client.Saml2Client(conf) self.name_id = self.server.ident.transient_nameid( "urn:mace:example.com:saml:roland:sp", "id12") self.ava = {"givenName": ["Derek"], "surName": ["Jeter"], "mail": ["*****@*****.**"], "title": "The man"} def teardown_class(self): ds.DefaultSignature = self.restet_default self.server.close() def verify_assertion(self, assertion): assert assertion assert assertion[0].attribute_statement ava = ava = get_ava(assertion[0]) assert ava ==\ {'mail': ['*****@*****.**'], 'givenName': ['Derek'], 'surName': ['Jeter'], 'title': ['The man']} def test_signed_response(self): print(ds.DefaultSignature().get_digest_alg()) name_id = self.server.ident.transient_nameid( "urn:mace:example.com:saml:roland:sp", "id12") ava = {"givenName": ["Derek"], "surName": ["Jeter"], "mail": ["*****@*****.**"], "title": "The man"} signed_resp = self.server.create_authn_response( ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=name_id, sign_assertion=True ) print(signed_resp) assert signed_resp sresponse = response_from_string(signed_resp) assert ds.SIG_RSA_SHA512 in str(sresponse), "Not correctly signed!" assert ds.DIGEST_SHA512 in str(sresponse), "Not correctly signed!" def test_signed_response_1(self): signed_resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=True, sign_assertion=True, ) sresponse = response_from_string(signed_resp) assert ds.SIG_RSA_SHA512 in str(sresponse), "Not correctly signed!" assert ds.DIGEST_SHA512 in str(sresponse), "Not correctly signed!" valid = self.server.sec.verify_signature(signed_resp, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:protocol:Response', node_id=sresponse.id, id_attr="") assert valid assert ds.SIG_RSA_SHA512 in str(sresponse.assertion[0]), "Not correctly signed!" assert ds.DIGEST_SHA512 in str(sresponse.assertion[0]), "Not correctly signed!" valid = self.server.sec.verify_signature(signed_resp, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:assertion:Assertion', node_id=sresponse.assertion[0].id, id_attr="") assert valid self.verify_assertion(sresponse.assertion) def test_signed_response_2(self): signed_resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=True, sign_assertion=True, sign_alg=ds.SIG_RSA_SHA256, digest_alg=ds.DIGEST_SHA256 ) sresponse = response_from_string(signed_resp) assert ds.SIG_RSA_SHA256 in str(sresponse), "Not correctly signed!" assert ds.DIGEST_SHA256 in str(sresponse), "Not correctly signed!" valid = self.server.sec.verify_signature(signed_resp, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:protocol:Response', node_id=sresponse.id, id_attr="") assert valid assert ds.SIG_RSA_SHA256 in str(sresponse.assertion[0]), "Not correctly signed!" assert ds.DIGEST_SHA256 in str(sresponse.assertion[0]), "Not correctly signed!" valid = self.server.sec.verify_signature(signed_resp, self.server.config.cert_file, node_name='urn:oasis:names:tc:SAML:2.0:assertion:Assertion', node_id=sresponse.assertion[0].id, id_attr="") assert valid self.verify_assertion(sresponse.assertion)
class TestClient: def setup_class(self): self.server = Server("idp_conf") conf = config.SPConfig() conf.load_file("server_conf") self.client = Saml2Client(conf) def test_create_attribute_query1(self): req = self.client.create_attribute_query( "https://idp.example.com/idp/", "E8042FB4-4D5B-48C3-8E14-8EDD852790DD", nameid_format=saml.NAMEID_FORMAT_PERSISTENT, id="id1") reqstr = "%s" % req.to_string() assert req.destination == "https://idp.example.com/idp/" assert req.id == "id1" assert req.version == "2.0" subject = req.subject name_id = subject.name_id assert name_id.format == saml.NAMEID_FORMAT_PERSISTENT assert name_id.text == "E8042FB4-4D5B-48C3-8E14-8EDD852790DD" issuer = req.issuer assert issuer.text == "urn:mace:example.com:saml:roland:sp" attrq = samlp.attribute_query_from_string(reqstr) print attrq.keyswv() assert _leq(attrq.keyswv(), [ 'destination', 'subject', 'issue_instant', 'version', 'id', 'issuer' ]) assert attrq.destination == req.destination assert attrq.id == req.id assert attrq.version == req.version assert attrq.issuer.text == issuer.text assert attrq.issue_instant == req.issue_instant assert attrq.subject.name_id.format == name_id.format assert attrq.subject.name_id.text == name_id.text def test_create_attribute_query2(self): req = self.client.create_attribute_query( "https://idp.example.com/idp/", "E8042FB4-4D5B-48C3-8E14-8EDD852790DD", attribute={ ("urn:oid:2.5.4.42", "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", "givenName"): None, ("urn:oid:2.5.4.4", "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", "surname"): None, ("urn:oid:1.2.840.113549.1.9.1", "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"): None, }, nameid_format=saml.NAMEID_FORMAT_PERSISTENT, id="id1") print req.to_string() assert req.destination == "https://idp.example.com/idp/" assert req.id == "id1" assert req.version == "2.0" subject = req.subject name_id = subject.name_id assert name_id.format == saml.NAMEID_FORMAT_PERSISTENT assert name_id.text == "E8042FB4-4D5B-48C3-8E14-8EDD852790DD" assert len(req.attribute) == 3 # one is givenName seen = [] for attribute in req.attribute: if attribute.name == "urn:oid:2.5.4.42": assert attribute.name_format == saml.NAME_FORMAT_URI assert attribute.friendly_name == "givenName" seen.append("givenName") elif attribute.name == "urn:oid:2.5.4.4": assert attribute.name_format == saml.NAME_FORMAT_URI assert attribute.friendly_name == "surname" seen.append("surname") elif attribute.name == "urn:oid:1.2.840.113549.1.9.1": assert attribute.name_format == saml.NAME_FORMAT_URI if getattr(attribute, "friendly_name"): assert False seen.append("email") assert _leq(seen, ["givenName", "surname", "email"]) def test_create_attribute_query_3(self): req = self.client.create_attribute_query( "https://aai-demo-idp.switch.ch/idp/shibboleth", "_e7b68a04488f715cda642fbdd90099f5", nameid_format=saml.NAMEID_FORMAT_TRANSIENT, id="id1") assert isinstance(req, samlp.AttributeQuery) assert req.destination == "https://aai-demo-idp.switch.ch/idp/shibboleth" assert req.id == "id1" assert req.version == "2.0" assert req.issue_instant assert req.issuer.text == "urn:mace:example.com:saml:roland:sp" nameid = req.subject.name_id assert nameid.format == saml.NAMEID_FORMAT_TRANSIENT assert nameid.text == "_e7b68a04488f715cda642fbdd90099f5" # def test_idp_entry(self): # idp_entry = self.client.idp_entry(name="Umeå Universitet", # location="https://idp.umu.se/") # # assert idp_entry.name == "Umeå Universitet" # assert idp_entry.loc == "https://idp.umu.se/" # # def test_scope(self): # entity_id = "urn:mace:example.com:saml:roland:idp" # locs = self.client.metadata.single_sign_on_services(entity_id) # scope = self.client.scoping_from_metadata(entity_id, locs) # # assert scope.idp_list # assert len(scope.idp_list.idp_entry) == 1 # idp_entry = scope.idp_list.idp_entry[0] # assert idp_entry.name == 'Exempel AB' # assert idp_entry.loc == ['http://*****:*****@nyy.mlb.com"], "title": ["The man"] } nameid_policy = samlp.NameIDPolicy( allow_create="false", format=saml.NAMEID_FORMAT_PERSISTENT) resp = self.server.create_authn_response( identity=ava, in_response_to="id1", destination="http://lingon.catalogix.se:8087/", sp_entity_id="urn:mace:example.com:saml:roland:sp", name_id_policy=nameid_policy, userid="*****@*****.**", authn=AUTHN) resp_str = "%s" % resp resp_str = base64.encodestring(resp_str) authn_response = self.client.authn_request_response( {"SAMLResponse": resp_str}, {"id1": "http://foo.example.com/service"}) assert authn_response is not None assert authn_response.issuer() == IDP assert authn_response.response.assertion[0].issuer.text == IDP session_info = authn_response.session_info() print session_info assert session_info["ava"] == { 'mail': ['*****@*****.**'], 'givenName': ['Derek'], 'sn': ['Jeter'], 'title': ["The man"] } assert session_info["issuer"] == IDP assert session_info["came_from"] == "http://foo.example.com/service" response = samlp.response_from_string(authn_response.xmlstr) assert response.destination == "http://lingon.catalogix.se:8087/" # One person in the cache assert len(self.client.users.subjects()) == 1 subject_id = self.client.users.subjects()[0] print "||||", self.client.users.get_info_from(subject_id, IDP) # The information I have about the subject comes from one source assert self.client.users.issuers_of_info(subject_id) == [IDP] # --- authenticate another person ava = { "givenName": ["Alfonson"], "surName": ["Soriano"], "mail": ["*****@*****.**"], "title": ["outfielder"] } resp_str = "%s" % self.server.create_authn_response( identity=ava, in_response_to="id2", destination="http://lingon.catalogix.se:8087/", sp_entity_id="urn:mace:example.com:saml:roland:sp", name_id_policy=nameid_policy, userid="*****@*****.**", authn=AUTHN) resp_str = base64.encodestring(resp_str) self.client.authn_request_response( {"SAMLResponse": resp_str}, {"id2": "http://foo.example.com/service"}) # Two persons in the cache assert len(self.client.users.subjects()) == 2 issuers = [ self.client.users.issuers_of_info(s) for s in self.client.users.subjects() ] # The information I have about the subjects comes from the same source print issuers assert issuers == [[IDP], [IDP]] def test_init_values(self): entityid = self.client.config.entityid print entityid assert entityid == "urn:mace:example.com:saml:roland:sp" print self.client.metadata.with_descriptor("idpsso") location = self.client._sso_location() print location assert location == 'http://localhost:8088/sso' service_url = self.client.service_url() print service_url assert service_url == "http://lingon.catalogix.se:8087/" my_name = self.client._my_name() print my_name assert my_name == "urn:mace:example.com:saml:roland:sp"
def __init__(self, config_file=""): Server.__init__(self, config_file)
class IdPHandlerViewMixin: """ Contains some methods used by multiple views """ error_view = import_string( getattr(settings, 'SAML_IDP_ERROR_VIEW_CLASS', 'djangosaml2idp.error_views.SamlIDPErrorView')) def handle_error(self, request, **kwargs): # Log the exception and the statuscode logger.error(kwargs) return self.error_view.as_view()(request, **kwargs) def dispatch(self, request, *args, **kwargs): """ Construct IDP server with config from settings dict """ conf = IdPConfig() try: conf.load(copy.deepcopy(settings.SAML_IDP_CONFIG)) self.IDP = Server(config=conf) except Exception as e: return self.handle_error(request, exception=e) return super().dispatch(request, *args, **kwargs) def get_sp_config(self, sp_entity_id): """ Get a dict with the configuration for a SP according to the SAML_IDP_SPCONFIG settings. Raises an exception if no SP matching the given entity id can be found. """ d = {'id': sp_entity_id} try: d['config'] = settings.SAML_IDP_SPCONFIG[sp_entity_id] except KeyError: raise ImproperlyConfigured( _("No config for SP {} defined in SAML_IDP_SPCONFIG").format( sp_entity_id)) return d def get_processor(self, sp_entity_id: str, processor_class_path: str) -> BaseProcessor: """ Instantiate user-specified processor or default to an all-access base processor. Raises an exception if the processor class can not be found or initialized. """ if processor_class_path: try: processor_cls = import_string(processor_class_path) except ImportError as e: msg = _("Failed to import processor class {}").format( processor_class_path) logger.error(msg, exc_info=True) raise ImproperlyConfigured(msg) from e else: processor_cls = BaseProcessor try: processor_instance = processor_cls(sp_entity_id) except Exception as e: msg = _("Failed to instantiate processor: {} - {}").format( processor_cls, e) logger.error(msg, exc_info=True) raise return processor_instance def verify_request_signature(self, req_info): """ Signature verification for authn request signature_check is at saml2.sigver.SecurityContext.correctly_signed_authn_request """ # TODO: Add unit tests for this if not req_info.signature_check(req_info.xmlstr): raise ValueError(_("Message signature verification failure")) def check_access(self, processor, request): """ Check if user has access to the service of this SP. Raises a PermissionDenied exception if not. """ if not processor.has_access(request): raise PermissionDenied( _("You do not have access to this resource")) def get_authn(self, req_info=None): req_authn_context = req_info.message.requested_authn_context if req_info else PASSWORD broker = AuthnBroker() broker.add(authn_context_class_ref(req_authn_context), "") return broker.get_authn_by_accr(req_authn_context) def build_authn_response(self, user, authn, resp_args, processor: BaseProcessor, sp_config: dict): """ pysaml2 server.Server.create_authn_response wrapper """ sp_config['name_id_format'] = resp_args.get( 'name_id_policy').format or NAMEID_FORMAT_UNSPECIFIED idp_name_id_format_list = self.IDP.config.getattr( "name_id_format", "idp") or [NAMEID_FORMAT_UNSPECIFIED] if sp_config['name_id_format'] not in idp_name_id_format_list: raise ImproperlyConfigured( _('SP requested a name_id_format that is not supported in the IDP' )) user_id = processor.get_user_id(user, sp_config, self.IDP.config) name_id = NameID(format=sp_config['name_id_format'], sp_name_qualifier=sp_config['id'], text=user_id) authn_resp = self.IDP.create_authn_response( authn=authn, identity=processor.create_identity( user, sp_config.get('attribute_mapping')), name_id=name_id, userid=user_id, sp_entity_id=sp_config['id'], # Signing sign_response=sp_config['config'].get("sign_response") or self.IDP.config.getattr("sign_response", "idp") or False, sign_assertion=sp_config['config'].get("sign_assertion") or self.IDP.config.getattr("sign_assertion", "idp") or False, sign_alg=sp_config['config'].get("signing_algorithm") or getattr( settings, "SAML_AUTHN_SIGN_ALG", xmldsig.SIG_RSA_SHA256), digest_alg=sp_config['config'].get("digest_algorithm") or getattr( settings, "SAML_AUTHN_DIGEST_ALG", xmldsig.DIGEST_SHA256), # Encryption encrypt_assertion=sp_config['config'].get('encrypt_saml_responses') or getattr(settings, 'SAML_ENCRYPT_AUTHN_RESPONSE', False), encrypted_advice_attributes=sp_config['config'].get( 'encrypt_saml_responses') or getattr(settings, 'SAML_ENCRYPT_AUTHN_RESPONSE', False), **resp_args) return authn_resp def render_login_html_to_string(self, template_name, context=None, request=None, using=None): """ Render the html response for the login action. Can be using a custom html template if set on the view. """ default_login_template_name = 'djangosaml2idp/login.html' custom_login_template_name = getattr(self, 'login_html_template') if custom_login_template_name: try: template = get_template(custom_login_template_name, using=using) except (TemplateDoesNotExist, TemplateSyntaxError) as e: logger.error( 'Specified template {} cannot be used due to: {}. Falling back to default login template' .format(custom_login_template_name, str(e))) template = get_template(default_login_template_name, using=using) else: template = get_template(default_login_template_name, using=using) return template.render(context, request) def create_html_response(self, request, binding, authn_resp, destination, relay_state): """ Login form for SSO """ if binding == BINDING_HTTP_POST: context = { "acs_url": destination, "saml_response": base64.b64encode(str(authn_resp).encode()).decode(), "relay_state": relay_state, } html_response = { "data": self.render_login_html_to_string(context=context, request=request), "type": "POST", } else: http_args = self.IDP.apply_binding(binding=binding, msg_str=authn_resp, destination=destination, relay_state=relay_state, response=True) logger.debug('http args are: %s' % http_args) html_response = { "data": http_args['headers'][0][1], "type": "REDIRECT", } return html_response def render_response(self, request, html_response, processor: BaseProcessor = None): """ Return either a response as redirect to MultiFactorView or as html with self-submitting form to log in. """ if not processor: # In case of SLO, where processor isn't relevant if html_response['type'] == 'POST': return HttpResponse(html_response['data']) else: return HttpResponseRedirect(html_response['data']) request.session['saml_data'] = html_response if processor.enable_multifactor(request.user): logger.debug("Redirecting to process_multi_factor") return HttpResponseRedirect( reverse('djangosaml2idp:saml_multi_factor')) # No multifactor logger.debug("Performing SAML redirect") if html_response['type'] == 'POST': return HttpResponse(html_response['data']) else: return HttpResponseRedirect(html_response['data'])
class IdpServer(object): ticket = {} responses = {} challenges = {} _binding_mapping = { 'http-redirect': BINDING_HTTP_REDIRECT, 'http-post': BINDING_HTTP_POST } _endpoint_types = ['single_sign_on_service', 'single_logout_service'] _spid_levels = SPID_LEVELS _spid_attributes = SPID_ATTRIBUTES.copy() # digitalAddress => PEC challenges_timeout = CHALLENGES_TIMEOUT def __init__(self, app, conf=None, *args, **kwargs): """ :param app: Flask instance :param conf: config.Config instance :param args: :param kwargs: """ # bind Flask app self.app = app self.user_manager = JsonUserManager() # setup self._config = conf or config.params self.app.secret_key = 'sosecret' handler = RotatingFileHandler( 'spid.log', maxBytes=500000, backupCount=1 ) self.app.logger.addHandler(handler) self._prepare_server() @property def _mode(self): return 'https' if self._config.https else 'http' def _setup_app_routes(self): """ Setup Flask routes """ # Setup SSO and SLO endpoints endpoints = self._config.endpoints if endpoints: for ep_type in self._endpoint_types: _url = endpoints.get(ep_type) if _url: for _binding in self._binding_mapping.keys(): self.app.add_url_rule( _url, ep_type, getattr(self, ep_type), methods=['GET', 'POST'] ) self.app.add_url_rule('/', 'index', self.index, methods=['GET']) self.app.add_url_rule( '/login', 'login', self.login, methods=['POST', 'GET'] ) # Endpoint for user add action self.app.add_url_rule( '/users', 'users', self.users, methods=['GET', 'POST'] ) self.app.add_url_rule( '/continue-response', 'continue_response', self.continue_response, methods=['POST'] ) self.app.add_url_rule( '/metadata', 'metadata', self.metadata, methods=['POST', 'GET'] ) def _prepare_server(self): """ Setup server """ # FIXME: remove after pysaml2 drop from saml2.config import Config as Saml2Config self.idp_config = Saml2Config() self.idp_config.load(cnf=self._config.pysaml2compat) self.server = Server(config=self.idp_config) # self._setup_app_routes() def _verify_spid(self, level, verify=False, **kwargs): """ :param level: integer, SPID level :param verify: boolean, if True verify spid extra challenge (otp etc.), if False prepare the challenge :param kwargs: dictionary, extra arguments """ level = self._spid_levels.index(level) self.app.logger.debug('spid level {} - verifica ({})'.format(level, verify)) if verify: # Verify the challenge if level == 2: # spid level 2 otp = kwargs.get('data').get('otp') key = kwargs.get('key') if key and key not in self.challenges or not otp: return False total_seconds = ( datetime.now() - self.challenges[key][1] ).total_seconds() # Check that opt value is equal and not expired _is_expired = total_seconds > self.challenges_timeout if self.challenges[key][0] != otp or _is_expired: del self.challenges[key] return False return True else: # Prepare the challenge if level == 2: # spid level 2 # very simple otp implementation, # while opt is a random 6 digits string # with a lifetime setup in the server instance key = kwargs.get('key') otp = ''.join(random.choice(string.digits) for _ in range(6)) self.challenges[key] = [otp, datetime.now()] extra_challenge = '<span>Otp ({})</span>'\ '<input type="text" name="otp" />'.format( otp ) else: extra_challenge = '' return extra_challenge def unpack_args(self, elems): """ Unpack arguments from request """ return dict([(k, v) for k, v in elems.items()]) def _raise_error(self, msg, extra=None): """ Raise some error using 'abort' function from Flask :param msg: string for error type :param extra: optional string for error details """ abort( Response( render_template( "error.html", **{'msg': msg, 'extra': extra or ""} ), 200 ) ) def _store_request(self, authnreq): """ Store authnrequest in a dictionary :param authnreq: authentication request string """ self.app.logger.debug('store_request: {}'.format(authnreq)) # FIXME: improve this from lxml.etree import tostring key = sha1(tostring(authnreq._xml_doc)).hexdigest() # store the AuthnRequest self.ticket[key] = authnreq return key def _handle_errors(self, xmlstr, errors=None): rendered_error_response = render_template( 'spid_error.html', **{ 'lines': xmlstr.splitlines(), 'errors': errors } ) return rendered_error_response def _parse_message(self, action): """ Parse an AuthnRequest or a LogoutRequest :param action: type of request """ method = request.method if method == 'GET': return self._handle_http_redirect(action) elif method == 'POST': return self._handle_http_post(action) else: self._raise_error( 'I metodi consentiti sono' ' GET (Http-Redirect) o POST (Http-Post)' ) def _handle_http_redirect(self, action): # FIXME: replace the following code with a call to a function # in the parser.py module after metadata refactoring. # The IdpServer class should not # be responsible of request parsing, or know anything # about request parsing *at all*. saml_msg = self.unpack_args(request.args) request_data = HTTPRedirectRequestParser(saml_msg).parse() deserializer = get_http_redirect_request_deserializer( request_data, action, self.server.metadata) saml_tree = deserializer.deserialize() certs = self._get_certificates_by_issuer(saml_tree.issuer.text) for cert in certs: HTTPRedirectSignatureVerifier(cert, request_data).verify() return SPIDRequest(request_data, saml_tree) def _handle_http_post(self, action): # FIXME: replace the following code with a call to a function # in the parser.py module after metadata refactoring. # The IdpServer class should not # be responsible of request parsing, or know anything # about request parsing *at all*. saml_msg = self.unpack_args(request.form) request_data = HTTPPostRequestParser(saml_msg).parse() deserializer = get_http_post_request_deserializer( request_data, action, self.server.metadata) saml_tree = deserializer.deserialize() certs = self._get_certificates_by_issuer(saml_tree.issuer.text) for cert in certs: HTTPPostSignatureVerifier(cert, request_data).verify() return SPIDRequest(request_data, saml_tree) def _get_certificates_by_issuer(self, issuer): try: return self.server.metadata.certs(issuer, 'any', 'signing') except KeyError: self._raise_error( 'entity ID {} non registrato, impossibile ricavare'\ ' un certificato valido.'.format(issuer) ) def single_sign_on_service(self): """ Process Http-Redirect or Http-POST request :param request: Flask request object """ try: spid_request = self._parse_message(action='login') self.app.logger.debug( 'AuthnRequest: \n{}'.format(spid_request.data.saml_request) ) # Perform login key = self._store_request(spid_request.saml_tree) session['request_key'] = key session['relay_state'] = spid_request.data.relay_state or '' return redirect(url_for('login')) except RequestParserError as err: self._raise_error(err.args[0]) except SignatureVerificationError as err: self._raise_error(err.args[0]) except UnknownEntityIDError as err: self._raise_error(err.args[0]) except DeserializationError as err: return self._handle_errors(err.initial_data, err.details) @property def _spid_main_fields(self): """ Returns a list of spid main attributes """ return self._spid_attributes['primary'].keys() @property def _spid_secondary_fields(self): """ Returns a list of spid secondary attributes """ return self._spid_attributes['secondary'].keys() @property def _all_attributes(self): _dct = self._spid_attributes['primary'].copy() _dct.update(self._spid_attributes['secondary']) return _dct def users(self): """ Add user endpoint """ spid_main_fields = self._spid_main_fields spid_secondary_fields = self._spid_secondary_fields rendered_form = render_template( "users.html", **{ 'action': '/users', 'primary_attributes': spid_main_fields, 'secondary_attributes': spid_secondary_fields, 'users': self.user_manager.all(), 'sp_list': self.server.metadata.service_providers() } ) if request.method == 'GET': return rendered_form, 200 elif request.method == 'POST': username = request.form.get('username') password = request.form.get('password') sp = request.form.get('service_provider') if not sp: sp = None if not username or not password: abort(400) extra = {} for spid_field in spid_main_fields: spid_value = request.form.get(spid_field) if spid_value: extra[spid_field] = spid_value for spid_field in spid_secondary_fields: spid_value = request.form.get(spid_field) if spid_value: extra[spid_field] = spid_value self.user_manager.add(username, password, sp, extra) return redirect(url_for('users')) def index(self): rendered_form = render_template( "home.html", **{ 'sp_list': [ { "name": sp, "spId": sp } for sp in self.server.metadata.service_providers() ], } ) return rendered_form, 200 def get_destination(self, req, sp_id): destination = None acs_index = getattr(req, 'assertion_consumer_service_index', None) protocol_binding = getattr(req, 'protocol_binding', None) if acs_index is not None: acss = self.server.metadata.assertion_consumer_service( sp_id, protocol_binding ) for acs in acss: if acs.get('index') == acs_index: destination = acs.get('location') break self.app.logger.debug( 'AssertionConsumerServiceIndex Location: {}'.format( destination ) ) if destination is None: destination = getattr(req, 'assertion_consumer_service_url', None) if destination is not None and protocol_binding is not None: self.app.logger.debug( 'AssertionConsumerServiceURL: {}'.format( destination ) ) return destination def login(self): """ Login endpoint (verify user credentials) """ key = from_session('request_key') relay_state = from_session('relay_state') self.app.logger.debug('Request key: {}'.format(key)) if key and key in self.ticket: authn_request = self.ticket[key] sp_id = authn_request.issuer.text destination = self.get_destination(authn_request, sp_id) authn_context = authn_request.requested_authn_context spid_level = authn_context.authn_context_class_ref.text if request.method == 'GET': # inject extra data in form login based on spid level extra_challenge = self._verify_spid(level=spid_level, **{'key': key}) rendered_form = render_template( 'login.html', **{ 'action': url_for('login'), 'request_key': key, 'relay_state': relay_state, 'extra_challenge': extra_challenge } ) return rendered_form, 200 if 'confirm' in request.form: # verify optional challenge based on spid level verified = self._verify_spid( level=spid_level, verify=True, **{ 'key': key, 'data': request.form } ) if verified: # verify user credentials user_id, user = self.user_manager.get( request.form['username'], request.form['password'], sp_id ) if user_id is not None: # setup response identity = user['attrs'].copy() self.app.logger.debug( 'Unfiltered data: {}'.format(identity) ) atcs_idx = getattr(authn_request, 'attribute_consuming_service_index', None) self.app.logger.debug( 'AttributeConsumingServiceIndex: {}'.format( atcs_idx ) ) if atcs_idx: # TODO: Remove this pysaml2 dependency attrs = self.server.wants(sp_id, atcs_idx) required = [el.get('name') for el in attrs.get('required')] optional = [el.get('name') for el in attrs.get('optional')] else: required = [] optional = [] for k, v in identity.items(): if k in self._spid_main_fields: _type = self._spid_attributes['primary'][k] else: _type = self._spid_attributes['secondary'][k] identity[k] = (_type, v) _identity = {} for _key in required: _identity[_key] = identity[_key] for _key in optional: _identity[_key] = identity[_key] self.app.logger.debug( 'Filtered data: {}'.format(_identity) ) response_xmlstr = create_response( { 'response': { 'attrs': { 'in_response_to': authn_request.id, 'destination': destination } }, 'issuer': { 'attrs': { 'name_qualifier': self._config.entity_id, }, 'text': self._config.entity_id }, 'name_id': { 'attrs': { 'name_qualifier': self._config.entity_id, } }, 'subject_confirmation_data': { 'attrs': { 'recipient': destination } }, 'audience': { 'text': sp_id }, 'authn_context_class_ref': { 'text': spid_level } }, { 'status_code': STATUS_SUCCESS }, _identity.copy() ).to_xml() response = sign_http_post( response_xmlstr, self._config.idp_key, self._config.idp_certificate, ) self.app.logger.debug( 'Response: \n{}'.format(response) ) rendered_template = render_template( 'form_http_post.html', **{ 'action': destination, 'relay_state': relay_state, 'message': response, 'message_type': 'SAMLResponse' } ) self.responses[key] = rendered_template # Setup confirmation page data rendered_response = render_template( 'confirm.html', **{ 'destination_service': sp_id, 'lines': escape( response_xmlstr.decode('ascii') ).splitlines(), 'attrs': _identity.keys(), 'action': '/continue-response', 'request_key': key } ) return rendered_response, 200 elif 'delete' in request.form: error_info = get_spid_error( AUTH_NO_CONSENT ) response = create_error_response( { 'response': { 'attrs': { 'in_response_to': authn_request.id, 'destination': destination } }, 'issuer': { 'attrs': { 'name_qualifier': self._config.entity_id, }, 'text': self._config.entity_id }, }, { 'status_code': error_info[0], 'status_message': error_info[1] } ).to_xml() self.app.logger.debug( 'Error response: \n{}'.format(response) ) response = sign_http_post( response, self._config.idp_key, self._config.idp_certificate, ) del self.ticket[key] rendered_template = render_template( 'form_http_post.html', **{ 'action': destination, 'relay_state': relay_state, 'message': response, 'message_type': 'SAMLResponse' } ) return rendered_template, 200 return render_template('403.html'), 403 def continue_response(self): key = request.form['request_key'] relay_state = from_session('relay_state') if key and key in self.responses: _response = self.responses.pop(key) auth_req = self.ticket.pop(key) if 'confirm' in request.form: return _response, 200 elif 'delete' in request.form: destination = self.get_destination( auth_req, auth_req.issuer.text ) error_info = get_spid_error( AUTH_NO_CONSENT ) response = create_error_response( { 'response': { 'attrs': { 'in_response_to': auth_req.id, 'destination': destination } }, 'issuer': { 'attrs': { 'name_qualifier': 'something', }, 'text': self._config.entity_id }, }, { 'status_code': error_info[0], 'status_message': error_info[1] } ).to_xml() self.app.logger.debug( 'Error response: \n{}'.format(response) ) response = sign_http_post( response, self._config.idp_key, self._config.idp_certificate, ) rendered_template = render_template( 'form_http_post.html', **{ 'action': destination, 'relay_state': relay_state, 'message': response, 'message_type': 'SAMLResponse' } ) return rendered_template, 200 return render_template('403.html'), 403 def _sp_single_logout_service(self, issuer_name): _slo = None for binding in [BINDING_HTTP_POST, BINDING_HTTP_REDIRECT]: try: _slo = self.server.metadata.single_logout_service( issuer_name, binding=binding, typ='spsso' ) except UnsupportedBinding: pass return _slo def single_logout_service(self): """ SLO endpoint """ self.app.logger.debug("req: '%s'", request) try: spid_request = self._parse_message(action='logout') issuer_name = spid_request.saml_tree.issuer.text # TODO: retrieve the following data from some custom structure _slo = self._sp_single_logout_service(issuer_name) if _slo is None: self._raise_error( 'Impossibile trovare un servizio di'\ ' Single Logout per il service provider {}'.format( issuer_name ) ) response_binding = _slo[0].get('binding') self.app.logger.debug( 'Response binding: \n{}'.format( response_binding ) ) destination = _slo[0].get('location') response = create_logout_response( { 'logout_response': { 'attrs': { 'in_response_to': spid_request.saml_tree.id, 'destination': destination } }, 'issuer': { 'attrs': { 'name_qualifier': 'something', }, 'text': self._config.entity_id } }, { 'status_code': STATUS_SUCCESS } ).to_xml() relay_state = spid_request.data.relay_state or '' if response_binding == BINDING_HTTP_POST: response = sign_http_post( response, self._config.idp_key, self._config.idp_certificate, message=True, assertion=False ) rendered_template = render_template( 'form_http_post.html', **{ 'action': destination, 'relay_state': relay_state, 'message': response, 'message_type': 'SAMLResponse' } ) return rendered_template, 200 elif response_binding == BINDING_HTTP_REDIRECT: query_string = sign_http_redirect( response, self._config.idp_key, relay_state, ) location = '{}?{}'.format(destination, query_string) if location: return redirect(location) except RequestParserError as err: self._raise_error(err.args[0]) except SignatureVerificationError as err: self._raise_error(err.args[0]) except UnknownEntityIDError as err: self._raise_error(err.args[0]) except DeserializationError as err: return self._handle_errors(err.initial_data, err.details) abort(400) def metadata(self): cert_file = self.server.config.cert_file with open(cert_file, 'r') as fp: cert = fp.readlines()[1:-1] cert = ''.join(cert) endpoints = getattr(self.server.config, '_idp_endpoints') sso = endpoints.get('single_sign_on_service') slo = endpoints.get('single_logout_service') sso = [Sso(*_sso) for _sso in sso] slo = [Slo(*_slo) for _slo in slo] metadata = create_idp_metadata( entity_id=self.server.config.entityid, want_authn_requests_signed='true', keys=[Key(use='signing', value=cert)], single_sign_on_services=sso, single_logout_services=slo ).to_xml() return Response(metadata, mimetype='text/xml') @property def _wsgiconf(self): _cnf = { 'host': self._config.host, 'port': self._config.port, 'debug': self._config.debug, } if self._config.https: key = self._config.https_key_file_path cert = self._config.https_certificate_file_path _cnf['ssl_context'] = (cert, key,) return _cnf def start(self): """ Start the server instance """ self.app.run( **self._wsgiconf )
class TestClient: def setup_class(self): self.server = Server("idp_conf") conf = config.SPConfig() conf.load_file("server_conf") self.client = Saml2Client(conf) def test_create_attribute_query1(self): req = self.client.create_attribute_query( "id1", "E8042FB4-4D5B-48C3-8E14-8EDD852790DD", "https://idp.example.com/idp/", nameid_format=saml.NAMEID_FORMAT_PERSISTENT) reqstr = "%s" % req.to_string() assert req.destination == "https://idp.example.com/idp/" assert req.id == "id1" assert req.version == "2.0" subject = req.subject name_id = subject.name_id assert name_id.format == saml.NAMEID_FORMAT_PERSISTENT assert name_id.text == "E8042FB4-4D5B-48C3-8E14-8EDD852790DD" issuer = req.issuer assert issuer.text == "urn:mace:example.com:saml:roland:sp" attrq = samlp.attribute_query_from_string(reqstr) print attrq.keyswv() assert _leq(attrq.keyswv(), [ 'destination', 'subject', 'issue_instant', 'version', 'id', 'issuer' ]) assert attrq.destination == req.destination assert attrq.id == req.id assert attrq.version == req.version assert attrq.issuer.text == issuer.text assert attrq.issue_instant == req.issue_instant assert attrq.subject.name_id.format == name_id.format assert attrq.subject.name_id.text == name_id.text def test_create_attribute_query2(self): req = self.client.create_attribute_query( "id1", "E8042FB4-4D5B-48C3-8E14-8EDD852790DD", "https://idp.example.com/idp/", attribute={ ("urn:oid:2.5.4.42", "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", "givenName"): None, ("urn:oid:2.5.4.4", "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", "surname"): None, ("urn:oid:1.2.840.113549.1.9.1", "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"): None, }, nameid_format=saml.NAMEID_FORMAT_PERSISTENT) print req.to_string() assert req.destination == "https://idp.example.com/idp/" assert req.id == "id1" assert req.version == "2.0" subject = req.subject name_id = subject.name_id assert name_id.format == saml.NAMEID_FORMAT_PERSISTENT assert name_id.text == "E8042FB4-4D5B-48C3-8E14-8EDD852790DD" assert len(req.attribute) == 3 # one is givenName seen = [] for attribute in req.attribute: if attribute.name == "urn:oid:2.5.4.42": assert attribute.name_format == saml.NAME_FORMAT_URI assert attribute.friendly_name == "givenName" seen.append("givenName") elif attribute.name == "urn:oid:2.5.4.4": assert attribute.name_format == saml.NAME_FORMAT_URI assert attribute.friendly_name == "surname" seen.append("surname") elif attribute.name == "urn:oid:1.2.840.113549.1.9.1": assert attribute.name_format == saml.NAME_FORMAT_URI if getattr(attribute, "friendly_name"): assert False seen.append("email") assert set(seen) == set(["givenName", "surname", "email"]) def test_create_attribute_query_3(self): req = self.client.create_attribute_query( "id1", "_e7b68a04488f715cda642fbdd90099f5", "https://aai-demo-idp.switch.ch/idp/shibboleth", nameid_format=saml.NAMEID_FORMAT_TRANSIENT) assert isinstance(req, samlp.AttributeQuery) assert req.destination == "https://aai-demo-idp.switch.ch/idp/shibboleth" assert req.id == "id1" assert req.version == "2.0" assert req.issue_instant assert req.issuer.text == "urn:mace:example.com:saml:roland:sp" nameid = req.subject.name_id assert nameid.format == saml.NAMEID_FORMAT_TRANSIENT assert nameid.text == "_e7b68a04488f715cda642fbdd90099f5" def test_attribute_query(self): req = self.client.attribute_query( "_e7b68a04488f715cda642fbdd90099f5", "https://aai-demo-idp.switch.ch/idp/shibboleth", nameid_format=saml.NAMEID_FORMAT_TRANSIENT) # since no one is answering on the other end assert req is None # def test_idp_entry(self): # idp_entry = self.client.idp_entry(name="Umeå Universitet", # location="https://idp.umu.se/") # # assert idp_entry.name == "Umeå Universitet" # assert idp_entry.loc == "https://idp.umu.se/" # # def test_scope(self): # entity_id = "urn:mace:example.com:saml:roland:idp" # locs = self.client.metadata.single_sign_on_services(entity_id) # scope = self.client.scoping_from_metadata(entity_id, locs) # # assert scope.idp_list # assert len(scope.idp_list.idp_entry) == 1 # idp_entry = scope.idp_list.idp_entry[0] # assert idp_entry.name == 'Exempel AB' # assert idp_entry.loc == ['http://*****:*****@nyy.mlb.com"] } resp_str = "\n".join( self.server.authn_response( identity=ava, in_response_to="id1", destination="http://lingon.catalogix.se:8087/", sp_entity_id="urn:mace:example.com:saml:roland:sp", name_id_policy=samlp.NameIDPolicy( format=saml.NAMEID_FORMAT_PERSISTENT), userid="*****@*****.**")) resp_str = base64.encodestring(resp_str) authn_response = self.client.response( {"SAMLResponse": resp_str}, {"id1": "http://foo.example.com/service"}) assert authn_response is not None assert authn_response.issuer() == IDP assert authn_response.response.assertion[0].issuer.text == IDP session_info = authn_response.session_info() print session_info assert session_info["ava"] == { 'mail': ['*****@*****.**'], 'givenName': ['Derek'], 'sn': ['Jeter'] } assert session_info["issuer"] == IDP assert session_info["came_from"] == "http://foo.example.com/service" response = samlp.response_from_string(authn_response.xmlstr) assert response.destination == "http://lingon.catalogix.se:8087/" # One person in the cache assert len(self.client.users.subjects()) == 1 subject_id = self.client.users.subjects()[0] print "||||", self.client.users.get_info_from(subject_id, IDP) # The information I have about the subject comes from one source assert self.client.users.issuers_of_info(subject_id) == [IDP] # --- authenticate another person ava = { "givenName": ["Alfonson"], "surname": ["Soriano"], "mail": ["*****@*****.**"] } resp_str = "\n".join( self.server.authn_response( identity=ava, in_response_to="id2", destination="http://lingon.catalogix.se:8087/", sp_entity_id="urn:mace:example.com:saml:roland:sp", name_id_policy=samlp.NameIDPolicy( format=saml.NAMEID_FORMAT_PERSISTENT), userid="*****@*****.**")) resp_str = base64.encodestring(resp_str) self.client.response({"SAMLResponse": resp_str}, {"id2": "http://foo.example.com/service"}) # Two persons in the cache assert len(self.client.users.subjects()) == 2 issuers = [ self.client.users.issuers_of_info(s) for s in self.client.users.subjects() ] # The information I have about the subjects comes from the same source print issuers assert issuers == [[IDP], [IDP]] def test_init_values(self): entityid = self.client.config.entityid print entityid assert entityid == "urn:mace:example.com:saml:roland:sp" print self.client.config.idp print self.client.config.metadata.idps() print self.client.config.idps() location = self.client._sso_location() print location assert location == 'http://*****:*****@example.com" } } self.client.users.add_information_about_person(session_info) entity_ids = self.client.users.issuers_of_info("123456") assert entity_ids == ["urn:mace:example.com:saml:roland:idp"] resp = self.client.global_logout("123456", "Tired", in_a_while(minutes=5)) print resp assert resp assert resp[0] # a session_id assert resp[1] == '200 OK' assert resp[2] == [('Content-type', 'text/html')] assert resp[3][0] == '<head>' assert resp[3][1] == '<title>SAML 2.0 POST</title>' session_info = self.client.state[resp[0]] print session_info assert session_info["entity_id"] == entity_ids[0] assert session_info["subject_id"] == "123456" assert session_info["reason"] == "Tired" assert session_info["operation"] == "SLO" assert session_info["entity_ids"] == entity_ids assert session_info["sign"] == False def test_logout_2(self): """ one IdP/AA with BINDING_SOAP, can't actually send something""" conf = config.SPConfig() conf.load_file("server2_conf") client = Saml2Client(conf) # information about the user from an IdP session_info = { "name_id": "123456", "issuer": "urn:mace:example.com:saml:roland:idp", "not_on_or_after": in_a_while(minutes=15), "ava": { "givenName": "Anders", "surName": "Andersson", "mail": "*****@*****.**" } } client.users.add_information_about_person(session_info) entity_ids = self.client.users.issuers_of_info("123456") assert entity_ids == ["urn:mace:example.com:saml:roland:idp"] destinations = client.config.single_logout_services( entity_ids[0], BINDING_SOAP) print destinations assert destinations == ['http://*****:*****@example.com" } } client.users.add_information_about_person(session_info_authn) session_info_aa = { "name_id": "123456", "issuer": "urn:mace:example.com:saml:roland:aa", "not_on_or_after": in_a_while(minutes=15), "ava": { "eduPersonEntitlement": "Foobar", } } client.users.add_information_about_person(session_info_aa) entity_ids = client.users.issuers_of_info("123456") assert _leq(entity_ids, [ "urn:mace:example.com:saml:roland:idp", "urn:mace:example.com:saml:roland:aa" ]) resp = client.global_logout("123456", "Tired", in_a_while(minutes=5)) print resp assert resp assert resp[0] # a session_id assert resp[1] == '200 OK' # HTTP POST assert resp[2] == [('Content-type', 'text/html')] assert resp[3][0] == '<head>' assert resp[3][1] == '<title>SAML 2.0 POST</title>' state_info = client.state[resp[0]] print state_info assert state_info["entity_id"] == entity_ids[0] assert state_info["subject_id"] == "123456" assert state_info["reason"] == "Tired" assert state_info["operation"] == "SLO" assert state_info["entity_ids"] == entity_ids assert state_info["sign"] == False def test_authz_decision_query(self): conf = config.SPConfig() conf.load_file("server3_conf") client = Saml2Client(conf) AVA = { 'mail': u'*****@*****.**', 'eduPersonTargetedID': '95e9ae91dbe62d35198fbbd5e1fb0976', 'displayName': u'Roland Hedberg', 'uid': 'http://roland.hedberg.myopenid.com/' } sp_entity_id = "sp_entity_id" in_response_to = "1234" consumer_url = "http://example.com/consumer" name_id = saml.NameID(saml.NAMEID_FORMAT_TRANSIENT, text="name_id") policy = Policy() ava = Assertion(AVA) assertion = ava.construct(sp_entity_id, in_response_to, consumer_url, name_id, conf.attribute_converters, policy, issuer=client._issuer()) adq = client.authz_decision_query_using_assertion( "entity_id", assertion, "read", "http://example.com/text") assert adq print adq assert adq.keyswv() != [] assert adq.destination == "entity_id" assert adq.resource == "http://example.com/text" assert adq.action[0].text == "read" def test_request_to_discovery_service(self): disc_url = "http://example.com/saml2/idp/disc" url = self.client.discovery_service_request_url(disc_url) print url assert url == "http://example.com/saml2/idp/disc?entityID=urn%3Amace%3Aexample.com%3Asaml%3Aroland%3Asp" url = self.client.discovery_service_request_url( disc_url, return_url="http://example.org/saml2/sp/ds") print url assert url == "http://example.com/saml2/idp/disc?entityID=urn%3Amace%3Aexample.com%3Asaml%3Aroland%3Asp&return=http%3A%2F%2Fexample.org%2Fsaml2%2Fsp%2Fds" def test_get_idp_from_discovery_service(self): pdir = {"entityID": "http://example.org/saml2/idp/sso"} params = urllib.urlencode(pdir) redirect_url = "http://example.com/saml2/sp/disc?%s" % params entity_id = self.client.discovery_service_response(url=redirect_url) assert entity_id == "http://example.org/saml2/idp/sso" pdir = {"idpID": "http://example.org/saml2/idp/sso"} params = urllib.urlencode(pdir) redirect_url = "http://example.com/saml2/sp/disc?%s" % params entity_id = self.client.discovery_service_response( url=redirect_url, returnIDParam="idpID") assert entity_id == "http://example.org/saml2/idp/sso" def test_unsolicited_response(self): """ """ self.server = Server("idp_conf") conf = config.SPConfig() conf.load_file("server_conf") self.client = Saml2Client(conf) for subject in self.client.users.subjects(): self.client.users.remove_person(subject) IDP = "urn:mace:example.com:saml:roland:idp" ava = { "givenName": ["Derek"], "surname": ["Jeter"], "mail": ["*****@*****.**"] } resp_str = "\n".join( self.server.authn_response( identity=ava, in_response_to="id1", destination="http://lingon.catalogix.se:8087/", sp_entity_id="urn:mace:example.com:saml:roland:sp", name_id_policy=samlp.NameIDPolicy( format=saml.NAMEID_FORMAT_PERSISTENT), userid="*****@*****.**")) resp_str = base64.encodestring(resp_str) self.client.allow_unsolicited = True authn_response = self.client.response({"SAMLResponse": resp_str}, ()) assert authn_response is not None assert authn_response.issuer() == IDP assert authn_response.response.assertion[0].issuer.text == IDP session_info = authn_response.session_info() print session_info assert session_info["ava"] == { 'mail': ['*****@*****.**'], 'givenName': ['Derek'], 'sn': ['Jeter'] } assert session_info["issuer"] == IDP assert session_info["came_from"] == "" response = samlp.response_from_string(authn_response.xmlstr) assert response.destination == "http://lingon.catalogix.se:8087/" # One person in the cache assert len(self.client.users.subjects()) == 1
def test_encrypted_response_8(self): try: _resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=True, encrypt_assertion_self_contained=True, pefim=True, encrypt_cert_advice="whatever", encrypt_cert_assertion="whatever") assert False, "Must throw an exception" except EncryptError as ex: pass except Exception as ex: assert False, "Wrong exception!" try: _resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=False, encrypt_assertion_self_contained=True, pefim=True, encrypt_cert_advice="whatever", ) assert False, "Must throw an exception" except EncryptError as ex: pass except Exception as ex: assert False, "Wrong exception!" try: _resp = self.server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=True, encrypt_assertion_self_contained=True, encrypted_advice_attributes=False, encrypt_cert_assertion="whatever") assert False, "Must throw an exception" except EncryptError as ex: pass except Exception as ex: assert False, "Wrong exception!" _server = Server("idp_conf_verify_cert") try: _resp = _server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=True, encrypt_assertion_self_contained=True, pefim=True, encrypt_cert_advice="whatever", encrypt_cert_assertion="whatever") assert False, "Must throw an exception" except CertificateError as ex: pass except Exception as ex: assert False, "Wrong exception!" try: _resp = _server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=False, encrypt_assertion_self_contained=True, pefim=True, encrypt_cert_advice="whatever", ) assert False, "Must throw an exception" except CertificateError as ex: pass except Exception as ex: assert False, "Wrong exception!" try: _resp = _server.create_authn_response( self.ava, "id12", # in_response_to "http://lingon.catalogix.se:8087/", # consumer_url "urn:mace:example.com:saml:roland:sp", # sp_entity_id name_id=self.name_id, sign_response=False, sign_assertion=False, encrypt_assertion=True, encrypt_assertion_self_contained=True, encrypted_advice_attributes=False, encrypt_cert_assertion="whatever") assert False, "Must throw an exception" except CertificateError as ex: pass except Exception as ex: assert False, "Wrong exception!"
def setup_class(self): self.sp = make_plugin("rem", saml_conf="server_conf") self.server = Server(config_file="idp_conf")
from saml2.pack import http_redirect_message from saml2.sigver import verify_redirect_signature from saml2.sigver import import_rsa_key_from_file from saml2.sigver import SIG_RSA_SHA1 from saml2.server import Server from saml2 import BINDING_HTTP_REDIRECT from saml2.client import Saml2Client from saml2.config import SPConfig from urlparse import parse_qs from pathutils import dotname __author__ = 'rolandh' idp = Server(config_file=dotname("idp_all_conf")) conf = SPConfig() conf.load_file(dotname("servera_conf")) sp = Saml2Client(conf) def test(): srvs = sp.metadata.single_sign_on_service(idp.config.entityid, BINDING_HTTP_REDIRECT) destination = srvs[0]["location"] req_id, req = sp.create_authn_request(destination, id="id1") try: key = sp.sec.key except AttributeError:
class TestClient: def setup_class(self): self.server = Server("idp_conf") conf = config.SPConfig() conf.load_file("server_conf") self.client = Saml2Client(conf) def test_create_attribute_query1(self): req_id, req = self.client.create_attribute_query( "https://idp.example.com/idp/", "E8042FB4-4D5B-48C3-8E14-8EDD852790DD", format=saml.NAMEID_FORMAT_PERSISTENT, message_id="id1") reqstr = "%s" % req.to_string() assert req.destination == "https://idp.example.com/idp/" assert req.id == "id1" assert req.version == "2.0" subject = req.subject name_id = subject.name_id assert name_id.format == saml.NAMEID_FORMAT_PERSISTENT assert name_id.text == "E8042FB4-4D5B-48C3-8E14-8EDD852790DD" issuer = req.issuer assert issuer.text == "urn:mace:example.com:saml:roland:sp" attrq = samlp.attribute_query_from_string(reqstr) print attrq.keyswv() assert _leq(attrq.keyswv(), [ 'destination', 'subject', 'issue_instant', 'version', 'id', 'issuer' ]) assert attrq.destination == req.destination assert attrq.id == req.id assert attrq.version == req.version assert attrq.issuer.text == issuer.text assert attrq.issue_instant == req.issue_instant assert attrq.subject.name_id.format == name_id.format assert attrq.subject.name_id.text == name_id.text def test_create_attribute_query2(self): req_id, req = self.client.create_attribute_query( "https://idp.example.com/idp/", "E8042FB4-4D5B-48C3-8E14-8EDD852790DD", attribute={ ("urn:oid:2.5.4.42", "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", "givenName"): None, ("urn:oid:2.5.4.4", "urn:oasis:names:tc:SAML:2.0:attrname-format:uri", "surname"): None, ("urn:oid:1.2.840.113549.1.9.1", "urn:oasis:names:tc:SAML:2.0:attrname-format:uri"): None, }, format=saml.NAMEID_FORMAT_PERSISTENT, message_id="id1") print req.to_string() assert req.destination == "https://idp.example.com/idp/" assert req.id == "id1" assert req.version == "2.0" subject = req.subject name_id = subject.name_id assert name_id.format == saml.NAMEID_FORMAT_PERSISTENT assert name_id.text == "E8042FB4-4D5B-48C3-8E14-8EDD852790DD" assert len(req.attribute) == 3 # one is givenName seen = [] for attribute in req.attribute: if attribute.name == "urn:oid:2.5.4.42": assert attribute.name_format == saml.NAME_FORMAT_URI assert attribute.friendly_name == "givenName" seen.append("givenName") elif attribute.name == "urn:oid:2.5.4.4": assert attribute.name_format == saml.NAME_FORMAT_URI assert attribute.friendly_name == "surname" seen.append("surname") elif attribute.name == "urn:oid:1.2.840.113549.1.9.1": assert attribute.name_format == saml.NAME_FORMAT_URI if getattr(attribute, "friendly_name"): assert False seen.append("email") assert _leq(seen, ["givenName", "surname", "email"]) def test_create_attribute_query_3(self): req_id, req = self.client.create_attribute_query( "https://aai-demo-idp.switch.ch/idp/shibboleth", "_e7b68a04488f715cda642fbdd90099f5", format=saml.NAMEID_FORMAT_TRANSIENT, message_id="id1") assert isinstance(req, samlp.AttributeQuery) assert req.destination == "https://aai-demo-idp.switch" \ ".ch/idp/shibboleth" assert req.id == "id1" assert req.version == "2.0" assert req.issue_instant assert req.issuer.text == "urn:mace:example.com:saml:roland:sp" nameid = req.subject.name_id assert nameid.format == saml.NAMEID_FORMAT_TRANSIENT assert nameid.text == "_e7b68a04488f715cda642fbdd90099f5" def test_create_auth_request_0(self): ar_str = "%s" % self.client.create_authn_request( "http://www.example.com/sso", message_id="id1")[1] ar = samlp.authn_request_from_string(ar_str) print ar assert ar.assertion_consumer_service_url == ("http://lingon.catalogix" ".se:8087/") assert ar.destination == "http://www.example.com/sso" assert ar.protocol_binding == BINDING_HTTP_POST assert ar.version == "2.0" assert ar.provider_name == "urn:mace:example.com:saml:roland:sp" assert ar.issuer.text == "urn:mace:example.com:saml:roland:sp" nid_policy = ar.name_id_policy assert nid_policy.allow_create == "false" assert nid_policy.format == saml.NAMEID_FORMAT_TRANSIENT def test_create_auth_request_vo(self): assert self.client.config.vorg.keys() == [ "urn:mace:example.com:it:tek" ] ar_str = "%s" % self.client.create_authn_request( "http://www.example.com/sso", "urn:mace:example.com:it:tek", # vo nameid_format=NAMEID_FORMAT_PERSISTENT, message_id="666")[1] ar = samlp.authn_request_from_string(ar_str) print ar assert ar.id == "666" assert ar.assertion_consumer_service_url == "http://lingon.catalogix" \ ".se:8087/" assert ar.destination == "http://www.example.com/sso" assert ar.protocol_binding == BINDING_HTTP_POST assert ar.version == "2.0" assert ar.provider_name == "urn:mace:example.com:saml:roland:sp" assert ar.issuer.text == "urn:mace:example.com:saml:roland:sp" nid_policy = ar.name_id_policy assert nid_policy.allow_create == "false" assert nid_policy.format == saml.NAMEID_FORMAT_PERSISTENT assert nid_policy.sp_name_qualifier == "urn:mace:example.com:it:tek" def test_sign_auth_request_0(self): #print self.client.config req_id, areq = self.client.create_authn_request( "http://www.example.com/sso", sign=True, message_id="id1") ar_str = "%s" % areq ar = samlp.authn_request_from_string(ar_str) assert ar assert ar.signature assert ar.signature.signature_value signed_info = ar.signature.signed_info #print signed_info assert len(signed_info.reference) == 1 assert signed_info.reference[0].uri == "#id1" assert signed_info.reference[0].digest_value print "------------------------------------------------" try: assert self.client.sec.correctly_signed_authn_request( ar_str, self.client.config.xmlsec_binary, self.client.config.metadata) except Exception: # missing certificate self.client.sec.verify_signature(ar_str, node_name=class_name(ar)) def test_response(self): IDP = "urn:mace:example.com:saml:roland:idp" ava = { "givenName": ["Derek"], "surName": ["Jeter"], "mail": ["*****@*****.**"], "title": ["The man"] } nameid_policy = samlp.NameIDPolicy( allow_create="false", format=saml.NAMEID_FORMAT_PERSISTENT) resp = self.server.create_authn_response( identity=ava, in_response_to="id1", destination="http://lingon.catalogix.se:8087/", sp_entity_id="urn:mace:example.com:saml:roland:sp", name_id_policy=nameid_policy, userid="*****@*****.**", authn=AUTHN) resp_str = "%s" % resp resp_str = base64.encodestring(resp_str) authn_response = self.client.parse_authn_request_response( resp_str, BINDING_HTTP_POST, {"id1": "http://foo.example.com/service"}) assert authn_response is not None assert authn_response.issuer() == IDP assert authn_response.response.assertion[0].issuer.text == IDP session_info = authn_response.session_info() print session_info assert session_info["ava"] == { 'mail': ['*****@*****.**'], 'givenName': ['Derek'], 'sn': ['Jeter'], 'title': ["The man"] } assert session_info["issuer"] == IDP assert session_info["came_from"] == "http://foo.example.com/service" response = samlp.response_from_string(authn_response.xmlstr) assert response.destination == "http://lingon.catalogix.se:8087/" # One person in the cache assert len(self.client.users.subjects()) == 1 subject_id = self.client.users.subjects()[0] print "||||", self.client.users.get_info_from(subject_id, IDP) # The information I have about the subject comes from one source assert self.client.users.issuers_of_info(subject_id) == [IDP] # --- authenticate another person ava = { "givenName": ["Alfonson"], "surName": ["Soriano"], "mail": ["*****@*****.**"], "title": ["outfielder"] } resp_str = "%s" % self.server.create_authn_response( identity=ava, in_response_to="id2", destination="http://lingon.catalogix.se:8087/", sp_entity_id="urn:mace:example.com:saml:roland:sp", name_id_policy=nameid_policy, userid="*****@*****.**", authn=AUTHN) resp_str = base64.encodestring(resp_str) self.client.parse_authn_request_response( resp_str, BINDING_HTTP_POST, {"id2": "http://foo.example.com/service"}) # Two persons in the cache assert len(self.client.users.subjects()) == 2 issuers = [ self.client.users.issuers_of_info(s) for s in self.client.users.subjects() ] # The information I have about the subjects comes from the same source print issuers assert issuers == [[IDP], [IDP]] def test_init_values(self): entityid = self.client.config.entityid print entityid assert entityid == "urn:mace:example.com:saml:roland:sp" print self.client.metadata.with_descriptor("idpsso") location = self.client._sso_location() print location assert location == 'http://localhost:8088/sso' my_name = self.client._my_name() print my_name assert my_name == "urn:mace:example.com:saml:roland:sp" def test_sign_then_encrypt_assertion(self): # Begin with the IdPs side _sec = self.server.sec assertion = s_utils.assertion_factory( subject=factory(saml.Subject, text="_aaa", name_id=factory( saml.NameID, format=saml.NAMEID_FORMAT_TRANSIENT)), attribute_statement=do_attribute_statement({ ("", "", "surName"): ("Jeter", ""), ("", "", "givenName"): ("Derek", ""), }), issuer=self.server._issuer(), ) assertion.signature = sigver.pre_signature_part( assertion.id, _sec.my_cert, 1) sigass = _sec.sign_statement(assertion, class_name(assertion), key_file="pki/mykey.pem", node_id=assertion.id) # Create an Assertion instance from the signed assertion _ass = saml.assertion_from_string(sigass) response = sigver.response_factory( in_response_to="_012345", destination="https:#www.example.com", status=s_utils.success_status_factory(), issuer=self.server._issuer(), assertion=_ass) enctext = _sec.crypto.encrypt_assertion(response, _sec.cert_file, pre_encryption_part()) seresp = samlp.response_from_string(enctext) # Now over to the client side _csec = self.client.sec if seresp.encrypted_assertion: decr_text = _csec.decrypt(enctext) seresp = samlp.response_from_string(decr_text) resp_ass = [] sign_cert_file = "pki/mycert.pem" for enc_ass in seresp.encrypted_assertion: assers = extension_elements_to_elements( enc_ass.extension_elements, [saml, samlp]) for ass in assers: if ass.signature: if not _csec.verify_signature( "%s" % ass, sign_cert_file, node_name=class_name(ass)): continue resp_ass.append(ass) seresp.assertion = resp_ass seresp.encrypted_assertion = None #print _sresp assert seresp.assertion def test_sign_then_encrypt_assertion2(self): # Begin with the IdPs side _sec = self.server.sec nameid_policy = samlp.NameIDPolicy( allow_create="false", format=saml.NAMEID_FORMAT_PERSISTENT) asser = Assertion({"givenName": "Derek", "surName": "Jeter"}) assertion = asser.construct( self.client.config.entityid, "_012345", "http://lingon.catalogix.se:8087/", factory(saml.NameID, format=saml.NAMEID_FORMAT_TRANSIENT), policy=self.server.config.getattr("policy", "idp"), issuer=self.server._issuer(), attrconvs=self.server.config.attribute_converters, authn_class=INTERNETPROTOCOLPASSWORD, authn_auth="http://www.example.com/login") assertion.signature = sigver.pre_signature_part( assertion.id, _sec.my_cert, 1) sigass = _sec.sign_statement(assertion, class_name(assertion), key_file=self.client.sec.key_file, node_id=assertion.id) sigass = rm_xmltag(sigass) response = sigver.response_factory( in_response_to="_012345", destination="https://www.example.com", status=s_utils.success_status_factory(), issuer=self.server._issuer(), encrypted_assertion=EncryptedAssertion()) xmldoc = "%s" % response # strangely enough I get different tags if I run this test separately # or as part of a bunch of tests. xmldoc = add_subelement(xmldoc, "EncryptedAssertion", sigass) enctext = _sec.crypto.encrypt_assertion(xmldoc, _sec.cert_file, pre_encryption_part()) #seresp = samlp.response_from_string(enctext) resp_str = base64.encodestring(enctext) # Now over to the client side resp = self.client.parse_authn_request_response( resp_str, BINDING_HTTP_POST, {"_012345": "http://foo.example.com/service"}) #assert resp.encrypted_assertion == [] assert resp.assertion assert resp.ava == {'givenName': ['Derek'], 'sn': ['Jeter']}
def __init__(self, config_file="", config=None, cache=None): Server.__init__(self, config_file, config, cache)
def test_basic_flow(): sp = Saml2Client(config_file="servera_conf") with closing(Server(config_file="idp_all_conf")) as idp: # -------- @IDP ------------- relay_state = "FOO" # -- dummy request --- orig_req = AuthnRequest(issuer=sp._issuer(), name_id_policy=NameIDPolicy( allow_create="true", format=NAMEID_FORMAT_TRANSIENT)) # == Create an AuthnRequest response name_id = idp.ident.transient_nameid("id12", sp.config.entityid) binding, destination = idp.pick_binding("assertion_consumer_service", entity_id=sp.config.entityid) resp = idp.create_authn_response( { "eduPersonEntitlement": "Short stop", "surName": "Jeter", "givenName": "Derek", "mail": "*****@*****.**", "title": "The man" }, "id-123456789", destination, sp.config.entityid, name_id=name_id, authn=AUTHN) hinfo = idp.apply_binding(binding, "%s" % resp, destination, relay_state) # --------- @SP ------------- xmlstr = get_msg(hinfo, binding) aresp = sp.parse_authn_request_response(xmlstr, binding, {resp.in_response_to: "/"}) # == Look for assertion X asid = aresp.assertion.id binding, destination = sp.pick_binding("assertion_id_request_service", entity_id=idp.config.entityid) hinfo = sp.apply_binding(binding, asid, destination) # ---------- @IDP ------------ aid = get_msg(hinfo, binding, response=False) # == construct response resp = idp.create_assertion_id_request_response(aid) hinfo = idp.apply_binding(binding, "%s" % resp, None, "", response=True) # ----------- @SP ------------- xmlstr = get_msg(hinfo, binding, response=True) final = sp.parse_assertion_id_request_response(xmlstr, binding) print final.response assert isinstance(final.response, Assertion)
def test_artifact_flow(): #SP = 'urn:mace:example.com:saml:roland:sp' sp = Saml2Client(config_file="servera_conf") idp = Server(config_file="idp_all_conf") # original request binding, destination = sp.pick_binding("single_sign_on_service", entity_id=idp.config.entityid) relay_state = "RS0" req_id, req = sp.create_authn_request(destination, id="id1") artifact = sp.use_artifact(req, 1) binding, destination = sp.pick_binding("single_sign_on_service", [BINDING_HTTP_ARTIFACT], entity_id=idp.config.entityid) hinfo = sp.apply_binding(binding, artifact, destination, relay_state) # ========== @IDP ============ artifact2 = get_msg(hinfo, binding) assert artifact == artifact2 # The IDP now wants to replace the artifact with the real request destination = idp.artifact2destination(artifact2, "spsso") msg_id, msg = idp.create_artifact_resolve(artifact2, destination, sid()) hinfo = idp.use_soap(msg, destination, None, False) # ======== @SP ========== msg = get_msg(hinfo, BINDING_SOAP) ar = sp.parse_artifact_resolve(msg) assert ar.artifact.text == artifact # The SP picks the request out of the repository with the artifact as the key oreq = sp.artifact[ar.artifact.text] # Should be the same as req above # Returns the information over the existing SOAP connection so # no transport information needed msg = sp.create_artifact_response(ar, artifact) hinfo = sp.use_soap(msg, destination) # ========== @IDP ============ msg = get_msg(hinfo, BINDING_SOAP) # The IDP untangles the request from the artifact resolve response spreq = idp.parse_artifact_resolve_response(msg) # should be the same as req above assert spreq.id == req.id # That was one way, the Request from the SP # ---------------------------------------------# # Now for the other, the response from the IDP name_id = idp.ident.transient_nameid(sp.config.entityid, "derek") resp_args = idp.response_args(spreq, [BINDING_HTTP_POST]) response = idp.create_authn_response( { "eduPersonEntitlement": "Short stop", "surName": "Jeter", "givenName": "Derek", "mail": "*****@*****.**", "title": "The man" }, name_id=name_id, authn=AUTHN, **resp_args) print(response) # with the response in hand create an artifact artifact = idp.use_artifact(response, 1) binding, destination = sp.pick_binding("single_sign_on_service", [BINDING_HTTP_ARTIFACT], entity_id=idp.config.entityid) hinfo = sp.apply_binding(binding, artifact, destination, relay_state, response=True) # ========== SP ========= artifact3 = get_msg(hinfo, binding) assert artifact == artifact3 destination = sp.artifact2destination(artifact, "idpsso") # Got an artifact want to replace it with the real message msg_id, msg = sp.create_artifact_resolve(artifact, destination, sid()) print(msg) hinfo = sp.use_soap(msg, destination, None, False) # ======== IDP ========== msg = get_msg(hinfo, BINDING_SOAP) ar = idp.parse_artifact_resolve(msg) print(ar) assert ar.artifact.text == artifact3 # The IDP retrieves the response from the database using the artifact as the key #oreq = idp.artifact[ar.artifact.text] binding, destination = idp.pick_binding("artifact_resolution_service", entity_id=sp.config.entityid) resp = idp.create_artifact_response(ar, ar.artifact.text) hinfo = idp.use_soap(resp, destination) # ========== SP ============ msg = get_msg(hinfo, BINDING_SOAP) sp_resp = sp.parse_artifact_resolve_response(msg) assert sp_resp.id == response.id
class IdPHandlerViewMixin: """ Contains some methods used by multiple views """ error_view = import_string( getattr(settings, 'SAML_IDP_ERROR_VIEW_CLASS', 'djangosaml2idp.error_views.SamlIDPErrorView')) def handle_error(self, request, **kwargs): return self.error_view.as_view()(request, **kwargs) def dispatch(self, request, *args, **kwargs): """ Construct IDP server with config from settings dict """ conf = IdPConfig() try: conf.load(copy.deepcopy(settings.SAML_IDP_CONFIG)) self.IDP = Server(config=conf) except Exception as e: return self.handle_error(request, exception=e) return super().dispatch(request, *args, **kwargs) def set_sp(self, sp_entity_id): """ Saves SP info to instance variable Raises an exception if sp matching the given entity id cannot be found. """ self.sp = {'id': sp_entity_id} try: self.sp['config'] = settings.SAML_IDP_SPCONFIG[sp_entity_id] except KeyError: raise ImproperlyConfigured( "No config for SP {} defined in SAML_IDP_SPCONFIG".format( sp_entity_id)) def set_processor(self): """ Instantiate user-specified processor or default to an all-access base processor. Raises an exception if the configured processor class can not be found or initialized. """ processor_string = self.sp['config'].get('processor', None) if processor_string: try: self.processor = import_string(processor_string)(self.sp['id']) return except Exception as e: logger.error("Failed to instantiate processor: {} - {}".format( processor_string, e), exc_info=True) raise e self.processor = BaseProcessor(self.sp['id']) def get_authn(self, req_info=None): req_authn_context = req_info.message.requested_authn_context if req_info else PASSWORD broker = AuthnBroker() broker.add(authn_context_class_ref(req_authn_context), "") return broker.get_authn_by_accr(req_authn_context) def build_authn_response(self, user, authn, resp_args): name_id_formats = [resp_args.get('name_id_policy').format ] or self.IDP.config.getattr( "name_id_format", "idp") or [NAMEID_FORMAT_UNSPECIFIED] authn_resp = self.IDP.create_authn_response( authn=authn, identity=self.processor.create_identity(user, self.sp['config']), userid=self.processor.get_user_id(user, self.sp['config']), name_id=NameID(format=name_id_formats[0], sp_name_qualifier=self.sp['id'], text=self.processor.get_user_id( user, self.sp['config'])), sign_response=self.sp['config'].get("sign_response") or self.IDP.config.getattr("sign_response", "idp") or False, sign_assertion=self.sp['config'].get("sign_assertion") or self.IDP.config.getattr("sign_assertion", "idp") or False, **resp_args) return authn_resp def create_html_response(self, request, binding, authn_resp, destination, relay_state): """ Login form for SSO """ if binding == BINDING_HTTP_POST: context = { "acs_url": destination, "saml_response": base64.b64encode(authn_resp.encode()).decode(), "relay_state": relay_state, } html_response = render_to_string("djangosaml2idp/login.html", context=context, request=request) else: http_args = self.IDP.apply_binding(binding=binding, msg_str=authn_resp, destination=destination, relay_state=relay_state, response=True) logger.debug('http args are: %s' % http_args) html_response = http_args['data'] return html_response def render_response(self, request, html_response): """ Return either as redirect to MultiFactorView or as html with self-submitting form. """ if self.processor.enable_multifactor(request.user): # Store http_args in session for after multi factor is complete request.session['saml_data'] = html_response logger.debug("Redirecting to process_multi_factor") return HttpResponseRedirect(reverse('saml_multi_factor')) logger.debug("Performing SAML redirect") return HttpResponse(html_response)