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 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") authn = (AUTHN_PASSWORD, "http://www.example.com/login") 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", "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.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_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") # Explicitly allow unsigned responses for this test self.sp.saml_client.want_response_signed = False 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.encode('utf-8')) 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']}
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 == ""
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 = "%s" % self.server.create_authn_response(ava, "id1", "http://lingon.catalogix.se:8087/", "urn:mace:example.com:saml:roland:sp", trans_name_policy, "*****@*****.**") 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': ['*****@*****.**'], 'surName': ['Jeter']}
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.encode('utf-8')) 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']}
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_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_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 send_assertion(): logger.debug("Sending SAML assertion") idp_config = current_app.config['IDP_CONFIG'] idp_config_object = IdPConfig().load(copy.deepcopy(idp_config), metadata_construction=False) idp = Server(config=idp_config_object) name_id_format = 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient' name_id_policy = NameIDPolicy(format=name_id_format) args = { 'identity': {}, 'name_id': None, 'authn': { 'class_ref': 'https://refeds.org/profile/mfa', 'authn_auth': idp_config['entityid'] }, 'sign_response': True, 'sign_assertion': False, 'encrypted_advice_attributes': False, 'in_response_to': session['saml_authn_request_id'], 'sp_entity_id': session['saml_sp_entity_id'], 'name_id_policy': name_id_policy, 'binding': 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST', 'destination': session['saml_sp_acs'], 'sign_alg': 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256', 'digest_alg': 'http://www.w3.org/2001/04/xmlenc#sha256', } saml_response_string = idp.create_authn_response(**args) saml_response_string_encoded = saml_response_string.encode('utf-8') saml_response = b64encode(saml_response_string_encoded) saml_response_encoded = saml_response.decode('ascii') action = session['saml_sp_acs'] saml_relay_state = session['saml_relay_state'] rendered_template = render_template( 'saml_http_post_binding.html', action=action, saml_response_encoded=saml_response_encoded, saml_relay_state=saml_relay_state) # Kill the session session.clear() return rendered_template
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_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") 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_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_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_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", full_path("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_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!"
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 = 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_id, areq = self.client.create_authn_request( "http://www.example.com/sso", message_id="id1") ar_str = areq.to_string() 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 list(self.client.config.vorg.keys()) == [ "urn:mace:example.com:it:tek"] ar_id, areq = 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") ar_str = "%s" % areq 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, a_req = self.client.create_authn_request( "http://www.example.com/sso", sign=True, message_id="id1") if isinstance(a_req, bytes): ar_str = a_req else: ar_str = a_req.to_string() 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.encodebytes(resp_str.encode("utf8")) 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.encodebytes(resp_str.encode("utf8")) 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"
class TestServer1(): def setup_class(self): self.server = Server("idp_conf") conf = config.SPConfig() conf.load_file("server_conf") self.client = client.Saml2Client(conf) def teardown_class(self): self.server.ident.close() 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_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") _req = "%s" % logout_request intermed = base64.b64encode(_req.encode("utf8")) #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() idp = Server("idp_soap_conf") request = idp.parse_logout_request(saml_soap) idp.ident.close() assert request
class TestServer1(): def setup_class(self): self.server = Server("idp_conf") conf = config.SPConfig() conf.load_file("server_conf") self.client = client.Saml2Client(conf) def teardown_class(self): self.server.ident.close() 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_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") _req = "%s" % logout_request intermed = base64.b64encode(_req.encode("utf8")) #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() idp = Server("idp_soap_conf") request = idp.parse_logout_request(saml_soap) idp.ident.close() assert request
def test_basic_flow(): sp = Saml2Client(config_file="servera_conf") idp = Server(config_file="idp_all_conf") # -------- @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)
class SamlIDP(service.Service): 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 verify_request(self, query, binding): """ Parses and verifies the SAML Authentication Request :param query: The SAML authn request, transport encoded :param binding: Which binding the query came in over :returns: dictionary """ if not query: logger.info("Missing QUERY") resp = Unauthorized('Unknown user') return {"response": resp(self.environ, self.start_response)} req_info = self.idp.parse_authn_request(query, binding) logger.info("parsed OK") _authn_req = req_info.message logger.debug("%s" % _authn_req) # Check that I know where to send the reply to. try: binding_out, destination = self.idp.pick_binding( "assertion_consumer_service", bindings=self.response_bindings, entity_id=_authn_req.issuer.text, request=_authn_req) except Exception as err: logger.error("Couldn't find receiver endpoint: %s" % err) raise logger.debug("Binding: %s, destination: %s" % (binding_out, destination)) resp_args = {} try: resp_args = self.idp.response_args(_authn_req) _resp = None except UnknownPrincipal as excp: _resp = self.idp.create_error_response(_authn_req.id, destination, excp) except UnsupportedBinding as excp: _resp = self.idp.create_error_response(_authn_req.id, destination, excp) req_args = {} for key in ["subject", "name_id_policy", "conditions", "requested_authn_context", "scoping", "force_authn", "is_passive"]: try: val = getattr(_authn_req, key) except AttributeError: pass else: if val is not None: req_args[key] = val return {"resp_args": resp_args, "response": _resp, "authn_req": _authn_req, "req_args": req_args} def handle_authn_request(self, binding_in): """ Deal with an authentication request :param binding_in: Which binding was used when receiving the query :return: A response if an error occurred or session information in a dictionary """ _request = self.unpack(binding_in) _binding_in = service.INV_BINDING_MAP[binding_in] try: _dict = self.verify_request(_request["SAMLRequest"], _binding_in) except UnknownPrincipal as excp: logger.error("UnknownPrincipal: %s" % (excp,)) resp = ServiceError("UnknownPrincipal: %s" % (excp,)) return resp(self.environ, self.start_response) except UnsupportedBinding as excp: logger.error("UnsupportedBinding: %s" % (excp,)) resp = ServiceError("UnsupportedBinding: %s" % (excp,)) return resp(self.environ, self.start_response) _binding = _dict["resp_args"]["binding"] if _dict["response"]: # An error response. http_args = self.idp.apply_binding( _binding, "%s" % _dict["response"], _dict["resp_args"]["destination"], _request["RelayState"], response=True) logger.debug("HTTPargs: %s" % http_args) return self.response(_binding, http_args) else: return self.incoming(_dict, self.environ, self.start_response, _request["RelayState"]) def construct_authn_response(self, identity, name_id, authn, resp_args, relay_state, sign_response=True): """ :param identity: :param name_id: :param authn: :param resp_args: :param relay_state: :param sign_response: :return: """ _resp = self.idp.create_authn_response(identity, name_id=name_id, authn=authn, sign_response=sign_response, **resp_args) http_args = self.idp.apply_binding( resp_args["binding"], "%s" % _resp, resp_args["destination"], relay_state, response=True) logger.debug("HTTPargs: %s" % http_args) resp = None if http_args["data"]: resp = Response(http_args["data"], headers=http_args["headers"]) else: for header in http_args["headers"]: if header[0] == "Location": resp = Redirect(header[1]) if not resp: resp = ServiceError("Don't know how to return response") return resp(self.environ, self.start_response) def register_endpoints(self): """ Given the configuration, return a set of URL to function mappings. """ url_map = [] idp_endpoints = self.idp.config.getattr("endpoints", "idp") for endp, binding in idp_endpoints["single_sign_on_service"]: p = urlparse(endp) url_map.append(("^%s/(.*)$" % p.path[1:], ("IDP", "handle_authn_request", service.BINDING_MAP[binding]))) url_map.append(("^%s$" % p.path[1:], ("IDP", "handle_authn_request", service.BINDING_MAP[binding]))) return url_map
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)
def test_basic_flow(): sp = Saml2Client(config_file="servera_conf") idp = Server(config_file="idp_all_conf") # -------- @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_flow(): sp = Saml2Client(config_file="servera_conf") idp = Server(config_file="idp_all_conf") 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(sp.config.entityid, "id12") 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: "/"}) binding, destination = sp.pick_binding("authn_query_service", entity_id=idp.config.entityid) authn_context = requested_authn_context(INTERNETPROTOCOLPASSWORD) subject = aresp.assertion.subject aq_id, aq = sp.create_authn_query(subject, destination, authn_context) print(aq) assert isinstance(aq, AuthnQuery) binding = BINDING_SOAP hinfo = sp.apply_binding(binding, "%s" % aq, destination, "state2") # -------- @IDP ---------- xmlstr = get_msg(hinfo, binding) pm = idp.parse_authn_query(xmlstr, binding) msg = pm.message assert msg.id == aq.id p_res = idp.create_authn_query_response(msg.subject, msg.session_index, msg.requested_authn_context) print(p_res) hinfo = idp.apply_binding(binding, "%s" % p_res, "", "state2", response=True) # ------- @SP ---------- xmlstr = get_msg(hinfo, binding) final = sp.parse_authn_query_response(xmlstr, binding) print(final) assert final.response.id == p_res.id
class SamlIDP(service.Service): def __init__(self, environ, start_response, conf, cache, incomming, tid1_to_tid2, tid2_to_tid1, encmsg_to_iv, tid_handler, force_persistant_nameid, force_no_userid_subject_cacheing, idp=None): """ 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 if idp is None: self.idp = Server(config=conf, cache=cache) else: self.idp = idp self.incomming = incomming self.tid1_to_tid2 = tid1_to_tid2 self.tid2_to_tid1 = tid2_to_tid1 self.encmsg_to_iv = encmsg_to_iv self.tid_handler = tid_handler self.force_persistant_nameid = force_persistant_nameid self.force_no_userid_subject_cacheing = force_no_userid_subject_cacheing def verify_request(self, query, binding): """ Parses and verifies the SAML Authentication Request :param query: The SAML authn request, transport encoded :param binding: Which binding the query came in over :returns: dictionary """ if not query: logger.info("Missing QUERY") resp = Unauthorized('Unknown user') return {"response": resp} req_info = self.idp.parse_authn_request(query, binding) encrypt_cert = encrypt_cert_from_item(req_info.message) logger.info("parsed OK") _authn_req = req_info.message logger.debug("%s" % _authn_req) # Check that I know where to send the reply to try: binding_out, destination = self.idp.pick_binding( "assertion_consumer_service", bindings=self.response_bindings, entity_id=_authn_req.issuer.text, request=_authn_req) except Exception as err: logger.error("Couldn't find receiver endpoint: %s" % err) raise logger.debug("Binding: %s, destination: %s" % (binding_out, destination)) resp_args = {} try: resp_args = self.idp.response_args(_authn_req) _resp = None except UnknownPrincipal as excp: _resp = self.idp.create_error_response(_authn_req.id, destination, excp) except UnsupportedBinding as excp: _resp = self.idp.create_error_response(_authn_req.id, destination, excp) req_args = {} for key in ["subject", "name_id_policy", "conditions", "requested_authn_context", "scoping", "force_authn", "is_passive"]: try: val = getattr(_authn_req, key) except AttributeError: pass else: if val is not None: req_args[key] = val return {"resp_args": resp_args, "response": _resp, "authn_req": _authn_req, "req_args": req_args, "encrypt_cert": encrypt_cert} def handle_authn_request(self, binding_in): """ Deal with an authentication request :param binding_in: Which binding was used when receiving the query :return: A response if an error occurred or session information in a dictionary """ _request = self.unpack(binding_in) _binding_in = service.INV_BINDING_MAP[binding_in] try: _dict = self.verify_request(_request["SAMLRequest"], _binding_in) except UnknownPrincipal as excp: logger.error("UnknownPrincipal: %s" % (excp,)) resp = ServiceError("UnknownPrincipal: %s" % (excp,)) return resp except UnsupportedBinding as excp: logger.error("UnsupportedBinding: %s" % (excp,)) resp = ServiceError("UnsupportedBinding: %s" % (excp,)) return resp _binding = _dict["resp_args"]["binding"] if _dict["response"]: # An error response http_args = self.idp.apply_binding( _binding, "%s" % _dict["response"], _dict["resp_args"]["destination"], _request["RelayState"], response=True) logger.debug("HTTPargs: %s" % http_args) return self.response(_binding, http_args) else: return self.incomming(_dict, self, self.environ, self.start_response, _request["RelayState"]) def get_tid1_resp(self, org_resp): tid1 = org_resp.assertion.subject.name_id.text return tid1 def get_sp_entityid(self, resp_args): sp_entityid = resp_args["destination"] return sp_entityid def get_tid2_enc(self, tid1, sp_entityid): iv = None if self.encmsg_to_iv is not None: iv = self.tid_handler.get_new_iv() tid2_enc = self.tid_handler.tid2_encrypt(tid1, sp_entityid, iv=iv) if self.encmsg_to_iv is not None: self.encmsg_to_iv[tid2_enc] = iv return tid2_enc def get_tid2_hash(self, tid1, sp_entityid): tid2_hash = self.tid_handler.tid2_hash(tid1, sp_entityid) return tid2_hash def handle_tid(self, tid1, tid2): if self.tid1_to_tid2 is not None: self.tid1_to_tid2[tid1] = tid2 if self.tid2_to_tid1 is not None: self.tid2_to_tid1[tid2] = tid1 def name_id_exists(self, userid, name_id_policy, sp_entity_id): try: snq = name_id_policy.sp_name_qualifier except AttributeError: snq = sp_entity_id if not snq: snq = sp_entity_id kwa = {"sp_name_qualifier": snq} try: kwa["format"] = name_id_policy.format except AttributeError: pass return self.idp.ident.find_nameid(userid, **kwa) def construct_authn_response(self, identity, userid, authn, resp_args, relay_state, name_id=None, sign_response=True, org_resp=None, org_xml_response=None): """ :param identity: :param name_id: :param authn: :param resp_args: :param relay_state: :param sign_response: :return: """ if self.force_persistant_nameid: if "name_id_policy" in resp_args: resp_args["name_id_policy"].format = NAMEID_FORMAT_PERSISTENT sp_entityid = self.get_sp_entityid(resp_args) tid1 = self.get_tid1_resp(org_resp) userid = self.tid_handler.uid_hash(tid1) if self.force_no_userid_subject_cacheing: self.idp.ident = IdentDB({}) name_id_exist = False if self.name_id_exists(userid, resp_args["name_id_policy"], resp_args["sp_entity_id"]): name_id_exist = True if not name_id_exist: if identity is not None: identity["uid"] = userid if self.tid2_to_tid1 is None: tid2 = self.get_tid2_enc(tid1, sp_entityid) else: tid2 = self.get_tid2_hash(tid1, sp_entityid) else: tid2 = None _resp = self.idp.create_authn_response(identity, userid=userid, name_id=name_id, authn=authn, sign_response=False, **resp_args) if not name_id_exist: #Fix for name_id so sid2 is used instead. _resp.assertion.subject.name_id.text = tid2 self.idp.ident.remove_local(userid) self.idp.ident.store(userid, _resp.assertion.subject.name_id) tid2 = _resp.assertion.subject.name_id.text if self.tid2_to_tid1 is not None: self.tid2_to_tid1[tid2] = tid1 if self.tid1_to_tid2 is not None: self.tid1_to_tid2[tid1] = tid2 advice = None for tmp_assertion in org_resp.response.assertion: if tmp_assertion.advice is not None: advice = tmp_assertion.advice break if advice is not None: _resp.assertion.advice = advice #_resp.assertion = [] if sign_response: _class_sign = class_name(_resp) _resp.signature = pre_signature_part(_resp.id, self.idp.sec.my_cert, 1) _resp = self.idp.sec.sign_statement(_resp, _class_sign, node_id=_resp.id) http_args = self.idp.apply_binding( resp_args["binding"], "%s" % _resp, resp_args["destination"], relay_state, response=True) logger.debug("HTTPargs: %s" % http_args) resp = None if http_args["data"]: resp = Response(http_args["data"], headers=http_args["headers"]) else: for header in http_args["headers"]: if header[0] == "Location": resp = Redirect(header[1]) if not resp: resp = ServiceError("Don't know how to return response") return resp def register_endpoints(self): """ Given the configuration, return a set of URL to function mappings. """ url_map = [] for endp, binding in self.idp.config.getattr("endpoints", "idp")[ "single_sign_on_service"]: p = urlparse(endp) url_map.append(("^%s/(.*)$" % p.path[1:], ("IDP", "handle_authn_request", service.BINDING_MAP[binding]))) url_map.append(("^%s$" % p.path[1:], ("IDP", "handle_authn_request", service.BINDING_MAP[binding]))) return url_map
def login_process(request): """ View which processes the actual SAML request and returns a self-submitting form with the SAML response. The login_required decorator ensures the user authenticates first on the IdP using 'normal' ways. """ # Construct server with config from settings dict conf = IdPConfig() conf.load(copy.deepcopy(settings.SAML_IDP_CONFIG)) IDP = Server(config=conf) # Parse incoming request try: req_info = IDP.parse_authn_request(request.session['SAMLRequest'], BINDING_HTTP_POST) except Exception as excp: return HttpResponseBadRequest(excp) # TODO this is taken from example, but no idea how this works or whats it does. Check SAML2 specification? # Signed request for HTTP-REDIRECT if "SigAlg" in request.session and "Signature" in request.session: _certs = IDP.metadata.certs(req_info.message.issuer.text, "any", "signing") verified_ok = False for cert in _certs: # TODO implement #if verify_redirect_signature(_info, IDP.sec.sec_backend, cert): # verified_ok = True # break pass if not verified_ok: return HttpResponseBadRequest( "Message signature verification failure") binding_out, destination = IDP.pick_binding( service="assertion_consumer_service", entity_id=req_info.message.issuer.text) # Gather response arguments try: resp_args = IDP.response_args(req_info.message) except (UnknownPrincipal, UnsupportedBinding) as excp: return HttpResponseServerError(excp) try: sp_config = settings.SAML_IDP_SPCONFIG[resp_args['sp_entity_id']] except Exception: raise ImproperlyConfigured( "No config for SP %s defined in SAML_IDP_SPCONFIG" % resp_args['sp_entity_id']) # Create user-specified processor or fallback to all-access base processor processor_string = sp_config.get('processor', None) if processor_string is None: processor = BaseProcessor else: processor_class = import_string(processor_string) processor = processor_class() # Check if user has access to the service of this SP if not processor.has_access(request.user): raise PermissionDenied("You do not have access to this resource") # Create Identity dict (SP-specific) sp_mapping = sp_config.get('attribute_mapping', {'username': '******'}) identity = processor.create_identity(request.user, sp_mapping) # TODO investigate how this works, because I don't get it. Specification? req_authn_context = req_info.message.requested_authn_context or PASSWORD AUTHN_BROKER = AuthnBroker() AUTHN_BROKER.add(authn_context_class_ref(req_authn_context), "") # Construct SamlResponse message try: authn_resp = IDP.create_authn_response( identity=identity, userid=request.user.username, name_id=NameID(format=resp_args['name_id_policy'].format, sp_name_qualifier=destination, text=request.user.username), authn=AUTHN_BROKER.get_authn_by_accr(req_authn_context), sign_response=IDP.config.getattr("sign_response", "idp") or False, sign_assertion=IDP.config.getattr("sign_assertion", "idp") or False, **resp_args) except Exception as excp: return HttpResponseServerError(excp) # Return as html with self-submitting form. http_args = IDP.apply_binding(binding=binding_out, msg_str="%s" % authn_resp, destination=destination, relay_state=request.session['RelayState'], response=True) logger.debug('http args are: %s' % http_args) if processor.enable_multifactor(request.user): # Store http_args in session for after multi factor is complete request.session['saml_data'] = http_args['data'] logger.debug("Redirecting to process_multi_factor") return HttpResponseRedirect(reverse('saml_multi_factor')) else: logger.debug("Performing SAML redirect") return HttpResponse(http_args['data'])
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 = 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, "%s" % 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 = 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, ar.artifact.text) 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, "%s" % artifact, destination, relay_state, response=True) # ========== SP ========= artifact3 = get_msg(hinfo, binding) assert artifact == artifact3 destination = sp.artifact2destination(artifact3, "idpsso") # Got an artifact want to replace it with the real message msg = sp.create_artifact_resolve(artifact3, 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 TestClient: def setup_class(self): self.server = Server("idp_conf") conf = config.SPConfig() conf.load_file("server_conf") self.client = Saml2Client(conf) def teardown_class(self): self.server.close() 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://*****:*****@test.se" }) a_assertion = a_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") a_assertion.signature = sigver.pre_signature_part( a_assertion.id, _sec.my_cert, 1) assertion.advice = Advice() assertion.advice.encrypted_assertion = [] assertion.advice.encrypted_assertion.append(EncryptedAssertion()) assertion.advice.encrypted_assertion[0].add_extension_element( a_assertion) response = sigver.response_factory( in_response_to="_012345", destination="http://lingon.catalogix.se:8087/", status=s_utils.success_status_factory(), issuer=self.server._issuer()) response.assertion.append(assertion) response = _sec.sign_statement("%s" % response, class_name(a_assertion), key_file=self.client.sec.key_file, node_id=a_assertion.id) #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) node_xpath = ''.join([ "/*[local-name()=\"%s\"]" % v for v in [ "Response", "Assertion", "Advice", "EncryptedAssertion", "Assertion" ] ]) enctext = _sec.crypto.encrypt_assertion(response, _sec.cert_file, pre_encryption_part(), node_xpath=node_xpath) #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.assertion.advice assert resp.assertion.advice.assertion assert resp.ava == \ {'sn': ['Jeter'], 'givenName': ['Derek'], 'uid': ['test01'], 'email': ['*****@*****.**']} def test_signed_redirect(self): msg_str = "%s" % self.client.create_authn_request( "http://localhost:8088/sso", message_id="id1")[1] key = self.client.signkey info = self.client.apply_binding(BINDING_HTTP_REDIRECT, msg_str, destination="", relay_state="relay2", sigalg=SIG_RSA_SHA256, key=key) loc = info["headers"][0][1] qs = urlparse.parse_qs(loc[1:]) assert _leq(qs.keys(), ['SigAlg', 'SAMLRequest', 'RelayState', 'Signature']) assert verify_redirect_signature(list_values2simpletons(qs), sigkey=key) res = self.server.parse_authn_request(qs["SAMLRequest"][0], BINDING_HTTP_REDIRECT) print res
class TestSignedResponse(): 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 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_SHA1 in str(sresponse), "Not correctly signed!" assert ds.DIGEST_SHA1 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_SHA1 in str(sresponse), "Not correctly signed!" assert ds.DIGEST_SHA1 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) assert valid assert ds.SIG_RSA_SHA1 in str(sresponse.assertion[0]), "Not correctly signed!" assert ds.DIGEST_SHA1 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) 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) 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) assert valid self.verify_assertion(sresponse.assertion)
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)
def test_flow(): sp = Saml2Client(config_file="servera_conf") idp = Server(config_file="idp_all_conf") 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(sp.config.entityid, "id12") 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: "/"}) binding, destination = sp.pick_binding("authn_query_service", entity_id=idp.config.entityid) authn_context = requested_authn_context(INTERNETPROTOCOLPASSWORD) subject = aresp.assertion.subject aq = sp.create_authn_query(subject, destination, authn_context) print aq assert isinstance(aq, AuthnQuery) binding = BINDING_SOAP hinfo = sp.apply_binding(binding, "%s" % aq, destination, "state2") # -------- @IDP ---------- xmlstr = get_msg(hinfo, binding) pm = idp.parse_authn_query(xmlstr, binding) msg = pm.message assert msg.id == aq.id p_res = idp.create_authn_query_response(msg.subject, msg.session_index, msg.requested_authn_context) print p_res hinfo = idp.apply_binding(binding, "%s" % p_res, "", "state2", response=True) # ------- @SP ---------- xmlstr = get_msg(hinfo, binding) final = sp.parse_authn_query_response(xmlstr, binding) print final assert final.response.id == p_res.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, exception, **kwargs): if not getattr(settings, 'SAML_IDP_HANDLE_ERRORS', True): raise exception # Log the exception and the statuscode kwargs['exception'] = exception 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 """ policy = resp_args.get('name_id_policy', None) if policy is None: sp_config['name_id_format'] = NAMEID_FORMAT_UNSPECIFIED else: sp_config['name_id_format'] = policy.format 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, 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', None) 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'])
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!"
class SamlIDP(service.Service): def __init__(self, environ, start_response, conf, cache, incomming): """ 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.incomming = incomming def verify_request(self, query, binding): """ Parses and verifies the SAML Authentication Request :param query: The SAML authn request, transport encoded :param binding: Which binding the query came in over :returns: dictionary """ if not query: logger.info("Missing QUERY") resp = Unauthorized('Unknown user') return {"response": resp(self.environ, self.start_response)} req_info = self.idp.parse_authn_request(query, binding) logger.info("parsed OK") _authn_req = req_info.message logger.debug("%s" % _authn_req) # Check that I know where to send the reply to try: binding_out, destination = self.idp.pick_binding( "assertion_consumer_service", bindings=self.response_bindings, entity_id=_authn_req.issuer.text, request=_authn_req) except Exception as err: logger.error("Couldn't find receiver endpoint: %s" % err) raise logger.debug("Binding: %s, destination: %s" % (binding_out, destination)) resp_args = {} try: resp_args = self.idp.response_args(_authn_req) _resp = None except UnknownPrincipal as excp: _resp = self.idp.create_error_response(_authn_req.id, destination, excp) except UnsupportedBinding as excp: _resp = self.idp.create_error_response(_authn_req.id, destination, excp) req_args = {} for key in [ "subject", "name_id_policy", "conditions", "requested_authn_context", "scoping", "force_authn", "is_passive" ]: try: val = getattr(_authn_req, key) except AttributeError: pass else: req_args[key] = val return { "resp_args": resp_args, "response": _resp, "authn_req": _authn_req, "req_args": req_args } def handle_authn_request(self, binding_in): """ Deal with an authentication request :param binding_in: Which binding was used when receiving the query :return: A response if an error occurred or session information in a dictionary """ _request = self.unpack(binding_in) _binding_in = service.INV_BINDING_MAP[binding_in] try: _dict = self.verify_request(_request["SAMLRequest"], _binding_in) except UnknownPrincipal as excp: logger.error("UnknownPrincipal: %s" % (excp, )) resp = ServiceError("UnknownPrincipal: %s" % (excp, )) return resp(self.environ, self.start_response) except UnsupportedBinding as excp: logger.error("UnsupportedBinding: %s" % (excp, )) resp = ServiceError("UnsupportedBinding: %s" % (excp, )) return resp(self.environ, self.start_response) _binding = _dict["resp_args"]["binding"] if _dict["response"]: # An error response http_args = self.idp.apply_binding( _binding, "%s" % _dict["response"], _dict["resp_args"]["destination"], _request["RelayState"], response=True) logger.debug("HTTPargs: %s" % http_args) return self.response(_binding, http_args) else: return self.incomming(_dict, self, self.environ, self.start_response, _request["RelayState"]) def construct_authn_response(self, identity, name_id, authn, resp_args, relay_state, sign_response=True): """ :param identity: :param name_id: :param authn: :param resp_args: :param relay_state: :param sign_response: :return: """ _resp = self.idp.create_authn_response(identity, name_id=name_id, authn=authn, sign_response=sign_response, **resp_args) http_args = self.idp.apply_binding(resp_args["binding"], "%s" % _resp, resp_args["destination"], relay_state, response=True) logger.debug("HTTPargs: %s" % http_args) resp = None if http_args["data"]: resp = Response(http_args["data"], headers=http_args["headers"]) else: for header in http_args["headers"]: if header[0] == "Location": resp = Redirect(header[1]) if not resp: resp = ServiceError("Don't know how to return response") return resp(self.environ, self.start_response) def register_endpoints(self): """ Given the configuration, return a set of URL to function mappings. """ url_map = [] for endp, binding in self.idp.config.getattr( "endpoints", "idp")["single_sign_on_service"]: p = urlparse(endp) url_map.append( ("^%s/(.*)$" % p.path[1:], ("IDP", "handle_authn_request", service.BINDING_MAP[binding]))) url_map.append( ("^%s$" % p.path[1:], ("IDP", "handle_authn_request", service.BINDING_MAP[binding]))) return url_map
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(str(cert_key_str).encode('ascii'), 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(str(cert_key_str).encode('ascii'), 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
class IdPHandlerViewMixin(ErrorHandler): """ Contains some methods used by multiple views """ 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: msg = _("No config for SP {} defined in SAML_IDP_SPCONFIG").format(sp_entity_id) raise ImproperlyConfigured(msg) 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: msg = _("Failed to instantiate processor: {} - {}") logger.error(msg.format(processor_string,e), exc_info=True) raise ImproperlyConfigured(_(msg.format(processor_string, e), exc_info=True)) self.processor = BaseProcessor(self.sp['id']) 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, request): """ Check if user has access to the service of this SP """ if not self.processor.has_access(request): raise PermissionDenied(_("You do not have access to this resource")) def get_authn(self, req_info=None): if req_info: req_authn_context = req_info.message.requested_authn_context else: req_authn_context = 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): """ pysaml2 server.Server.create_authn_response wrapper """ self.sp['name_id_format'] = resp_args.get('name_id_policy').format idp_name_id_format_list = self.IDP.config.getattr("name_id_format", "idp") # name_id format availability if idp_name_id_format_list and not self.sp['name_id_format']: name_id_format = idp_name_id_format_list[0] elif self.sp['name_id_format'] and not idp_name_id_format_list: name_id_format = self.sp['name_id_format'] elif self.sp['name_id_format'] not in idp_name_id_format_list: return self.handle_error(request, exception=_('SP requested a name_id_format ' 'that is not supported in the IDP')) elif self.sp['name_id_format'] in idp_name_id_format_list: name_id_format = self.sp['name_id_format'] else: name_id_format = NAMEID_FORMAT_UNSPECIFIED # if SP doesn't request a specific name_id_format... if not self.sp['name_id_format']: self.sp['name_id_format'] = name_id_format user_id = self.processor.get_user_id(user, self.sp, self.IDP.config) name_id = NameID(format=self.sp['name_id_format'], sp_name_qualifier=self.sp['id'], text=user_id) user_attrs = self.processor.create_identity(user, self.sp) # ASSERTION ENCRYPTED enrypt_response = getattr(settings, 'SAML_ENCRYPT_AUTHN_RESPONSE', False) if 'encrypt_saml_responses' in self.sp['config'].keys(): enrypt_response = self.sp['config'].get('encrypt_saml_responses') authn_resp = self.IDP.create_authn_response( authn=authn, identity=user_attrs, userid=user_id, name_id=name_id, # signature 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, # default will be sha1 in pySAML2 sign_alg=self.sp['config'].get("signing_algorithm") or \ getattr(settings, 'SAML_AUTHN_SIGN_ALG', False), digest_alg=self.sp['config'].get("digest_algorithm") or \ getattr(settings, 'SAML_AUTHN_DIGEST_ALG', False), # Encryption encrypt_assertion=enrypt_response, encrypted_advice_attributes=enrypt_response, **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, } template = "saml_login.html" html_response = render_to_string(template, 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 not hasattr(self, 'processor'): # In case of SLO, where processor isn't relevant return HttpResponse(html_response) request.session['saml_data'] = html_response # Generate request session stuff needed for user agreement screen attrs_to_exclude = self.sp['config'].get('user_agreement_attr_exclude', []) + \ getattr(settings, "SAML_IDP_USER_AGREEMENT_ATTR_EXCLUDE", []) request.session['identity'] = { k: v for k, v in self.processor.create_identity(request.user, self.sp).items() if k not in attrs_to_exclude } request.session['sp_display_info'] = { 'display_name': self.sp['config'].get('display_name', self.sp['id']), 'display_description': self.sp['config'].get('display_description'), 'display_agreement_message': self.sp['config'].get('display_agreement_message') } request.session['sp_entity_id'] = self.sp['id'] # Conditions for showing user agreement screen user_agreement_enabled_for_sp = self.sp['config'].get('show_user_agreement_screen', getattr(settings, "SAML_IDP_SHOW_USER_AGREEMENT_SCREEN")) try: agreement_for_sp = AgreementRecord.objects.get(user=request.user, sp_entity_id=self.sp['id']) if agreement_for_sp.is_expired() or \ agreement_for_sp.wants_more_attrs(request.session['identity'].keys()): agreement_for_sp.delete() already_agreed = False else: already_agreed = True except AgreementRecord.DoesNotExist: already_agreed = False # Multifactor goes before user agreement because might result in user not being authenticated if self.processor.enable_multifactor(request.user): logger.debug("Redirecting to process_multi_factor") return HttpResponseRedirect(reverse('djangosaml2idp:saml_multi_factor')) # If we are here, there's no multifactor. Check whether to show user agreement if user_agreement_enabled_for_sp and not already_agreed: logger.debug("Redirecting to process_user_agreement") return HttpResponseRedirect(reverse('djangosaml2idp:saml_user_agreement')) # No multifactor or user agreement logger.debug("Performing SAML redirect") return HttpResponse(html_response)
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"], "sn": ["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'], 'sn': ['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( { ("", "", "sn"): ("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 == "sn" assert attr1.attribute_value[0].text == "Jeter" else: assert attr1.friendly_name == "givenName" assert attr1.attribute_value[0].text == "Derek" assert attr0.friendly_name == "sn" 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( { ("", "", "sn"): ("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"], "sn": ["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", "sn": "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", "sn": "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
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 = [ 'https://www.spid.gov.it/SpidL1', 'https://www.spid.gov.it/SpidL2', 'https://www.spid.gov.it/SpidL3' ] _spid_attributes = { 'primary': { 'spidCode': 'xs:string', 'name': 'xs:string', 'familyName': 'xs:string', 'placeOfBirth': 'xs:string', 'countryOfBirth': 'xs:string', 'dateOfBirth': 'xs:date', 'gender': 'xs:string', 'companyName': 'xs:string', 'registeredOffice': 'xs:string', 'fiscalNumber': 'xs:string', 'ivaCode': 'xs:string', 'idCard': 'xs:string', }, 'secondary': { 'mobilePhone': 'xs:string', 'email': 'xs:string', 'address': 'xs:string', 'expirationDate': 'xs:date', 'digitalAddress': 'xs:string' # PEC } } CHALLENGES_TIMEOUT = 30 # seconds def __init__(self, app, config, *args, **kwargs): """ :param app: Flask instance :param config: dictionary containing the configuration :param args: :param kwargs: """ # bind Flask app self.app = app self.user_manager = JsonUserManager() # setup self._config = config 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.get('https', False) else 'http' def _idp_config(self): """ Process pysaml2 configuration """ key_file_path = self._config.get('key_file') cert_file_path = self._config.get('cert_file') metadata = self._config.get('metadata') metadata = metadata if metadata else [] existing_key = os.path.isfile(key_file_path) if key_file_path else None existing_cert = os.path.isfile( cert_file_path) if cert_file_path else None if not existing_key: raise BadConfiguration( 'Chiave privata dell\'IdP di test non trovata: {} non trovato'. format(key_file_path)) if not existing_cert: raise BadConfiguration( 'Certificato dell\'IdP di test non trovato: {} non trovato'. format(cert_file_path)) self.entity_id = self._config.get('hostname') if not self.entity_id: self.entity_id = self._config.get('host') self.entity_id = '{}://{}'.format(self._mode, self.entity_id) port = self._config.get('port') if port: self.entity_id = '{}:{}'.format(self.entity_id, port) idp_conf = { "entityid": self.entity_id, "description": "Spid Test IdP", "service": { "idp": { "name": "Spid Testenv", "endpoints": { "single_sign_on_service": [], "single_logout_service": [], }, "policy": { "default": { "name_form": NAME_FORMAT_BASIC, }, }, "name_id_format": [ NAMEID_FORMAT_TRANSIENT, ] }, }, "debug": 1, "key_file": self._config.get('key_file'), "cert_file": self._config.get('cert_file'), "metadata": metadata, "organization": { "display_name": "Spid testenv", "name": "Spid testenv", "url": "http://www.example.com", }, "contact_person": [ { "contact_type": "technical", "given_name": "support", "sur_name": "support", "email_address": "*****@*****.**" }, ], "logger": { "rotating": { "filename": "idp.log", "maxBytes": 500000, "backupCount": 1, }, "loglevel": "debug", } } # setup services url for _service_type in self._endpoint_types: endpoint = self._config['endpoints'][_service_type] idp_conf['service']['idp']['endpoints'][_service_type].append( ('{}{}'.format(self.entity_id, endpoint), BINDING_HTTP_REDIRECT)) idp_conf['service']['idp']['endpoints'][_service_type].append( ('{}{}'.format(self.entity_id, endpoint), BINDING_HTTP_POST)) return idp_conf def _setup_app_routes(self): """ Setup Flask routes """ # Setup SSO and SLO endpoints endpoints = self._config.get('endpoints') if endpoints: for ep_type in self._endpoint_types: _url = endpoints.get(ep_type) if _url: if not _url.startswith('/'): raise BadConfiguration( 'Errore nella configurazione delle url, i path devono essere relativi ed iniziare con "/" (slash) - url {}' .format(_url)) for _binding in self._binding_mapping.keys(): self.app.add_url_rule(_url, '{}_{}'.format( ep_type, _binding), getattr(self, ep_type), methods=[ 'GET', ]) self.app.add_url_rule('/login', 'login', self.login, methods=[ 'POST', 'GET', ]) # Endpoint for user add action self.app.add_url_rule('/add-user', 'add_user', self.add_user, 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 """ self.idp_config = Saml2Config() self.BASE = '{}://{}:{}'.format(self._mode, self._config.get('host'), self._config.get('port')) if 'entityid' not in self._config: # as fallback for entityid use host:port string self._config['entityid'] = self.BASE self.idp_config.load(cnf=self._idp_config()) self.server = Server(config=self.idp_config) self._setup_app_routes() # setup custom methods in order to # prepare the login form and verify the challenge (optional) # for every spid level (1-2-3) self.authn_broker = AuthnBroker() for index, _level in enumerate(self._spid_levels): self.authn_broker.add( authn_context_class_ref(_level), getattr(self, '_verify_spid_{}'.format(index + 1))) def _verify_spid_1(self, verify=False, **kwargs): self.app.logger.debug('spid level 1 - verifica ({})'.format(verify)) return self._verify_spid(1, verify, **kwargs) def _verify_spid_2(self, verify=False, **kwargs): self.app.logger.debug('spid level 2 - verifica ({})'.format(verify)) return self._verify_spid(2, verify, **kwargs) def _verify_spid_3(self, verify=False, **kwargs): self.app.logger.debug('spid level 3 - verifica ({})'.format(verify)) return self._verify_spid(3, verify, **kwargs) def _verify_spid(self, level=1, 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 """ 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 if self.challenges[key][ 0] != otp or total_seconds > self.CHALLENGES_TIMEOUT: 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(error_table.format(msg, extra), 200)) def _check_saml_message_restrictions(self, obj): # TODO: Implement here or somewhere (e.g. mixin on pysaml2 subclasses) # the logic to validate spid rules on saml entities raise NotImplementedError def _store_request(self, authnreq): """ Store authnrequest in a dictionary :param authnreq: authentication request string """ self.app.logger.debug('store_request: {}'.format(authnreq)) key = sha1(authnreq.xmlstr).hexdigest() # store the AuthnRequest self.ticket[key] = authnreq return key def single_sign_on_service(self): """ Process Http-Redirect or Http-POST request :param request: Flask request object """ self.app.logger.info("Http-Redirect") # Unpack parameters saml_msg = self.unpack_args(request.args) try: _key = session['request_key'] req_info = self.ticket[_key] except KeyError as e: try: binding = self._get_binding('single_sign_on_service', request) # Parse AuthnRequest req_info = self.server.parse_authn_request( saml_msg["SAMLRequest"], binding) authn_req = req_info.message except KeyError as err: self.app.logger.debug(str(err)) self._raise_error('Parametro SAMLRequest assente.') if not req_info: self._raise_error('Processo di parsing del messaggio fallito.') self.app.logger.debug('AuthnRequest: {}'.format(authn_req)) # Check if it is signed if "SigAlg" in saml_msg and "Signature" in saml_msg: # Signed request self.app.logger.debug('Messaggio SAML firmato.') issuer_name = authn_req.issuer.text _certs = self.server.metadata.certs(issuer_name, "any", "signing") verified_ok = False for cert in _certs: self.app.logger.debug('security backend: {}'.format( self.server.sec.sec_backend.__class__.__name__)) # Check signature if verify_redirect_signature(saml_msg, self.server.sec.sec_backend, cert): verified_ok = True break if not verified_ok: self._raise_error( 'Verifica della firma del messaggio fallita.') # Perform login key = self._store_request(req_info) relay_state = saml_msg.get('RelayState', '') session['request_key'] = key session['relay_state'] = relay_state return redirect(url_for('login')) def _get_binding(self, endpoint_type, request): try: endpoint = request.endpoint binding = endpoint.split('{}_'.format(endpoint_type))[1] return self._binding_mapping.get(binding) except IndexError: pass @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() def add_user(self): """ Add user endpoint """ spid_main_fields = self._spid_main_fields spid_secondary_fields = self._spid_secondary_fields _fields = '<br><b>{}</b><br>'.format('Primary attributes') for _field_name in spid_main_fields: _fields = '{}<span>{}</span> <input type="text" name={} /><br>'.format( _fields, _field_name, _field_name) _fields = '{}<br><b>{}</b><br>'.format(_fields, 'Secondary attributes') for _field_name in spid_secondary_fields: _fields = '{}<span>{}</span> <input type="text" name={} /><br>'.format( _fields, _field_name, _field_name) if request.method == 'GET': return FORM_ADD_USER.format('/add-user', _fields), 200 elif request.method == 'POST': username = request.form.get('username') password = request.form.get('password') sp = request.form.get('service_provider') if not username or not password or not sp: 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 'Added a new user', 200 def login(self): """ Login endpoint (verify user credentials) """ key = session['request_key'] if 'request_key' in session else None relay_state = session['relay_state'] if 'relay_state' in session else '' self.app.logger.debug('Request key: {}'.format(key)) if key and key in self.ticket: authn_request = self.ticket[key] sp_id = authn_request.message.issuer.text destination = authn_request.message.assertion_consumer_service_url spid_level = authn_request.message.requested_authn_context.authn_context_class_ref[ 0].text authn_info = self.authn_broker.pick( authn_request.message.requested_authn_context) callback, reference = authn_info[0] if request.method == 'GET': # inject extra data in form login based on spid level extra_challenge = callback(**{'key': key}) return FORM_LOGIN.format(url_for('login'), key, relay_state, extra_challenge), 200 # verify optional challenge based on spid level verified = callback(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 attribute_statement_on_response = self._config.get( 'attribute_statement_on_response') identity = user['attrs'] AUTHN = {"class_ref": spid_level, "authn_auth": spid_level} _data = dict(identity=identity, userid=user_id, in_response_to=authn_request.message.id, destination=destination, sp_entity_id=sp_id, authn=AUTHN, issuer=self.server.config.entityid, sign_alg=SIGN_ALG, digest_alg=DIGEST_ALG, sign_assertion=True) response = self.server.create_authn_response(**_data) http_args = self.server.apply_binding( BINDING_HTTP_POST, response, destination, response=True, sign=True, relay_state=relay_state) # Setup confirmation page data ast = Assertion(identity) policy = self.server.config.getattr("policy", "idp") ast.acs = self.server.config.getattr( "attribute_converters", "idp") res = ast.apply_policy(sp_id, policy, self.server.metadata) attrs = res.keys() attrs_list = '' for _attr in attrs: attrs_list = '{}<tr><td>{}</td></tr>'.format( attrs_list, _attr) self.responses[key] = http_args['data'] return CONFIRM_PAGE.format(attrs_list, '/continue-response', key), 200 abort(403) def continue_response(self): key = request.form['request_key'] if key and key in self.ticket and key in self.responses: return self.responses[key], 200 abort(403) def single_logout_service(self): """ SLO endpoint :param binding: 'redirect' is http-redirect, 'post' is http-post binding """ self.app.logger.debug("req: '%s'", request) saml_msg = self.unpack_args(request.args) _binding = self._get_binding('single_logout_service', request) req_info = self.server.parse_logout_request(saml_msg['SAMLRequest'], _binding) msg = req_info.message response = self.server.create_logout_response( msg, [BINDING_HTTP_POST, BINDING_HTTP_REDIRECT], sign_alg=SIGN_ALG, digest_alg=DIGEST_ALG, sign=True) binding, destination = self.server.pick_binding( "single_logout_service", [BINDING_HTTP_POST, BINDING_HTTP_REDIRECT], "spsso", req_info) http_args = self.server.apply_binding(binding, "%s" % response, destination, response=True, sign=True) return http_args['data'], 200 def metadata(self): metadata = create_metadata_string( __file__, self.server.config, ) return Response(metadata, mimetype='text/xml') @property def _wsgiconf(self): _cnf = { 'host': self._config.get('host', '0.0.0.0'), 'port': self._config.get('port', '8000'), 'debug': self._config.get('debug', True), } if self._config.get('https', False): key = self._config.get('https_key_file') cert = self._config.get('https_cert_file') if not key or not cert: raise KeyError( 'Errore modalità https: Chiave e/o certificato assenti!') _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_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']}
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 set(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_attribute_query(self): resp = self.client.do_attribute_query( "urn:mace:example.com:saml:roland:idp", "_e7b68a04488f715cda642fbdd90099f5", nameid_format=saml.NAMEID_FORMAT_TRANSIENT) # since no one is answering on the other end assert resp 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"]} 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="*****@*****.**") 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'], 'surName': ['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 = "%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="*****@*****.**") 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.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"] == True 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"] == True 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.create_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 = discovery_service_request_url("urn:mace:example.com:saml:roland:sp", disc_url) print url assert url == "http://example.com/saml2/idp/disc?entityID=urn%3Amace%3Aexample.com%3Asaml%3Aroland%3Asp" url = discovery_service_request_url( self.client.config.entityid, 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 = 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 = 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 = "%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