#!/usr/bin/env python # -*- coding: utf-8 -*- import sys import logging from saml2.mdstore import MetadataStore, name from saml2 import BINDING_HTTP_REDIRECT, BINDING_SOAP, BINDING_HTTP_POST from saml2.config import SPConfig, IdPConfig, Config from saml2 import root_logger from pathutils import dotname, full_path from saml2.sigver import security_context, CryptoBackendXMLSecurity sp1 = { "entityid": "urn:mace:umu.se:saml:roland:sp", "service": { "sp": { "endpoints": { "assertion_consumer_service": [ "http://lingon.catalogix.se:8087/"], }, "name": "test", "idp": { "urn:mace:example.com:saml:roland:idp": { 'single_sign_on_service': {'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect': 'http://localhost:8088/sso/'}}, } } }, "key_file": full_path("test.key"), "cert_file": full_path("test.pem"), "metadata": [{ "class": "saml2.mdstore.MetaDataFile", "metadata": [(full_path("metadata.xml"), ), (full_path("urn-mace-swami.se-swamid-test-1.0-metadata.xml"), )], }], "virtual_organization": { "coip": { "nameid_format": "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", "common_identifier": "eduPersonPrincipalName", "attribute_auth": [ "https://coip-test.sunet.se/idp/shibboleth", ] } }, "attribute_map_dir": full_path("attributemaps"), "only_use_keys_in_metadata": True, "xmlsec_path": ["/opt/local/bin"] } sp2 = { "entityid": "urn:mace:umu.se:saml:roland:sp", "name": "Rolands SP", "service": { "sp": { "endpoints": { "assertion_consumer_service": [ "http://lingon.catalogix.se:8087/"], }, "required_attributes": ["surName", "givenName", "mail"], "optional_attributes": ["title"], "idp": { "": "https://example.com/saml2/idp/SSOService.php", }, "authn_requests_signed": True, "logout_requests_signed": True, "force_authn": True, } }, #"xmlsec_binary" : "/opt/local/bin/xmlsec1", } IDP1 = { "entityid": "urn:mace:umu.se:saml:roland:idp", "name": "Rolands IdP", "service": { "idp": { "endpoints": { "single_sign_on_service": ["http://localhost:8088/"], }, "policy": { "default": { "attribute_restrictions": { "givenName": None, "surName": None, "eduPersonAffiliation": ["(member|staff)"], "mail": [".*@example.com"], } }, "urn:mace:umu.se:saml:roland:sp": None }, } }, #"xmlsec_binary" : "/usr/local/bin/xmlsec1", } IDP2 = { "entityid": "urn:mace:umu.se:saml:roland:idp", "name": "Rolands IdP", "service": { "idp": { "endpoints": { "single_sign_on_service": ["http://localhost:8088/"], "single_logout_service": [ ("http://localhost:8088/", BINDING_HTTP_REDIRECT)], }, "policy": { "default": { "attribute_restrictions": { "givenName": None, "surName": None, "eduPersonAffiliation": ["(member|staff)"], "mail": [".*@example.com"], } }, "urn:mace:umu.se:saml:roland:sp": None }, } }, #"xmlsec_binary" : "/usr/local/bin/xmlsec1", } PDP = { "entityid": "http://example.org/pysaml2/pdp", "name": "Rolands PdP", "service": { "pdp": { "endpoints": { "authz_service": [("http://example.org/pysaml2/pdp/authz", BINDING_SOAP)], }, } }, "key_file": full_path("test.key"), "cert_file": full_path("test.pem"), "organization": { "name": "Exempel AB", "display_name": [("Exempel AB", "se"), ("Example Co.", "en")], "url": "http://www.example.com/roland", }, "contact_person": [{ "given_name": "John", "sur_name": "Smith", "email_address": ["john.smith@example.com"], "contact_type": "technical", }, ], } ECP_SP = { "entityid": "urn:mace:umu.se:saml:roland:ecpsp", "name": "Rolands ECP_SP", "service": { "sp": { "endpoints": { "assertion_consumer_service": [ "http://lingon.catalogix.se:8087/"], }, "ecp": { "130.239.": "http://example.com/idp", } } }, #"xmlsec_binary" : "/opt/local/bin/xmlsec1", } IDP_XMLSECURITY = { "entityid": "urn:mace:umu.se:saml:roland:idp", "name": "Rolands IdP", "service": { "idp": { "endpoints": { "single_sign_on_service": ["http://localhost:8088/"], "single_logout_service": [ ("http://localhost:8088/", BINDING_HTTP_REDIRECT)], }, "policy": { "default": { "attribute_restrictions": { "givenName": None, "surName": None, "eduPersonAffiliation": ["(member|staff)"], "mail": [".*@example.com"], } }, "urn:mace:umu.se:saml:roland:sp": None }, } }, "key_file": "pkcs11:///usr/lunasa/lib/libCryptoki2_64.so:1/eduID dev SAML signing key?pin=123456", "crypto_backend": "XMLSecurity" } def _eq(l1, l2): return set(l1) == set(l2) def test_1(): c = SPConfig().load(sp1) c.context = "sp" print(c) assert c._sp_endpoints assert c._sp_name assert c._sp_idp md = c.metadata assert isinstance(md, MetadataStore) assert len(c._sp_idp) == 1 assert list(c._sp_idp.keys()) == ["urn:mace:example.com:saml:roland:idp"] assert list(c._sp_idp.values()) == [{'single_sign_on_service': { 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect': 'http://localhost:8088/sso/'}}] assert c.only_use_keys_in_metadata def test_2(): c = SPConfig().load(sp2) c.context = "sp" print(c) assert c._sp_endpoints assert c.getattr("endpoints", "sp") assert c._sp_idp assert c._sp_optional_attributes assert c.name assert c._sp_required_attributes assert len(c._sp_idp) == 1 assert list(c._sp_idp.keys()) == [""] assert list(c._sp_idp.values()) == [ "https://example.com/saml2/idp/SSOService.php"] assert c.only_use_keys_in_metadata is True def test_minimum(): minimum = { "entityid": "urn:mace:example.com:saml:roland:sp", "service": { "sp": { "endpoints": { "assertion_consumer_service": ["http://sp.example.org/"], }, "name": "test", "idp": { "": "https://example.com/idp/SSOService.php", }, } }, #"xmlsec_binary" : "/usr/local/bin/xmlsec1", } c = SPConfig().load(minimum) c.context = "sp" assert c is not None def test_idp_1(): c = IdPConfig().load(IDP1) c.context = "idp" print(c) assert c.endpoint("single_sign_on_service")[0] == 'http://localhost:8088/' attribute_restrictions = c.getattr("policy", "idp").get_attribute_restrictions("") assert attribute_restrictions["edupersonaffiliation"][0].match("staff") def test_idp_2(): c = IdPConfig().load(IDP2) c.context = "idp" print(c) assert c.endpoint("single_logout_service", BINDING_SOAP) == [] assert c.endpoint("single_logout_service", BINDING_HTTP_REDIRECT) == ["http://localhost:8088/"] attribute_restrictions = c.getattr("policy", "idp").get_attribute_restrictions("") assert attribute_restrictions["edupersonaffiliation"][0].match("staff") def test_wayf(): c = SPConfig().load_file("server_conf") c.context = "sp" idps = c.metadata.with_descriptor("idpsso") ent = list(idps.values())[0] assert name(ent) == 'Example Co.' assert name(ent, "se") == 'Exempel AB' c.setup_logger() assert root_logger.level != logging.NOTSET assert root_logger.level == logging.INFO assert len(root_logger.handlers) == 1 assert isinstance(root_logger.handlers[0], logging.handlers.RotatingFileHandler) handler = root_logger.handlers[0] assert handler.backupCount == 5 try: assert handler.maxBytes == 100000 except AssertionError: assert handler.maxBytes == 500000 assert handler.mode == "a" assert root_logger.name == "saml2" assert root_logger.level == 20 def test_conf_syslog(): c = SPConfig().load_file("server_conf_syslog") c.context = "sp" # otherwise the logger setting is not changed root_logger.level = logging.NOTSET while root_logger.handlers: handler = root_logger.handlers[-1] root_logger.removeHandler(handler) handler.flush() handler.close() print(c.logger) c.setup_logger() assert root_logger.level != logging.NOTSET assert root_logger.level == logging.INFO assert len(root_logger.handlers) == 1 assert isinstance(root_logger.handlers[0], logging.handlers.SysLogHandler) handler = root_logger.handlers[0] print(handler.__dict__) assert handler.facility == "local3" assert handler.address == ('localhost', 514) if ((sys.version_info.major == 2 and sys.version_info.minor >= 7) or sys.version_info.major > 2): assert handler.socktype == 2 else: pass assert root_logger.name == "saml2" assert root_logger.level == 20 #noinspection PyUnresolvedReferences def test_3(): cnf = Config() cnf.load_file(dotname("sp_1_conf")) assert cnf.entityid == "urn:mace:example.com:saml:roland:sp" assert cnf.debug == 1 assert cnf.key_file == full_path("test.key") assert cnf.cert_file == full_path("test.pem") #assert cnf.xmlsec_binary == "/usr/local/bin/xmlsec1" assert cnf.accepted_time_diff == 60 assert cnf.secret == "0123456789" assert cnf.metadata is not None assert cnf.attribute_converters is not None def test_sp(): cnf = SPConfig() cnf.load_file(dotname("sp_1_conf")) assert cnf.endpoint("assertion_consumer_service") == \ ["http://lingon.catalogix.se:8087/"] def test_dual(): cnf = Config().load_file(dotname("idp_sp_conf")) spe = cnf.getattr("endpoints", "sp") idpe = cnf.getattr("endpoints", "idp") assert spe assert idpe assert spe != idpe def test_ecp(): cnf = SPConfig() cnf.load(ECP_SP) assert cnf.endpoint("assertion_consumer_service") == \ ["http://lingon.catalogix.se:8087/"] eid = cnf.ecp_endpoint("130.239.16.3") assert eid == "http://example.com/idp" eid = cnf.ecp_endpoint("130.238.20.20") assert eid is None def test_assertion_consumer_service(): c = IdPConfig() c.load_file(dotname("idp_conf")) c.context = "idp" c.metadata.load("local", full_path("InCommon-metadata.xml")) entity_id = "https://www.zimride.com/shibboleth" acs = c.metadata.assertion_consumer_service(entity_id) assert len(acs) == 1 assert acs[0][ "location"] == 'https://www.zimride.com/Shibboleth.sso/SAML2/POST' def test_crypto_backend(): idpc = IdPConfig() idpc.load(IDP_XMLSECURITY) assert idpc.crypto_backend == 'XMLSecurity' sec = security_context(idpc) assert isinstance(sec.crypto, CryptoBackendXMLSecurity) def test_unset_force_authn(): cnf = SPConfig().load(sp1) assert bool(cnf.getattr('force_authn', 'sp')) == False def test_set_force_authn(): cnf = SPConfig().load(sp2) assert bool(cnf.getattr('force_authn', 'sp')) == True if __name__ == "__main__": test_crypto_backend()