├── .editorconfig ├── .github ├── issue_template.md └── pull_request_template.md ├── .gitignore ├── .travis.yml ├── CHANGELOG.md ├── CONTRIBUTING.md ├── DEVELOPERS.md ├── LICENSE ├── README.md ├── RELEASE.md ├── SECURITY.md ├── docs ├── FIXUP_COMMITS.md ├── Makefile ├── _static │ └── ViewmeonGitHub.png ├── conf.py ├── examples │ ├── idp.rst │ ├── index.rst │ └── sp.rst ├── howto │ ├── config.rst │ └── index.rst ├── index.rst ├── install.rst ├── make.bat ├── make.sh └── sp_test │ └── internal.rst ├── example ├── README ├── all.sh ├── attributemaps │ ├── adfs_v1x.py │ ├── adfs_v20.py │ ├── basic.py │ ├── saml_uri.py │ └── shibboleth_uri.py ├── create_key.sh ├── idp2 │ ├── htdocs │ │ └── login.mako │ ├── idp.py │ ├── idp.xml │ ├── idp_conf.py.example │ ├── idp_user.py │ ├── idp_uwsgi.py │ ├── pki │ │ ├── mycert.pem │ │ └── mykey.pem │ ├── static │ │ └── css │ │ │ └── main.css │ └── templates │ │ └── root.mako ├── idp2_repoze │ ├── htdocs │ │ └── login.mako │ ├── idp.py │ ├── idp.subject │ ├── idp_conf.py.example │ ├── idp_user.py │ ├── modules │ │ ├── login.mako.py │ │ └── root.mako.py │ ├── pki │ │ ├── mycert.pem │ │ └── mykey.pem │ ├── static │ │ └── css │ │ │ └── main.css │ └── templates │ │ └── root.mako ├── requirements.txt ├── sp-repoze │ ├── attributemaps │ │ ├── basic.py │ │ ├── saml_uri.py │ │ └── shibboleth_uri.py │ ├── pki │ │ ├── certgeneration.py │ │ ├── mycert.pem │ │ └── mykey.pem │ ├── sp.py │ ├── sp_conf.example │ ├── sp_conf.py.example │ └── who.ini └── sp-wsgi │ ├── pki │ ├── mycert.pem │ └── mykey.pem │ ├── service_conf.py.example │ ├── sp.py │ └── sp_conf.py.example ├── poetry.lock ├── pyproject.toml ├── script ├── __init__.py ├── filter_testcase_ids.py ├── idp_testdrv.py ├── sp_testdrv.py └── utility │ ├── filter_testcase_ids.py │ ├── run_available_sp_tests.sh │ ├── run_list_of_tests.py │ └── run_oper.sh ├── src ├── saml2 │ ├── __init__.py │ ├── algsupport.py │ ├── argtree.py │ ├── assertion.py │ ├── attribute_converter.py │ ├── attribute_resolver.py │ ├── attributemaps │ │ ├── __init__.py │ │ ├── adfs_v1x.py │ │ ├── adfs_v20.py │ │ ├── basic.py │ │ ├── saml_uri.py │ │ └── shibboleth_uri.py │ ├── authn.py │ ├── authn_context │ │ ├── __init__.py │ │ ├── ippword.py │ │ ├── mobiletwofactor.py │ │ ├── ppt.py │ │ ├── pword.py │ │ ├── sslcert.py │ │ └── timesync.py │ ├── cache.py │ ├── cert.py │ ├── client.py │ ├── client_base.py │ ├── config.py │ ├── country_codes.py │ ├── cryptography │ │ ├── __init__.py │ │ ├── asymmetric.py │ │ ├── errors.py │ │ ├── pki.py │ │ └── symmetric.py │ ├── data │ │ ├── __init__.py │ │ ├── schemas │ │ │ ├── __init__.py │ │ │ ├── eidas-schema-attribute-legalperson.xsd │ │ │ ├── eidas-schema-attribute-naturalperson.xsd │ │ │ ├── eidas-schema-metadata-servicelist.xsd │ │ │ ├── eidas-schema-saml-extensions.xsd │ │ │ ├── envelope.xsd │ │ │ ├── saml-schema-assertion-2.0.xsd │ │ │ ├── saml-schema-authn-context-2.0.xsd │ │ │ ├── saml-schema-authn-context-auth-telephony-2.0.xsd │ │ │ ├── saml-schema-authn-context-ip-2.0.xsd │ │ │ ├── saml-schema-authn-context-ippword-2.0.xsd │ │ │ ├── saml-schema-authn-context-kerberos-2.0.xsd │ │ │ ├── saml-schema-authn-context-mobileonefactor-reg-2.0.xsd │ │ │ ├── saml-schema-authn-context-mobileonefactor-unreg-2.0.xsd │ │ │ ├── saml-schema-authn-context-mobiletwofactor-reg-2.0.xsd │ │ │ ├── saml-schema-authn-context-mobiletwofactor-unreg-2.0.xsd │ │ │ ├── saml-schema-authn-context-nomad-telephony-2.0.xsd │ │ │ ├── saml-schema-authn-context-personal-telephony-2.0.xsd │ │ │ ├── saml-schema-authn-context-pgp-2.0.xsd │ │ │ ├── saml-schema-authn-context-ppt-2.0.xsd │ │ │ ├── saml-schema-authn-context-pword-2.0.xsd │ │ │ ├── saml-schema-authn-context-session-2.0.xsd │ │ │ ├── saml-schema-authn-context-smartcard-2.0.xsd │ │ │ ├── saml-schema-authn-context-smartcardpki-2.0.xsd │ │ │ ├── saml-schema-authn-context-softwarepki-2.0.xsd │ │ │ ├── saml-schema-authn-context-spki-2.0.xsd │ │ │ ├── saml-schema-authn-context-srp-2.0.xsd │ │ │ ├── saml-schema-authn-context-sslcert-2.0.xsd │ │ │ ├── saml-schema-authn-context-telephony-2.0.xsd │ │ │ ├── saml-schema-authn-context-timesync-2.0.xsd │ │ │ ├── saml-schema-authn-context-types-2.0.xsd │ │ │ ├── saml-schema-authn-context-x509-2.0.xsd │ │ │ ├── saml-schema-authn-context-xmldsig-2.0.xsd │ │ │ ├── saml-schema-dce-2.0.xsd │ │ │ ├── saml-schema-ecp-2.0.xsd │ │ │ ├── saml-schema-metadata-2.0.xsd │ │ │ ├── saml-schema-protocol-2.0.xsd │ │ │ ├── saml-schema-x500-2.0.xsd │ │ │ ├── saml-schema-xacml-2.0.xsd │ │ │ ├── saml-subject-id-attr-v1.0.xsd │ │ │ ├── sstc-metadata-attr.xsd │ │ │ ├── sstc-req-attr-ext.xsd │ │ │ ├── sstc-saml-attribute-ext.xsd │ │ │ ├── sstc-saml-metadata-algsupport-v1.0.xsd │ │ │ ├── sstc-saml-metadata-ui-v1.0.xsd │ │ │ ├── xenc-schema-11.xsd │ │ │ ├── xenc-schema.xsd │ │ │ ├── xml.xsd │ │ │ └── xmldsig-core-schema.xsd │ │ └── templates │ │ │ ├── __init__.py │ │ │ └── template_enc.xml │ ├── discovery.py │ ├── ecp.py │ ├── ecp_client.py │ ├── entity.py │ ├── entity_category │ │ ├── __init__.py │ │ ├── at_egov_pvp2.py │ │ ├── edugain.py │ │ ├── incommon.py │ │ ├── refeds.py │ │ └── swamid.py │ ├── eptid.py │ ├── extension │ │ ├── __init__.py │ │ ├── algsupport.py │ │ ├── dri.py │ │ ├── idpdisc.py │ │ ├── mdattr.py │ │ ├── mdrpi.py │ │ ├── mdui.py │ │ ├── pefim.py │ │ ├── reqinit.py │ │ ├── requested_attributes.py │ │ ├── shibmd.py │ │ └── sp_type.py │ ├── filter.py │ ├── httpbase.py │ ├── httputil.py │ ├── ident.py │ ├── mcache.py │ ├── md.py │ ├── mdbcache.py │ ├── mdie.py │ ├── mdstore.py │ ├── metadata.py │ ├── mongo_store.py │ ├── pack.py │ ├── population.py │ ├── profile │ │ ├── __init__.py │ │ ├── ecp.py │ │ ├── paos.py │ │ └── samlec.py │ ├── request.py │ ├── response.py │ ├── s2repoze │ │ ├── __init__.py │ │ └── plugins │ │ │ ├── __init__.py │ │ │ ├── challenge_decider.py │ │ │ ├── entitlement.py │ │ │ ├── formswithhidden.py │ │ │ ├── ini.py │ │ │ └── sp.py │ ├── s_utils.py │ ├── saml.py │ ├── samlp.py │ ├── schema │ │ ├── __init__.py │ │ ├── soap.py │ │ ├── soapenv.py │ │ └── wsdl.py │ ├── sdb.py │ ├── server.py │ ├── sigver.py │ ├── soap.py │ ├── time_util.py │ ├── tools │ │ ├── make_metadata.py │ │ ├── mdexport.py │ │ ├── mdexport_test.py │ │ ├── mdimport.py │ │ ├── merge_metadata.py │ │ ├── parse_xsd2.py │ │ ├── sync_attrmaps.py │ │ ├── update_metadata.sh │ │ └── verify_metadata.py │ ├── userinfo │ │ ├── __init__.py │ │ └── ldapinfo.py │ ├── validate.py │ ├── version.py │ ├── virtual_org.py │ ├── ws │ │ ├── __init__.py │ │ ├── wsaddr.py │ │ ├── wspol.py │ │ ├── wssec.py │ │ ├── wstrust.py │ │ └── wsutil.py │ ├── xml │ │ ├── __init__.py │ │ └── schema │ │ │ └── __init__.py │ ├── xmldsig │ │ └── __init__.py │ └── xmlenc │ │ └── __init__.py ├── saml2test │ ├── __init__.py │ ├── check.py │ ├── interaction.py │ ├── opfunc.py │ ├── status.py │ └── tool.py └── utility │ ├── __init__.py │ └── metadata.py └── tests ├── InCommon-metadata.xml ├── SWITCHaaiRootCA.crt.pem ├── _test_80_p11_backend.py ├── aa_conf.py ├── attribute.map ├── attribute_response.xml ├── attribute_statement_data.py ├── attributemaps ├── basic.py ├── saml_uri.py └── shibboleth_uri.py ├── conftest.py ├── create_certs.sh ├── disco_conf.py ├── ds_data.py ├── ecp_soap.xml ├── edugain.pem ├── eidas_response.xml ├── empty_metadata_file.xml ├── enc_tmpl.xml ├── encrypted_attribute_statement.xml ├── entity_anonymous_sp.xml ├── entity_cat_re.xml ├── entity_cat_re_nren.xml ├── entity_cat_rs.xml ├── entity_cat_sfs_hei.xml ├── entity_esi_and_coco_sp.xml ├── entity_no_friendly_name_sp.xml ├── entity_personalized_sp.xml ├── entity_pseudonymous_sp.xml ├── extended.xml ├── extra_lines.crt ├── fakeIDP.py ├── get_metadata.sh ├── idp.xml ├── idp_2.xml ├── idp_aa.xml ├── idp_all.xml ├── idp_all_conf.py ├── idp_conf.py ├── idp_conf_ec.py ├── idp_conf_mdb.py ├── idp_conf_mdb2.py ├── idp_conf_sp_no_encrypt.py ├── idp_conf_verify_cert.py ├── idp_example.xml ├── idp_slo_redirect.xml ├── idp_slo_redirect_conf.py ├── idp_soap.xml ├── idp_soap_conf.py ├── idp_sp_conf.py ├── idp_test ├── config.py.example └── target_idp.py ├── idp_uiinfo.xml ├── inc-md-cert.pem ├── invalid_metadata_file.xml ├── kalmar2.pem ├── keys ├── mycert.pem └── mykey.pem ├── localhost.py ├── malformed.crt ├── md_data.py ├── metadata.aaitest.xml ├── metadata.xml ├── metadata ├── idp.xml ├── idp_2.xml └── idp_uiinfo.xml ├── metadata_cert.xml ├── metadata_example.xml ├── metadata_sp_1.xml ├── metadata_sp_1_no_encryption.xml ├── metadata_sp_2.xml ├── metasp.xml ├── myentitycategory.py ├── okta_assertion ├── okta_response.xml ├── openssl.cnf ├── otest_61_makemeta.py ├── pathutils.py ├── pdp_meta.xml ├── pki ├── cert.crt ├── test_3.crt └── test_3.key ├── pre_enc.xml ├── private_key.pem ├── pubkey.pem ├── remote_data ├── InCommon-metadata-export.xml └── metadata.aaitest.xml ├── restrictive_idp_conf.py ├── root_cert ├── localhost.ca.crt └── localhost.ca.key ├── saml2_data.py ├── saml2_response.xml ├── saml_false_signed.xml ├── saml_hok.xml ├── saml_hok_invalid.xml ├── saml_signed.xml ├── saml_unsigned.xml ├── samlp_data.py ├── server2_conf.py ├── server3_conf.py ├── server_conf.py ├── server_conf_syslog.py ├── servera.xml ├── servera_conf.py ├── simplesamlphp_authnresponse.xml ├── sp.xml ├── sp_0.metadata ├── sp_1_conf.py ├── sp_2_conf.py ├── sp_conf_nameidpolicy.py ├── sp_mdext_conf.py ├── sp_slo_redirect.xml ├── sp_slo_redirect_conf.py ├── sp_test ├── config.py └── targetsp.py ├── swamid-1.0.xml ├── swamid-2.0.xml ├── swamid.md ├── test.key ├── test.key.p8 ├── test.pem ├── test_00_xmldsig.py ├── test_01_xmlenc.py ├── test_02_saml.py ├── test_03_saml2.py ├── test_04_samlp.py ├── test_05_md.py ├── test_06_setarg.py ├── test_1.crt ├── test_1.der ├── test_1.key ├── test_10_time_util.py ├── test_12_s_utils.py ├── test_13_validate.py ├── test_19_attribute_converter.py ├── test_2.crt ├── test_2.key ├── test_20_assertion.py ├── test_22_mdie.py ├── test_30_mdstore.py ├── test_30_mdstore_old.py ├── test_31_config.py ├── test_32_cache.py ├── test_33_identifier.py ├── test_34_population.py ├── test_36_mdbcache.py ├── test_37_entity_categories.py ├── test_38_metadata_filter.py ├── test_39_metadata.py ├── test_40_sigver.py ├── test_41_response.py ├── test_42_enc.py ├── test_43_soap.py ├── test_44_authnresp.py ├── test_50_server.py ├── test_51_client.py ├── test_52_default_sign_alg.py ├── test_60_sp.py ├── test_62_vo.py ├── test_63_ecp.py ├── test_64_artifact.py ├── test_65_authn_query.py ├── test_66_name_id_mapping.py ├── test_67_manage_name_id.py ├── test_68_assertion_id.py ├── test_69_discovery.py ├── test_70_redirect_signing.py ├── test_71_authn_request.py ├── test_72_eptid.py ├── test_75_mongodb.py ├── test_76_metadata_in_mdb.py ├── test_77_authn_context.py ├── test_81_certificates.py ├── test_82_pefim.py ├── test_83_md_extensions.py ├── test_88_nsprefix.py ├── test_89_http_post_relay_state.py ├── test_92_aes.py ├── test_93_hok.py ├── test_94_read_cert.py ├── test_chain.pem ├── test_chain_with_linebreaks.pem ├── test_schema_validator.py ├── test_xmlsec1_key_data.py ├── test_xsw.py ├── urn-mace-swami.se-swamid-test-1.0-metadata.xml ├── uu.xml ├── vo_metadata.xml ├── xmlsec1-keydata ├── signed-assertion-random-embedded-cert.xml ├── signed-assertion-with-hmac.xml └── signed-response-with-hmac.xml └── xsw ├── signed-xsw-assertion-assertion.xml ├── signed-xsw-assertion-extensions.xml ├── signed-xsw-assertion-in-assertion-first-sig.xml ├── signed-xsw-assertion-wrapper.xml └── signed-xsw-response-in-response-first-sig.xml /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | tab_width = 4 8 | end_of_line = lf 9 | insert_final_newline = true 10 | trim_trailing_whitespace = true 11 | max_line_length = 120 12 | 13 | [{*.y{a,}ml,*.html,*.xhtml,*.xml,*.xsd}] 14 | indent_size = 2 15 | tab_width = 2 16 | -------------------------------------------------------------------------------- /.github/issue_template.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Code Version 4 | 5 | 6 | ### Expected Behavior 7 | 8 | 9 | ### Current Behavior 10 | 11 | 12 | ### Possible Solution 13 | 14 | 15 | ### Steps to Reproduce 16 | 17 | 18 | 19 | 20 | 1. 21 | 2. 22 | 3. 23 | 4. 24 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ### Description 2 | 3 | ##### The feature or problem addressed by this PR 4 | 5 | 6 | 7 | 8 | 9 | 10 | ##### What your changes do and why you chose this solution 11 | 12 | 13 | 14 | 15 | ### Checklist 16 | 17 | * [ ] Checked that no other issues or pull requests exist for the same issue/change 18 | * [ ] Added tests covering the new functionality 19 | * [ ] Updated documentation OR the change is too minor to be documented 20 | * [ ] Updated CHANGELOG.md OR changes are insignificant 21 | -------------------------------------------------------------------------------- /RELEASE.md: -------------------------------------------------------------------------------- 1 | ## Release instructions 2 | 3 | When releasing a new version, the following steps should be taken: 4 | 5 | 1. Make sure the package metadata in `pyproject.toml` is up-to-date. 6 | 7 | ``` 8 | poetry check 9 | ``` 10 | 11 | 2. Make sure all automated tests pass: 12 | 13 | ``` 14 | poetry run pytest 15 | ``` 16 | 17 | 3. Bump the version of the package 18 | 19 | ``` 20 | poetry version -- X.Y.Z 21 | ``` 22 | 23 | 4. Update the [CHANGELOG.md] 24 | 25 | 5. Commit and sign the changes: 26 | 27 | ``` 28 | git add -u # CHANGELOG.md pyproject.toml 29 | git commit -v -s -m "Release version X.Y.Z" 30 | ``` 31 | 32 | 6. Create a signed release [tag]: 33 | 34 | ``` 35 | git tag -a -s vX.Y.Z -m "Version X.Y.Z" 36 | ``` 37 | 38 | 7. Push the changes and the release to Github: 39 | 40 | ``` 41 | git push --follow-tags 42 | ``` 43 | 44 | 8. Publish the release on PyPI: 45 | 46 | ``` 47 | poetry publish --build 48 | ``` 49 | 50 | 9. Send an email to the pysaml2 list announcing this release 51 | 52 | 53 | [VERSION]: https://github.com/IdentityPython/pysaml2/blob/master/VERSION 54 | [CHANGELOG.md]: https://github.com/IdentityPython/pysaml2/blob/master/CHANGELOG.md 55 | [docutils]: http://docutils.sourceforge.net/ 56 | [branch]: https://git-scm.com/book/en/v2/Git-Branching-Branches-in-a-Nutshell 57 | [tag]: https://git-scm.com/book/en/v2/Git-Basics-Tagging#_annotated_tags 58 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | # Security Policy 2 | 3 | You can find more information on security incidents 4 | on the [IdPy security webpage](https://idpy.org/security/). 5 | 6 | You read on the [incident response policy](https://github.com/IdentityPython/Governance/blob/master/idpy-incidentresponse.md) 7 | under the [governance documentation](https://github.com/IdentityPython/Governance). 8 | 9 | 10 | ## Incident report / Reporting a Vulnerability 11 | 12 | Anyone can submit a potential security vulnerability to `incident-response@idpy.org`. 13 | The incident-response team will verify the issue and contact you on how this will be 14 | handled. 15 | 16 | 17 | ## Public Discussions 18 | 19 | When a new vulnerability is reported and verified, a new security advisory is created on 20 | GitHub and the issue is assigned a CVE identifier. Progress on the mitigation is tracked 21 | on a private fork, where the incident-response team and developers communicate to fix 22 | the issue. 23 | 24 | When the fix is ready, a release plan is prepared and all communication channels are 25 | used to notify the community of the presence of a new issue and the expected release 26 | plan. This allows the community time to prepare for a security upgrade. (Notice that 27 | security fixes are not backported at the moment.) 28 | 29 | When the advisory is published, GitHub automatically notifies all associated projects of 30 | the published advisory. Projects that use IdPy projects as dependencies should 31 | automatically get Pull Requests by dependabot. Additionally, all communication channels 32 | are used again, to notify the community of the release of a new version of the affected 33 | software that contains the relevant fixes that mitigate the reported issue. 34 | 35 | 36 | ## Supported versions 37 | 38 | Notice, that security fixes are not backported at the moment to older releases than the 39 | latest. The team does not have the capacity to guarantee that these backports will exist. 40 | You are advised to be prepared to upgrade to the latest version once the fix is out. 41 | -------------------------------------------------------------------------------- /docs/_static/ViewmeonGitHub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IdentityPython/pysaml2/0252ec96058c87c43f89e05d978e72b6ba2e6978/docs/_static/ViewmeonGitHub.png -------------------------------------------------------------------------------- /docs/examples/idp.rst: -------------------------------------------------------------------------------- 1 | .. _example_idp: 2 | 3 | An extremely simple example of a SAML2 identity provider. 4 | ======================================================== 5 | 6 | There are 2 example IDPs in the project's example directory: 7 | 8 | * idp2 has a static definition of users: 9 | 10 | * user attributes are defined in idp_user.py 11 | * the password is defined in the PASSWD dict in idp.py 12 | 13 | * idp2_repoze is using repoze.who middleware to perform authentication and attribute retrieval 14 | 15 | Configuration 16 | ------------- 17 | Entity configuration is described in "Configuration of pysaml2 entities" 18 | Server parameters like host and port and various command line parameters are 19 | defined in the main part of idp.py 20 | 21 | Setup: 22 | ****** 23 | 24 | The folder [your path]/pysaml2/example/idp2 contains a file named idp_conf.py.example 25 | 26 | Take the file named idp_conf.py.example and rename it idp_conf.py 27 | 28 | Generate a metadata file based in the configuration file (idp_conf.py) by using the command:: 29 | 30 | make_metadata.py idp_conf.py > idp.xml 31 | 32 | 33 | Run IDP: 34 | ******** 35 | 36 | Open a Terminal:: 37 | 38 | cd [your path]/pysaml2/example/idp2 39 | python idp.py idp_conf 40 | 41 | Note that you should not have the .py extension on the idp_conf.py while running the program 42 | -------------------------------------------------------------------------------- /docs/examples/index.rst: -------------------------------------------------------------------------------- 1 | .. _example_index: 2 | 3 | Quick pysaml2 example 4 | ===================== 5 | 6 | :Release: |version| 7 | :Date: |today| 8 | 9 | In order to confirm that pysaml2 has been installed correctly and are ready to use you could run this basic example 10 | 11 | Contents: 12 | 13 | .. toctree:: 14 | :maxdepth: 1 15 | 16 | sp 17 | idp 18 | 19 | -------------------------------------------------------------------------------- /docs/howto/index.rst: -------------------------------------------------------------------------------- 1 | .. _howto: 2 | 3 | How to use PySAML2 4 | =================== 5 | 6 | :Release: |release| 7 | :Date: |today| 8 | 9 | Before you can use Pysaml2, you'll need to get it installed. 10 | If you have not done it yet, read the :ref:`install` 11 | 12 | Well, now you have it installed and you want to do something. 13 | 14 | And I'm sorry to tell you this; but there isn't really a lot you can do with 15 | this code on its own. 16 | 17 | Sure you can send a AuthenticationRequest to an IdentityProvider or a 18 | AttributeQuery to an AttributeAuthority, but in order to get what they 19 | return you have to sit behind a Web server. Well that is not really true since 20 | the AttributeQuery would be over SOAP and you would get the result over the 21 | connection you have to the AttributeAuthority. 22 | 23 | But anyway, you may get my point. This is middleware stuff! 24 | 25 | PySAML2 is built to fit into a 26 | `WSGI `_ application 27 | 28 | But it can be used in a non-WSGI environment too. 29 | 30 | So you will find descriptions of both cases here. 31 | 32 | The configuration is the same regardless of whether you are using PySAML2 in a 33 | WSGI or non-WSGI environment. 34 | 35 | .. toctree:: 36 | :maxdepth: 1 37 | 38 | config 39 | 40 | 41 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | :Release: |release| 2 | :Date: |today| 3 | 4 | About SAML 2.0 5 | ============== 6 | 7 | SAML 2.0 or Security Assertion Markup Language 2.0 is a version of the SAML standard for exchanging authentication and authorization data between security domains. 8 | 9 | About PySAML2 10 | ============= 11 | 12 | PySAML2 is a pure python implementation of SAML2. It contains all 13 | necessary pieces for building a SAML2 service provider or an identity provider. 14 | The distribution contains examples of both. 15 | Originally written to work in a WSGI environment, there are extensions that 16 | allow you to use it with other frameworks. 17 | 18 | 19 | How to use PySAML2 20 | =================== 21 | 22 | Before you can use Pysaml2, you'll need to get it installed. 23 | If you have not done it yet, read the :ref:`install` 24 | 25 | Well, now you have it installed, and you want to do something. 26 | 27 | And I'm sorry to tell you this, but there isn't really a lot you can do with 28 | this code on it's own. 29 | 30 | Sure you can send a AuthenticationRequest to an IdentityProvider or an 31 | AttributeQuery to an AttributeAuthority but in order to get what they 32 | return you have to sit behind a Web server. Well, that is not really true since 33 | the AttributeQuery would be over SOAP and you would get the result over the 34 | connection you have to the AttributeAuthority. 35 | 36 | But anyway, you may get my point. This is middleware stuff! 37 | 38 | PySAML2 is built to fit into a 39 | `WSGI `_ application 40 | 41 | But it can be used in a non-WSGI environment too. 42 | 43 | So you will find descriptions of both cases here. 44 | 45 | The configuration is the same disregarding whether you are using PySAML2 in a 46 | WSGI or non-WSGI environment. 47 | 48 | 49 | Python compatibility 50 | ^^^^^^^^^^^^^^^^^^^^ 51 | 52 | PySAML2 has transitioned to Python3. Master Apps is maintaining a fork with Python2 53 | compatibility on [GitHub](https://github.com/masterapps-au/pysaml2). 54 | 55 | 56 | Table of contents 57 | ================== 58 | 59 | .. toctree:: 60 | :maxdepth: 2 61 | 62 | install 63 | examples/index 64 | howto/index 65 | sp_test/internal 66 | 67 | 68 | 69 | * :ref:`genindex` 70 | * :ref:`modindex` 71 | * :ref:`search` 72 | 73 | -------------------------------------------------------------------------------- /docs/install.rst: -------------------------------------------------------------------------------- 1 | .. _install: 2 | 3 | Quick install guide 4 | =================== 5 | 6 | Before you can use PySAML2, you'll need to get it installed. This guide 7 | will guide you to a simple, minimal installation. 8 | 9 | Install PySAML2 10 | --------------- 11 | 12 | For all this to work, you need to have Python installed. 13 | The development has been done using 2.7. 14 | There is now a 3.X version. 15 | 16 | Prerequisites 17 | ^^^^^^^^^^^^^ 18 | 19 | You have to have ElementTree, which is either part of your Python distribution 20 | if it's recent enough, or if the Python is too old you have to install it, 21 | for instance by getting it from the Python Package Instance by using 22 | easy_install. 23 | 24 | You also need xmlsec1 which you can download from http://www.aleksey.com/xmlsec/ 25 | 26 | If you're on macOS, you can get xmlsec1 installed from MacPorts or Fink. 27 | 28 | If you're on rhel/centos 7 you will need to install xmlsec1 and xmlsec1-openssl:: 29 | 30 | yum install xmlsec1 xmlsec1-openssl 31 | 32 | Depending on how you are going to use PySAML2 you might also need 33 | 34 | * Mako 35 | * pyASN1 36 | * repoze.who 37 | * python-memcache 38 | * memcached 39 | 40 | Quick build instructions 41 | ^^^^^^^^^^^^^^^^^^^^^^^^ 42 | 43 | Once you have installed all the necessary prerequisites a simple:: 44 | 45 | python setup.py install 46 | 47 | will install the basic code. 48 | 49 | Note for rhel/centos 6: cffi depends on libffi-devel, and cryptography on openssl-devel to compile 50 | So you might want first to do: 51 | yum install libffi-devel openssl-devel 52 | 53 | After this, you ought to be able to run the tests without a hitch. 54 | The tests are based on the pypy test environment, so:: 55 | 56 | cd tests 57 | pip install -r test-requirements.txt 58 | pytest 59 | 60 | is what you should use. If you don't have py.test, get it it's part of pypy! 61 | It's really good! 62 | 63 | -------------------------------------------------------------------------------- /docs/make.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | rm -f saml2* 3 | sphinx-apidoc -F -o ../docs/ ../src/saml2 4 | make clean 5 | make html 6 | -------------------------------------------------------------------------------- /example/README: -------------------------------------------------------------------------------- 1 | This is a very simple setup just to check that all your gear are in order. 2 | 3 | The setup consists of one IdP and one SP, in idp2/ and sp-wsgi/ respectively. 4 | 5 | To run the setup do: 6 | 7 | ./all.sh start 8 | 9 | and then use your favourite webbrowser to look at "http://localhost:8087/" 10 | 11 | To shut it down do: 12 | 13 | ./all.sh stop 14 | 15 | The IdP authenticates users using a dictionary built in to idp2/idp.py; 16 | look for the dictionary called PASSWD inside that file. 17 | 18 | Other metadata about the accounts (names, email addresses, etc) are 19 | stored in idp2/idp_user.py. (Note, not all accounts have all such data 20 | defined.) 21 | 22 | The username:password pairs in PASSWD: 23 | 24 | daev0001:qwerty 25 | testuser:qwerty 26 | roland:dianakra 27 | babs:howes 28 | upper:crust 29 | 30 | The SP doesn't do anything but show you the information that the IdP sent. 31 | 32 | Note, the listeners are all configured to bind to localhost (127.0.0.1) only. 33 | If you want to be able to connect to them externally, grep "HOST = '127.0.0.1'" 34 | example/*/*.py and replace 127.0.0.1 with 0.0.0.0 or a specific IP. 35 | 36 | To make it easy, for me :-), both the IdP and the SP uses the same keys. 37 | To generate new keys, run create_key.sh and follow its instructions. 38 | 39 | There are alternate IdP and SP configs in idp2_repoze/ and sp-repoze/ that 40 | are still in flux; do not use them unless you know what you are doing. 41 | 42 | -------------------------------------------------------------------------------- /example/all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | startme() { 4 | cd sp-wsgi 5 | if [ ! -f sp_conf.py ] ; then 6 | cp sp_conf.py.example sp_conf.py 7 | fi 8 | if [ ! -f service_conf.py ] ; then 9 | cp service_conf.py.example service_conf.py 10 | fi 11 | ../../src/saml2/tools/make_metadata.py sp_conf > sp.xml 12 | 13 | cd ../idp2 14 | if [ ! -f idp_conf.py ] ; then 15 | cp idp_conf.py.example idp_conf.py 16 | fi 17 | ../../src/saml2/tools/make_metadata.py idp_conf > idp.xml 18 | 19 | cd ../sp-wsgi 20 | ./sp.py sp_conf & 21 | 22 | cd ../idp2 23 | ./idp.py idp_conf & 24 | 25 | cd .. 26 | } 27 | 28 | stopme() { 29 | pkill -f "sp.py" 30 | pkill -f "idp.py" 31 | } 32 | 33 | case "$1" in 34 | start) startme ;; 35 | stop) stopme ;; 36 | restart) stopme; startme ;; 37 | *) echo "usage: $0 start|stop|restart" >&2 38 | exit 1 39 | ;; 40 | esac 41 | -------------------------------------------------------------------------------- /example/attributemaps/adfs_v1x.py: -------------------------------------------------------------------------------- 1 | # See http://technet.microsoft.com/en-us/library/cc733065(v=ws.10).aspx 2 | # and http://technet.microsoft.com/en-us/library/ee913589(v=ws.10).aspx 3 | # for information regarding the default claim types supported by 4 | # Microsoft ADFS v1.x. 5 | 6 | MAP = { 7 | "identifier": "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified", 8 | "fro": { 9 | "http://schemas.xmlsoap.org/claims/commonname": "commonName", 10 | "http://schemas.xmlsoap.org/claims/emailaddress": "emailAddress", 11 | "http://schemas.xmlsoap.org/claims/group": "group", 12 | "http://schemas.xmlsoap.org/claims/upn": "upn", 13 | }, 14 | "to": { 15 | "commonName": "http://schemas.xmlsoap.org/claims/commonname", 16 | "emailAddress": "http://schemas.xmlsoap.org/claims/emailaddress", 17 | "group": "http://schemas.xmlsoap.org/claims/group", 18 | "upn": "http://schemas.xmlsoap.org/claims/upn", 19 | }, 20 | } 21 | -------------------------------------------------------------------------------- /example/create_key.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | cat < 2 | 3 |

Please log in

4 |

5 | To register it's quite simple: enter a login and a password 6 |

7 | 8 |
9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 |
17 |
18 |
19 | 20 |
21 | 22 |
23 |
24 | 26 |
27 | 28 | 29 |
30 | -------------------------------------------------------------------------------- /example/idp2/pki/mycert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFjTCCA3WgAwIBAgIUPqN85BV5eJsrEW8IN2rVIcHGliUwDQYJKoZIhvcNAQEL 3 | BQAwVTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0dGxl 4 | MRowGAYDVQQKDBFweXNhbWwyIERlbW8gQ2VydDELMAkGA1UECwwCSVQwIBcNMjEx 5 | MDA3MDgwNzE1WhgPMjEyMTEwMzEwODA3MTVaMFUxCzAJBgNVBAYTAlVTMQswCQYD 6 | VQQIDAJXQTEQMA4GA1UEBwwHU2VhdHRsZTEaMBgGA1UECgwRcHlzYW1sMiBEZW1v 7 | IENlcnQxCzAJBgNVBAsMAklUMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC 8 | AgEAuWw/2OYDIO3pAZyQFQadOWnOh/ilqWXd66M3gD3tft+zydi2IRTUphhlrVmL 9 | /7UrWjHYfrBbF6UrliwvXfjgdECRAA6ziMlzbc+RYHqw1XhR/Y1DxH1V3OZmidaH 10 | B3oisfXe68v28kTHnq0iGQjivkRaMWZXrMSQfakm5ZQz6HC92DgxsOXcSTMlCETN 11 | lziEik2C/4mhA96p+upQPoAC3e9Dn7lNUS2oJT/cZBggJOtLScV6Cyzv+k+u2MCu 12 | ddzenrFONHBe7MNUYEg7Ho9Utas3y6prH36ZwsArtUPpNwBZrNlVenFeF6rUN80x 13 | hKNwvptlXQqAC+2NIhunYgNRoUHmy5opGBJpjqy0k16k+aac2t9Y/Lc+Q52PSh5Z 14 | e3C9Ip5CxoxA6hMk7IZT1kpsUl6uc8FBX9SLNRovIOC4r1anaxIareQjRab2iw+a 15 | RhTmKlGSXzlaSMS2FZMpT0q73d/rFo1/RKeE2dzG9fXe27jXJTv50J4Y46RF/6/C 16 | 0BWxTAzdBPQj1I5KWVVX9XuZklSbhKv/sGNXxPe5p5hR9rwt2oCrAhHzFZzddrzI 17 | BVRi6UtgnPMg8klnUcHsDt7elCuAF2ouB4Z8YplxhhYJga3NfLmHXYw1R7PBF2Ud 18 | 4FSgkjCO/ME/+FgyUKAaG6qKBMe3FJzuXch314qR1+1xT+ECAwEAAaNTMFEwHQYD 19 | VR0OBBYEFIwaXBfOL6adUTxpgv/NJGZ3gLrIMB8GA1UdIwQYMBaAFIwaXBfOL6ad 20 | UTxpgv/NJGZ3gLrIMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIB 21 | AI4SQxTjjNvV7E903U/l1pd7PoyvM9LxcYkmnjTGMHBoK1/HbpU27s9ZHjodfT7k 22 | k1uMCh6tx7kuaSboWX9BX8Sdgtfj8Y9hs7Bb4MhTH3qJ3LFGK2d0LgK+lTFNRtmy 23 | JfExfmDcXi/YO+0IVd3mK49BfGdpN8lT7KjtcbUk4gObGLuN/XGCTjFPmyIRJfgZ 24 | K6kLWskthddoa4beqzpVQnbn5TBtShzlzmrjmU5YieP54IoQXDzE5mWn4PFuMsZ9 25 | wCLcSwRYiP3fUMGCChs64QADuIjZYB8xl3CHKxZMu9Ki+dHQ2W1X1UyozzF5i19S 26 | rhVdYXlumVOS6jnAjZ/UNIS1mYCZk5AT8jtsCBkgmNmhZab3BNbLklLz2AYi9fpI 27 | CYkHei2fYlAHKXPFZFKIHmG3mqpgu9nClyelrjRsxvrvkJ4Di/aPZ3qA+I006uYw 28 | Ti+NsL6eXBhIPUuA/NHax53kzJSW/AcjRhSKriKdSqD+lLOoQ+z9ZnZXeBKCWzGU 29 | 9bcMUk1x2pJJJBI7AJC/ObAbJDCtQreZZE+u+CABhkwbLPkWx8ZgS9a0/gBVmIJ3 30 | 4qsVmrv0sHnk1JQoFkMNT2oQmCuoA17HtfqxGyEPCxEJu/kqgK3fviKgd1MEP4Y7 31 | 3F6yVtiEWnspy4cQqr6pHBBggDbjve6DPNsIM1jHGUsR 32 | -----END CERTIFICATE----- 33 | -------------------------------------------------------------------------------- /example/idp2/static/css/main.css: -------------------------------------------------------------------------------- 1 | /* Sample css file */ 2 | 3 | -------------------------------------------------------------------------------- /example/idp2/templates/root.mako: -------------------------------------------------------------------------------- 1 | <% self.seen_css = set() %> 2 | <%def name="css_link(path, media='')" filter="trim"> 3 | % if path not in self.seen_css: 4 | 5 | % endif 6 | <% self.seen_css.add(path) %> 7 | 8 | <%def name="css()" filter="trim"> 9 | ${css_link('/static/css/main.css', 'screen')} 10 | 11 | <%def name="pre()" filter="trim"> 12 |
13 |

Login

14 |
15 | 16 | <%def name="post()" filter="trim"> 17 |
18 | 21 |
22 | 23 | ## 25 | 26 | IDP test login 27 | ${self.css()} 28 | 29 | 30 | 31 | ${pre()} 32 | ## ${comps.dict_to_table(pageargs)} 33 | ##

34 | ${next.body()} 35 | ${post()} 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/idp2_repoze/htdocs/login.mako: -------------------------------------------------------------------------------- 1 | <%inherit file="root.mako"/> 2 | 3 |

Please log in

4 |

5 | To register it's quite simple: enter a login and a password 6 |

7 | 8 |
9 | 10 | 11 | 12 | 13 |
14 | 15 |
16 |
17 |
18 |
19 | 20 |
21 | 22 |
23 |
24 | 26 |
27 | 28 | 29 |
30 | -------------------------------------------------------------------------------- /example/idp2_repoze/idp.subject: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IdentityPython/pysaml2/0252ec96058c87c43f89e05d978e72b6ba2e6978/example/idp2_repoze/idp.subject -------------------------------------------------------------------------------- /example/idp2_repoze/idp_user.py: -------------------------------------------------------------------------------- 1 | USERS = { 2 | "haho0032": { 3 | "sn": "Hoerberg", 4 | "givenName": "Hans", 5 | "eduPersonScopedAffiliation": "staff@example.com", 6 | "eduPersonPrincipalName": "haho@example.com", 7 | "uid": "haho", 8 | "eduPersonTargetedID": "one!for!all", 9 | "c": "SE", 10 | "o": "Example Co.", 11 | "ou": "IT", 12 | "initials": "P", 13 | "schacHomeOrganization": "example.com", 14 | "email": "hans@example.com", 15 | "displayName": "Hans Hoerberg", 16 | "labeledURL": "http://www.example.com/haho My homepage", 17 | "norEduPersonNIN": "SE199012315555", 18 | }, 19 | "roland": { 20 | "sn": "Hedberg", 21 | "givenName": "Roland", 22 | "eduPersonScopedAffiliation": "staff@example.com", 23 | "eduPersonPrincipalName": "rohe@example.com", 24 | "uid": "rohe", 25 | "eduPersonTargetedID": "one!for!all", 26 | "c": "SE", 27 | "o": "Example Co.", 28 | "ou": "IT", 29 | "initials": "P", 30 | # "schacHomeOrganization": "example.com", 31 | "email": "roland@example.com", 32 | "displayName": "P. Roland Hedberg", 33 | "labeledURL": "http://www.example.com/rohe My homepage", 34 | "norEduPersonNIN": "SE197001012222", 35 | }, 36 | "babs": {"surname": "Babs", "givenName": "Ozzie", "eduPersonAffiliation": "affiliate"}, 37 | "upper": {"surname": "Jeter", "givenName": "Derek", "eduPersonAffiliation": "affiliate"}, 38 | } 39 | 40 | EXTRA = { 41 | "roland": { 42 | "eduPersonEntitlement": "urn:mace:swamid.se:foo:bar", 43 | "schacGender": "male", 44 | "schacUserPresenceID": "skype:pepe.perez", 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /example/idp2_repoze/pki/mycert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFjTCCA3WgAwIBAgIUPqN85BV5eJsrEW8IN2rVIcHGliUwDQYJKoZIhvcNAQEL 3 | BQAwVTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0dGxl 4 | MRowGAYDVQQKDBFweXNhbWwyIERlbW8gQ2VydDELMAkGA1UECwwCSVQwIBcNMjEx 5 | MDA3MDgwNzE1WhgPMjEyMTEwMzEwODA3MTVaMFUxCzAJBgNVBAYTAlVTMQswCQYD 6 | VQQIDAJXQTEQMA4GA1UEBwwHU2VhdHRsZTEaMBgGA1UECgwRcHlzYW1sMiBEZW1v 7 | IENlcnQxCzAJBgNVBAsMAklUMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC 8 | AgEAuWw/2OYDIO3pAZyQFQadOWnOh/ilqWXd66M3gD3tft+zydi2IRTUphhlrVmL 9 | /7UrWjHYfrBbF6UrliwvXfjgdECRAA6ziMlzbc+RYHqw1XhR/Y1DxH1V3OZmidaH 10 | B3oisfXe68v28kTHnq0iGQjivkRaMWZXrMSQfakm5ZQz6HC92DgxsOXcSTMlCETN 11 | lziEik2C/4mhA96p+upQPoAC3e9Dn7lNUS2oJT/cZBggJOtLScV6Cyzv+k+u2MCu 12 | ddzenrFONHBe7MNUYEg7Ho9Utas3y6prH36ZwsArtUPpNwBZrNlVenFeF6rUN80x 13 | hKNwvptlXQqAC+2NIhunYgNRoUHmy5opGBJpjqy0k16k+aac2t9Y/Lc+Q52PSh5Z 14 | e3C9Ip5CxoxA6hMk7IZT1kpsUl6uc8FBX9SLNRovIOC4r1anaxIareQjRab2iw+a 15 | RhTmKlGSXzlaSMS2FZMpT0q73d/rFo1/RKeE2dzG9fXe27jXJTv50J4Y46RF/6/C 16 | 0BWxTAzdBPQj1I5KWVVX9XuZklSbhKv/sGNXxPe5p5hR9rwt2oCrAhHzFZzddrzI 17 | BVRi6UtgnPMg8klnUcHsDt7elCuAF2ouB4Z8YplxhhYJga3NfLmHXYw1R7PBF2Ud 18 | 4FSgkjCO/ME/+FgyUKAaG6qKBMe3FJzuXch314qR1+1xT+ECAwEAAaNTMFEwHQYD 19 | VR0OBBYEFIwaXBfOL6adUTxpgv/NJGZ3gLrIMB8GA1UdIwQYMBaAFIwaXBfOL6ad 20 | UTxpgv/NJGZ3gLrIMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIB 21 | AI4SQxTjjNvV7E903U/l1pd7PoyvM9LxcYkmnjTGMHBoK1/HbpU27s9ZHjodfT7k 22 | k1uMCh6tx7kuaSboWX9BX8Sdgtfj8Y9hs7Bb4MhTH3qJ3LFGK2d0LgK+lTFNRtmy 23 | JfExfmDcXi/YO+0IVd3mK49BfGdpN8lT7KjtcbUk4gObGLuN/XGCTjFPmyIRJfgZ 24 | K6kLWskthddoa4beqzpVQnbn5TBtShzlzmrjmU5YieP54IoQXDzE5mWn4PFuMsZ9 25 | wCLcSwRYiP3fUMGCChs64QADuIjZYB8xl3CHKxZMu9Ki+dHQ2W1X1UyozzF5i19S 26 | rhVdYXlumVOS6jnAjZ/UNIS1mYCZk5AT8jtsCBkgmNmhZab3BNbLklLz2AYi9fpI 27 | CYkHei2fYlAHKXPFZFKIHmG3mqpgu9nClyelrjRsxvrvkJ4Di/aPZ3qA+I006uYw 28 | Ti+NsL6eXBhIPUuA/NHax53kzJSW/AcjRhSKriKdSqD+lLOoQ+z9ZnZXeBKCWzGU 29 | 9bcMUk1x2pJJJBI7AJC/ObAbJDCtQreZZE+u+CABhkwbLPkWx8ZgS9a0/gBVmIJ3 30 | 4qsVmrv0sHnk1JQoFkMNT2oQmCuoA17HtfqxGyEPCxEJu/kqgK3fviKgd1MEP4Y7 31 | 3F6yVtiEWnspy4cQqr6pHBBggDbjve6DPNsIM1jHGUsR 32 | -----END CERTIFICATE----- 33 | -------------------------------------------------------------------------------- /example/idp2_repoze/static/css/main.css: -------------------------------------------------------------------------------- 1 | /* Sample css file */ 2 | 3 | -------------------------------------------------------------------------------- /example/idp2_repoze/templates/root.mako: -------------------------------------------------------------------------------- 1 | <% self.seen_css = set() %> 2 | <%def name="css_link(path, media='')" filter="trim"> 3 | % if path not in self.seen_css: 4 | 5 | % endif 6 | <% self.seen_css.add(path) %> 7 | 8 | <%def name="css()" filter="trim"> 9 | ${css_link('/static/css/main.css', 'screen')} 10 | 11 | <%def name="pre()" filter="trim"> 12 |
13 |

Login

14 |
15 | 16 | <%def name="post()" filter="trim"> 17 |
18 | 21 |
22 | 23 | ## 25 | 26 | IDP test login 27 | ${self.css()} 28 | 29 | 30 | 31 | ${pre()} 32 | ## ${comps.dict_to_table(pageargs)} 33 | ##

34 | ${next.body()} 35 | ${post()} 36 | 37 | 38 | -------------------------------------------------------------------------------- /example/requirements.txt: -------------------------------------------------------------------------------- 1 | mako 2 | cherrypy>14.0.2 3 | -------------------------------------------------------------------------------- /example/sp-repoze/pki/certgeneration.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from saml2.cert import OpenSSLWrapper 3 | 4 | 5 | __author__ = "haho0032" 6 | 7 | 8 | cert_info_ca = { 9 | "cn": "localhost.ca", 10 | "country_code": "se", 11 | "state": "ac", 12 | "city": "umea", 13 | "organization": "ITS Umea University", 14 | "organization_unit": "DIRG", 15 | } 16 | 17 | osw = OpenSSLWrapper() 18 | 19 | ca_cert, ca_key = osw.create_certificate(cert_info_ca, request=False, write_to_file=True, cert_dir="./") 20 | -------------------------------------------------------------------------------- /example/sp-repoze/pki/mycert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFjTCCA3WgAwIBAgIUPqN85BV5eJsrEW8IN2rVIcHGliUwDQYJKoZIhvcNAQEL 3 | BQAwVTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0dGxl 4 | MRowGAYDVQQKDBFweXNhbWwyIERlbW8gQ2VydDELMAkGA1UECwwCSVQwIBcNMjEx 5 | MDA3MDgwNzE1WhgPMjEyMTEwMzEwODA3MTVaMFUxCzAJBgNVBAYTAlVTMQswCQYD 6 | VQQIDAJXQTEQMA4GA1UEBwwHU2VhdHRsZTEaMBgGA1UECgwRcHlzYW1sMiBEZW1v 7 | IENlcnQxCzAJBgNVBAsMAklUMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC 8 | AgEAuWw/2OYDIO3pAZyQFQadOWnOh/ilqWXd66M3gD3tft+zydi2IRTUphhlrVmL 9 | /7UrWjHYfrBbF6UrliwvXfjgdECRAA6ziMlzbc+RYHqw1XhR/Y1DxH1V3OZmidaH 10 | B3oisfXe68v28kTHnq0iGQjivkRaMWZXrMSQfakm5ZQz6HC92DgxsOXcSTMlCETN 11 | lziEik2C/4mhA96p+upQPoAC3e9Dn7lNUS2oJT/cZBggJOtLScV6Cyzv+k+u2MCu 12 | ddzenrFONHBe7MNUYEg7Ho9Utas3y6prH36ZwsArtUPpNwBZrNlVenFeF6rUN80x 13 | hKNwvptlXQqAC+2NIhunYgNRoUHmy5opGBJpjqy0k16k+aac2t9Y/Lc+Q52PSh5Z 14 | e3C9Ip5CxoxA6hMk7IZT1kpsUl6uc8FBX9SLNRovIOC4r1anaxIareQjRab2iw+a 15 | RhTmKlGSXzlaSMS2FZMpT0q73d/rFo1/RKeE2dzG9fXe27jXJTv50J4Y46RF/6/C 16 | 0BWxTAzdBPQj1I5KWVVX9XuZklSbhKv/sGNXxPe5p5hR9rwt2oCrAhHzFZzddrzI 17 | BVRi6UtgnPMg8klnUcHsDt7elCuAF2ouB4Z8YplxhhYJga3NfLmHXYw1R7PBF2Ud 18 | 4FSgkjCO/ME/+FgyUKAaG6qKBMe3FJzuXch314qR1+1xT+ECAwEAAaNTMFEwHQYD 19 | VR0OBBYEFIwaXBfOL6adUTxpgv/NJGZ3gLrIMB8GA1UdIwQYMBaAFIwaXBfOL6ad 20 | UTxpgv/NJGZ3gLrIMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIB 21 | AI4SQxTjjNvV7E903U/l1pd7PoyvM9LxcYkmnjTGMHBoK1/HbpU27s9ZHjodfT7k 22 | k1uMCh6tx7kuaSboWX9BX8Sdgtfj8Y9hs7Bb4MhTH3qJ3LFGK2d0LgK+lTFNRtmy 23 | JfExfmDcXi/YO+0IVd3mK49BfGdpN8lT7KjtcbUk4gObGLuN/XGCTjFPmyIRJfgZ 24 | K6kLWskthddoa4beqzpVQnbn5TBtShzlzmrjmU5YieP54IoQXDzE5mWn4PFuMsZ9 25 | wCLcSwRYiP3fUMGCChs64QADuIjZYB8xl3CHKxZMu9Ki+dHQ2W1X1UyozzF5i19S 26 | rhVdYXlumVOS6jnAjZ/UNIS1mYCZk5AT8jtsCBkgmNmhZab3BNbLklLz2AYi9fpI 27 | CYkHei2fYlAHKXPFZFKIHmG3mqpgu9nClyelrjRsxvrvkJ4Di/aPZ3qA+I006uYw 28 | Ti+NsL6eXBhIPUuA/NHax53kzJSW/AcjRhSKriKdSqD+lLOoQ+z9ZnZXeBKCWzGU 29 | 9bcMUk1x2pJJJBI7AJC/ObAbJDCtQreZZE+u+CABhkwbLPkWx8ZgS9a0/gBVmIJ3 30 | 4qsVmrv0sHnk1JQoFkMNT2oQmCuoA17HtfqxGyEPCxEJu/kqgK3fviKgd1MEP4Y7 31 | 3F6yVtiEWnspy4cQqr6pHBBggDbjve6DPNsIM1jHGUsR 32 | -----END CERTIFICATE----- 33 | -------------------------------------------------------------------------------- /example/sp-repoze/sp_conf.example: -------------------------------------------------------------------------------- 1 | from saml2 import BINDING_HTTP_REDIRECT 2 | from saml2.saml import NAME_FORMAT_URI 3 | 4 | HOST = 'localhost' 5 | PORT = 8087 6 | 7 | BASE = "http://%s:%s" % (HOST, PORT) 8 | 9 | CONFIG = { 10 | "entityid": "%s/sp.xml" % BASE, 11 | "description": "My SP", 12 | "service": { 13 | "sp": { 14 | "name": "Rolands SP", 15 | "endpoints": { 16 | "assertion_consumer_service": [BASE], 17 | "single_logout_service": [(BASE + "/slo", 18 | BINDING_HTTP_REDIRECT)], 19 | }, 20 | "required_attributes": ["surname", "givenname", 21 | "edupersonaffiliation"], 22 | "optional_attributes": ["title"], 23 | } 24 | }, 25 | "debug": 1, 26 | "key_file": "pki/mykey.pem", 27 | "cert_file": "pki/mycert.pem", 28 | "attribute_map_dir": "./attributemaps", 29 | "metadata": {"local": ["../idp2/idp.xml"]}, 30 | # -- below used by make_metadata -- 31 | "organization": { 32 | "name": "Exempel AB", 33 | "display_name": [("Exempel AB", "se"), ("Example Co.", "en")], 34 | "url": "http://www.example.com/roland", 35 | }, 36 | "contact_person": [{ 37 | "given_name":"John", 38 | "sur_name": "Smith", 39 | "email_address": ["john.smith@example.com"], 40 | "contact_type": "technical", 41 | }, 42 | ], 43 | #"xmlsec_binary":"/opt/local/bin/xmlsec1", 44 | "name_form": NAME_FORMAT_URI, 45 | "logger": { 46 | "rotating": { 47 | "filename": "sp.log", 48 | "maxBytes": 100000, 49 | "backupCount": 5, 50 | }, 51 | "loglevel": "debug", 52 | } 53 | } -------------------------------------------------------------------------------- /example/sp-repoze/sp_conf.py.example: -------------------------------------------------------------------------------- 1 | from saml2 import BINDING_HTTP_REDIRECT 2 | from saml2.saml import NAME_FORMAT_URI 3 | 4 | BASE= "http://localhost:8087" 5 | #BASE= "http://lingon.catalogix.se:8087" 6 | 7 | CONFIG = { 8 | "entityid": "%s/sp.xml" % BASE, 9 | "description": "My SP", 10 | "service": { 11 | "sp": { 12 | "name": "Rolands SP", 13 | "endpoints": { 14 | "assertion_consumer_service": [BASE], 15 | "single_logout_service": [(BASE + "/slo", 16 | BINDING_HTTP_REDIRECT)], 17 | }, 18 | "required_attributes": ["surname", "givenname", 19 | "edupersonaffiliation"], 20 | "optional_attributes": ["title"], 21 | } 22 | }, 23 | "debug": 1, 24 | "key_file": "pki/mykey.pem", 25 | "cert_file": "pki/mycert.pem", 26 | "attribute_map_dir": "./attributemaps", 27 | "metadata": {"local": ["../idp2/idp.xml"]}, 28 | # -- below used by make_metadata -- 29 | "organization": { 30 | "name": "Exempel AB", 31 | "display_name": [("Exempel AB", "se"), ("Example Co.", "en")], 32 | "url": "http://www.example.com/roland", 33 | }, 34 | "contact_person": [{ 35 | "given_name":"John", 36 | "sur_name": "Smith", 37 | "email_address": ["john.smith@example.com"], 38 | "contact_type": "technical", 39 | }, 40 | ], 41 | #"xmlsec_binary":"/opt/local/bin/xmlsec1", 42 | "name_form": NAME_FORMAT_URI, 43 | "logger": { 44 | "rotating": { 45 | "filename": "sp.log", 46 | "maxBytes": 100000, 47 | "backupCount": 5, 48 | }, 49 | "loglevel": "debug", 50 | } 51 | } -------------------------------------------------------------------------------- /example/sp-repoze/who.ini: -------------------------------------------------------------------------------- 1 | [plugin:auth_tkt] 2 | # identification 3 | use = repoze.who.plugins.auth_tkt:make_plugin 4 | secret = kasamark 5 | cookie_name = pysaml2 6 | secure = False 7 | include_ip = True 8 | timeout = 3600 9 | reissue_time = 3000 10 | 11 | # IDENTIFIER 12 | # @param : 13 | # - rememberer_name : name of the plugin for remembering (delegate) 14 | [plugin:saml2auth] 15 | use = s2repoze.plugins.sp:make_plugin 16 | saml_conf = sp_conf 17 | remember_name = auth_tkt 18 | sid_store = outstanding 19 | idp_query_param = IdPEntityId 20 | #discovery = http://130.239.201.5/role/idp.ds 21 | 22 | [general] 23 | request_classifier = s2repoze.plugins.challenge_decider:my_request_classifier 24 | challenge_decider = repoze.who.classifiers:default_challenge_decider 25 | remote_user_key = REMOTE_USER 26 | 27 | [identifiers] 28 | # plugin_name;classifier_name:.. or just plugin_name (good for any) 29 | plugins = 30 | saml2auth 31 | auth_tkt 32 | 33 | [authenticators] 34 | # plugin_name;classifier_name.. or just plugin_name (good for any) 35 | plugins = saml2auth 36 | 37 | [challengers] 38 | # plugin_name;classifier_name:.. or just plugin_name (good for any) 39 | plugins = saml2auth 40 | 41 | [mdproviders] 42 | plugins = saml2auth 43 | -------------------------------------------------------------------------------- /example/sp-wsgi/pki/mycert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFjTCCA3WgAwIBAgIUPqN85BV5eJsrEW8IN2rVIcHGliUwDQYJKoZIhvcNAQEL 3 | BQAwVTELMAkGA1UEBhMCVVMxCzAJBgNVBAgMAldBMRAwDgYDVQQHDAdTZWF0dGxl 4 | MRowGAYDVQQKDBFweXNhbWwyIERlbW8gQ2VydDELMAkGA1UECwwCSVQwIBcNMjEx 5 | MDA3MDgwNzE1WhgPMjEyMTEwMzEwODA3MTVaMFUxCzAJBgNVBAYTAlVTMQswCQYD 6 | VQQIDAJXQTEQMA4GA1UEBwwHU2VhdHRsZTEaMBgGA1UECgwRcHlzYW1sMiBEZW1v 7 | IENlcnQxCzAJBgNVBAsMAklUMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKC 8 | AgEAuWw/2OYDIO3pAZyQFQadOWnOh/ilqWXd66M3gD3tft+zydi2IRTUphhlrVmL 9 | /7UrWjHYfrBbF6UrliwvXfjgdECRAA6ziMlzbc+RYHqw1XhR/Y1DxH1V3OZmidaH 10 | B3oisfXe68v28kTHnq0iGQjivkRaMWZXrMSQfakm5ZQz6HC92DgxsOXcSTMlCETN 11 | lziEik2C/4mhA96p+upQPoAC3e9Dn7lNUS2oJT/cZBggJOtLScV6Cyzv+k+u2MCu 12 | ddzenrFONHBe7MNUYEg7Ho9Utas3y6prH36ZwsArtUPpNwBZrNlVenFeF6rUN80x 13 | hKNwvptlXQqAC+2NIhunYgNRoUHmy5opGBJpjqy0k16k+aac2t9Y/Lc+Q52PSh5Z 14 | e3C9Ip5CxoxA6hMk7IZT1kpsUl6uc8FBX9SLNRovIOC4r1anaxIareQjRab2iw+a 15 | RhTmKlGSXzlaSMS2FZMpT0q73d/rFo1/RKeE2dzG9fXe27jXJTv50J4Y46RF/6/C 16 | 0BWxTAzdBPQj1I5KWVVX9XuZklSbhKv/sGNXxPe5p5hR9rwt2oCrAhHzFZzddrzI 17 | BVRi6UtgnPMg8klnUcHsDt7elCuAF2ouB4Z8YplxhhYJga3NfLmHXYw1R7PBF2Ud 18 | 4FSgkjCO/ME/+FgyUKAaG6qKBMe3FJzuXch314qR1+1xT+ECAwEAAaNTMFEwHQYD 19 | VR0OBBYEFIwaXBfOL6adUTxpgv/NJGZ3gLrIMB8GA1UdIwQYMBaAFIwaXBfOL6ad 20 | UTxpgv/NJGZ3gLrIMA8GA1UdEwEB/wQFMAMBAf8wDQYJKoZIhvcNAQELBQADggIB 21 | AI4SQxTjjNvV7E903U/l1pd7PoyvM9LxcYkmnjTGMHBoK1/HbpU27s9ZHjodfT7k 22 | k1uMCh6tx7kuaSboWX9BX8Sdgtfj8Y9hs7Bb4MhTH3qJ3LFGK2d0LgK+lTFNRtmy 23 | JfExfmDcXi/YO+0IVd3mK49BfGdpN8lT7KjtcbUk4gObGLuN/XGCTjFPmyIRJfgZ 24 | K6kLWskthddoa4beqzpVQnbn5TBtShzlzmrjmU5YieP54IoQXDzE5mWn4PFuMsZ9 25 | wCLcSwRYiP3fUMGCChs64QADuIjZYB8xl3CHKxZMu9Ki+dHQ2W1X1UyozzF5i19S 26 | rhVdYXlumVOS6jnAjZ/UNIS1mYCZk5AT8jtsCBkgmNmhZab3BNbLklLz2AYi9fpI 27 | CYkHei2fYlAHKXPFZFKIHmG3mqpgu9nClyelrjRsxvrvkJ4Di/aPZ3qA+I006uYw 28 | Ti+NsL6eXBhIPUuA/NHax53kzJSW/AcjRhSKriKdSqD+lLOoQ+z9ZnZXeBKCWzGU 29 | 9bcMUk1x2pJJJBI7AJC/ObAbJDCtQreZZE+u+CABhkwbLPkWx8ZgS9a0/gBVmIJ3 30 | 4qsVmrv0sHnk1JQoFkMNT2oQmCuoA17HtfqxGyEPCxEJu/kqgK3fviKgd1MEP4Y7 31 | 3F6yVtiEWnspy4cQqr6pHBBggDbjve6DPNsIM1jHGUsR 32 | -----END CERTIFICATE----- 33 | -------------------------------------------------------------------------------- /example/sp-wsgi/service_conf.py.example: -------------------------------------------------------------------------------- 1 | from saml2.assertion import Policy 2 | import saml2.xmldsig as ds 3 | 4 | HOST = 'localhost' 5 | PORT = 8087 6 | HTTPS = False 7 | SIGN_ALG = None 8 | DIGEST_ALG = None 9 | #SIGN_ALG = ds.SIG_RSA_SHA512 10 | #DIGEST_ALG = ds.DIGEST_SHA512 11 | 12 | # Which groups of entity categories to use 13 | POLICY = Policy( 14 | { 15 | "default": {"entity_categories": ["swamid", "edugain"]} 16 | } 17 | ) 18 | 19 | # HTTPS cert information 20 | SERVER_CERT = "pki/mycert.pem" 21 | SERVER_KEY = "pki/mykey.pem" 22 | CERT_CHAIN = "" 23 | -------------------------------------------------------------------------------- /example/sp-wsgi/sp_conf.py.example: -------------------------------------------------------------------------------- 1 | from saml2.entity_category.edugain import COC 2 | from saml2 import BINDING_HTTP_REDIRECT 3 | from saml2 import BINDING_HTTP_POST 4 | from saml2.saml import NAME_FORMAT_URI 5 | 6 | try: 7 | from saml2.sigver import get_xmlsec_binary 8 | except ImportError: 9 | get_xmlsec_binary = None 10 | 11 | 12 | if get_xmlsec_binary: 13 | xmlsec_path = get_xmlsec_binary(["/opt/local/bin","/usr/local/bin"]) 14 | else: 15 | xmlsec_path = '/usr/local/bin/xmlsec1' 16 | 17 | # Make sure the same port number appear in service_conf.py 18 | BASE = "http://localhost:8087" 19 | 20 | CONFIG = { 21 | "entityid": "%s/%ssp.xml" % (BASE, ""), 22 | 'entity_category': [COC], 23 | "description": "Example SP", 24 | "service": { 25 | "sp": { 26 | "want_response_signed": False, 27 | "authn_requests_signed": True, 28 | "logout_requests_signed": True, 29 | "endpoints": { 30 | "assertion_consumer_service": [ 31 | ("%s/acs/post" % BASE, BINDING_HTTP_POST) 32 | ], 33 | "single_logout_service": [ 34 | ("%s/slo/redirect" % BASE, BINDING_HTTP_REDIRECT), 35 | ("%s/slo/post" % BASE, BINDING_HTTP_POST), 36 | ], 37 | } 38 | }, 39 | }, 40 | "key_file": "pki/mykey.pem", 41 | "cert_file": "pki/mycert.pem", 42 | "xmlsec_binary": xmlsec_path, 43 | "metadata": {"local": ["../idp2/idp.xml"]}, 44 | "name_form": NAME_FORMAT_URI, 45 | } 46 | -------------------------------------------------------------------------------- /script/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "rolandh" 2 | -------------------------------------------------------------------------------- /script/filter_testcase_ids.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # extract test case IDs from json-formatted list (`sp_testdrv.py -l` or `idp_testdrv.py -l`) 3 | # usage: 4 | # sp_testdrv.py -l | filter_testcase_ids.py 5 | __author__ = "rhoerbe" 6 | 7 | import json 8 | import sys 9 | 10 | 11 | jdata = json.load(sys.stdin) 12 | for k in jdata: 13 | print(k["id"]) 14 | -------------------------------------------------------------------------------- /script/idp_testdrv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | __author__ = "rohe0002" 3 | 4 | # from idp_test import saml2base 5 | from idp_test import SAML2client 6 | from idp_test.check import factory 7 | 8 | 9 | cli = SAML2client(factory) 10 | cli.run() 11 | -------------------------------------------------------------------------------- /script/sp_testdrv.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | __author__ = "rohe0002" 3 | 4 | from sp_test import Client 5 | from sp_test import tests 6 | from sp_test.check import factory 7 | 8 | 9 | cli = Client(tests, factory) 10 | cli.run() 11 | -------------------------------------------------------------------------------- /script/utility/filter_testcase_ids.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # extract test case IDs from json-formatted list (`sp_testdrv.py -l` or `idp_testdrv.py -l`) 3 | # usage: 4 | # sp_testdrv.py -l | filter_testcase_ids.py 5 | __author__ = "rhoerbe" 6 | 7 | import json 8 | import sys 9 | 10 | 11 | jdata = json.load(sys.stdin) 12 | for k in jdata: 13 | print(k["id"]) 14 | -------------------------------------------------------------------------------- /script/utility/run_available_sp_tests.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # run all tests that are availabe in sp_test 3 | /usr/bin/env python ./tt_config.py > tt_config.json 4 | mkdir -p log 5 | sp_testdrv.py -l | ./filter_testcase_ids.py | sort | ./run_list_of_tests.py 6 | -------------------------------------------------------------------------------- /script/utility/run_list_of_tests.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import fileinput 4 | from subprocess import call 5 | 6 | 7 | for line in fileinput.input(): 8 | cmd = f"./run_oper.sh {line.rstrip()}" 9 | print(f"executing {cmd}") 10 | call(cmd, shell=True) 11 | -------------------------------------------------------------------------------- /script/utility/run_oper.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | 4 | sp_testdrv.py -H -d -Y -J tt_config.json -c td_config $1 2> log/$1.log 5 | -------------------------------------------------------------------------------- /src/saml2/attribute_resolver.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | 4 | """ 5 | Contains classes and functions that a SAML2.0 Service Provider (SP) may use 6 | to do attribute aggregation. 7 | """ 8 | import logging 9 | 10 | # from saml2 import client 11 | from saml2 import BINDING_SOAP 12 | 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | DEFAULT_BINDING = BINDING_SOAP 17 | 18 | 19 | class AttributeResolver: 20 | def __init__(self, saml2client, metadata=None, config=None): 21 | self.metadata = metadata 22 | self.saml2client = saml2client 23 | self.metadata = saml2client.config.metadata 24 | 25 | def extend(self, name_id, issuer, vo_members): 26 | """ 27 | :param name_id: The identifier by which the subject is know 28 | among all the participents of the VO 29 | :param issuer: Who am I the poses the query 30 | :param vo_members: The entity IDs of the IdP who I'm going to ask 31 | for extra attributes 32 | :return: A dictionary with all the collected information about the 33 | subject 34 | """ 35 | result = [] 36 | for member in vo_members: 37 | for ass in self.metadata.attribute_consuming_service(member): 38 | for attr_serv in ass.attribute_service: 39 | logger.info("Send attribute request to %s", attr_serv.location) 40 | if attr_serv.binding != BINDING_SOAP: 41 | continue 42 | # attribute query assumes SOAP binding 43 | session_info = self.saml2client.attribute_query(name_id, attr_serv.location, issuer_id=issuer) 44 | if session_info: 45 | result.append(session_info) 46 | return result 47 | -------------------------------------------------------------------------------- /src/saml2/attributemaps/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "rohe0002" 2 | __all__ = ["adfs_v1x", "adfs_v20", "basic", "saml_uri", "shibboleth_uri"] 3 | -------------------------------------------------------------------------------- /src/saml2/attributemaps/adfs_v1x.py: -------------------------------------------------------------------------------- 1 | CLAIMS = "http://schemas.xmlsoap.org/claims/" 2 | 3 | 4 | MAP = { 5 | "identifier": "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified", 6 | "fro": { 7 | f"{CLAIMS}commonname": "commonName", 8 | f"{CLAIMS}emailaddress": "emailAddress", 9 | f"{CLAIMS}group": "group", 10 | f"{CLAIMS}upn": "upn", 11 | }, 12 | "to": { 13 | "commonName": f"{CLAIMS}commonname", 14 | "emailAddress": f"{CLAIMS}emailaddress", 15 | "group": f"{CLAIMS}group", 16 | "upn": f"{CLAIMS}upn", 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /src/saml2/attributemaps/adfs_v20.py: -------------------------------------------------------------------------------- 1 | CLAIMS = "http://schemas.xmlsoap.org/claims/" 2 | COM_WS_CLAIMS = "http://schemas.xmlsoap.com/ws/2005/05/identity/claims/" 3 | MS_CLAIMS = "http://schemas.microsoft.com/ws/2008/06/identity/claims/" 4 | ORG_WS_CLAIMS = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/" 5 | 6 | 7 | MAP = { 8 | "identifier": "urn:oasis:names:tc:SAML:2.0:attrname-format:unspecified", 9 | "fro": { 10 | f"{CLAIMS}commonname": "commonName", 11 | f"{CLAIMS}group": "group", 12 | f"{COM_WS_CLAIMS}denyonlysid": "denyOnlySid", 13 | f"{MS_CLAIMS}authenticationmethod": "authenticationMethod", 14 | f"{MS_CLAIMS}denyonlyprimarygroupsid": "denyOnlyPrimaryGroupSid", 15 | f"{MS_CLAIMS}denyonlyprimarysid": "denyOnlyPrimarySid", 16 | f"{MS_CLAIMS}groupsid": "groupSid", 17 | f"{MS_CLAIMS}primarygroupsid": "primaryGroupSid", 18 | f"{MS_CLAIMS}primarysid": "primarySid", 19 | f"{MS_CLAIMS}role": "role", 20 | f"{MS_CLAIMS}windowsaccountname": "windowsAccountName", 21 | f"{ORG_WS_CLAIMS}emailaddress": "emailAddress", 22 | f"{ORG_WS_CLAIMS}givenname": "givenName", 23 | f"{ORG_WS_CLAIMS}name": "name", 24 | f"{ORG_WS_CLAIMS}nameidentifier": "nameId", 25 | f"{ORG_WS_CLAIMS}privatepersonalidentifier": "privatePersonalId", 26 | f"{ORG_WS_CLAIMS}surname": "surname", 27 | f"{ORG_WS_CLAIMS}upn": "upn", 28 | }, 29 | "to": { 30 | "authenticationMethod": f"{MS_CLAIMS}authenticationmethod", 31 | "commonName": f"{CLAIMS}commonname", 32 | "denyOnlyPrimaryGroupSid": f"{MS_CLAIMS}denyonlyprimarygroupsid", 33 | "denyOnlyPrimarySid": f"{MS_CLAIMS}denyonlyprimarysid", 34 | "denyOnlySid": f"{COM_WS_CLAIMS}denyonlysid", 35 | "emailAddress": f"{ORG_WS_CLAIMS}emailaddress", 36 | "givenName": f"{ORG_WS_CLAIMS}givenname", 37 | "group": f"{CLAIMS}group", 38 | "groupSid": f"{MS_CLAIMS}groupsid", 39 | "name": f"{ORG_WS_CLAIMS}name", 40 | "nameId": f"{ORG_WS_CLAIMS}nameidentifier", 41 | "primaryGroupSid": f"{MS_CLAIMS}primarygroupsid", 42 | "primarySid": f"{MS_CLAIMS}primarysid", 43 | "privatePersonalId": f"{ORG_WS_CLAIMS}privatepersonalidentifier", 44 | "role": f"{MS_CLAIMS}role", 45 | "surname": f"{ORG_WS_CLAIMS}surname", 46 | "upn": f"{ORG_WS_CLAIMS}upn", 47 | "windowsAccountName": f"{MS_CLAIMS}windowsaccountname", 48 | }, 49 | } 50 | -------------------------------------------------------------------------------- /src/saml2/cryptography/__init__.py: -------------------------------------------------------------------------------- 1 | """This module provides cryptographic elements needed by saml2.""" 2 | -------------------------------------------------------------------------------- /src/saml2/cryptography/asymmetric.py: -------------------------------------------------------------------------------- 1 | """This module provides methods for asymmetric cryptography.""" 2 | 3 | import cryptography.hazmat.primitives.asymmetric as _asymmetric 4 | import cryptography.hazmat.primitives.hashes as _hashes 5 | import cryptography.hazmat.primitives.serialization as _serialization 6 | 7 | 8 | def load_pem_private_key(data, password=None): 9 | """Load RSA PEM certificate.""" 10 | key = _serialization.load_pem_private_key(data, password) 11 | return key 12 | 13 | 14 | def key_sign(rsakey, message, digest): 15 | """Sign the given message with the RSA key.""" 16 | padding = _asymmetric.padding.PKCS1v15() 17 | signature = rsakey.sign(message, padding, digest) 18 | return signature 19 | 20 | 21 | def key_verify(rsakey, signature, message, digest): 22 | """Verify the given signature with the RSA key.""" 23 | padding = _asymmetric.padding.PKCS1v15() 24 | if isinstance(rsakey, _asymmetric.rsa.RSAPrivateKey): 25 | rsakey = rsakey.public_key() 26 | 27 | try: 28 | rsakey.verify(signature, message, padding, digest) 29 | except Exception: 30 | return False 31 | else: 32 | return True 33 | 34 | 35 | hashes = _hashes 36 | -------------------------------------------------------------------------------- /src/saml2/cryptography/errors.py: -------------------------------------------------------------------------------- 1 | from saml2 import Error 2 | 3 | 4 | class CryptographyError(Error): 5 | """Generic error from saml2.cryptography modules""" 6 | 7 | 8 | class SymmetricCryptographyError(CryptographyError): 9 | """Generic error from saml2.cryptography.symmetric modules""" 10 | 11 | 12 | class AsymmetricCryptographyError(CryptographyError): 13 | """Generic error from saml2.cryptography.asymmetric modules""" 14 | 15 | 16 | class PKICryptographyError(CryptographyError): 17 | """Generic error from saml2.cryptography.pki modules""" 18 | -------------------------------------------------------------------------------- /src/saml2/cryptography/pki.py: -------------------------------------------------------------------------------- 1 | """This module provides methods for PKI operations.""" 2 | 3 | from logging import getLogger as get_logger 4 | 5 | from cryptography.hazmat.primitives.serialization import Encoding as _cryptography_encoding 6 | import cryptography.x509 as _x509 7 | 8 | 9 | logger = get_logger(__name__) 10 | 11 | DEFAULT_CERT_TYPE = "pem" 12 | 13 | 14 | def load_pem_x509_certificate(data): 15 | """Load X.509 PEM certificate.""" 16 | return _x509.load_pem_x509_certificate(data) 17 | 18 | 19 | def load_der_x509_certificate(data): 20 | """Load X.509 DER certificate.""" 21 | return _x509.load_der_x509_certificate(data) 22 | 23 | 24 | def load_x509_certificate(data, cert_type="pem"): 25 | cert_reader = _x509_loaders.get(cert_type) 26 | 27 | if not cert_reader: 28 | cert_reader = _x509_loaders.get("pem") 29 | context = { 30 | "message": "Unknown cert_type, falling back to default", 31 | "cert_type": cert_type, 32 | "default": DEFAULT_CERT_TYPE, 33 | } 34 | logger.warning(context) 35 | 36 | cert = cert_reader(data) 37 | return cert 38 | 39 | 40 | def get_public_bytes_from_cert(cert): 41 | data = cert.public_bytes(_cryptography_encoding.PEM).decode() 42 | return data 43 | 44 | 45 | _x509_loaders = { 46 | "pem": load_pem_x509_certificate, 47 | "der": load_der_x509_certificate, 48 | } 49 | -------------------------------------------------------------------------------- /src/saml2/data/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IdentityPython/pysaml2/0252ec96058c87c43f89e05d978e72b6ba2e6978/src/saml2/data/__init__.py -------------------------------------------------------------------------------- /src/saml2/data/schemas/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IdentityPython/pysaml2/0252ec96058c87c43f89e05d978e72b6ba2e6978/src/saml2/data/schemas/__init__.py -------------------------------------------------------------------------------- /src/saml2/data/schemas/eidas-schema-saml-extensions.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/saml2/data/schemas/saml-schema-authn-context-2.0.xsd: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 10 | 11 | Document identifier: saml-schema-authn-context-2.0 12 | Location: http://docs.oasis-open.org/security/saml/v2.0/ 13 | Revision history: 14 | V2.0 (March, 2005): 15 | New core authentication context schema for SAML V2.0. 16 | This is just an include of all types from the schema 17 | referred to in the include statement below. 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/saml2/data/schemas/saml-schema-dce-2.0.xsd: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | Document identifier: saml-schema-dce-2.0 12 | Location: http://docs.oasis-open.org/security/saml/v2.0/ 13 | Revision history: 14 | V2.0 (March, 2005): 15 | Custom schema for DCE attribute profile, first published in SAML 2.0. 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /src/saml2/data/schemas/saml-schema-ecp-2.0.xsd: -------------------------------------------------------------------------------- 1 | 2 | 13 | 15 | 17 | 19 | 20 | 21 | Document identifier: saml-schema-ecp-2.0 22 | Location: http://docs.oasis-open.org/security/saml/v2.0/ 23 | Revision history: 24 | V2.0 (March, 2005): 25 | Custom schema for ECP profile, first published in SAML 2.0. 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | -------------------------------------------------------------------------------- /src/saml2/data/schemas/saml-schema-x500-2.0.xsd: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | Document identifier: saml-schema-x500-2.0 12 | Location: http://docs.oasis-open.org/security/saml/v2.0/ 13 | Revision history: 14 | V2.0 (March, 2005): 15 | Custom schema for X.500 attribute profile, first published in SAML 2.0. 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/saml2/data/schemas/saml-schema-xacml-2.0.xsd: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | Document identifier: saml-schema-xacml-2.0 12 | Location: http://docs.oasis-open.org/security/saml/v2.0/ 13 | Revision history: 14 | V2.0 (March, 2005): 15 | Custom schema for XACML attribute profile, first published in SAML 2.0. 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/saml2/data/schemas/saml-subject-id-attr-v1.0.xsd: -------------------------------------------------------------------------------- 1 | 2 | 12 | 20 | 21 | 22 | 23 | Document title: Metadata Extension Schema for 24 | SAML V2.0 Subject Identifier Attributes Profile Version 1.0 25 | Document identifier: saml-subject-id-attr-v1.0.xsd 26 | Location: http://docs.oasis-open.org/security/saml-subject-id-attr/v1.0/ 27 | Revision history: 28 | September 2018: 29 | Initial version contributed to OASIS, (c) The Ohio State University 30 | 31 | 32 | 33 | 34 | 35 | 36 | SAML metadata extension used to regulate allowable attribute scopes. 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /src/saml2/data/schemas/sstc-metadata-attr.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Document title: SAML V2.0 Metadata Extention for Entity Attributes Schema 7 | Document identifier: sstc-metadata-attr.xsd 8 | Location: http://www.oasis-open.org/committees/documents.php?wg_abbrev=security 9 | Revision history: 10 | V1.0 (November 2008): 11 | Initial version. 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/saml2/data/schemas/sstc-req-attr-ext.xsd: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 20 | 21 | 23 | 24 | 25 | 26 | Document title: SAML V2.0 Protocol Extension For Requesting Attributes Per Request 27 | Document identifier: sstc-req-attr-ext 28 | Location: http://docs.oasis-open.org/security/saml-protoc-req-attr-req/v1.0/csprd01/schema/ 29 | Revision history: WD-03 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/saml2/data/schemas/sstc-saml-attribute-ext.xsd: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 12 | Document title: SAML V2.0 Attribute Extension Schema 13 | Document identifier: sstc-saml-attribute-ext.xsd 14 | Location: http://www.oasis-open.org/committees/documents.php?wg_abbrev=security 15 | Revision history: 16 | V1.0 (October 2008): 17 | Initial version. 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /src/saml2/data/schemas/sstc-saml-metadata-algsupport-v1.0.xsd: -------------------------------------------------------------------------------- 1 | 2 | 3 | 12 | 13 | 21 | 22 | 23 | 24 | Document title: Metadata Extension Schema for SAML V2.0 Metadata Profile for Algorithm Support Version 1.0 25 | Document identifier: sstc-saml-metadata-algsupport.xsd 26 | Location: http://docs.oasis-open.org/security/saml/Post2.0/ 27 | Revision history: 28 | V1.0 (June 2010): 29 | Initial version. 30 | (October 2010): 31 | Add processContents="lax" to wildcards. 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /src/saml2/data/templates/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IdentityPython/pysaml2/0252ec96058c87c43f89e05d978e72b6ba2e6978/src/saml2/data/templates/__init__.py -------------------------------------------------------------------------------- /src/saml2/data/templates/template_enc.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/saml2/entity_category/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "rolandh" 2 | 3 | ENTITYATTRIBUTES = "urn:oasis:names:tc:SAML:metadata:attribute&EntityAttributes" 4 | 5 | 6 | def entity_categories(md): 7 | res = [] 8 | if "extensions" in md: 9 | for elem in md["extensions"]["extension_elements"]: 10 | if elem["__class__"] == ENTITYATTRIBUTES: 11 | for attr in elem["attribute"]: 12 | res.append(attr["text"]) 13 | 14 | return res 15 | -------------------------------------------------------------------------------- /src/saml2/entity_category/at_egov_pvp2.py: -------------------------------------------------------------------------------- 1 | __author__ = "rhoerbe" # 2013-09-05 2 | # Entity Categories specifying the PVP eGov Token as of "PVP2-Allgemein V2.1.0", http://www.ref.gv.at/ 3 | 4 | 5 | EGOVTOKEN = [ 6 | "PVP-VERSION", 7 | "PVP-PRINCIPAL-NAME", 8 | "PVP-GIVENNAME", 9 | "PVP-BIRTHDATE", 10 | "PVP-USERID", 11 | "PVP-GID", 12 | "PVP-BPK", 13 | "PVP-MAIL", 14 | "PVP-TEL", 15 | "PVP-PARTICIPANT-ID", 16 | "PVP-PARTICIPANT-OKZ", 17 | "PVP-OU-OKZ", 18 | "PVP-OU", 19 | "PVP-OU-GV-OU-ID", 20 | "PVP-FUNCTION", 21 | "PVP-ROLES", 22 | ] 23 | 24 | 25 | CHARGEATTR = [ 26 | "PVP-INVOICE-RECPT-ID", 27 | "PVP-COST-CENTER-ID", 28 | "PVP-CHARGE-CODE", 29 | ] 30 | 31 | # all eGov Token attributes except (1) transaction charging and (2) chaining 32 | PVP2 = "http://www.ref.gv.at/ns/names/agiz/pvp/egovtoken" 33 | # transaction charging extension 34 | PVP2CHARGE = "http://www.ref.gv.at/ns/names/agiz/pvp/egovtoken-charge" 35 | 36 | RELEASE = { 37 | PVP2: EGOVTOKEN, 38 | PVP2CHARGE: CHARGEATTR, 39 | } 40 | -------------------------------------------------------------------------------- /src/saml2/entity_category/edugain.py: -------------------------------------------------------------------------------- 1 | __author__ = "rolandh" 2 | 3 | COC = "http://www.geant.net/uri/dataprotection-code-of-conduct/v1" 4 | COCO = COC 5 | 6 | RELEASE = { 7 | "": ["eduPersonTargetedID"], 8 | # COC: ["eduPersonPrincipalName", "eduPersonScopedAffiliation", "mail", 9 | # "displayName", "schacHomeOrganization"], 10 | COCO: [ 11 | "eduPersonPrincipalName", 12 | "eduPersonScopedAffiliation", 13 | "eduPersonAffiliation", 14 | "mail", 15 | "displayName", 16 | "cn", 17 | "schacHomeOrganization", 18 | ], 19 | } 20 | 21 | ONLY_REQUIRED = {COCO: True} 22 | -------------------------------------------------------------------------------- /src/saml2/entity_category/incommon.py: -------------------------------------------------------------------------------- 1 | __author__ = "rolandh" 2 | 3 | RESEARCH_AND_SCHOLARSHIP = "http://id.incommon.org/category/research-and-scholarship" 4 | 5 | RELEASE = { 6 | "": ["eduPersonTargetedID"], 7 | RESEARCH_AND_SCHOLARSHIP: [ 8 | "eduPersonPrincipalName", 9 | "eduPersonScopedAffiliation", 10 | "mail", 11 | "givenName", 12 | "sn", 13 | "displayName", 14 | ], 15 | } 16 | -------------------------------------------------------------------------------- /src/saml2/entity_category/refeds.py: -------------------------------------------------------------------------------- 1 | __author__ = "rolandh" 2 | 3 | RESEARCH_AND_SCHOLARSHIP = "http://refeds.org/category/research-and-scholarship" 4 | 5 | RELEASE = { 6 | "": ["eduPersonTargetedID"], 7 | RESEARCH_AND_SCHOLARSHIP: [ 8 | "eduPersonPrincipalName", 9 | "eduPersonScopedAffiliation", 10 | "mail", 11 | "givenName", 12 | "sn", 13 | "displayName", 14 | ], 15 | } 16 | -------------------------------------------------------------------------------- /src/saml2/eptid.py: -------------------------------------------------------------------------------- 1 | # An eduPersonTargetedID comprises 2 | # the entity name of the identity provider, the entity name of the service 3 | # provider, and a opaque string value. 4 | # These strings are separated by "!" symbols. This form is advocated by 5 | # Internet2 and may overtake the other form in due course. 6 | 7 | import hashlib 8 | import logging 9 | import shelve 10 | 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class Eptid: 16 | def __init__(self, secret): 17 | self._db = {} 18 | self.secret = secret 19 | 20 | def make(self, idp, sp, args): 21 | md5 = hashlib.md5() 22 | for arg in args: 23 | md5.update(arg.encode("utf-8")) 24 | if isinstance(sp, bytes): 25 | md5.update(sp) 26 | else: 27 | md5.update(sp.encode("utf-8")) 28 | if isinstance(self.secret, bytes): 29 | md5.update(self.secret) 30 | else: 31 | md5.update(self.secret.encode("utf-8")) 32 | md5.digest() 33 | hashval = md5.hexdigest() 34 | if isinstance(hashval, bytes): 35 | hashval = hashval.decode("ascii") 36 | return "!".join([idp, sp, hashval]) 37 | 38 | def __getitem__(self, key): 39 | if isinstance(key, bytes): 40 | key = key.decode("utf-8") 41 | return self._db[key] 42 | 43 | def __setitem__(self, key, value): 44 | if isinstance(key, bytes): 45 | key = key.decode("utf-8") 46 | self._db[key] = value 47 | 48 | def get(self, idp, sp, *args): 49 | # key is a combination of sp_entity_id and object id 50 | key = ("__".join([sp, args[0]])).encode("utf-8") 51 | try: 52 | return self[key] 53 | except KeyError: 54 | val = self.make(idp, sp, args) 55 | self[key] = val 56 | return val 57 | 58 | def close(self): 59 | pass 60 | 61 | 62 | class EptidShelve(Eptid): 63 | def __init__(self, secret, filename): 64 | Eptid.__init__(self, secret) 65 | if filename.endswith(".db"): 66 | filename = filename.rsplit(".db", 1)[0] 67 | self._db = shelve.open(filename, writeback=True, protocol=2) 68 | 69 | def close(self): 70 | self._db.close() 71 | -------------------------------------------------------------------------------- /src/saml2/extension/__init__.py: -------------------------------------------------------------------------------- 1 | # metadata extensions mainly 2 | __all__ = ["dri", "mdrpi", "mdui", "shibmd", "idpdisc", "algsupport", "mdattr"] 3 | -------------------------------------------------------------------------------- /src/saml2/extension/idpdisc.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # 4 | # Generated Thu Jun 23 09:01:47 2011 by parse_xsd.py version 0.4. 5 | # 6 | 7 | import saml2 8 | from saml2 import md 9 | 10 | 11 | NAMESPACE = "urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol" 12 | BINDING_DISCO = "urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol" 13 | 14 | 15 | class DiscoveryResponse(md.IndexedEndpointType_): 16 | """The urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol: 17 | DiscoveryResponse element""" 18 | 19 | c_tag = "DiscoveryResponse" 20 | c_namespace = NAMESPACE 21 | c_children = md.IndexedEndpointType_.c_children.copy() 22 | c_attributes = md.IndexedEndpointType_.c_attributes.copy() 23 | c_child_order = md.IndexedEndpointType_.c_child_order[:] 24 | c_cardinality = md.IndexedEndpointType_.c_cardinality.copy() 25 | 26 | 27 | def discovery_response_from_string(xml_string): 28 | return saml2.create_class_from_xml_string(DiscoveryResponse, xml_string) 29 | 30 | 31 | ELEMENT_FROM_STRING = { 32 | DiscoveryResponse.c_tag: discovery_response_from_string, 33 | } 34 | 35 | ELEMENT_BY_TAG = { 36 | "DiscoveryResponse": DiscoveryResponse, 37 | } 38 | 39 | 40 | def factory(tag, **kwargs): 41 | return ELEMENT_BY_TAG[tag](**kwargs) 42 | -------------------------------------------------------------------------------- /src/saml2/extension/pefim.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import saml2 4 | from saml2 import SamlBase 5 | from saml2.xmldsig import KeyInfo 6 | 7 | 8 | NAMESPACE = "urn:net:eustix:names:tc:PEFIM:0.0:assertion" 9 | 10 | 11 | class SPCertEncType_(SamlBase): 12 | """The urn:net:eustix:names:tc:PEFIM:0.0:assertion:SPCertEncType element""" 13 | 14 | c_tag = "SPCertEncType" 15 | c_namespace = NAMESPACE 16 | c_children = SamlBase.c_children.copy() 17 | c_attributes = SamlBase.c_attributes.copy() 18 | c_child_order = SamlBase.c_child_order[:] 19 | c_cardinality = SamlBase.c_cardinality.copy() 20 | c_children["{http://www.w3.org/2000/09/xmldsig#}KeyInfo"] = ("key_info", [KeyInfo]) 21 | c_cardinality["key_info"] = {"min": 1} 22 | c_attributes["VerifyDepth"] = ("verify_depth", "unsignedByte", False) 23 | c_child_order.extend(["key_info"]) 24 | 25 | def __init__( 26 | self, 27 | key_info=None, 28 | x509_data=None, 29 | verify_depth="1", 30 | text=None, 31 | extension_elements=None, 32 | extension_attributes=None, 33 | ): 34 | SamlBase.__init__( 35 | self, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes 36 | ) 37 | if key_info: 38 | self.key_info = key_info 39 | elif x509_data: 40 | self.key_info = KeyInfo(x509_data=x509_data) 41 | else: 42 | self.key_info = [] 43 | self.verify_depth = verify_depth 44 | # self.x509_data = x509_data 45 | 46 | 47 | def spcertenc_type__from_string(xml_string): 48 | return saml2.create_class_from_xml_string(SPCertEncType_, xml_string) 49 | 50 | 51 | class SPCertEnc(SPCertEncType_): 52 | """The urn:net:eustix:names:tc:PEFIM:0.0:assertion:SPCertEnc element""" 53 | 54 | c_tag = "SPCertEnc" 55 | c_namespace = NAMESPACE 56 | c_children = SPCertEncType_.c_children.copy() 57 | c_attributes = SPCertEncType_.c_attributes.copy() 58 | c_child_order = SPCertEncType_.c_child_order[:] 59 | c_cardinality = SPCertEncType_.c_cardinality.copy() 60 | 61 | 62 | def spcertenc_from_string(xml_string): 63 | return saml2.create_class_from_xml_string(SPCertEnc, xml_string) 64 | 65 | 66 | ELEMENT_FROM_STRING = { 67 | SPCertEnc.c_tag: spcertenc_from_string, 68 | SPCertEncType_.c_tag: spcertenc_type__from_string, 69 | } 70 | 71 | ELEMENT_BY_TAG = { 72 | "SPCertEnc": SPCertEnc, 73 | "SPCertEncType": SPCertEncType_, 74 | } 75 | 76 | 77 | def factory(tag, **kwargs): 78 | return ELEMENT_BY_TAG[tag](**kwargs) 79 | -------------------------------------------------------------------------------- /src/saml2/extension/reqinit.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # 4 | # Generated Thu May 15 13:58:36 2014 by parse_xsd.py version 0.5. 5 | # 6 | 7 | import saml2 8 | from saml2 import md 9 | 10 | 11 | NAMESPACE = "urn:oasis:names:tc:SAML:profiles:SSO:request-init" 12 | 13 | 14 | class RequestInitiator(md.EndpointType_): 15 | """The urn:oasis:names:tc:SAML:profiles:SSO:request-init:RequestInitiator 16 | element""" 17 | 18 | c_tag = "RequestInitiator" 19 | c_namespace = NAMESPACE 20 | c_children = md.EndpointType_.c_children.copy() 21 | c_attributes = md.EndpointType_.c_attributes.copy() 22 | c_child_order = md.EndpointType_.c_child_order[:] 23 | c_cardinality = md.EndpointType_.c_cardinality.copy() 24 | 25 | 26 | def request_initiator_from_string(xml_string): 27 | return saml2.create_class_from_xml_string(RequestInitiator, xml_string) 28 | 29 | 30 | ELEMENT_FROM_STRING = { 31 | RequestInitiator.c_tag: request_initiator_from_string, 32 | } 33 | 34 | ELEMENT_BY_TAG = { 35 | "RequestInitiator": RequestInitiator, 36 | } 37 | 38 | 39 | def factory(tag, **kwargs): 40 | return ELEMENT_BY_TAG[tag](**kwargs) 41 | -------------------------------------------------------------------------------- /src/saml2/extension/shibmd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # 4 | # Generated Sun Mar 20 18:06:44 2011 by parse_xsd.py version 0.4. 5 | # 6 | 7 | import saml2 8 | from saml2 import SamlBase 9 | from saml2 import xmldsig as ds 10 | 11 | 12 | NAMESPACE = "urn:mace:shibboleth:metadata:1.0" 13 | 14 | 15 | class Scope(SamlBase): 16 | """The urn:mace:shibboleth:metadata:1.0:Scope element""" 17 | 18 | c_tag = "Scope" 19 | c_namespace = NAMESPACE 20 | c_value_type = {"base": "string"} 21 | c_children = SamlBase.c_children.copy() 22 | c_attributes = SamlBase.c_attributes.copy() 23 | c_child_order = SamlBase.c_child_order[:] 24 | c_cardinality = SamlBase.c_cardinality.copy() 25 | c_attributes["regexp"] = ("regexp", "boolean", False) 26 | 27 | def __init__(self, regexp="false", text=None, extension_elements=None, extension_attributes=None): 28 | SamlBase.__init__( 29 | self, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes 30 | ) 31 | self.regexp = regexp 32 | 33 | 34 | def scope_from_string(xml_string): 35 | return saml2.create_class_from_xml_string(Scope, xml_string) 36 | 37 | 38 | class KeyAuthority(SamlBase): 39 | """The urn:mace:shibboleth:metadata:1.0:KeyAuthority element""" 40 | 41 | c_tag = "KeyAuthority" 42 | c_namespace = NAMESPACE 43 | c_children = SamlBase.c_children.copy() 44 | c_attributes = SamlBase.c_attributes.copy() 45 | c_child_order = SamlBase.c_child_order[:] 46 | c_cardinality = SamlBase.c_cardinality.copy() 47 | c_children["{http://www.w3.org/2000/09/xmldsig#}KeyInfo"] = ("key_info", [ds.KeyInfo]) 48 | c_cardinality["key_info"] = {"min": 1} 49 | c_attributes["VerifyDepth"] = ("verify_depth", "unsignedByte", False) 50 | c_child_order.extend(["key_info"]) 51 | 52 | def __init__(self, key_info=None, verify_depth="1", text=None, extension_elements=None, extension_attributes=None): 53 | SamlBase.__init__( 54 | self, text=text, extension_elements=extension_elements, extension_attributes=extension_attributes 55 | ) 56 | self.key_info = key_info or [] 57 | self.verify_depth = verify_depth 58 | 59 | 60 | def key_authority_from_string(xml_string): 61 | return saml2.create_class_from_xml_string(KeyAuthority, xml_string) 62 | 63 | 64 | ELEMENT_FROM_STRING = { 65 | Scope.c_tag: scope_from_string, 66 | KeyAuthority.c_tag: key_authority_from_string, 67 | } 68 | 69 | ELEMENT_BY_TAG = { 70 | "Scope": Scope, 71 | "KeyAuthority": KeyAuthority, 72 | } 73 | 74 | 75 | def factory(tag, **kwargs): 76 | return ELEMENT_BY_TAG[tag](**kwargs) 77 | -------------------------------------------------------------------------------- /src/saml2/extension/sp_type.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # 4 | # Generated Tue Jul 18 15:03:44 2017 by parse_xsd.py version 0.5. 5 | # 6 | 7 | import saml2 8 | from saml2 import SamlBase 9 | 10 | 11 | NAMESPACE = "http://eidas.europa.eu/saml-extensions" 12 | 13 | 14 | class SPTypeType_(SamlBase): 15 | """The http://eidas.europa.eu/saml-extensions:SPTypeType element""" 16 | 17 | c_tag = "SPTypeType" 18 | c_namespace = NAMESPACE 19 | c_value_type = {"base": "xsd:string", "enumeration": ["public", "private"]} 20 | c_children = SamlBase.c_children.copy() 21 | c_attributes = SamlBase.c_attributes.copy() 22 | c_child_order = SamlBase.c_child_order[:] 23 | c_cardinality = SamlBase.c_cardinality.copy() 24 | 25 | 26 | def sp_type_type__from_string(xml_string): 27 | return saml2.create_class_from_xml_string(SPTypeType_, xml_string) 28 | 29 | 30 | class SPType(SPTypeType_): 31 | """The http://eidas.europa.eu/saml-extensions:SPType element""" 32 | 33 | c_tag = "SPType" 34 | c_namespace = NAMESPACE 35 | c_children = SPTypeType_.c_children.copy() 36 | c_attributes = SPTypeType_.c_attributes.copy() 37 | c_child_order = SPTypeType_.c_child_order[:] 38 | c_cardinality = SPTypeType_.c_cardinality.copy() 39 | 40 | 41 | def sp_type_from_string(xml_string): 42 | return saml2.create_class_from_xml_string(SPType, xml_string) 43 | 44 | 45 | ELEMENT_FROM_STRING = { 46 | SPType.c_tag: sp_type_from_string, 47 | SPTypeType_.c_tag: sp_type_type__from_string, 48 | } 49 | 50 | ELEMENT_BY_TAG = { 51 | "SPType": SPType, 52 | "SPTypeType": SPTypeType_, 53 | } 54 | 55 | 56 | def factory(tag, **kwargs): 57 | return ELEMENT_BY_TAG[tag](**kwargs) 58 | -------------------------------------------------------------------------------- /src/saml2/filter.py: -------------------------------------------------------------------------------- 1 | __author__ = "roland" 2 | 3 | 4 | class Filter: 5 | def __init__(self): 6 | pass 7 | 8 | def __call__(self, *args, **kwargs): 9 | pass 10 | 11 | 12 | class AllowDescriptor(Filter): 13 | def __init__(self, allow): 14 | """ 15 | 16 | :param allow: List of allowed descriptors 17 | :return: 18 | """ 19 | super().__init__() 20 | self.allow = allow 21 | 22 | def __call__(self, entity_descriptor): 23 | # get descriptors 24 | _all = [] 25 | for desc in list(entity_descriptor.keys()): 26 | if desc.endswith("_descriptor"): 27 | typ, _ = desc.rsplit("_", 1) 28 | if typ in self.allow: 29 | _all.append(typ) 30 | else: 31 | del entity_descriptor[desc] 32 | 33 | if not _all: 34 | return None 35 | else: 36 | return entity_descriptor 37 | -------------------------------------------------------------------------------- /src/saml2/population.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from saml2.cache import Cache 4 | 5 | 6 | logger = logging.getLogger(__name__) 7 | 8 | 9 | class Population: 10 | def __init__(self, cache=None): 11 | if cache: 12 | if isinstance(cache, str): 13 | self.cache = Cache(cache) 14 | else: 15 | self.cache = cache 16 | else: 17 | self.cache = Cache() 18 | 19 | def add_information_about_person(self, session_info): 20 | """If there already are information from this source in the cache 21 | this function will overwrite that information""" 22 | 23 | session_info = dict(session_info) 24 | name_id = session_info["name_id"] 25 | issuer = session_info.pop("issuer") 26 | self.cache.set(name_id, issuer, session_info, session_info["not_on_or_after"]) 27 | return name_id 28 | 29 | def stale_sources_for_person(self, name_id, sources=None): 30 | """ 31 | 32 | :param name_id: Identifier of the subject, a NameID instance 33 | :param sources: Sources for information about the subject 34 | :return: 35 | """ 36 | if not sources: 37 | # assume that all the members has be asked 38 | # once before, hence they are represented in the cache 39 | sources = self.cache.entities(name_id) 40 | sources = [m for m in sources if not self.cache.active(name_id, m)] 41 | return sources 42 | 43 | def issuers_of_info(self, name_id): 44 | return self.cache.entities(name_id) 45 | 46 | def get_identity(self, name_id, entities=None, check_not_on_or_after=True): 47 | return self.cache.get_identity(name_id, entities, check_not_on_or_after) 48 | 49 | def get_info_from(self, name_id, entity_id, check_not_on_or_after=True): 50 | return self.cache.get(name_id, entity_id, check_not_on_or_after) 51 | 52 | def subjects(self): 53 | """Returns the name id's for all the persons in the cache""" 54 | return self.cache.subjects() 55 | 56 | def remove_person(self, name_id): 57 | self.cache.delete(name_id) 58 | 59 | def get_entityid(self, name_id, source_id, check_not_on_or_after=True): 60 | try: 61 | return self.cache.get(name_id, source_id, check_not_on_or_after)["name_id"] 62 | except (KeyError, ValueError): 63 | return "" 64 | 65 | def sources(self, name_id): 66 | return self.cache.entities(name_id) 67 | -------------------------------------------------------------------------------- /src/saml2/profile/__init__.py: -------------------------------------------------------------------------------- 1 | # profile schema descriptions 2 | __author__ = "rolandh" 3 | -------------------------------------------------------------------------------- /src/saml2/profile/samlec.py: -------------------------------------------------------------------------------- 1 | from saml2 import SamlBase 2 | 3 | 4 | NAMESPACE = "urn:ietf:params:xml:ns:samlec" 5 | 6 | 7 | class GeneratedKey(SamlBase): 8 | c_tag = "GeneratedKey" 9 | c_namespace = NAMESPACE 10 | 11 | 12 | ELEMENT_BY_TAG = { 13 | "GeneratedKey": GeneratedKey, 14 | } 15 | -------------------------------------------------------------------------------- /src/saml2/s2repoze/__init__.py: -------------------------------------------------------------------------------- 1 | # Created by Roland Hedberg 2 | -------------------------------------------------------------------------------- /src/saml2/s2repoze/plugins/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IdentityPython/pysaml2/0252ec96058c87c43f89e05d978e72b6ba2e6978/src/saml2/s2repoze/plugins/__init__.py -------------------------------------------------------------------------------- /src/saml2/s2repoze/plugins/ini.py: -------------------------------------------------------------------------------- 1 | import ConfigParser 2 | 3 | # from repoze.who.interfaces import IChallenger, IIdentifier, IAuthenticator 4 | from repoze.who.interfaces import IMetadataProvider 5 | from zope.interface import implements 6 | 7 | 8 | class INIMetadataProvider: 9 | 10 | implements(IMetadataProvider) 11 | 12 | def __init__(self, ini_file, key_attribute): 13 | 14 | self.users = ConfigParser.ConfigParser() 15 | self.users.readfp(open(ini_file)) 16 | self.key_attribute = key_attribute 17 | 18 | def add_metadata(self, _environ, identity): 19 | # logger = environ.get('repoze.who.logger','') 20 | 21 | key = identity.get("repoze.who.userid") 22 | try: 23 | if self.key_attribute: 24 | for sec in self.users.sections(): 25 | if self.users.has_option(sec, self.key_attribute): 26 | if key in self.users.get(sec, self.key_attribute): 27 | identity["user"] = dict(self.users.items(sec)) 28 | break 29 | else: 30 | identity["user"] = dict(self.users.items(key)) 31 | except ValueError: 32 | pass 33 | 34 | 35 | def make_plugin(ini_file, key_attribute=""): 36 | return INIMetadataProvider(ini_file, key_attribute) 37 | -------------------------------------------------------------------------------- /src/saml2/schema/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "rolandh" 2 | -------------------------------------------------------------------------------- /src/saml2/sdb.py: -------------------------------------------------------------------------------- 1 | from hashlib import sha1 2 | import logging 3 | 4 | from saml2.ident import code_binary 5 | 6 | 7 | logger = logging.getLogger(__name__) 8 | 9 | 10 | def context_match(cfilter, cntx): 11 | # TODO 12 | return True 13 | 14 | 15 | # The key to the stored authn statement is placed encrypted in the cookie 16 | 17 | 18 | class SessionStorage: 19 | """In memory storage of session information""" 20 | 21 | def __init__(self): 22 | self.db = {"assertion": {}, "authn": {}} 23 | self.assertion = self.db["assertion"] 24 | self.authn = self.db["authn"] 25 | 26 | def store_assertion(self, assertion, to_sign): 27 | self.assertion[assertion.id] = (assertion, to_sign) 28 | key = sha1(code_binary(assertion.subject.name_id)).hexdigest() 29 | try: 30 | self.authn[key].append(assertion.authn_statement) 31 | except KeyError: 32 | self.authn[key] = [assertion.authn_statement] 33 | 34 | def get_assertion(self, cid): 35 | return self.assertion[cid] 36 | 37 | def get_authn_statements(self, name_id, session_index=None, requested_context=None): 38 | """ 39 | 40 | :param name_id: 41 | :param session_index: 42 | :param requested_context: 43 | :return: 44 | """ 45 | result = [] 46 | key = sha1(code_binary(name_id)).hexdigest() 47 | try: 48 | statements = self.authn[key] 49 | except KeyError: 50 | logger.info("Unknown subject %s", name_id) 51 | return [] 52 | 53 | for statement in statements: 54 | if session_index: 55 | if statement.session_index != session_index: 56 | continue 57 | if requested_context: 58 | if not context_match(requested_context, statement[0].authn_context): 59 | continue 60 | result.append(statement) 61 | 62 | return result 63 | 64 | def remove_authn_statements(self, name_id): 65 | logger.debug("remove authn about: %s", name_id) 66 | nkey = sha1(code_binary(name_id)).hexdigest() 67 | 68 | del self.authn[nkey] 69 | -------------------------------------------------------------------------------- /src/saml2/tools/mdexport.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | 4 | from saml2.attribute_converter import ac_factory 5 | from saml2.httpbase import HTTPBase 6 | from saml2.mdstore import MetaDataExtern 7 | from saml2.mdstore import MetaDataFile 8 | from saml2.sigver import SecurityContext 9 | from saml2.sigver import _get_xmlsec_cryptobackend 10 | 11 | 12 | __author__ = "rolandh" 13 | 14 | """ 15 | A script that imports and verifies metadata and then dumps it in a basic 16 | dictionary format. 17 | """ 18 | 19 | 20 | def main(): 21 | parser = argparse.ArgumentParser() 22 | parser.add_argument("-t", dest="type") 23 | parser.add_argument("-u", dest="url") 24 | parser.add_argument("-c", dest="cert") 25 | parser.add_argument("-a", dest="attrsmap") 26 | parser.add_argument("-o", dest="output") 27 | parser.add_argument("-x", dest="xmlsec") 28 | parser.add_argument(dest="item") 29 | args = parser.parse_args() 30 | 31 | metad = None 32 | 33 | if args.type == "local": 34 | metad = MetaDataFile(args.item, args.item) 35 | elif args.type == "external": 36 | ATTRCONV = ac_factory(args.attrsmap) 37 | httpc = HTTPBase() 38 | crypto = _get_xmlsec_cryptobackend(args.xmlsec) 39 | sc = SecurityContext(crypto) 40 | metad = MetaDataExtern(ATTRCONV, args.url, sc, cert=args.cert, http=httpc) 41 | 42 | if metad is not None: 43 | metad.load() 44 | txt = metad.dumps() 45 | if args.output: 46 | f = open(args.output, "w") 47 | f.write(txt) 48 | f.close() 49 | else: 50 | print(txt) 51 | 52 | 53 | if __name__ == "__main__": 54 | main() 55 | -------------------------------------------------------------------------------- /src/saml2/tools/mdexport_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | """ 4 | A script that imports and verifies metadata and then dumps it in a basic 5 | dictionary format. 6 | """ 7 | 8 | import sys 9 | 10 | from saml2.mdstore import MetaDataExtern 11 | from saml2.mdstore import MetaDataFile 12 | 13 | 14 | MDIMPORT = { 15 | "swamid": { 16 | "url": "https://kalmar2.org/simplesaml/module.php/aggregator/?id=kalmarcentral2&set=saml2", 17 | "cert": "kalmar2.pem", 18 | "type": "external", 19 | }, 20 | "incommon": {"file": "InCommon-metadata.xml", "type": "local"}, 21 | "test": {"file": "mdtest.xml", "type": "local"}, 22 | } 23 | 24 | 25 | def main(): 26 | item = MDIMPORT[sys.argv[1]] 27 | 28 | metad = None 29 | 30 | if item["type"] == "local": 31 | metad = MetaDataFile(sys.argv[1], item["file"]) 32 | elif item["type"] == "external": 33 | metad = MetaDataExtern(sys.argv[1], item["url"], "/opt/local/bin/xmlsec1", item["cert"]) 34 | 35 | if metad: 36 | metad.load() 37 | print(metad.dumps()) 38 | 39 | 40 | if __name__ == "__main__": 41 | main() 42 | -------------------------------------------------------------------------------- /src/saml2/tools/mdimport.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import time 3 | 4 | from saml2.attribute_converter import ac_factory 5 | from saml2.mdstore import MetaDataFile 6 | from saml2.mdstore import MetaDataMD 7 | 8 | 9 | __author__ = "rolandh" 10 | 11 | 12 | def main(): 13 | start = time.time() 14 | for i in range(1, 10): 15 | mdmd = MetaDataMD(ac_factory("../tests/attributemaps"), "swamid2.md") 16 | mdmd.load() 17 | 18 | _ = mdmd.keys() 19 | 20 | print(time.time() - start) 21 | 22 | start = time.time() 23 | for i in range(1, 10): 24 | mdf = MetaDataFile(ac_factory("../tests/attributemaps"), "../tests/swamid-2.0.xml") 25 | mdf.load() 26 | _ = mdf.keys() 27 | 28 | print(time.time() - start) 29 | 30 | 31 | if __name__ == "__main__": 32 | main() 33 | -------------------------------------------------------------------------------- /src/saml2/tools/merge_metadata.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import argparse 3 | 4 | from saml2.attribute_converter import ac_factory 5 | from saml2.httpbase import HTTPBase 6 | from saml2.mdstore import MetaDataExtern 7 | from saml2.mdstore import MetaDataFile 8 | from saml2.mdstore import MetadataStore 9 | from saml2.sigver import SecurityContext 10 | from saml2.sigver import _get_xmlsec_cryptobackend 11 | 12 | 13 | __author__ = "rolandh" 14 | 15 | """ 16 | A script that imports and verifies metadata. 17 | """ 18 | 19 | 20 | def main(): 21 | parser = argparse.ArgumentParser() 22 | parser.add_argument("-a", dest="attrsmap") 23 | parser.add_argument("-o", dest="output", default="local") 24 | parser.add_argument("-x", dest="xmlsec") 25 | parser.add_argument("-i", dest="ignore_valid", action="store_true") 26 | parser.add_argument(dest="conf") 27 | args = parser.parse_args() 28 | 29 | metad = None 30 | 31 | # config file format 32 | # 33 | # local 34 | # remote 35 | # 36 | # for instance 37 | # 38 | # local metadata_sp_1.xml 39 | # local InCommon-metadata.xml 40 | # remote https://kalmar2.org/simplesaml/module.php/aggregator/?id=kalmarcentral2&set=saml2 kalmar2.pem 41 | # 42 | 43 | ATTRCONV = ac_factory(args.attrsmap) 44 | 45 | mds = MetadataStore(None, None) 46 | 47 | for line in open(args.conf).readlines(): 48 | line = line.strip() 49 | if len(line) == 0: 50 | continue 51 | elif line[0] == "#": 52 | continue 53 | spec = line.split(" ") 54 | 55 | if args.ignore_valid: 56 | kwargs = {"check_validity": False} 57 | else: 58 | kwargs = {} 59 | 60 | if spec[0] == "local": 61 | metad = MetaDataFile(spec[1], spec[1], **kwargs) 62 | elif spec[0] == "remote": 63 | ATTRCONV = ac_factory(args.attrsmap) 64 | httpc = HTTPBase() 65 | crypto = _get_xmlsec_cryptobackend(args.xmlsec) 66 | sc = SecurityContext(crypto, key_type="", cert_type="") 67 | metad = MetaDataExtern(ATTRCONV, spec[1], sc, cert=spec[2], http=httpc, **kwargs) 68 | 69 | if metad is not None: 70 | metad.load() 71 | 72 | mds.metadata[spec[1]] = metad 73 | 74 | print(mds.dumps(args.output)) 75 | 76 | 77 | if __name__ == "__main__": 78 | main() 79 | -------------------------------------------------------------------------------- /src/saml2/tools/update_metadata.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | curl -O -G http://md.swamid.se/md/swamid-2.0.xml 3 | python3 mdexport.py -t local -o swamid2.md swamid-2.0.xml 4 | -------------------------------------------------------------------------------- /src/saml2/tools/verify_metadata.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import argparse 4 | 5 | from saml2.attribute_converter import ac_factory 6 | from saml2.httpbase import HTTPBase 7 | from saml2.mdstore import MetaDataExtern 8 | from saml2.mdstore import MetaDataFile 9 | from saml2.sigver import SecurityContext 10 | from saml2.sigver import _get_xmlsec_cryptobackend 11 | 12 | 13 | __author__ = "rolandh" 14 | 15 | """ 16 | A script that imports and verifies metadata. 17 | """ 18 | 19 | 20 | def main(): 21 | parser = argparse.ArgumentParser() 22 | parser.add_argument("-t", dest="type") 23 | parser.add_argument("-u", dest="url") 24 | parser.add_argument("-c", dest="cert") 25 | parser.add_argument("-a", dest="attrsmap") 26 | parser.add_argument("-o", dest="output") 27 | parser.add_argument("-x", dest="xmlsec") 28 | parser.add_argument("-i", dest="ignore_valid", action="store_true") 29 | parser.add_argument(dest="item") 30 | args = parser.parse_args() 31 | 32 | metad = None 33 | 34 | if args.ignore_valid: 35 | kwargs = {"check_validity": False} 36 | else: 37 | kwargs = {} 38 | 39 | if args.type == "local": 40 | if args.cert and args.xmlsec: 41 | crypto = _get_xmlsec_cryptobackend(args.xmlsec) 42 | sc = SecurityContext(crypto) 43 | metad = MetaDataFile(args.item, args.item, cert=args.cert, security=sc, **kwargs) 44 | else: 45 | metad = MetaDataFile(args.item, args.item, **kwargs) 46 | elif args.type == "external": 47 | ATTRCONV = ac_factory(args.attrsmap) 48 | httpc = HTTPBase() 49 | crypto = _get_xmlsec_cryptobackend(args.xmlsec) 50 | sc = SecurityContext(crypto) 51 | metad = MetaDataExtern(ATTRCONV, args.url, sc, cert=args.cert, http=httpc, **kwargs) 52 | 53 | if metad: 54 | metad.load() 55 | print("OK") 56 | 57 | 58 | if __name__ == "__main__": 59 | main() 60 | -------------------------------------------------------------------------------- /src/saml2/userinfo/__init__.py: -------------------------------------------------------------------------------- 1 | # Interface to external user info resources 2 | 3 | import copy 4 | 5 | 6 | class UserInfo: 7 | """Read only interface to a user info store""" 8 | 9 | def __init__(self): 10 | pass 11 | 12 | def __call__(self, **kwargs): 13 | pass 14 | 15 | 16 | class UserInfoDB(UserInfo): 17 | """Read only interface to a user info store""" 18 | 19 | def __init__(self, db=None): 20 | self.db = db 21 | 22 | @staticmethod 23 | def filter(userinfo, user_info_claims=None): 24 | """ 25 | Return only those claims that are asked for. 26 | It's a best effort task; if essential claims are not present 27 | no error is flagged. 28 | 29 | :param userinfo: A dictionary containing the available user info. 30 | :param user_info_claims: A dictionary specifying the asked for claims 31 | :return: A dictionary of filtered claims. 32 | """ 33 | 34 | if user_info_claims is None: 35 | return copy.copy(userinfo) 36 | else: 37 | result = {} 38 | missing = [] 39 | optional = [] 40 | for key, restr in user_info_claims.items(): 41 | try: 42 | result[key] = userinfo[key] 43 | except KeyError: 44 | if restr == {"essential": True}: 45 | missing.append(key) 46 | else: 47 | optional.append(key) 48 | return result 49 | 50 | def __call__(self, userid, user_info_claims=None, **kwargs): 51 | try: 52 | return self.filter(self.db[userid], user_info_claims) 53 | except KeyError: 54 | return {} 55 | -------------------------------------------------------------------------------- /src/saml2/userinfo/ldapinfo.py: -------------------------------------------------------------------------------- 1 | import ldap 2 | from ldap import SCOPE_SUBTREE 3 | 4 | from saml2.userinfo import UserInfo 5 | 6 | 7 | class UserInfoLDAP(UserInfo): 8 | def __init__( 9 | self, uri, base, filter_pattern, scope=SCOPE_SUBTREE, tls=False, user="", passwd="", attr=None, attrsonly=False 10 | ): 11 | UserInfo.__init__(self) 12 | self.ldapuri = uri 13 | self.base = base 14 | self.filter_pattern = filter_pattern 15 | self.scope = scope 16 | self.tls = tls 17 | self.attr = attr 18 | self.attrsonly = attrsonly 19 | self.ld = ldap.initialize(uri) 20 | self.ld.protocol_version = ldap.VERSION3 21 | self.ld.simple_bind_s(user, passwd) 22 | 23 | def __call__( 24 | self, userid, base="", filter_pattern="", scope=SCOPE_SUBTREE, tls=False, attr=None, attrsonly=False, **kwargs 25 | ): 26 | 27 | if filter_pattern: 28 | _filter = filter_pattern % userid 29 | else: 30 | _filter = self.filter_pattern % userid 31 | 32 | _base = base or self.base 33 | _scope = scope or self.scope 34 | _attr = attr or self.attr 35 | _attrsonly = attrsonly or self.attrsonly 36 | arg = [_base, _scope, _filter, _attr, _attrsonly] 37 | res = self.ld.search_s(*arg) 38 | # should only be one entry and the information per entry is 39 | # the tuple (dn, ava) 40 | return res[0][1] 41 | -------------------------------------------------------------------------------- /src/saml2/version.py: -------------------------------------------------------------------------------- 1 | try: 2 | from importlib.metadata import version as _resolve_package_version 3 | except ImportError: 4 | from importlib_metadata import version as _resolve_package_version # type: ignore[no-redef] 5 | 6 | 7 | def _parse_version(): 8 | value = _resolve_package_version("pysaml2") 9 | return value 10 | 11 | 12 | version = _parse_version() 13 | -------------------------------------------------------------------------------- /src/saml2/ws/__init__.py: -------------------------------------------------------------------------------- 1 | __author__ = "roland" 2 | -------------------------------------------------------------------------------- /src/saml2/xml/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IdentityPython/pysaml2/0252ec96058c87c43f89e05d978e72b6ba2e6978/src/saml2/xml/__init__.py -------------------------------------------------------------------------------- /src/saml2test/__init__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import socket 3 | from subprocess import PIPE 4 | from subprocess import Popen 5 | import sys 6 | import time 7 | import traceback 8 | 9 | import requests 10 | 11 | from saml2test.check import CRITICAL 12 | 13 | 14 | logger = logging.getLogger(__name__) 15 | 16 | __author__ = "rolandh" 17 | 18 | 19 | class FatalError(Exception): 20 | pass 21 | 22 | 23 | class CheckError(Exception): 24 | pass 25 | 26 | 27 | class HTTP_ERROR(Exception): 28 | pass 29 | 30 | 31 | class Unknown(Exception): 32 | pass 33 | 34 | 35 | class OperationError(Exception): 36 | pass 37 | 38 | 39 | class ContextFilter(logging.Filter): 40 | """ 41 | This is a filter which injects time laps information into the log. 42 | """ 43 | 44 | def start(self): 45 | self.start = time.time() 46 | 47 | def filter(self, record): 48 | record.delta = time.time() - self.start 49 | return True 50 | 51 | 52 | def start_script(path, *args): 53 | popen_args = [path] 54 | popen_args.extend(args) 55 | return Popen(popen_args, stdout=PIPE, stderr=PIPE) 56 | 57 | 58 | def stop_script_by_name(name): 59 | import os 60 | import signal 61 | import subprocess 62 | 63 | p = subprocess.Popen(["ps", "-A"], stdout=subprocess.PIPE) 64 | out, err = p.communicate() 65 | 66 | for line in out.splitlines(): 67 | if name in line: 68 | pid = int(line.split(None, 1)[0]) 69 | os.kill(pid, signal.SIGKILL) 70 | 71 | 72 | def stop_script_by_pid(pid): 73 | import os 74 | import signal 75 | 76 | os.kill(pid, signal.SIGKILL) 77 | 78 | 79 | def get_page(url): 80 | resp = requests.get(url) 81 | if resp.status_code == 200: 82 | return resp.text 83 | else: 84 | raise HTTP_ERROR(resp.status) 85 | 86 | 87 | def exception_trace(tag, exc, log=None): 88 | message = traceback.format_exception(*sys.exc_info()) 89 | 90 | try: 91 | _exc = f"Exception: {exc}" 92 | except UnicodeEncodeError: 93 | _exc = f"Exception: {exc.message.encode('utf-8', 'replace')}" 94 | 95 | return {"status": CRITICAL, "message": _exc, "content": "".join(message)} 96 | 97 | 98 | def ip_addresses(): 99 | return [ip for ip in socket.gethostbyname_ex(socket.gethostname())[2] if not ip.startswith("127.")] 100 | -------------------------------------------------------------------------------- /src/saml2test/status.py: -------------------------------------------------------------------------------- 1 | __author__ = "rolandh" 2 | 3 | INFORMATION = 0 4 | OK = 1 5 | WARNING = 2 6 | ERROR = 3 7 | CRITICAL = 4 8 | INTERACTION = 5 9 | 10 | STATUSCODE = ["INFORMATION", "OK", "WARNING", "ERROR", "CRITICAL", "INTERACTION"] 11 | -------------------------------------------------------------------------------- /src/utility/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IdentityPython/pysaml2/0252ec96058c87c43f89e05d978e72b6ba2e6978/src/utility/__init__.py -------------------------------------------------------------------------------- /src/utility/metadata.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os.path 3 | import time 4 | from time import strftime 5 | import urllib 6 | 7 | 8 | __author__ = "rhoerbe" 9 | 10 | logger = logging.getLogger(__name__) 11 | 12 | 13 | def fetch_metadata(url, path, maxage=600): 14 | """ 15 | :param url: metadata remote location 16 | :param path: metdata file name 17 | :param maxage: if max age of existing metadata file (s) is exceeded, 18 | the file will be fetched from the remote location 19 | """ 20 | fetch = False 21 | if not os.path.isfile(path): 22 | fetch = True 23 | logger.debug("metadata file %s not found", path) 24 | elif (os.path.getmtime(path) + maxage) < time.time(): 25 | fetch = True 26 | logger.debug( 27 | "metadata file %s from %s is more than %s s old", 28 | path, 29 | strftime("%Y-%m-%d %H:%M:%S", time.localtime(os.path.getmtime(path))), 30 | maxage, 31 | ) 32 | else: 33 | logger.debug("metadata file %s is less than %s s old", path, maxage) 34 | if fetch: 35 | f = urllib.URLopener() 36 | try: 37 | f.retrieve(url, path) 38 | logger.debug("downloaded metadata from %s into %s", url, path) 39 | except Exception as e: 40 | logger.debug("downloaded metadata from %s failed: %s", url, str(e)) 41 | -------------------------------------------------------------------------------- /tests/SWITCHaaiRootCA.crt.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDnzCCAoegAwIBAgINSWITCHaai+Root+CAzANBgkqhkiG9w0BAQUFADBrMQsw 3 | CQYDVQQGEwJDSDFAMD4GA1UEChM3U3dpdGNoIC0gVGVsZWluZm9ybWF0aWtkaWVu 4 | c3RlIGZ1ZXIgTGVocmUgdW5kIEZvcnNjaHVuZzEaMBgGA1UEAxMRU1dJVENIYWFp 5 | IFJvb3QgQ0EwHhcNMDgwNTE1MDYzMDAwWhcNMjgwNTE1MDYyOTU5WjBrMQswCQYD 6 | VQQGEwJDSDFAMD4GA1UEChM3U3dpdGNoIC0gVGVsZWluZm9ybWF0aWtkaWVuc3Rl 7 | IGZ1ZXIgTGVocmUgdW5kIEZvcnNjaHVuZzEaMBgGA1UEAxMRU1dJVENIYWFpIFJv 8 | b3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUSWbn/rhWew/s 9 | LJRyciyRKDGyFXSgiDO/EohYuZLw6EAKLLlhZorNtEHQbbn0Oo13S33MclHMvGWT 10 | KJM0u1hG+6gLy78EPmJbqAE1Uv23wVEH4SX0VJfl3JVqIebiAH/CjuLubgMUspDI 11 | jOdQHNLS7pthTbm7Tgh7zMsiLPyMTZJep5CGbqv8NoK6bMaF0Z+Bt7e1JRlhHFCV 12 | iJJaR/+hfpzLsJ8NWVivvrpRGaGJ1XR+9FGsTkjNdMCirNJJZ6XvUOe5w7pHSd9M 13 | cppFP0eyLs02AMzMXI4iz6PK/w3EdzXGXpK+gSgvLxWYct4xHpv1e2NXhNgdJOSN 14 | 9ra/wJLVAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG 15 | MB0GA1UdDgQWBBTpmuIGWOsP14EDXVyXubG1k307hDANBgkqhkiG9w0BAQUFAAOC 16 | AQEAMV/eIW6pFB+mbk7rD7hUPTWDRaoca3kHqmFGFnHfuY8+c0/Mqjh8Y/jyX1yb 17 | f58crTSWrbyGbUZ3oxDGQ34tuZSkmeR32NqryiX3sP5qlNSozVguQKt8o4vhS1Qe 18 | WPsXALs3em2pdKuIGSOpbuDnopPcmU2g5Zi2R5P7qpKDKAKtNUEwV+LW7GBMEksO 19 | Nj7BFXk4AFBFBijaYJGgHmoKSImVgeNIvsV+BSv5HJ4q6vcxfnwuvvGHM0AGphYO 20 | 6f5qtHMUgvAblI8M/2QsBgethaGrirtKJ3aCRLdaR2R1QfaGRpck/Ron5/MpMxiJ 21 | wLT8YlW/zjx2yNABhPSAjfzeMw== 22 | -----END CERTIFICATE----- 23 | -------------------------------------------------------------------------------- /tests/aa_conf.py: -------------------------------------------------------------------------------- 1 | from saml2 import BINDING_HTTP_REDIRECT 2 | from saml2 import BINDING_SOAP 3 | from saml2 import NAME_FORMAT_URI 4 | 5 | 6 | BASE = "http://localhost:8089/" 7 | 8 | from pathutils import full_path 9 | 10 | 11 | CONFIG = { 12 | "service": { 13 | "aa": { 14 | "endpoints": { 15 | "attribute_service": [(f"{BASE}as", BINDING_HTTP_REDIRECT)], 16 | "single_logout_service": [(f"{BASE}slo", BINDING_SOAP)], 17 | }, 18 | "release_policy": { 19 | "default": { 20 | "lifetime": {"minutes": 15}, 21 | "attribute_restrictions": None, # means all I have 22 | "name_form": NAME_FORMAT_URI, 23 | }, 24 | }, 25 | "subject_data": full_path("aa.db"), 26 | } 27 | }, 28 | "entityid": f"{BASE}aa", 29 | "name": "Rolands AA", 30 | "debug": 1, 31 | "key_file": full_path("test.key"), 32 | "cert_file": full_path("test.pem"), 33 | # "xmlsec_binary" : None, 34 | "metadata": { 35 | "local": [full_path("metadata.xml"), full_path("vo_metadata.xml")], 36 | }, 37 | "attribute_map_dir": full_path("attributemaps"), 38 | "organization": { 39 | "name": "Exempel AB", 40 | "display_name": [("Exempel AB", "se"), ("Example Co.", "en")], 41 | "url": "http://www.example.com/roland", 42 | }, 43 | "contact_person": [ 44 | { 45 | "given_name": "John", 46 | "sur_name": "Smith", 47 | "email_address": ["john.smith@example.com"], 48 | "contact_type": "technical", 49 | }, 50 | ], 51 | } 52 | -------------------------------------------------------------------------------- /tests/attribute.map: -------------------------------------------------------------------------------- 1 | urn:oid:2.5.4.4 surName urn:oasis:names:tc:SAML:2.0:attrname-format:uri 2 | urn:oid:2.5.4.42 givenName urn:oasis:names:tc:SAML:2.0:attrname-format:uri 3 | urn:oid:2.5.4.12 title urn:oasis:names:tc:SAML:2.0:attrname-format:uri 4 | urn:oid:0.9.2342.19200300.100.1.1 uid urn:oasis:names:tc:SAML:2.0:attrname-format:uri 5 | urn:oid:0.9.2342.19200300.100.1.3 mail urn:oasis:names:tc:SAML:2.0:attrname-format:uri 6 | urn:oid:1.3.6.1.4.1.5923.1.1.1.1 eduPersonAffiliation urn:oasis:names:tc:SAML:2.0:attrname-format:uri 7 | urn:oid:1.3.6.1.4.1.5923.1.1.1.7 eduPersonEntitlement urn:oasis:names:tc:SAML:2.0:attrname-format:uri 8 | -------------------------------------------------------------------------------- /tests/conftest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import pytest 4 | 5 | 6 | # TODO: On my system this function seems to be returning an incorrect location 7 | @pytest.fixture 8 | def xmlsec(request): 9 | for path in os.environ["PATH"].split(":"): 10 | fil = os.path.join(path, "xmlsec1") 11 | if os.access(fil, os.X_OK): 12 | return fil 13 | 14 | raise Exception("Can't find xmlsec1") 15 | 16 | 17 | @pytest.fixture 18 | def AVA(request): 19 | return [ 20 | { 21 | "surName": ["Jeter"], 22 | "givenName": ["Derek"], 23 | }, 24 | { 25 | "surName": ["Howard"], 26 | "givenName": ["Ryan"], 27 | }, 28 | { 29 | "surName": ["Suzuki"], 30 | "givenName": ["Ischiro"], 31 | }, 32 | { 33 | "surName": ["Hedberg"], 34 | "givenName": ["Roland"], 35 | }, 36 | ] 37 | -------------------------------------------------------------------------------- /tests/create_certs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | newcert="openssl req -x509 -new -days 365000 -sha256 -config openssl.cnf -set_serial 1" 3 | $newcert -key test_1.key -out test_1.crt -subj '/C=zz/ST=zz/L=zzzz/O=Zzzzz/OU=Zzzzz/CN=test' 4 | $newcert -key test_2.key -out test_2.crt -subj '/C=zz/ST=zz/L=zzzz/O=Zzzzz/OU=Zzzzz/CN=test' 5 | $newcert -key pki/test_3.key -out pki/test_3.crt -subj '/C=zz/ST=zz/L=zzzz/O=Zzzzz/OU=Zzzzz/CN=test' 6 | $newcert -key root_cert/localhost.ca.key -out root_cert/localhost.ca.crt -subj '/C=se/ST=ac/L=umea/O=ITS Umea University/OU=DIRG/CN=localhost.ca' 7 | -------------------------------------------------------------------------------- /tests/disco_conf.py: -------------------------------------------------------------------------------- 1 | from pathutils import full_path 2 | from pathutils import xmlsec_path 3 | 4 | from saml2.extension.idpdisc import BINDING_DISCO 5 | 6 | 7 | BASE = "http://localhost:8088" 8 | 9 | CONFIG = { 10 | "entityid": f"{BASE}/disco.xml", 11 | "name": "Rolands Discoserver", 12 | "service": { 13 | "ds": { 14 | "endpoints": { 15 | "disco_service": [ 16 | (f"{BASE}/disco", BINDING_DISCO), 17 | ] 18 | }, 19 | }, 20 | }, 21 | "debug": 1, 22 | "xmlsec_binary": xmlsec_path, 23 | "metadata": [ 24 | { 25 | "class": "saml2.mdstore.MetaDataFile", 26 | "metadata": [(full_path("servera.xml"),)], 27 | } 28 | ], 29 | } 30 | -------------------------------------------------------------------------------- /tests/ecp_soap.xml: -------------------------------------------------------------------------------- 1 | 6 | 7 | 11 | 14 | 15 | 18 | 19 | https://sp.example.org/entity 20 | 21 | ABCDEFGHI01234567 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /tests/edugain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICVzCCAcACCQDnwXdaJ4G3vTANBgkqhkiG9w0BAQUFADBwMQswCQYDVQQGEwJO 3 | TzESMBAGA1UECBMJVHJvbmRoZWltMRIwEAYDVQQHEwlUcm9uZGhlaW0xDjAMBgNV 4 | BAoTBUZlaWRlMQ4wDAYDVQQLEwVGZWlkZTEZMBcGA1UEAxMQZWR1Z2Fpbi5mZWlk 5 | ZS5ubzAeFw0wOTA4MzEwNjU2NDJaFw0zNzAxMTUwNjU2NDJaMHAxCzAJBgNVBAYT 6 | Ak5PMRIwEAYDVQQIEwlUcm9uZGhlaW0xEjAQBgNVBAcTCVRyb25kaGVpbTEOMAwG 7 | A1UEChMFRmVpZGUxDjAMBgNVBAsTBUZlaWRlMRkwFwYDVQQDExBlZHVnYWluLmZl 8 | aWRlLm5vMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC0/w1x1eIAbLYSVqCc 9 | OWiPC3lhWRNEBgRXFdCo/CFQt1DSAh6AYIw9nGWatqiKD0dtXhn+g4or36fF+l4t 10 | FlKwMjIRdB9EM3dp8ErhecauLTAXzJGI16YrfI5932UJr4NDJB/Wm1GKefyu5QIR 11 | w9NTEImw8CmUzzzmMd7TBM2epwIDAQABMA0GCSqGSIb3DQEBBQUAA4GBAJpIWdXn 12 | FL/j9Cm/Pdn6Yoxkf1mWy8L8WSwF8j9xfkvp53/GMd9IFkgkBbZo+F9CDH2la6H3 13 | vseA3ZJrXrxSn5RBhI5XJ85DGfdcMYJy3K42Y6mAUghVv1n+rf39w/cyuSRIW0IY 14 | XE3ANufnryezpDUffXpzdUltuTCpu2qfKEj2 15 | -----END CERTIFICATE----- 16 | -------------------------------------------------------------------------------- /tests/empty_metadata_file.xml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IdentityPython/pysaml2/0252ec96058c87c43f89e05d978e72b6ba2e6978/tests/empty_metadata_file.xml -------------------------------------------------------------------------------- /tests/enc_tmpl.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 7 | 8 | 9 | 11 | 12 | my-rsa-key 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /tests/extra_lines.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICITCCAYoCAQEwDQYJKoZIhvcNAQELBQAwWDELMAkGA1UEBhMCenoxCzAJBgNV 3 | BAgMAnp6MQ0wCwYDVQQHDAR6enp6MQ4wDAYDVQQKDAVaenp6ejEOMAwGA1UECwwF 4 | Wnp6enoxDTALBgNVBAMMBHRlc3QwIBcNMTkwNDEyMTk1MDM0WhgPMzAxODA4MTMx 5 | OTUwMzRaMFgxCzAJBgNVBAYTAnp6MQswCQYDVQQIDAJ6ejENMAsGA1UEBwwEenp6 6 | ejEOMAwGA1UECgwFWnp6enoxDjAMBgNVBAsMBVp6enp6MQ0wCwYDVQQDDAR0ZXN0 7 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjW0kJM+4baWKtvO24ZsGXNvNK 8 | KkwTMz7OW5Z6BRqhSOq2WA0c5NCpMk6rD8Z2OTFEolPojEjf8dVyd/Ds/hrjFKQv 9 | 8wQgbdXLN51YTIsgd6h+hBJO+vzhl0PT4aT7M0JKo5ALtS6qk4tsworW2BnwyvsG 10 | SAinwfeWt4t/b1J3kwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAFtj7WArQQBugmh/ 11 | KQjjlfTQ5A052QeXfgTyO9vv1S6MRIi7qgiaEv49cGXnJv/TWbySkMKObPMUApjg 12 | 6z8PqcxuShew5FCTkNvwhABFPiyu0fUj3e2FEPHfsBu76jz4ugtmhUqjqhzwFY9c 13 | tnWRkkl6J0AjM3LnHOSgjNIclDZG 14 | -----END CERTIFICATE----- 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/get_metadata.sh: -------------------------------------------------------------------------------- 1 | curl -G -O http://md.incommon.org/InCommon/InCommon-metadata.xml 2 | curl -G -O http://metadata.aai.switch.ch/metadata.aaitest.xml 3 | -------------------------------------------------------------------------------- /tests/idp_conf_ec.py: -------------------------------------------------------------------------------- 1 | from pathutils import full_path 2 | 3 | from saml2 import BINDING_HTTP_POST 4 | from saml2 import BINDING_HTTP_REDIRECT 5 | from saml2 import BINDING_SOAP 6 | from saml2.saml import NAME_FORMAT_URI 7 | from saml2.sigver import get_xmlsec_binary 8 | 9 | 10 | xmlsec_path = get_xmlsec_binary(["/opt/local/bin"]) 11 | 12 | BASE = "http://localhost:8088" 13 | 14 | CONFIG = { 15 | "entityid": "urn:mace:example.com:saml:roland:idp", 16 | "name": "Rolands IdP", 17 | "service": { 18 | "idp": { 19 | "endpoints": { 20 | "single_sign_on_service": [(f"{BASE}/sso", BINDING_HTTP_REDIRECT)], 21 | "single_logout_service": [(f"{BASE}/slo", BINDING_SOAP), (f"{BASE}/slop", BINDING_HTTP_POST)], 22 | }, 23 | "policy": { 24 | "default": { 25 | "lifetime": {"minutes": 15}, 26 | "entity_categories": ["swamid", "edugain"], 27 | "name_form": NAME_FORMAT_URI, 28 | } 29 | }, 30 | "subject_data": full_path("subject_data.db"), 31 | # "domain": "umu.se", 32 | # "name_qualifier": "" 33 | }, 34 | }, 35 | "debug": 1, 36 | "key_file": full_path("test.key"), 37 | "cert_file": full_path("test.pem"), 38 | "xmlsec_binary": xmlsec_path, 39 | "metadata": [ 40 | { 41 | "class": "saml2.mdstore.MetaDataFile", 42 | "metadata": [(full_path("metadata_sp_1.xml"),), (full_path("vo_metadata.xml"),)], 43 | } 44 | ], 45 | "attribute_map_dir": full_path("attributemaps"), 46 | "organization": { 47 | "name": "Exempel AB", 48 | "display_name": [("Exempel AB", "se"), ("Example Co.", "en")], 49 | "url": "http://www.example.com/roland", 50 | }, 51 | "contact_person": [ 52 | { 53 | "given_name": "John", 54 | "sur_name": "Smith", 55 | "email_address": ["john.smith@example.com"], 56 | "contact_type": "technical", 57 | }, 58 | ], 59 | } 60 | -------------------------------------------------------------------------------- /tests/idp_conf_sp_no_encrypt.py: -------------------------------------------------------------------------------- 1 | from pathutils import full_path 2 | from pathutils import xmlsec_path 3 | 4 | from saml2 import BINDING_HTTP_POST 5 | from saml2 import BINDING_HTTP_REDIRECT 6 | from saml2 import BINDING_SOAP 7 | from saml2.saml import NAME_FORMAT_URI 8 | from saml2.saml import NAMEID_FORMAT_PERSISTENT 9 | 10 | 11 | BASE = "http://localhost:8088" 12 | 13 | CONFIG = { 14 | "entityid": "urn:mace:example.com:saml:roland:idp", 15 | "name": "Rolands IdP", 16 | "service": { 17 | "idp": { 18 | "endpoints": { 19 | "single_sign_on_service": [(f"{BASE}/sso", BINDING_HTTP_REDIRECT)], 20 | "single_logout_service": [(f"{BASE}/slo", BINDING_SOAP), (f"{BASE}/slop", BINDING_HTTP_POST)], 21 | }, 22 | "policy": { 23 | "default": { 24 | "lifetime": {"minutes": 15}, 25 | "attribute_restrictions": None, # means all I have 26 | "name_form": NAME_FORMAT_URI, 27 | }, 28 | "urn:mace:example.com:saml:roland:sp": { 29 | "lifetime": {"minutes": 5}, 30 | "nameid_format": NAMEID_FORMAT_PERSISTENT, 31 | # "attribute_restrictions":{ 32 | # "givenName": None, 33 | # "surName": None, 34 | # } 35 | }, 36 | }, 37 | # "domain": "umu.se", 38 | # "name_qualifier": "" 39 | }, 40 | }, 41 | "debug": 1, 42 | "key_file": full_path("test.key"), 43 | "cert_file": full_path("test.pem"), 44 | "xmlsec_binary": xmlsec_path, 45 | "metadata": [ 46 | { 47 | "class": "saml2.mdstore.MetaDataFile", 48 | "metadata": [(full_path("metadata_sp_1_no_encryption.xml"),), (full_path("vo_metadata.xml"),)], 49 | } 50 | ], 51 | "attribute_map_dir": full_path("attributemaps"), 52 | "organization": { 53 | "name": "Exempel AB", 54 | "display_name": [("Exempel AB", "se"), ("Example Co.", "en")], 55 | "url": "http://www.example.com/roland", 56 | }, 57 | "contact_person": [ 58 | { 59 | "given_name": "John", 60 | "sur_name": "Smith", 61 | "email_address": ["john.smith@example.com"], 62 | "contact_type": "technical", 63 | }, 64 | ], 65 | } 66 | -------------------------------------------------------------------------------- /tests/idp_slo_redirect.xml: -------------------------------------------------------------------------------- 1 | 2 | MIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX 4 | aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF 5 | MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 6 | ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB 7 | gQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8vQdzkihscQMXy 8 | 3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2PdTZcvDBKGbiaN 9 | efiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQABo4GnMIGkMB0G 10 | A1UdDgQWBBRePsKHKYJsiojE78ZWXccK9K4aJTB1BgNVHSMEbjBsgBRePsKHKYJs 11 | iojE78ZWXccK9K4aJaFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt 12 | U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrzqSSw 13 | mDY9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJSrKOEzHO7TL5cy6 14 | h3qh+3+JAk8HbGBW+cbX6KBCAw/mzU8flK25vnWwXS3dv2FF3Aod0/S7AWNfKib5 15 | U/SA9nJaz/mWeF9S0farz9AQFc8/NSzAzaVq7YbM4F6f6N2FRl7GikdXRCed45j6 16 | mrPzGzk3ECbupFnqyREH3+ZPSdk= 17 | Exempel ABExempel ABExample Co.http://www.example.com/rolandJohnSmithjohn.smith@example.com 18 | -------------------------------------------------------------------------------- /tests/idp_slo_redirect_conf.py: -------------------------------------------------------------------------------- 1 | from pathutils import full_path 2 | 3 | from saml2 import BINDING_HTTP_REDIRECT 4 | from saml2.saml import NAME_FORMAT_URI 5 | from saml2.saml import NAMEID_FORMAT_PERSISTENT 6 | 7 | 8 | CONFIG = { 9 | "entityid": "urn:mace:example.com:saml:roland:idp", 10 | "name": "Rolands IdP", 11 | "service": { 12 | "idp": { 13 | "endpoints": { 14 | "single_sign_on_service": [("http://localhost:8088/sso", BINDING_HTTP_REDIRECT)], 15 | "single_logout_service": [("http://localhost:8088/slo", BINDING_HTTP_REDIRECT)], 16 | }, 17 | "policy": { 18 | "default": { 19 | "lifetime": {"minutes": 15}, 20 | "attribute_restrictions": None, # means all I have 21 | "name_form": NAME_FORMAT_URI, 22 | }, 23 | "urn:mace:example.com:saml:roland:sp": { 24 | "lifetime": {"minutes": 5}, 25 | "nameid_format": NAMEID_FORMAT_PERSISTENT, 26 | }, 27 | }, 28 | "subject_data": full_path("subject_data.db"), 29 | } 30 | }, 31 | "debug": 1, 32 | "key_file": full_path("test.key"), 33 | "cert_file": full_path("test.pem"), 34 | "xmlsec_binary": None, 35 | "metadata": [ 36 | { 37 | "class": "saml2.mdstore.MetaDataFile", 38 | "metadata": [(full_path("sp_slo_redirect.xml"),)], 39 | } 40 | ], 41 | "attribute_map_dir": full_path("attributemaps"), 42 | "organization": { 43 | "name": "Exempel AB", 44 | "display_name": [("Exempel AB", "se"), ("Example Co.", "en")], 45 | "url": "http://www.example.com/roland", 46 | }, 47 | "contact_person": [ 48 | { 49 | "given_name": "John", 50 | "sur_name": "Smith", 51 | "email_address": ["john.smith@example.com"], 52 | "contact_type": "technical", 53 | }, 54 | ], 55 | } 56 | -------------------------------------------------------------------------------- /tests/idp_soap.xml: -------------------------------------------------------------------------------- 1 | 2 | MIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX 4 | aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF 5 | MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 6 | ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB 7 | gQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8vQdzkihscQMXy 8 | 3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2PdTZcvDBKGbiaN 9 | efiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQABo4GnMIGkMB0G 10 | A1UdDgQWBBRePsKHKYJsiojE78ZWXccK9K4aJTB1BgNVHSMEbjBsgBRePsKHKYJs 11 | iojE78ZWXccK9K4aJaFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt 12 | U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrzqSSw 13 | mDY9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJSrKOEzHO7TL5cy6 14 | h3qh+3+JAk8HbGBW+cbX6KBCAw/mzU8flK25vnWwXS3dv2FF3Aod0/S7AWNfKib5 15 | U/SA9nJaz/mWeF9S0farz9AQFc8/NSzAzaVq7YbM4F6f6N2FRl7GikdXRCed45j6 16 | mrPzGzk3ECbupFnqyREH3+ZPSdk= 17 | Exempel ABExempel ABExample Co.http://www.example.com/rolandJohnSmithjohn.smith@example.com 18 | -------------------------------------------------------------------------------- /tests/idp_soap_conf.py: -------------------------------------------------------------------------------- 1 | from pathutils import full_path 2 | 3 | from saml2 import BINDING_HTTP_REDIRECT 4 | from saml2 import BINDING_SOAP 5 | from saml2.saml import NAME_FORMAT_URI 6 | from saml2.saml import NAMEID_FORMAT_PERSISTENT 7 | 8 | 9 | CONFIG = { 10 | "entityid": "urn:mace:example.com:saml:roland:idp", 11 | "name": "Rolands IdP", 12 | "service": { 13 | "idp": { 14 | "endpoints": { 15 | "single_sign_on_service": [("http://localhost:8088/sso", BINDING_HTTP_REDIRECT)], 16 | "single_logout_service": [("http://localhost:8088/slo", BINDING_SOAP)], 17 | }, 18 | "policy": { 19 | "default": { 20 | "lifetime": {"minutes": 15}, 21 | "attribute_restrictions": None, # means all I have 22 | "name_form": NAME_FORMAT_URI, 23 | }, 24 | "urn:mace:example.com:saml:roland:sp": { 25 | "lifetime": {"minutes": 5}, 26 | "nameid_format": NAMEID_FORMAT_PERSISTENT, 27 | # "attribute_restrictions":{ 28 | # "givenName": None, 29 | # "surName": None, 30 | # } 31 | }, 32 | }, 33 | "subject_data": full_path("subject_data.db"), 34 | } 35 | }, 36 | "debug": 1, 37 | "key_file": full_path("test.key"), 38 | "cert_file": full_path("test.pem"), 39 | # "xmlsec_binary" : None, 40 | "metadata": [ 41 | { 42 | "class": "saml2.mdstore.MetaDataFile", 43 | "metadata": [(full_path("metadata.xml"),), (full_path("vo_metadata.xml"),)], 44 | } 45 | ], 46 | "attribute_map_dir": full_path("attributemaps"), 47 | "organization": { 48 | "name": "Exempel AB", 49 | "display_name": [("Exempel AB", "se"), ("Example Co.", "en")], 50 | "url": "http://www.example.com/roland", 51 | }, 52 | "contact_person": [ 53 | { 54 | "given_name": "John", 55 | "sur_name": "Smith", 56 | "email_address": ["john.smith@example.com"], 57 | "contact_type": "technical", 58 | }, 59 | ], 60 | } 61 | -------------------------------------------------------------------------------- /tests/idp_sp_conf.py: -------------------------------------------------------------------------------- 1 | __author__ = "rolandh" 2 | 3 | from saml2 import BINDING_HTTP_POST 4 | from saml2 import BINDING_HTTP_REDIRECT 5 | from saml2 import BINDING_SOAP 6 | from saml2.saml import NAME_FORMAT_URI 7 | from saml2.saml import NAMEID_FORMAT_PERSISTENT 8 | 9 | 10 | BASE = "http://localhost:8088/" 11 | 12 | from pathutils import full_path 13 | 14 | 15 | CONFIG = { 16 | "entityid": "urn:mace:example.com:saml:roland:idp", 17 | "name": "Rolands IdP", 18 | "service": { 19 | "idp": { 20 | "endpoints": { 21 | "single_sign_on_service": [(f"{BASE}sso", BINDING_HTTP_REDIRECT)], 22 | "single_logout_service": [(f"{BASE}slo", BINDING_SOAP), (f"{BASE}slop", BINDING_HTTP_POST)], 23 | }, 24 | "policy": { 25 | "default": { 26 | "lifetime": {"minutes": 15}, 27 | "attribute_restrictions": None, # means all I have 28 | "name_form": NAME_FORMAT_URI, 29 | }, 30 | "urn:mace:example.com:saml:roland:sp": { 31 | "lifetime": {"minutes": 5}, 32 | "nameid_format": NAMEID_FORMAT_PERSISTENT, 33 | # "attribute_restrictions":{ 34 | # "givenName": None, 35 | # "surName": None, 36 | # } 37 | }, 38 | }, 39 | "subject_data": full_path("subject_data.db"), 40 | }, 41 | "sp": { 42 | "endpoints": { 43 | "assertion_consumer_service": [(BASE, BINDING_HTTP_REDIRECT)], 44 | }, 45 | "required_attributes": ["surName", "givenName", "mail"], 46 | "optional_attributes": ["title"], 47 | }, 48 | }, 49 | "debug": 1, 50 | "key_file": full_path("test.key"), 51 | "cert_file": full_path("test.pem"), 52 | "xmlsec_binary": None, 53 | "metadata": [ 54 | { 55 | "class": "saml2.mdstore.MetaDataFile", 56 | "metadata": [(full_path("metadata.xml"),), (full_path("vo_metadata.xml"),)], 57 | } 58 | ], 59 | "attribute_map_dir": full_path("attributemaps"), 60 | "organization": { 61 | "name": "Exempel AB", 62 | "display_name": [("Exempel AB", "se"), ("Example Co.", "en")], 63 | "url": "http://www.example.com/roland", 64 | }, 65 | "contact_person": [ 66 | { 67 | "given_name": "John", 68 | "sur_name": "Smith", 69 | "email_address": ["john.smith@example.com"], 70 | "contact_type": "technical", 71 | }, 72 | ], 73 | } 74 | -------------------------------------------------------------------------------- /tests/idp_uiinfo.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | descriptor-example.org 6 | descriptor-example[^0-9]*\.org 7 | 8 | 9 | 10 | idpssodescriptor-example.org 11 | foo barhttp://example.com/logo.jpghttp://example.com/saml2/info.htmlExample Co.Exempel bolaghttp://example.com/saml2/privacyStatement.html 12 | 13 | 14 | MIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBFMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8vQdzkihscQMXy3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2PdTZcvDBKGbiaNefiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQABo4GnMIGkMB0GA1UdDgQWBBRePsKHKYJsiojE78ZWXccK9K4aJTB1BgNVHSMEbjBsgBRePsKHKYJsiojE78ZWXccK9K4aJaFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUtU3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrzqSSwmDY9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJSrKOEzHO7TL5cy6h3qh+3+JAk8HbGBW+cbX6KBCAw/mzU8flK25vnWwXS3dv2FF3Aod0/S7AWNfKib5U/SA9nJaz/mWeF9S0farz9AQFc8/NSzAzaVq7YbM4F6f6N2FRl7GikdXRCed45j6mrPzGzk3ECbupFnqyREH3+ZPSdk= 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /tests/inc-md-cert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDgTCCAmmgAwIBAgIJAJRJzvdpkmNaMA0GCSqGSIb3DQEBCwUAMFcxCzAJBgNV 3 | BAYTAlVTMRUwEwYDVQQKDAxJbkNvbW1vbiBMTEMxMTAvBgNVBAMMKEluQ29tbW9u 4 | IEZlZGVyYXRpb24gTWV0YWRhdGEgU2lnbmluZyBLZXkwHhcNMTMxMjE2MTkzNDU1 5 | WhcNMzcxMjE4MTkzNDU1WjBXMQswCQYDVQQGEwJVUzEVMBMGA1UECgwMSW5Db21t 6 | b24gTExDMTEwLwYDVQQDDChJbkNvbW1vbiBGZWRlcmF0aW9uIE1ldGFkYXRhIFNp 7 | Z25pbmcgS2V5MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0Chdkrn+ 8 | dG5Zj5L3UIw+xeWgNzm8ajw7/FyqRQ1SjD4Lfg2WCdlfjOrYGNnVZMCTfItoXTSp 9 | g4rXxHQsykeNiYRu2+02uMS+1pnBqWjzdPJE0od+q8EbdvE6ShimjyNn0yQfGyQK 10 | CNdYuc+75MIHsaIOAEtDZUST9Sd4oeU1zRjV2sGvUd+JFHveUAhRc0b+JEZfIEuq 11 | /LIU9qxm/+gFaawlmojZPyOWZ1JlswbrrJYYyn10qgnJvjh9gZWXKjmPxqvHKJcA 12 | TPhAh2gWGabWTXBJCckMe1hrHCl/vbDLCmz0/oYuoaSDzP6zE9YSA/xCplaHA0mo 13 | C1Vs2H5MOQGlewIDAQABo1AwTjAdBgNVHQ4EFgQU5ij9YLU5zQ6K75kPgVpyQ2N/ 14 | lPswHwYDVR0jBBgwFoAU5ij9YLU5zQ6K75kPgVpyQ2N/lPswDAYDVR0TBAUwAwEB 15 | /zANBgkqhkiG9w0BAQsFAAOCAQEAaQkEx9xvaLUt0PNLvHMtxXQPedCPw5xQBd2V 16 | WOsWPYspRAOSNbU1VloY+xUkUKorYTogKUY1q+uh2gDIEazW0uZZaQvWPp8xdxWq 17 | Dh96n5US06lszEc+Lj3dqdxWkXRRqEbjhBFh/utXaeyeSOtaX65GwD5svDHnJBcl 18 | AGkzeRIXqxmYG+I2zMm/JYGzEnbwToyC7yF6Q8cQxOr37hEpqz+WN/x3qM2qyBLE 19 | CQFjmlJrvRLkSL15PCZiu+xFNFd/zx6btDun5DBlfDS9DG+SHCNH6Nq+NfP+ZQ8C 20 | GzP/3TaZPzMlKPDCjp0XOQfyQqFIXdwjPFTWjEusDBlm4qJAlQ== 21 | -----END CERTIFICATE----- 22 | -------------------------------------------------------------------------------- /tests/invalid_metadata_file.xml: -------------------------------------------------------------------------------- 1 | this content is invalid for a metadata file 2 | -------------------------------------------------------------------------------- /tests/kalmar2.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC+zCCAmSgAwIBAgIJALIv7VqXanQYMA0GCSqGSIb3DQEBBQUAMF0xCzAJBgNV 3 | BAYTAk5PMRIwEAYDVQQIEwlUcm9uZGhlaW0xEjAQBgNVBAcTCVRyb25kaGVpbTEQ 4 | MA4GA1UEChMHVU5JTkVUVDEUMBIGA1UEAxMLa2FsbWFyMi5vcmcwHhcNMDkxMDI2 5 | MDY1OTQyWhcNMTkxMDI2MDY1OTQyWjBdMQswCQYDVQQGEwJOTzESMBAGA1UECBMJ 6 | VHJvbmRoZWltMRIwEAYDVQQHEwlUcm9uZGhlaW0xEDAOBgNVBAoTB1VOSU5FVFQx 7 | FDASBgNVBAMTC2thbG1hcjIub3JnMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB 8 | gQCi7bEwud2nKm9FSojyIFGXN1p2ZGpUOKSFxhmeicCujHQr1VYUhyXBDHONwPDt 9 | K9PdHFZ4lLgLKcAIqUcOoAWr65m/MJJVkX0P7TUTZ6OS4mDAo1NsZexZxTBof9hC 10 | wnFz9dbqEThqZw2UtyEDnhW6kCb6SBd+2Yjvd+YDZg8lfwIDAQABo4HCMIG/MB0G 11 | A1UdDgQWBBS2Sw/w4drjYrTiAHeWXyN2W1j1iDCBjwYDVR0jBIGHMIGEgBS2Sw/w 12 | 4drjYrTiAHeWXyN2W1j1iKFhpF8wXTELMAkGA1UEBhMCTk8xEjAQBgNVBAgTCVRy 13 | b25kaGVpbTESMBAGA1UEBxMJVHJvbmRoZWltMRAwDgYDVQQKEwdVTklORVRUMRQw 14 | EgYDVQQDEwtrYWxtYXIyLm9yZ4IJALIv7VqXanQYMAwGA1UdEwQFMAMBAf8wDQYJ 15 | KoZIhvcNAQEFBQADgYEALx5V6xKtPr7urC/QOWiHxUChQO+SJsbnlwIquwaEGgUf 16 | 0WrGidPu04zdv+VpKtR+/KZbIDuSWx0/AkbexiE9ZUzJ2GvdVSxr/uON9CtQIQTp 17 | 5WjZD0KaieaoIMy/w5shc+trjkV550g/MWFFqAjproXwHRrEQoAxWL0smtR1R/I= 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /tests/keys/mycert.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIC8jCCAlugAwIBAgIJAJHg2V5J31I8MA0GCSqGSIb3DQEBBQUAMFoxCzAJBgNV 3 | BAYTAlNFMQ0wCwYDVQQHEwRVbWVhMRgwFgYDVQQKEw9VbWVhIFVuaXZlcnNpdHkx 4 | EDAOBgNVBAsTB0lUIFVuaXQxEDAOBgNVBAMTB1Rlc3QgU1AwHhcNMDkxMDI2MTMz 5 | MTE1WhcNMTAxMDI2MTMzMTE1WjBaMQswCQYDVQQGEwJTRTENMAsGA1UEBxMEVW1l 6 | YTEYMBYGA1UEChMPVW1lYSBVbml2ZXJzaXR5MRAwDgYDVQQLEwdJVCBVbml0MRAw 7 | DgYDVQQDEwdUZXN0IFNQMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkJWP7 8 | bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr6/ROgW96ZeQ57fzVy2MC 9 | FiQRw2fzBs0n7leEmDJyVVtBTavYlhAVXDNa3stgvh43qCfLx+clUlOvtnsoMiiR 10 | mo7qf0BoPKTj7c0uLKpDpEbAHQT4OF1HRYVxMwIDAQABo4G/MIG8MB0GA1UdDgQW 11 | BBQ7RgbMJFDGRBu9o3tDQDuSoBy7JjCBjAYDVR0jBIGEMIGBgBQ7RgbMJFDGRBu9 12 | o3tDQDuSoBy7JqFepFwwWjELMAkGA1UEBhMCU0UxDTALBgNVBAcTBFVtZWExGDAW 13 | BgNVBAoTD1VtZWEgVW5pdmVyc2l0eTEQMA4GA1UECxMHSVQgVW5pdDEQMA4GA1UE 14 | AxMHVGVzdCBTUIIJAJHg2V5J31I8MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF 15 | BQADgYEAMuRwwXRnsiyWzmRikpwinnhTmbooKm5TINPE7A7gSQ710RxioQePPhZO 16 | zkM27NnHTrCe2rBVg0EGz7QTd1JIwLPvgoj4VTi/fSha/tXrYUaqc9AqU1kWI4WN 17 | +vffBGQ09mo+6CffuFTZYeOhzP/2stAPwCTU4kxEoiy0KpZMANI= 18 | -----END CERTIFICATE----- 19 | -------------------------------------------------------------------------------- /tests/keys/mykey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQDkJWP7bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr 3 | 6/ROgW96ZeQ57fzVy2MCFiQRw2fzBs0n7leEmDJyVVtBTavYlhAVXDNa3stgvh43 4 | qCfLx+clUlOvtnsoMiiRmo7qf0BoPKTj7c0uLKpDpEbAHQT4OF1HRYVxMwIDAQAB 5 | AoGAbx9rKH91DCw/ZEPhHsVXJ6cYHxGcMoAWvnMMC9WUN+bNo4gNL205DLfsxXA1 6 | jqXFXZj3+38vSFumGPA6IvXrN+Wyp3+Lz3QGc4K5OdHeBtYlxa6EsrxPgvuxYDUB 7 | vx3xdWPMjy06G/ML+pR9XHnRaPNubXQX3UxGBuLjwNXVmyECQQD2/D84tYoCGWoq 8 | 5FhUBxFUy2nnOLKYC/GGxBTX62iLfMQ3fbQcdg2pJsB5rrniyZf7UL+9FOsAO9k1 9 | 8DO7G12DAkEA7Hkdg1KEw4ZfjnnjEa+KqpyLTLRQ91uTVW6kzR+4zY719iUJ/PXE 10 | PxJqm1ot7mJd1LW+bWtjLpxs7jYH19V+kQJBAIEpn2JnxdmdMuFlcy/WVmDy09pg 11 | 0z0imdexeXkFmjHAONkQOv3bWv+HzYaVMo8AgCOksfEPHGqN4eUMTfFeuUMCQF+5 12 | E1JSd/2yCkJhYqKJHae8oMLXByNqRXTCyiFioutK4JPYIHfugJdLfC4QziD+Xp85 13 | RrGCU+7NUWcIJhqfiJECQAIgUAzfzhdj5AyICaFPaOQ+N8FVMLcTyqeTXP0sIlFk 14 | JStVibemTRCbxdXXM7OVipz1oW3PBVEO3t/VyjiaGGg= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /tests/localhost.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from saml2.saml import NAME_FORMAT_URI 3 | 4 | 5 | __author__ = "rolandh" 6 | 7 | import json 8 | 9 | 10 | BASE = "http://localhost:8088" 11 | 12 | metadata = open("idp_test/idp.xml").read() 13 | 14 | info = { 15 | "entity_id": f"{BASE}/idp.xml", 16 | "interaction": [ 17 | { 18 | "matches": {"url": f"{BASE}/login", "title": "IDP test login"}, 19 | "page-type": "login", 20 | "control": {"type": "form", "set": {"login": "roland", "password": "dianakra"}}, 21 | }, 22 | { 23 | "matches": {"url": f"{BASE}/sso/redirect", "title": "SAML 2.0 POST"}, 24 | "page-type": "other", 25 | "control": {"index": 0, "type": "form", "set": {}}, 26 | }, 27 | { 28 | "matches": {"url": f"{BASE}/sso/post", "title": "SAML 2.0 POST"}, 29 | "page-type": "other", 30 | "control": {"index": 0, "type": "form", "set": {}}, 31 | }, 32 | { 33 | "matches": {"url": f"{BASE}/slo/post", "title": "SAML 2.0 POST"}, 34 | "page-type": "other", 35 | "control": {"index": 0, "type": "form", "set": {}}, 36 | }, 37 | ], 38 | "metadata": metadata, 39 | "name_format": NAME_FORMAT_URI, 40 | } 41 | 42 | print(json.dumps(info)) 43 | -------------------------------------------------------------------------------- /tests/malformed.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICITCCAYoCAQEwDQYJKoZIhvcNAQELBQAwWDELMAkGA1UEBhMCenoxCzAJBgNV 3 | BAgMAnp6MQ0wCwYDVQQHDAR6enp6MQ4wDAYDVQQKDAVaenp6ejEOMAwGA1UECwwF 4 | Wnp6enoxDTALBgNVBAMMBHRlc3QwIBcNMTkwNDEyMTk1MDM0WhgPMzAxODA4MTMx 5 | OTUwMzRaMFgxCzAJBgNVBAYTAnp6MQswCQYDVQQIDAJ6ejENMAsGA1UEBwwEenp6 6 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjW0kJM+4baWKtvO24ZsGXNvNK 7 | KkwTMz7OW5Z6BRqhSOq2WA0c5NCpMk6rD8Z2OTFEolPojEjf8dVyd/Ds/hrjFKQv 8 | 8wQgbdXLN51YTIsgd6h+hBJO+vzhl0PT4aT7M0JKo5ALtS6qk4tsworW2BnwyvsG 9 | SAinwfeWt4t/b1J3kwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAFtj7WArQQBugmh/ 10 | KQjjlfTQ5A052QeXfgTyO9vv1S6MRIi7qgiaEv49cGXnJv/TWbySkMKObPMUApjg 11 | 6z8PqcxuShew5FCTkNvwhABFPiyu0fUj3e2FEPHfsBu76jz4ugtmhUqjqhzwFY9c 12 | tnWRkkl6J0AjM3LnHOSgjNIclDZG 13 | -------------------------------------------------------------------------------- /tests/metadata/idp_uiinfo.xml: -------------------------------------------------------------------------------- 1 | 2 | example.orgfoo barhttp://example.com/logo.jpghttp://example.com/saml2/info.htmlExample Co.Exempel bolaghttp://example.com/saml2/privacyStatement.htmlMIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX 4 | aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF 5 | MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 6 | ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB 7 | gQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8vQdzkihscQMXy 8 | 3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2PdTZcvDBKGbiaN 9 | efiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQABo4GnMIGkMB0G 10 | A1UdDgQWBBRePsKHKYJsiojE78ZWXccK9K4aJTB1BgNVHSMEbjBsgBRePsKHKYJs 11 | iojE78ZWXccK9K4aJaFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt 12 | U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrzqSSw 13 | mDY9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJSrKOEzHO7TL5cy6 14 | h3qh+3+JAk8HbGBW+cbX6KBCAw/mzU8flK25vnWwXS3dv2FF3Aod0/S7AWNfKib5 15 | U/SA9nJaz/mWeF9S0farz9AQFc8/NSzAzaVq7YbM4F6f6N2FRl7GikdXRCed45j6 16 | mrPzGzk3ECbupFnqyREH3+ZPSdk= 17 | 18 | -------------------------------------------------------------------------------- /tests/myentitycategory.py: -------------------------------------------------------------------------------- 1 | CUSTOM_R_AND_S = [ 2 | "eduPersonTargetedID", 3 | "eduPersonPrincipalName", 4 | "mail", 5 | "displayName", 6 | "givenName", 7 | "sn", 8 | "eduPersonScopedAffiliation", 9 | "eduPersonUniqueId", 10 | ] 11 | 12 | RESEARCH_AND_SCHOLARSHIP = "http://refeds.org/category/research-and-scholarship" 13 | 14 | RELEASE = { 15 | "": ["eduPersonTargetedID"], 16 | RESEARCH_AND_SCHOLARSHIP: CUSTOM_R_AND_S, 17 | } 18 | -------------------------------------------------------------------------------- /tests/okta_assertion: -------------------------------------------------------------------------------- 1 | 2 | http://www.okta.com/IssueruserNameaudienceurn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransportJohn Doe -------------------------------------------------------------------------------- /tests/openssl.cnf: -------------------------------------------------------------------------------- 1 | [req] 2 | req_extensions = v3_req 3 | distinguished_name = req_distinguished_name 4 | 5 | [req_distinguished_name] 6 | [v3_req] 7 | -------------------------------------------------------------------------------- /tests/pathutils.py: -------------------------------------------------------------------------------- 1 | import os.path 2 | 3 | 4 | BASEDIR = os.path.abspath(os.path.dirname(__file__)) 5 | 6 | 7 | def full_path(local_file): 8 | return os.path.join(BASEDIR, local_file) 9 | 10 | 11 | def dotname(module): 12 | if not BASEDIR.endswith("tests"): 13 | return f"tests.{module}" 14 | else: 15 | return module 16 | 17 | 18 | try: 19 | from saml2.sigver import get_xmlsec_binary 20 | except ImportError: 21 | get_xmlsec_binary = None 22 | 23 | if get_xmlsec_binary: 24 | xmlsec_path = get_xmlsec_binary(["/opt/local/bin"]) 25 | else: 26 | xmlsec_path = "/usr/bin/xmlsec1" 27 | -------------------------------------------------------------------------------- /tests/pki/cert.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICMjCCAZsCAQEwDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCcXcxDzANBgNV 3 | BAgTBnF3ZXJ0eTEPMA0GA1UEBxMGcXdlcnR5MQ8wDQYDVQQKEwZxd2VydHkxDzAN 4 | BgNVBAsTBnF3ZXJ0eTEPMA0GA1UEAxMGcXdlcnR5MB4XDTE0MDIwNDA4NTY0N1oX 5 | DTE0MDIwNTA4NTY0N1owYTELMAkGA1UEBhMCYXMxDzANBgNVBAgTBmFzZGZnaDEP 6 | MA0GA1UEBxMGYXNkZmdoMQ8wDQYDVQQKEwZhc2RmZ2gxDjAMBgNVBAsTBWFzZGZn 7 | MQ8wDQYDVQQDEwZhc2RmZ2gwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKUZ 8 | NSY8xkonIyXOMPwK2aRpkAxnM9/0l15aJ0DCrufCzMWZF+oqne1RZXNZbH4VOqwh 9 | CSq9iXk5ONe0RqIlxvvVvFO+jx6/4vulhEnhOXyrYTJ8AJhBoFRRkJ8VMgN1Hf0C 10 | wIhVqVQpe6UIOkg17PCXoYjicLTeV+CQrrKEajghAgMBAAEwDQYJKoZIhvcNAQEL 11 | BQADgYEAJTspJ8fDcTXlAM0Rgr73EVyhDpIN1MC5hUFay0YrLenOuXaNH9rFzg8j 12 | AdsB5+N6KJg7JB4+oqbucgz9/poqrKUG9amg/uv87vjMR7O7xtlKXt1iSLOdu/uU 13 | cYhtRVSlwRaVfhd6fiYylJag8ujraUmPbqmvM8y23QL5l+O3Nng= 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /tests/pki/test_3.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICITCCAYoCAQEwDQYJKoZIhvcNAQELBQAwWDELMAkGA1UEBhMCenoxCzAJBgNV 3 | BAgMAnp6MQ0wCwYDVQQHDAR6enp6MQ4wDAYDVQQKDAVaenp6ejEOMAwGA1UECwwF 4 | Wnp6enoxDTALBgNVBAMMBHRlc3QwIBcNMTkwNDEyMTk1MDM0WhgPMzAxODA4MTMx 5 | OTUwMzRaMFgxCzAJBgNVBAYTAnp6MQswCQYDVQQIDAJ6ejENMAsGA1UEBwwEenp6 6 | ejEOMAwGA1UECgwFWnp6enoxDjAMBgNVBAsMBVp6enp6MQ0wCwYDVQQDDAR0ZXN0 7 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvGSW8evdwkkQciY8kJ+x0h3/J 8 | +GCRmxsrOGgtNhfXSAL1DPVJsbFQngYpCf4iVoxZjhZOV7klQ+JsMg3mxi0XcYLk 9 | k9akj+wp5trueLslYE4J4CByBAwZi+5fhWRHYdi4yQo6z87d3W0B9B0SHx7QjWtf 10 | PJLZMSJXYYQJR5zUlQIDAQABMA0GCSqGSIb3DQEBCwUAA4GBABIjpaJbBYftDKCV 11 | k53bv6ArWJgBkAxq0GUdwf+X7MELfctFRXpksUyqtGff7Whw4EDkTGSCHmdmLXXG 12 | yGmc5sdEsAWGs68B7csJzznOPBlkPe5aIJFtlGRC0y1PHcXFM9H3Aof4rpAuJuby 13 | 7B8dlOXPEWoz+gLObD5bxWP8Qf+p 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /tests/pki/test_3.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICWwIBAAKBgQCvGSW8evdwkkQciY8kJ+x0h3/J+GCRmxsrOGgtNhfXSAL1DPVJ 3 | sbFQngYpCf4iVoxZjhZOV7klQ+JsMg3mxi0XcYLkk9akj+wp5trueLslYE4J4CBy 4 | BAwZi+5fhWRHYdi4yQo6z87d3W0B9B0SHx7QjWtfPJLZMSJXYYQJR5zUlQIDAQAB 5 | AoGACRxT3FTBnzfdF2cI7aauJPoP6iBkVe8uILeUpBWWc/spPDrqYGVAhqNSSrxc 6 | XskGEHrWKkliNtArbdnE42cYXXPbwxHOwzoXxaHlLYIORDH0b90ukv1Pp6DhzQaW 7 | guFb3yrxRO7tILhfA9V+nvYU4EcPLwRxRzLUipT7eNfIhYECQQDcTMWr3bvgLpD2 8 | KD8Pn6iihskBiAsvvz1uVOjfSS1bKGzsyYjtKdq/bO0AM0e8uUdSWHXH2yeEkG/f 9 | IMz9CXQlAkEAy3kqpl7uU+0Qe19mC+Icx/DbjJUV6Kv7dLozze3yjBzQFBMPQwi8 10 | b4zA+4lcuX5u9b8f1kpqC8hNMcE9jGM7sQJAClwp550z2qUV+B2IaamueoYwKbxG 11 | Gma58thXYzjDw0exZ6lKoSyYtuvecWX3964W5o52a4Go6BkKycl3Gmc5aQJAAsT3 12 | a6RHIiVL4CIARZEiSyZgFp3A2pXcqk4OfnMKphWOT3ei8Yqg5fPIfKP1+yBZakbb 13 | rBL/NoHXayHyMIL5QQJATnVslSzO10RzxBI+k1F+Mr1VRj1KnP4rjYP1gHQ9KqVP 14 | CveP1Cqnu7+tZ9HezUMZSlFZ60A+D6uB8AGB/Z8EHg== 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /tests/private_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXgIBAAKBgQCoOodEKMwSYOhf+HnERrrfORO2p4EY3dnk/tPafbY1S6JOOlIp 3 | 3Yp3GyHq7xr1gbAUuwoS0QXcPURtHMu97RjiLFTGDTnXaQLzDbRyXhMwbbEQWmX1 4 | KYvUxMZ54k3tWr5abtWLkqK4PPEWVdUh7Q1yYOnVRd958Da/aVTeI97YKQIDAQAB 5 | AoGAKburRstgJMcJnpVDj9+ECFBehZd0PTj8DwTwhWleREZpE0MAkwY0jWubtG5w 6 | n6r2hY4I57YW4BYK3Y6hNJKW803Hc3Qv75SxlTkGEg4s2H9j9xS27jpTIoEWPEGB 7 | SCFUHDdcM2BGFg8C+CjLLPXoRZB3ozhPTF59EJG5kslH5QECQQDXRw8NOf7ewPf+ 8 | 4LDWhFC/VWA1iv5qSuWiUEQG0WRHls0jvP7aUAEVVjcUJIqSnDt5mbe8Y7wdOaBx 9 | jC2Lp22xAkEAyA0Y2heoWNwSwxFuo2IMviDifUDzxF2MJrOZWGRKzh6tpOexlwNJ 10 | czunHBk+Xu0c8T7c0kj/NI964ZRnnR5X+QJBAI94pxpchXS1TFIMMy60PhBV+OmW 11 | OZpjUglLzxpwIJHpecwQoBSk7yPmMoz1EXlGMAkJnj8qhOtBRaGHF6+UJPECQQCY 12 | VnDgUzedQyj+Zp3ryNSPTBds7jMzC1GYfxd82hKk13O/qbfwmU4rc/eTTB3Ux5dM 13 | lmQtteyxJ7mZYhts6BhxAkEArx1IwLUA6hW6tPVmrK/A7TFl+bPacg3BH8HsLitB 14 | H16b9Fnyk0OSJc9+vxBRgIqTSq81X44aQHr0ZINCA6oPNQ== 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /tests/pubkey.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIDTDCCAjSgAwIBAgIJAKPpMz4LyS0VMA0GCSqGSIb3DQEBBQUAMCIxIDAeBgNV 3 | BAMTF2tleWJ1Y2tldC5hcHAubm9yZHUubmV0MB4XDTEyMDUzMTEyMjIyNFoXDTIy 4 | MDUyOTEyMjIyNFowIjEgMB4GA1UEAxMXa2V5YnVja2V0LmFwcC5ub3JkdS5uZXQw 5 | ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCofu5ht94gNZygBb3ZkwDG 6 | gjt5FTeZaCIE7+QshE72s5r79qlmLyhQ0KduecPdGt0vDs/aNIGIqj5yKXsWY8C3 7 | 7NpVvh7Ht1rBMhciF/KP2U2I/DeQSo5/Mk45uhhM8TdT5yNR0tZdjHMa1XIIilto 8 | yqPYYEbiUD299oCMbNd+qunK7+x4ltujVVtKq3DbOsCkheNkviiLjdzt26J1AD4P 9 | IGyl3m7nIITWqpibLScMgwgGjrX9RP8k/gctbWO2oKZlPwSun6xd4tbgOs8aU8zb 10 | qlKsOkRmoi2HVEyyBh7o+3h8DVXTAzR5VAjJaFst0yFKS91NhK60fmTQAB1+wmHR 11 | AgMBAAGjgYQwgYEwHQYDVR0OBBYEFGuSzft+tjDJiRwtEFBxvGc7f/NcMFIGA1Ud 12 | IwRLMEmAFGuSzft+tjDJiRwtEFBxvGc7f/NcoSakJDAiMSAwHgYDVQQDExdrZXli 13 | dWNrZXQuYXBwLm5vcmR1Lm5ldIIJAKPpMz4LyS0VMAwGA1UdEwQFMAMBAf8wDQYJ 14 | KoZIhvcNAQEFBQADggEBAGxfh0yZg/FNJlkalujlAyxAKl4enNYapyg8DtadRqnq 15 | WKAyVp3+OhDFKr1WrKghDAS8qARIoPEvPws24S/A1hrT0T2tux7j3puTFkt/JLbO 16 | vvjpFAcbu/ILfzsXk19zKInCXmFWW5UNl2iq9GRCKltMyWZ/8m74cZpm1X3x4u33 17 | u+VTbtj/ZeTzqsriFfXo2q8EsdEK+27tvluNbdUgEayz8YIFrLI9LJRlWH6X6BWh 18 | PbIrR0sd263vE/1fgWbeB66CRXys0oQ92k9arc+gkVB2hZ8o4BK82LkwO30Ueo+l 19 | oHJ24vcX55P2LXmP+x+hIjMXICkqly0QE7kUVUVBisQ= 20 | -----END CERTIFICATE----- 21 | -------------------------------------------------------------------------------- /tests/restrictive_idp_conf.py: -------------------------------------------------------------------------------- 1 | from saml2 import BINDING_HTTP_REDIRECT 2 | from saml2 import BINDING_SOAP 3 | from saml2.saml import NAME_FORMAT_URI 4 | 5 | 6 | BASE = "http://localhost:8089/" 7 | 8 | from pathutils import full_path 9 | 10 | 11 | CONFIG = { 12 | "entityid": "urn:mace:example.com:saml:roland:idpr", 13 | "name": "Rolands restrictied IdP", 14 | "service": { 15 | "idp": { 16 | "endpoints": { 17 | "single_sign_on_service": [(f"{BASE}sso", BINDING_HTTP_REDIRECT)], 18 | "attribute_service": [(f"{BASE}aa", BINDING_SOAP)], 19 | }, 20 | "policy": { 21 | "default": {"lifetime": {"minutes": 15}, "name_form": NAME_FORMAT_URI}, 22 | "urn:mace:example.com:saml:roland:sp": { 23 | "lifetime": {"minutes": 5}, 24 | "attribute_restrictions": { 25 | "givenName": None, 26 | "surName": None, 27 | "mail": [".*@example.com"], 28 | "eduPersonAffiliation": ["(employee|staff|faculty)"], 29 | }, 30 | }, 31 | }, 32 | "subject_data": full_path("subject_data.db"), 33 | } 34 | }, 35 | "key_file": full_path("test.key"), 36 | "cert_file": full_path("test.pem"), 37 | "xmlsec_binary": None, 38 | "metadata": [ 39 | { 40 | "class": "saml2.mdstore.MetaDataFile", 41 | "metadata": [(full_path("sp_0.metadata"),)], 42 | } 43 | ], 44 | "attribute_map_dir": full_path("attributemaps"), 45 | } 46 | -------------------------------------------------------------------------------- /tests/root_cert/localhost.ca.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICSzCCAbQCAQEwDQYJKoZIhvcNAQELBQAwbTELMAkGA1UEBhMCc2UxCzAJBgNV 3 | BAgMAmFjMQ0wCwYDVQQHDAR1bWVhMRwwGgYDVQQKDBNJVFMgVW1lYSBVbml2ZXJz 4 | aXR5MQ0wCwYDVQQLDARESVJHMRUwEwYDVQQDDAxsb2NhbGhvc3QuY2EwIBcNMTkw 5 | NDEyMTk1MDM0WhgPMzAxODA4MTMxOTUwMzRaMG0xCzAJBgNVBAYTAnNlMQswCQYD 6 | VQQIDAJhYzENMAsGA1UEBwwEdW1lYTEcMBoGA1UECgwTSVRTIFVtZWEgVW5pdmVy 7 | c2l0eTENMAsGA1UECwwERElSRzEVMBMGA1UEAwwMbG9jYWxob3N0LmNhMIGfMA0G 8 | CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMmx45iD6EoaycZaKW/0Wt8fZyxD1RkbCD 9 | AfPNHYiq5CD/6c43F3gwjCM6dCss4m6Gm7nllNKl5P3TUY0MuPjE9CcWtZbIRPw3 10 | FmDDsiWtrnmJA1qhRal2LQlBJcj/0Jjy35jXvT7sN8EnrOjjLr4cpo4s3FnLghsH 11 | RIRpkEaytQIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAHeVAl4z5gxyBCHrmlyfjR04 12 | Ndpt57FjBGARgULvCI3VFQpsXZvezVBfnbd/Ynith2mKRVsTx+h6aEBucWsSgLiG 13 | gCR/ZPaX9bIbXNNadpe96J402CHpGlnA2nBtkZHN9TGEW8wCKe2D+RDjNMyi/1PJ 14 | aiscKKlLdKqmRpTQZ7sy 15 | -----END CERTIFICATE----- 16 | -------------------------------------------------------------------------------- /tests/root_cert/localhost.ca.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXQIBAAKBgQDMmx45iD6EoaycZaKW/0Wt8fZyxD1RkbCDAfPNHYiq5CD/6c43 3 | F3gwjCM6dCss4m6Gm7nllNKl5P3TUY0MuPjE9CcWtZbIRPw3FmDDsiWtrnmJA1qh 4 | Ral2LQlBJcj/0Jjy35jXvT7sN8EnrOjjLr4cpo4s3FnLghsHRIRpkEaytQIDAQAB 5 | AoGAMw6aUjz/bNVzX2u1UPzOhIOWvjjeHFbAt1BraEnwasSWv4W2oeTHZ0XxHIsU 6 | oxS2A/0kPHgQwLkN5ge5rO0TlpAI5X9ZqlJ0SXF5zjJOBtyK6TWoUbwnyzS7lbFC 7 | q9AVrHwMX9uNCboccqzjrzHyNE+4/QT7z2G5AMzjfq+5EwECQQDvOJuUl2pbUUIK 8 | nMCmwkARFEZzZYV2oIBDsagTG8gX7glj5stoYXuez8EnYtNHRDBConyKqruuzqJk 9 | qSKlha7hAkEA2vT4CpAzHSCknwQKXmFwBD5hVBrv+JZSur6XpqEdwXkX2osScAaW 10 | xj3vQEQorJC2CryvUVOTeuFoog0f+6HiVQJBAMs0dMQ2ErxbPBQzr1p4K1/Wrzmb 11 | BVINaKcYJEOHF+Nr6kIYbLTQCeiPZe4E/p/NBomz6MMJ4L/O+xcyrSGZe0ECQQCZ 12 | ejELpnxNpH4AAKML+Ry9vMQYYjFnfGdNAx/l6vWikjEIPYeVAulY2D0GPUCNhXo1 13 | GIGDbiPodGwVe0G57oVpAkBjckA1LEE1Kzkq5sR9U9t9m+3WSBvMZxLUwJYdVmOY 14 | Y6xKVtJfe9XuQt9tzoWQW8iieWyKOw7yLYCBcjns2iZn 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /tests/saml_hok_invalid.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | https://idp:8443 8 | 9 | 10 | 11 | 12 | https://idp:8443 13 | 14 | 57a0a35eefdb29ca8b4ab78d5a118117 15 | 16 | 17 | 18 | 19 | 20 | 21 | urn:oasis:names:tc:SAML:2.0:ac:classes:Password 22 | 23 | 24 | 25 | 26 | testuser 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/server2_conf.py: -------------------------------------------------------------------------------- 1 | from pathutils import full_path 2 | 3 | 4 | CONFIG = { 5 | "entityid": "urn:mace:example.com:saml:roland:sp", 6 | "name": "urn:mace:example.com:saml:roland:sp", 7 | "description": "My own SP", 8 | "service": { 9 | "sp": { 10 | "endpoints": { 11 | "assertion_consumer_service": ["http://lingon.catalogix.se:8087/"], 12 | }, 13 | "required_attributes": ["surName", "givenName", "mail"], 14 | "optional_attributes": ["title"], 15 | "idp": ["urn:mace:example.com:saml:roland:idp"], 16 | "subject_data": "subject_data.db", 17 | } 18 | }, 19 | "debug": 1, 20 | "key_file": full_path("test.key"), 21 | "cert_file": full_path("test.pem"), 22 | "xmlsec_binary": None, 23 | "metadata": { 24 | "local": [full_path("idp_soap.xml"), full_path("vo_metadata.xml")], 25 | }, 26 | "virtual_organization": { 27 | "urn:mace:example.com:it:tek": { 28 | "nameid_format": "urn:oid:1.3.6.1.4.1.1466.115.121.1.15-NameID", 29 | "common_identifier": "umuselin", 30 | } 31 | }, 32 | "accepted_time_diff": 60, 33 | "attribute_map_dir": full_path("attributemaps"), 34 | "organization": { 35 | "name": ("AB Exempel", "se"), 36 | "display_name": ("AB Exempel", "se"), 37 | "url": "http://www.example.org", 38 | }, 39 | "contact_person": [ 40 | { 41 | "given_name": "Roland", 42 | "sur_name": "Hedberg", 43 | "telephone_number": "+46 70 100 0000", 44 | "email_address": ["tech@example.com", "tech@example.org"], 45 | "contact_type": "technical", 46 | }, 47 | ], 48 | } 49 | -------------------------------------------------------------------------------- /tests/server3_conf.py: -------------------------------------------------------------------------------- 1 | from pathutils import full_path 2 | 3 | 4 | CONFIG = { 5 | "entityid": "urn:mace:example.com:saml:roland:sp", 6 | "name": "urn:mace:example.com:saml:roland:sp", 7 | "description": "My own SP", 8 | "service": { 9 | "sp": { 10 | "endpoints": { 11 | "assertion_consumer_service": ["http://lingon.catalogix.se:8087/"], 12 | }, 13 | "required_attributes": ["surName", "givenName", "mail"], 14 | "optional_attributes": ["title"], 15 | "idp": ["urn:mace:example.com:saml:roland:idp"], 16 | "subject_data": full_path("subject_data.db"), 17 | } 18 | }, 19 | "debug": 1, 20 | "key_file": full_path("test.key"), 21 | "cert_file": full_path("test.pem"), 22 | "xmlsec_binary": None, 23 | "metadata": { 24 | "local": [full_path("idp_aa.xml"), full_path("vo_metadata.xml")], 25 | }, 26 | "virtual_organization": { 27 | "urn:mace:example.com:it:tek": { 28 | "nameid_format": "urn:oid:1.3.6.1.4.1.1466.115.121.1.15-NameID", 29 | "common_identifier": "umuselin", 30 | } 31 | }, 32 | "accepted_time_diff": 60, 33 | "attribute_map_dir": full_path("attributemaps"), 34 | "organization": { 35 | "name": ("AB Exempel", "se"), 36 | "display_name": ("AB Exempel", "se"), 37 | "url": "http://www.example.org", 38 | }, 39 | "contact_person": [ 40 | { 41 | "given_name": "Roland", 42 | "sur_name": "Hedberg", 43 | "telephone_number": "+46 70 100 0000", 44 | "email_address": ["tech@example.com", "tech@example.org"], 45 | "contact_type": "technical", 46 | }, 47 | ], 48 | } 49 | -------------------------------------------------------------------------------- /tests/server_conf_syslog.py: -------------------------------------------------------------------------------- 1 | __author__ = "rolandh" 2 | 3 | from pathutils import full_path 4 | 5 | 6 | CONFIG = { 7 | "entityid": "urn:mace:example.com:saml:roland:sp", 8 | "name": "urn:mace:example.com:saml:roland:sp", 9 | "description": "My own SP", 10 | "service": { 11 | "sp": { 12 | "endpoints": { 13 | "assertion_consumer_service": ["http://lingon.catalogix.se:8087/"], 14 | }, 15 | "required_attributes": ["surName", "givenName", "mail"], 16 | "optional_attributes": ["title"], 17 | "idp": ["urn:mace:example.com:saml:roland:idp"], 18 | } 19 | }, 20 | "debug": 1, 21 | "key_file": full_path("test.key"), 22 | "cert_file": full_path("test.pem"), 23 | # "xmlsec_binary" : None, 24 | "metadata": [ 25 | { 26 | "class": "saml2.mdstore.MetaDataFile", 27 | "metadata": [(full_path("idp.xml"),), (full_path("vo_metadata.xml"),)], 28 | } 29 | ], 30 | "virtual_organization": { 31 | "urn:mace:example.com:it:tek": { 32 | "nameid_format": "urn:oid:1.3.6.1.4.1.1466.115.121.1.15-NameID", 33 | "common_identifier": "umuselin", 34 | } 35 | }, 36 | "subject_data": full_path("subject_data.db"), 37 | "accepted_time_diff": 60, 38 | "attribute_map_dir": full_path("attributemaps"), 39 | "organization": { 40 | "name": ("AB Exempel", "se"), 41 | "display_name": ("AB Exempel", "se"), 42 | "url": "http://www.example.org", 43 | }, 44 | "contact_person": [ 45 | { 46 | "given_name": "Roland", 47 | "sur_name": "Hedberg", 48 | "telephone_number": "+46 70 100 0000", 49 | "email_address": ["tech@eample.com", "tech@example.org"], 50 | "contact_type": "technical", 51 | }, 52 | ], 53 | "logger": { 54 | "syslog": { 55 | "address": ("localhost", 514), 56 | "facility": "local3", 57 | "socktype": "dgram", 58 | }, 59 | "loglevel": "info", 60 | }, 61 | } 62 | -------------------------------------------------------------------------------- /tests/sp_0.metadata: -------------------------------------------------------------------------------- 1 | 2 | MIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX 4 | aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF 5 | MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 6 | ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB 7 | gQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8vQdzkihscQMXy 8 | 3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2PdTZcvDBKGbiaN 9 | efiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQABo4GnMIGkMB0G 10 | A1UdDgQWBBRePsKHKYJsiojE78ZWXccK9K4aJTB1BgNVHSMEbjBsgBRePsKHKYJs 11 | iojE78ZWXccK9K4aJaFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt 12 | U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrzqSSw 13 | mDY9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJSrKOEzHO7TL5cy6 14 | h3qh+3+JAk8HbGBW+cbX6KBCAw/mzU8flK25vnWwXS3dv2FF3Aod0/S7AWNfKib5 15 | U/SA9nJaz/mWeF9S0farz9AQFc8/NSzAzaVq7YbM4F6f6N2FRl7GikdXRCed45j6 16 | mrPzGzk3ECbupFnqyREH3+ZPSdk= 17 | Rolands SPRoland own test SP 18 | -------------------------------------------------------------------------------- /tests/sp_1_conf.py: -------------------------------------------------------------------------------- 1 | from pathutils import full_path 2 | 3 | 4 | CONFIG = { 5 | "entityid": "urn:mace:example.com:saml:roland:sp", 6 | "name": "urn:mace:example.com:saml:roland:sp", 7 | "description": "My own SP", 8 | "service": { 9 | "sp": { 10 | "endpoints": { 11 | "assertion_consumer_service": ["http://lingon.catalogix.se:8087/"], 12 | }, 13 | "required_attributes": ["surName", "givenName", "mail"], 14 | "optional_attributes": ["title"], 15 | "idp": ["urn:mace:example.com:saml:roland:idp"], 16 | } 17 | }, 18 | "debug": 1, 19 | "key_file": full_path("test.key"), 20 | "cert_file": full_path("test.pem"), 21 | "xmlsec_binary": None, 22 | "metadata": [ 23 | { 24 | "class": "saml2.mdstore.MetaDataFile", 25 | "metadata": [(full_path("idp.xml"),), (full_path("vo_metadata.xml"),)], 26 | } 27 | ], 28 | "virtual_organization": { 29 | "urn:mace:example.com:it:tek": { 30 | "nameid_format": "urn:oid:1.3.6.1.4.1.1466.115.121.1.15-NameID", 31 | "common_identifier": "umuselin", 32 | } 33 | }, 34 | "subject_data": full_path("subject_data.db"), 35 | "accepted_time_diff": 60, 36 | "attribute_map_dir": full_path("attributemaps"), 37 | "organization": { 38 | "name": ("AB Exempel", "se"), 39 | "display_name": ("AB Exempel", "se"), 40 | "url": "http://www.example.org", 41 | }, 42 | "contact_person": [ 43 | { 44 | "given_name": "Roland", 45 | "sur_name": "Hedberg", 46 | "telephone_number": "+46 70 100 0000", 47 | "email_address": ["tech@eample.com", "tech@example.org"], 48 | "contact_type": "technical", 49 | }, 50 | ], 51 | "secret": "0123456789", 52 | "http_client_timeout": 10, 53 | } 54 | -------------------------------------------------------------------------------- /tests/sp_2_conf.py: -------------------------------------------------------------------------------- 1 | from pathutils import full_path 2 | 3 | 4 | CONFIG = { 5 | "entityid": "urn:mace:example.com:saml:roland:sp", 6 | "name": "urn:mace:example.com:saml:roland:sp", 7 | "description": "My own SP", 8 | "service": { 9 | "sp": { 10 | "endpoints": { 11 | "assertion_consumer_service": ["http://lingon.catalogix.se:8087/"], 12 | }, 13 | "required_attributes": ["surName", "givenName", "mail"], 14 | "optional_attributes": ["title"], 15 | "idp": ["urn:mace:example.com:saml:roland:idp"], 16 | } 17 | }, 18 | "debug": 1, 19 | "key_file": full_path("test.key"), 20 | "cert_file": full_path("test.pem"), 21 | "xmlsec_binary": None, 22 | "metadata": { 23 | "local": [full_path("idp_2.xml")], 24 | }, 25 | "virtual_organization": { 26 | "urn:mace:example.com:it:tek": { 27 | "nameid_format": "urn:oid:1.3.6.1.4.1.1466.115.121.1.15-NameID", 28 | "common_identifier": "umuselin", 29 | } 30 | }, 31 | "subject_data": full_path("subject_data.db"), 32 | "accepted_time_diff": 60, 33 | "attribute_map_dir": full_path("attributemaps"), 34 | "organization": { 35 | "name": ("AB Exempel", "se"), 36 | "display_name": ("AB Exempel", "se"), 37 | "url": "http://www.example.org", 38 | }, 39 | "contact_person": [ 40 | { 41 | "given_name": "Roland", 42 | "sur_name": "Hedberg", 43 | "telephone_number": "+46 70 100 0000", 44 | "email_address": ["tech@eample.com", "tech@example.org"], 45 | "contact_type": "technical", 46 | }, 47 | ], 48 | "secret": "0123456789", 49 | "only_use_keys_in_metadata": True, 50 | } 51 | -------------------------------------------------------------------------------- /tests/sp_conf_nameidpolicy.py: -------------------------------------------------------------------------------- 1 | from pathutils import full_path 2 | from pathutils import xmlsec_path 3 | 4 | 5 | CONFIG = { 6 | "entityid": "urn:mace:example.com:saml:roland:sp", 7 | "name": "urn:mace:example.com:saml:roland:sp", 8 | "description": "My own SP", 9 | "service": { 10 | "sp": { 11 | "endpoints": { 12 | "assertion_consumer_service": ["http://lingon.catalogix.se:8087/"], 13 | }, 14 | "required_attributes": ["surName", "givenName", "mail"], 15 | "optional_attributes": ["title"], 16 | "idp": ["urn:mace:example.com:saml:roland:idp"], 17 | "name_id_policy_format": "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent", 18 | "name_id_format_allow_create": "true", 19 | } 20 | }, 21 | "debug": 1, 22 | "key_file": full_path("test.key"), 23 | "cert_file": full_path("test.pem"), 24 | "encryption_keypairs": [ 25 | {"key_file": full_path("test_1.key"), "cert_file": full_path("test_1.crt")}, 26 | {"key_file": full_path("test_2.key"), "cert_file": full_path("test_2.crt")}, 27 | ], 28 | "ca_certs": full_path("cacerts.txt"), 29 | "xmlsec_binary": xmlsec_path, 30 | "metadata": [ 31 | { 32 | "class": "saml2.mdstore.MetaDataFile", 33 | "metadata": [(full_path("idp.xml"),), (full_path("vo_metadata.xml"),)], 34 | } 35 | ], 36 | "virtual_organization": { 37 | "urn:mace:example.com:it:tek": { 38 | "nameid_format": "urn:oid:1.3.6.1.4.1.1466.115.121.1.15-NameID", 39 | "common_identifier": "umuselin", 40 | } 41 | }, 42 | "subject_data": "subject_data.db", 43 | "accepted_time_diff": 60, 44 | "attribute_map_dir": full_path("attributemaps"), 45 | "valid_for": 6, 46 | "organization": { 47 | "name": ("AB Exempel", "se"), 48 | "display_name": ("AB Exempel", "se"), 49 | "url": "http://www.example.org", 50 | }, 51 | "contact_person": [ 52 | { 53 | "given_name": "Roland", 54 | "sur_name": "Hedberg", 55 | "telephone_number": "+46 70 100 0000", 56 | "email_address": ["tech@eample.com", "tech@example.org"], 57 | "contact_type": "technical", 58 | }, 59 | ], 60 | "logger": { 61 | "rotating": { 62 | "filename": full_path("sp.log"), 63 | "maxBytes": 100000, 64 | "backupCount": 5, 65 | }, 66 | "loglevel": "info", 67 | }, 68 | } 69 | -------------------------------------------------------------------------------- /tests/sp_slo_redirect_conf.py: -------------------------------------------------------------------------------- 1 | from pathutils import full_path 2 | 3 | from saml2 import BINDING_HTTP_POST 4 | from saml2 import BINDING_HTTP_REDIRECT 5 | 6 | 7 | HOME = "http://lingon.catalogix.se:8087/" 8 | CONFIG = { 9 | "entityid": "urn:mace:example.com:saml:roland:sp", 10 | "name": "urn:mace:example.com:saml:roland:sp", 11 | "description": "My own SP", 12 | "service": { 13 | "sp": { 14 | "endpoints": { 15 | "assertion_consumer_service": [(HOME, BINDING_HTTP_POST)], 16 | "single_logout_service": [(f"{HOME}slo", BINDING_HTTP_REDIRECT)], 17 | }, 18 | "required_attributes": ["surName", "givenName", "mail"], 19 | "optional_attributes": ["title"], 20 | "idp": ["urn:mace:example.com:saml:roland:idp"], 21 | "subject_data": full_path("subject_data.db"), 22 | } 23 | }, 24 | "debug": 1, 25 | "key_file": full_path("test.key"), 26 | "cert_file": full_path("test.pem"), 27 | "xmlsec_binary": None, 28 | "metadata": [ 29 | { 30 | "class": "saml2.mdstore.MetaDataFile", 31 | "metadata": [(full_path("idp_slo_redirect.xml"),)], 32 | } 33 | ], 34 | "virtual_organization": { 35 | "urn:mace:example.com:it:tek": { 36 | "nameid_format": "urn:oid:1.3.6.1.4.1.1466.115.121.1.15-NameID", 37 | "common_identifier": "umuselin", 38 | } 39 | }, 40 | "accepted_time_diff": 60, 41 | "attribute_map_dir": full_path("attributemaps"), 42 | "organization": { 43 | "name": ("AB Exempel", "se"), 44 | "display_name": ("AB Exempel", "se"), 45 | "url": "http://www.example.org", 46 | }, 47 | "contact_person": [ 48 | { 49 | "given_name": "Roland", 50 | "sur_name": "Hedberg", 51 | "telephone_number": "+46 70 100 0000", 52 | "email_address": ["tech@eample.com", "tech@example.org"], 53 | "contact_type": "technical", 54 | }, 55 | ], 56 | } 57 | -------------------------------------------------------------------------------- /tests/sp_test/targetsp.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from saml2.saml import AUTHN_PASSWORD 3 | 4 | 5 | __author__ = "rolandh" 6 | 7 | import json 8 | 9 | 10 | BASE = "http://localhost:8087" 11 | # BASE= "http://lingon.catalogix.se:8087" 12 | 13 | metadata = open("./sp/sp.xml").read() 14 | 15 | AUTHN = {"class_ref": AUTHN_PASSWORD, "authn_auth": "http://lingon.catalogix.se/login"} 16 | 17 | info = { 18 | "start_page": BASE, 19 | "entity_id": f"{BASE}/sp.xml", 20 | "result": { 21 | "matches": {"content": "

Your identity are"}, 22 | }, 23 | "metadata": metadata, 24 | "args": { 25 | "AuthnResponse": { 26 | "sign_assertion": "always", # always, never 27 | "sign_response": "never", # always, never 28 | "sign_digest_alg": ds.DIGEST_SHA256, 29 | "sign_signature_alg": ds.SIG_RSA_SHA256, 30 | "authn": AUTHN, 31 | } 32 | }, 33 | # This is the set of attributes and values that are returned in the 34 | # SAML Assertion 35 | "identity": {"given_name": "Roland", "sn": "Hedberg"}, 36 | # This is the value of the NameID that is return in the Subject in the 37 | # Assertion 38 | "userid": "roland", 39 | # regex pattern that must be contained in the resulting echo page to validate 40 | # that the SP returned the right page after Login. 41 | "echopageIdPattern": r"SAML Echo Service", 42 | # list of regex patterns that must be contained in the resulting echo page to validate 43 | # that the SP's echo page returns expected SAMLe response values (e.g. attribute values) 44 | "echopageContentPattern": [ 45 | r"Given Name\s*\s*Roland", 46 | r"Userid\s*\s*roalnd", 47 | r"Surname\s*\s*Hedberg", 48 | ], 49 | "constraints": { 50 | "authnRequest_signature_required": True, 51 | # allowed for assertion & response signature: 52 | "signature_algorithm": [ 53 | # ds.SIG_RSA_SHA1, # you may need this for legacy deployments 54 | ds.SIG_RSA_SHA224, 55 | ds.SIG_RSA_SHA256, 56 | ds.SIG_RSA_SHA384, 57 | ds.SIG_RSA_SHA512, 58 | ], 59 | "digest_algorithm": [ 60 | # ds.DIGEST_SHA1, # you may need this for legacy deployments 61 | ds.DIGEST_SHA1, 62 | ds.DIGEST_SHA224, 63 | ds.DIGEST_SHA256, 64 | ds.DIGEST_SHA384, 65 | ds.DIGEST_SHA512, 66 | ds.DIGEST_RIPEMD160, 67 | ], 68 | }, 69 | } 70 | 71 | print(json.dumps(info)) 72 | -------------------------------------------------------------------------------- /tests/test.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXgIBAAKBgQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8v 3 | QdzkihscQMXy3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2Pd 4 | TZcvDBKGbiaNefiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQAB 5 | AoGBAKD2emW6ssmyhfQ9ztYFuJ4FlwiJf5icKuf7L4BsMRgjoHawUvt/k69l9aPK 6 | xZNrB7BycV+7lOqU57FaOf1MWGeWzsU5bYUVpFzOVwsY4umtsO78QGKLZe+91Z+k 7 | tOlmL3scAymAgE88Jmr0g8FC46Vv4Sam7zMCtmOvA9fYog1ZAkEA8lAe+XihSuZI 8 | 6IZcdRdB6QJ5cgAJoZdWKKtUovb5Ah2w4D/ebkfpsQJK44aSR5GbnrnqSaMeLJMR 9 | z++Td0edHwJBANTlUBzoo3ihcBOZ0VzGYgDIG8foCTEf3jDBYNYaY9RH/c4P50Gk 10 | Da4PBqtf1f+VORwAsC2NTeY6HUEWMpvfXyUCQQChQ3FZ1k6B6oDbP5CI3NGgoWTx 11 | 2dSPFojgyCWrz3IpVllA5UDDZFjC1SPCCO2Rc/Z9zH2ARG7we3B/UpJx79dBAkEA 12 | iPc6sk6NFQevpjyYcDqFRIF5NgQ3Ha6l8PIITdZOkXz7cX3Txuw3jNrH7KtMbxDe 13 | 3AApWDUHf+21cnFIf/WWLQJAeG0KKBfZw1iRu9vlcYakGWRUSga78QDy08uHDtxQ 14 | LXxOfSvm/y8N1KrEsXf/cJzHUGQJrqk8nLzR5mTRqnAZWA== 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /tests/test.key.p8: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMmDZyazsyqOeJPw 3 | WL9eQ0dk09s1VDIxQxcT21c52rBgIDVxzy9B3OSKGxxAxfLeI/DZwyCAG7qCMxM5 4 | I7EQKS69flkJyUqvylcAkzlxjqBU5pU7Y91Nly8MEoZuJo15+ITDmGehmiapkanz 5 | Ac0sBSTC0f2+Oqq/HJL+9yVzqTZ7AgMBAAECgYEAoPZ6ZbqyybKF9D3O1gW4ngWX 6 | CIl/mJwq5/svgGwxGCOgdrBS+3+Tr2X1o8rFk2sHsHJxX7uU6pTnsVo5/UxYZ5bO 7 | xTlthRWkXM5XCxji6a2w7vxAYotl773Vn6S06WYvexwDKYCATzwmavSDwULjpW/h 8 | JqbvMwK2Y68D19iiDVkCQQDyUB75eKFK5kjohlx1F0HpAnlyAAmhl1Yoq1Si9vkC 9 | HbDgP95uR+mxAkrjhpJHkZueuepJox4skxHP75N3R50fAkEA1OVQHOijeKFwE5nR 10 | XMZiAMgbx+gJMR/eMMFg1hpj1Ef9zg/nQaQNrg8Gq1/V/5U5HACwLY1N5jodQRYy 11 | m99fJQJBAKFDcVnWToHqgNs/kIjc0aChZPHZ1I8WiODIJavPcilWWUDlQMNkWMLV 12 | I8II7ZFz9n3MfYBEbvB7cH9SknHv10ECQQCI9zqyTo0VB6+mPJhwOoVEgXk2BDcd 13 | rqXw8ghN1k6RfPtxfdPG7DeM2sfsq0xvEN7cAClYNQd/7bVycUh/9ZYtAkB4bQoo 14 | F9nDWJG72+VxhqQZZFRKBrvxAPLTy4cO3FAtfE59K+b/Lw3UqsSxd/9wnMdQZAmu 15 | qTycvNHmZNGqcBlY 16 | -----END PRIVATE KEY----- 17 | -------------------------------------------------------------------------------- /tests/test.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX 4 | aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF 5 | MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 6 | ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB 7 | gQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8vQdzkihscQMXy 8 | 3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2PdTZcvDBKGbiaN 9 | efiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQABo4GnMIGkMB0G 10 | A1UdDgQWBBRePsKHKYJsiojE78ZWXccK9K4aJTB1BgNVHSMEbjBsgBRePsKHKYJs 11 | iojE78ZWXccK9K4aJaFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt 12 | U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrzqSSw 13 | mDY9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJSrKOEzHO7TL5cy6 14 | h3qh+3+JAk8HbGBW+cbX6KBCAw/mzU8flK25vnWwXS3dv2FF3Aod0/S7AWNfKib5 15 | U/SA9nJaz/mWeF9S0farz9AQFc8/NSzAzaVq7YbM4F6f6N2FRl7GikdXRCed45j6 16 | mrPzGzk3ECbupFnqyREH3+ZPSdk= 17 | -----END CERTIFICATE----- 18 | -------------------------------------------------------------------------------- /tests/test_06_setarg.py: -------------------------------------------------------------------------------- 1 | from saml2 import saml 2 | from saml2.argtree import add_path 3 | from saml2.argtree import find_paths 4 | from saml2.argtree import is_set 5 | from saml2.argtree import set_arg 6 | from saml2.saml import Subject 7 | from saml2.samlp import Response 8 | 9 | 10 | __author__ = "roland" 11 | 12 | 13 | def test_path(): 14 | result = find_paths(Subject, "in_response_to") 15 | 16 | assert result == [["subject_confirmation", "subject_confirmation_data", "in_response_to"]] 17 | 18 | result = find_paths(Response, "in_response_to") 19 | 20 | assert result == [ 21 | ["assertion", "subject", "subject_confirmation", "subject_confirmation_data", "in_response_to"], 22 | ["in_response_to"], 23 | ] 24 | 25 | 26 | def test_set_arg(): 27 | r = set_arg(Subject, "in_response_to", "123456") 28 | 29 | assert r == [{"subject_confirmation": {"subject_confirmation_data": {"in_response_to": "123456"}}}] 30 | 31 | 32 | def test_multi(): 33 | t = {} 34 | t = add_path(t, ["subject_confirmation", "method", saml.SCM_BEARER]) 35 | add_path(t["subject_confirmation"], ["subject_confirmation_data", "in_response_to", "1234"]) 36 | 37 | assert t == { 38 | "subject_confirmation": { 39 | "subject_confirmation_data": {"in_response_to": "1234"}, 40 | "method": "urn:oasis:names:tc:SAML:2.0:cm:bearer", 41 | } 42 | } 43 | 44 | 45 | def test_is_set(): 46 | t = {} 47 | t = add_path(t, ["subject_confirmation", "method", saml.SCM_BEARER]) 48 | add_path(t["subject_confirmation"], ["subject_confirmation_data", "in_response_to", "1234"]) 49 | 50 | assert is_set(t, ["subject_confirmation", "method"]) 51 | assert is_set(t, ["subject_confirmation", "subject_confirmation_data", "receiver"]) is False 52 | -------------------------------------------------------------------------------- /tests/test_1.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICITCCAYoCAQEwDQYJKoZIhvcNAQELBQAwWDELMAkGA1UEBhMCenoxCzAJBgNV 3 | BAgMAnp6MQ0wCwYDVQQHDAR6enp6MQ4wDAYDVQQKDAVaenp6ejEOMAwGA1UECwwF 4 | Wnp6enoxDTALBgNVBAMMBHRlc3QwIBcNMTkwNDEyMTk1MDM0WhgPMzAxODA4MTMx 5 | OTUwMzRaMFgxCzAJBgNVBAYTAnp6MQswCQYDVQQIDAJ6ejENMAsGA1UEBwwEenp6 6 | ejEOMAwGA1UECgwFWnp6enoxDjAMBgNVBAsMBVp6enp6MQ0wCwYDVQQDDAR0ZXN0 7 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHcj80WU/XBsd9FlyQmfjPUdfm 8 | edhCFDd6TEQmZNNqP/UG+VkGa+BXjRIHMfic/WxPTbGhCjv68ci0UDNomUXagFex 9 | LGNpkwa7+CRVtoc/1xgq+ySE6M4nhcCutScoxNvWNn5eSQ66i3U0sTv91MgsXxqE 10 | dTaiZg0BIufEc3dueQIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAGUV5B+USHvaRa8k 11 | gCNJSuNpo6ARlv0ekrk8bbdNRBiEUdCMyoGJFfuM9K0zybX6Vr25wai3nvaog294 12 | Vx/jWjX2g5SDbjItH6VGy6C9GCGf1A07VxFRCfJn5tA9HuJjPKiE+g/BmrV5N4Ce 13 | alzFxPHWYkNOzoRU8qI7OqUai1kL 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /tests/test_1.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/IdentityPython/pysaml2/0252ec96058c87c43f89e05d978e72b6ba2e6978/tests/test_1.der -------------------------------------------------------------------------------- /tests/test_1.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXgIBAAKBgQDHcj80WU/XBsd9FlyQmfjPUdfmedhCFDd6TEQmZNNqP/UG+VkG 3 | a+BXjRIHMfic/WxPTbGhCjv68ci0UDNomUXagFexLGNpkwa7+CRVtoc/1xgq+ySE 4 | 6M4nhcCutScoxNvWNn5eSQ66i3U0sTv91MgsXxqEdTaiZg0BIufEc3dueQIDAQAB 5 | AoGBAIhWkN44L1vORpA7uQsgNfWC/ROQN0T0jPgNKokUY3E+R0F9Ml4xYCp5RNmm 6 | T00B8AhGFCcB1/6zSX/5Uystm5GNO/V8pNnM7qTrTtnwX/umyceIVxq03z4RCVYb 7 | Gdv67opQAy/Mjy+cHBdYBcIg4ihDbr9CX5qedf9buG4140wBAkEA4nKWZX7DEnEh 8 | sbLnU9KhxwEjadzeZb0JKxoWUjfTTrBlIkHbm6toabV6TVqNENWMSCe0fcjJ7tr7 9 | xPBdOGIlTwJBAOF5ka0khatTH5zSUMRiz8LkqXmy+F0RMXYJeuvNvTSlj3EM9/tK 10 | eWGunHgxv4n1IhGybmfkiXt9MA3sAmUKTbcCQG7nUfM5Zw6EK81c4mCyOxs82nxB 11 | eQZ406GxcBcqUioqyA1EFesiwstq3xA9dfM1szOvhn1INmXuB/qHAhDYOI8CQQCT 12 | zc56ErPxMCdL9O5vHlsVZjHWjkSTNZ8XwnUquI6sQU97i0XQG+zf5Me7XtkxhVjV 13 | AwOu5ThelBz5M1oKhCuXAkEAjDqWQQAiMR2JOFiG/PIHSnPzpmALmf4+A+bjIzbg 14 | VIDhB0LMS5PZikLUWvXPFzdY5e3UKyNTKCxqxqLEYpLWGQ== 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /tests/test_2.crt: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICITCCAYoCAQEwDQYJKoZIhvcNAQELBQAwWDELMAkGA1UEBhMCenoxCzAJBgNV 3 | BAgMAnp6MQ0wCwYDVQQHDAR6enp6MQ4wDAYDVQQKDAVaenp6ejEOMAwGA1UECwwF 4 | Wnp6enoxDTALBgNVBAMMBHRlc3QwIBcNMTkwNDEyMTk1MDM0WhgPMzAxODA4MTMx 5 | OTUwMzRaMFgxCzAJBgNVBAYTAnp6MQswCQYDVQQIDAJ6ejENMAsGA1UEBwwEenp6 6 | ejEOMAwGA1UECgwFWnp6enoxDjAMBgNVBAsMBVp6enp6MQ0wCwYDVQQDDAR0ZXN0 7 | MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjW0kJM+4baWKtvO24ZsGXNvNK 8 | KkwTMz7OW5Z6BRqhSOq2WA0c5NCpMk6rD8Z2OTFEolPojEjf8dVyd/Ds/hrjFKQv 9 | 8wQgbdXLN51YTIsgd6h+hBJO+vzhl0PT4aT7M0JKo5ALtS6qk4tsworW2BnwyvsG 10 | SAinwfeWt4t/b1J3kwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAFtj7WArQQBugmh/ 11 | KQjjlfTQ5A052QeXfgTyO9vv1S6MRIi7qgiaEv49cGXnJv/TWbySkMKObPMUApjg 12 | 6z8PqcxuShew5FCTkNvwhABFPiyu0fUj3e2FEPHfsBu76jz4ugtmhUqjqhzwFY9c 13 | tnWRkkl6J0AjM3LnHOSgjNIclDZG 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /tests/test_2.key: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | MIICXAIBAAKBgQDjW0kJM+4baWKtvO24ZsGXNvNKKkwTMz7OW5Z6BRqhSOq2WA0c 3 | 5NCpMk6rD8Z2OTFEolPojEjf8dVyd/Ds/hrjFKQv8wQgbdXLN51YTIsgd6h+hBJO 4 | +vzhl0PT4aT7M0JKo5ALtS6qk4tsworW2BnwyvsGSAinwfeWt4t/b1J3kwIDAQAB 5 | AoGAGsVz+y5vOjEVmomloFIRN6IT0cbbQGOHYLsuI94X/afdY21I1f8nhtTSqJfp 6 | 8Z/YalGG+doS9rO+Q4VWDEH7nkrD32kX7GVHDArixjYNYtO4iOk+XW0lzGr1+ghJ 7 | p9+zguQRwAiUFJE6/pqKvFlwhoKppJGtfUsniQpy7ANfyNkCQQD6arntqjlJsYml 8 | cKgpgbA9rhZl7ebVKf1edd15kp411NH/FDWhIlxvg9c3HcAH3jQgnc4Gf4bdvZ6Z 9 | UVDSa63HAkEA6Gzww5cXgGHqQrJoQW/qVaqBAVakeS5uf0CCyb6xvIcHRPS3lLlu 10 | 1upuHeA87IjVeRis8yULgaPzvVyiT/IX1QJBAJvhq/PCLv8swR53TnboACmlINQ6 11 | j6LKDKqsfD2dg1bHMCG1Ft1DYn8YdvQcVNmQ/KoBEasB35ZQ31VZRRJ3bSkCQEXg 12 | UrYK27buORaaOnvJ4MKmgyha2xHPosrBI1Dx8s+CLO5PQE4HPcqBKl/zBX37WWqR 13 | v5VOAtqT5vh8PBQa7Y0CQGXFzSKBBp+Zmxab2t85qaZ2VNiQLvqsNA9v8YgNTT2j 14 | jYHRzwnAZymfO4JCfKnsT3KiVy1xbcYsNEO9+DYpqBA= 15 | -----END RSA PRIVATE KEY----- 16 | -------------------------------------------------------------------------------- /tests/test_22_mdie.py: -------------------------------------------------------------------------------- 1 | from saml2 import md 2 | from saml2.mdie import from_dict 3 | from saml2.mdstore import load_metadata_modules 4 | 5 | 6 | __author__ = "rolandh" 7 | 8 | ONTS = load_metadata_modules() 9 | 10 | 11 | def _eq(l1, l2): 12 | return set(l1) == set(l2) 13 | 14 | 15 | def _class(cls): 16 | return f"{cls.c_namespace}&{cls.c_tag}" 17 | 18 | 19 | def test_construct_contact(): 20 | c = from_dict( 21 | { 22 | "__class__": _class(md.ContactPerson), 23 | "given_name": {"text": "Roland", "__class__": _class(md.GivenName)}, 24 | "sur_name": {"text": "Hedberg", "__class__": _class(md.SurName)}, 25 | "email_address": [{"text": "roland@catalogix.se", "__class__": _class(md.EmailAddress)}], 26 | }, 27 | ONTS, 28 | ) 29 | 30 | print(c) 31 | assert c.given_name.text == "Roland" 32 | assert c.sur_name.text == "Hedberg" 33 | assert c.email_address[0].text == "roland@catalogix.se" 34 | assert _eq(c.keyswv(), ["given_name", "sur_name", "email_address"]) 35 | -------------------------------------------------------------------------------- /tests/test_38_metadata_filter.py: -------------------------------------------------------------------------------- 1 | from pathutils import full_path 2 | 3 | from saml2 import config 4 | from saml2.attribute_converter import ac_factory 5 | from saml2.filter import AllowDescriptor 6 | from saml2.mdstore import MetadataStore 7 | 8 | 9 | __author__ = "roland" 10 | 11 | sec_config = config.Config() 12 | 13 | 14 | ATTRCONV = ac_factory(full_path("attributemaps")) 15 | 16 | METADATACONF = { 17 | "1": [ 18 | { 19 | "class": "saml2.mdstore.MetaDataFile", 20 | "metadata": [(full_path("swamid-2.0.xml"),)], 21 | } 22 | ], 23 | } 24 | 25 | 26 | def test_swamid_sp(): 27 | mds = MetadataStore( 28 | ATTRCONV, sec_config, disable_ssl_certificate_validation=True, filter=AllowDescriptor(["spsso"]) 29 | ) 30 | 31 | mds.imp(METADATACONF["1"]) 32 | sps = mds.with_descriptor("spsso") 33 | assert len(sps) == 417 34 | idps = mds.with_descriptor("idpsso") 35 | assert idps == {} 36 | 37 | 38 | def test_swamid_idp(): 39 | mds = MetadataStore( 40 | ATTRCONV, sec_config, disable_ssl_certificate_validation=True, filter=AllowDescriptor(["idpsso"]) 41 | ) 42 | 43 | mds.imp(METADATACONF["1"]) 44 | sps = mds.with_descriptor("spsso") 45 | assert len(sps) == 0 46 | idps = mds.with_descriptor("idpsso") 47 | assert len(idps) == 275 48 | 49 | 50 | if __name__ == "__main__": 51 | test_swamid_idp() 52 | -------------------------------------------------------------------------------- /tests/test_66_name_id_mapping.py: -------------------------------------------------------------------------------- 1 | __author__ = "rolandh" 2 | 3 | from contextlib import closing 4 | 5 | from saml2.client import Saml2Client 6 | from saml2.saml import NAMEID_FORMAT_PERSISTENT 7 | from saml2.saml import NAMEID_FORMAT_TRANSIENT 8 | from saml2.saml import NameID 9 | from saml2.samlp import NameIDMappingRequest 10 | from saml2.samlp import NameIDPolicy 11 | from saml2.server import Server 12 | 13 | 14 | def test_base_request(): 15 | sp = Saml2Client(config_file="servera_conf") 16 | 17 | with closing(Server(config_file="idp_all_conf")) as idp: 18 | binding, destination = sp.pick_binding("name_id_mapping_service", entity_id=idp.config.entityid) 19 | 20 | policy = NameIDPolicy( 21 | format=NAMEID_FORMAT_TRANSIENT, sp_name_qualifier="urn:mace:swamid:junk", allow_create="true" 22 | ) 23 | 24 | nameid = NameID(format=NAMEID_FORMAT_TRANSIENT, text="foobar") 25 | 26 | mid, nmr = sp.create_name_id_mapping_request(policy, nameid, destination) 27 | 28 | print(nmr) 29 | 30 | assert isinstance(nmr, NameIDMappingRequest) 31 | 32 | 33 | def test_request_response(): 34 | sp = Saml2Client(config_file="servera_conf") 35 | 36 | with closing(Server(config_file="idp_all_conf")) as idp: 37 | binding, destination = sp.pick_binding("name_id_mapping_service", entity_id=idp.config.entityid) 38 | 39 | policy = NameIDPolicy( 40 | format=NAMEID_FORMAT_TRANSIENT, sp_name_qualifier="urn:mace:swamid:junk", allow_create="true" 41 | ) 42 | 43 | nameid = NameID(format=NAMEID_FORMAT_TRANSIENT, text="foobar") 44 | 45 | mid, nmr = sp.create_name_id_mapping_request(policy, nameid, destination) 46 | 47 | print(nmr) 48 | 49 | args = sp.use_soap(nmr, destination) 50 | 51 | # ------- IDP ------------ 52 | 53 | req = idp.parse_name_id_mapping_request(args["data"], binding) 54 | 55 | in_response_to = req.message.id 56 | name_id = NameID(format=NAMEID_FORMAT_PERSISTENT, text="foobar") 57 | 58 | idp_response = idp.create_name_id_mapping_response(name_id, in_response_to=in_response_to) 59 | 60 | print(idp_response) 61 | 62 | ht_args = sp.use_soap(idp_response) 63 | 64 | # ------- SP ------------ 65 | 66 | _resp = sp.parse_name_id_mapping_request_response(ht_args["data"], binding) 67 | 68 | print(_resp.response) 69 | 70 | r_name_id = _resp.response.name_id 71 | 72 | assert r_name_id.format == NAMEID_FORMAT_PERSISTENT 73 | assert r_name_id.text == "foobar" 74 | -------------------------------------------------------------------------------- /tests/test_67_manage_name_id.py: -------------------------------------------------------------------------------- 1 | from contextlib import closing 2 | 3 | from saml2 import BINDING_SOAP 4 | from saml2.client import Saml2Client 5 | from saml2.saml import NAMEID_FORMAT_TRANSIENT 6 | from saml2.saml import NameID 7 | from saml2.samlp import NewID 8 | from saml2.server import Server 9 | 10 | 11 | __author__ = "rolandh" 12 | 13 | 14 | def test_basic(): 15 | sp = Saml2Client(config_file="servera_conf") 16 | with closing(Server(config_file="idp_all_conf")) as idp: 17 | # -------- @SP ------------ 18 | binding, destination = sp.pick_binding("manage_name_id_service", entity_id=idp.config.entityid) 19 | 20 | nameid = NameID(format=NAMEID_FORMAT_TRANSIENT, text="foobar") 21 | newid = NewID(text="Barfoo") 22 | 23 | mid, mreq = sp.create_manage_name_id_request(destination, name_id=nameid, new_id=newid) 24 | 25 | print(mreq) 26 | rargs = sp.apply_binding(binding, f"{mreq}", destination, "") 27 | 28 | # --------- @IDP -------------- 29 | 30 | _req = idp.parse_manage_name_id_request(rargs["data"], binding) 31 | 32 | print(_req.message) 33 | 34 | assert mid == _req.message.id 35 | 36 | 37 | def test_flow(): 38 | sp = Saml2Client(config_file="servera_conf") 39 | with closing(Server(config_file="idp_all_conf")) as idp: 40 | binding, destination = sp.pick_binding("manage_name_id_service", entity_id=idp.config.entityid) 41 | 42 | nameid = NameID(format=NAMEID_FORMAT_TRANSIENT, text="foobar") 43 | newid = NewID(text="Barfoo") 44 | 45 | mid, midq = sp.create_manage_name_id_request(destination, name_id=nameid, new_id=newid) 46 | 47 | print(midq) 48 | rargs = sp.apply_binding(binding, f"{midq}", destination, "") 49 | 50 | # --------- @IDP -------------- 51 | 52 | _req = idp.parse_manage_name_id_request(rargs["data"], binding) 53 | 54 | print(_req.message) 55 | 56 | mnir = idp.create_manage_name_id_response(_req.message, [binding]) 57 | 58 | if binding != BINDING_SOAP: 59 | binding, destination = idp.pick_binding("manage_name_id_service", entity_id=sp.config.entityid) 60 | else: 61 | destination = "" 62 | 63 | respargs = idp.apply_binding(binding, f"{mnir}", destination, "") 64 | 65 | print(respargs) 66 | 67 | # ---------- @SP --------------- 68 | 69 | _response = sp.parse_manage_name_id_request_response(respargs["data"], binding) 70 | 71 | print(_response.response) 72 | 73 | assert _response.response.id == mnir.id 74 | 75 | 76 | if __name__ == "__main__": 77 | test_flow() 78 | -------------------------------------------------------------------------------- /tests/test_70_redirect_signing.py: -------------------------------------------------------------------------------- 1 | from contextlib import closing 2 | from urllib.parse import parse_qs 3 | 4 | from pathutils import dotname 5 | 6 | from saml2 import BINDING_HTTP_REDIRECT 7 | from saml2.client import Saml2Client 8 | from saml2.config import SPConfig 9 | from saml2.pack import http_redirect_message 10 | from saml2.server import Server 11 | from saml2.sigver import SIG_RSA_SHA1 12 | from saml2.sigver import verify_redirect_signature 13 | 14 | 15 | __author__ = "rolandh" 16 | 17 | 18 | def list_values2simpletons(_dict): 19 | return {k: v[0] for k, v in _dict.items()} 20 | 21 | 22 | def test(): 23 | with closing(Server(config_file=dotname("idp_all_conf"))) as idp: 24 | conf = SPConfig() 25 | conf.load_file(dotname("servera_conf")) 26 | sp = Saml2Client(conf) 27 | 28 | srvs = sp.metadata.single_sign_on_service(idp.config.entityid, BINDING_HTTP_REDIRECT) 29 | 30 | destination = srvs[0]["location"] 31 | req_id, req = sp.create_authn_request(destination, id="id1") 32 | 33 | info = http_redirect_message( 34 | req, 35 | destination, 36 | relay_state="RS", 37 | typ="SAMLRequest", 38 | sigalg=SIG_RSA_SHA1, 39 | sign=True, 40 | backend=sp.sec.sec_backend, 41 | ) 42 | 43 | verified_ok = False 44 | 45 | for param, val in info["headers"]: 46 | if param == "Location": 47 | _dict = parse_qs(val.split("?")[1]) 48 | _certs = idp.metadata.certs(sp.config.entityid, "any", "signing") 49 | for cert in _certs: 50 | if verify_redirect_signature(list_values2simpletons(_dict), sp.sec.sec_backend, cert[1]): 51 | verified_ok = True 52 | 53 | assert verified_ok 54 | 55 | 56 | if __name__ == "__main__": 57 | test() 58 | -------------------------------------------------------------------------------- /tests/test_71_authn_request.py: -------------------------------------------------------------------------------- 1 | from contextlib import closing 2 | 3 | from saml2.client import Saml2Client 4 | from saml2.saml import AuthnContextClassRef 5 | from saml2.server import Server 6 | 7 | 8 | def test_authn_request_with_acs_by_index(): 9 | # ACS index and location from SP metadata in servera.xml. 10 | ACS_INDEX = "4" 11 | ACS_LOCATION = "http://lingon.catalogix.se:8087/another/path" 12 | 13 | # Create SP using the configuration found in servera_conf.py. 14 | sp = Saml2Client(config_file="servera_conf") 15 | 16 | # Generate an authn request object that uses AssertionConsumerServiceIndex 17 | # instead of AssertionConsumerServiceURL. The index with label ACS_INDEX 18 | # exists in the SP metadata in servera.xml. 19 | request_id, authn_request = sp.create_authn_request(sp.config.entityid, assertion_consumer_service_index=ACS_INDEX) 20 | 21 | assert authn_request.requested_authn_context.authn_context_class_ref == [ 22 | AuthnContextClassRef(accr) 23 | for accr in sp.config.getattr("requested_authn_context").get("authn_context_class_ref") 24 | ] 25 | assert authn_request.requested_authn_context.comparison == ( 26 | sp.config.getattr("requested_authn_context").get("comparison") 27 | ) 28 | 29 | # Make sure the authn_request contains AssertionConsumerServiceIndex. 30 | acs_index = getattr(authn_request, "assertion_consumer_service_index", None) 31 | assert acs_index == ACS_INDEX 32 | 33 | # Create IdP. 34 | with closing(Server(config_file="idp_all_conf")) as idp: 35 | # Ask the IdP to pick out the binding and destination from the 36 | # authn_request. 37 | binding, destination = idp.pick_binding("assertion_consumer_service", request=authn_request) 38 | 39 | # Make sure the IdP pick_binding method picks the correct location 40 | # or destination based on the ACS index in the authn request. 41 | assert destination == ACS_LOCATION 42 | -------------------------------------------------------------------------------- /tests/test_72_eptid.py: -------------------------------------------------------------------------------- 1 | from saml2.eptid import Eptid 2 | from saml2.eptid import EptidShelve 3 | 4 | 5 | __author__ = "rolandh" 6 | 7 | 8 | def test_eptid(): 9 | edb = Eptid("secret") 10 | e1 = edb.get("idp_entity_id", "sp_entity_id", "user_id", "some other data") 11 | print(e1) 12 | assert e1.startswith("idp_entity_id!sp_entity_id!") 13 | e2 = edb.get("idp_entity_id", "sp_entity_id", "user_id", "some other data") 14 | assert e1 == e2 15 | 16 | e3 = edb.get("idp_entity_id", "sp_entity_id", "user_2", "some other data") 17 | print(e3) 18 | assert e1 != e3 19 | 20 | e4 = edb.get("idp_entity_id", "sp_entity_id2", "user_id", "some other data") 21 | assert e4 != e1 22 | assert e4 != e3 23 | 24 | 25 | def test_eptid_shelve(): 26 | edb = EptidShelve("secret", "eptid.db") 27 | e1 = edb.get("idp_entity_id", "sp_entity_id", "user_id", "some other data") 28 | print(e1) 29 | assert e1.startswith("idp_entity_id!sp_entity_id!") 30 | e2 = edb.get("idp_entity_id", "sp_entity_id", "user_id", "some other data") 31 | assert e1 == e2 32 | 33 | e3 = edb.get("idp_entity_id", "sp_entity_id", "user_2", "some other data") 34 | print(e3) 35 | assert e1 != e3 36 | 37 | e4 = edb.get("idp_entity_id", "sp_entity_id2", "user_id", "some other data") 38 | assert e4 != e1 39 | assert e4 != e3 40 | 41 | 42 | if __name__ == "__main__": 43 | test_eptid_shelve() 44 | -------------------------------------------------------------------------------- /tests/test_82_pefim.py: -------------------------------------------------------------------------------- 1 | from pathutils import full_path 2 | 3 | from saml2 import config 4 | from saml2 import element_to_extension_element 5 | from saml2 import extension_elements_to_elements 6 | from saml2 import saml 7 | from saml2 import xmldsig as ds 8 | from saml2.client import Saml2Client 9 | from saml2.extension import pefim 10 | from saml2.extension.pefim import SPCertEnc 11 | from saml2.samlp import Extensions 12 | from saml2.samlp import authn_request_from_string 13 | from saml2.sigver import read_cert_from_file 14 | 15 | 16 | __author__ = "roland" 17 | 18 | conf = config.SPConfig() 19 | conf.load_file("server_conf") 20 | client = Saml2Client(conf) 21 | 22 | # place a certificate in an authn request 23 | cert = read_cert_from_file(full_path("test.pem")) 24 | 25 | spcertenc = SPCertEnc(x509_data=ds.X509Data(x509_certificate=ds.X509Certificate(text=cert))) 26 | 27 | extensions = Extensions(extension_elements=[element_to_extension_element(spcertenc)]) 28 | 29 | req_id, req = client.create_authn_request( 30 | "http://www.example.com/sso", 31 | "urn:mace:example.com:it:tek", 32 | nameid_format=saml.NAMEID_FORMAT_PERSISTENT, 33 | message_id="666", 34 | extensions=extensions, 35 | ) 36 | 37 | 38 | print(req) 39 | 40 | # Get a certificate from an authn request 41 | 42 | xml = f"{req}" 43 | 44 | parsed = authn_request_from_string(xml) 45 | 46 | _elem = extension_elements_to_elements(parsed.extensions.extension_elements, [pefim, ds]) 47 | 48 | assert len(_elem) == 1 49 | _spcertenc = _elem[0] 50 | _cert = _spcertenc.key_info[0].x509_data[0].x509_certificate.text 51 | assert cert == _cert 52 | -------------------------------------------------------------------------------- /tests/test_83_md_extensions.py: -------------------------------------------------------------------------------- 1 | from saml2 import create_class_from_xml_string as parse_str_as 2 | from saml2.config import Config 3 | from saml2.extension.sp_type import SPType 4 | from saml2.metadata import Attribute 5 | from saml2.metadata import entity_descriptor 6 | 7 | 8 | class TestMDExt: 9 | def test_sp_type_true(self): 10 | fil = "sp_mdext_conf.py" 11 | cnf = Config().load_file(fil) 12 | ed = entity_descriptor(cnf) 13 | 14 | assert ed.spsso_descriptor.extensions 15 | assert len(ed.spsso_descriptor.extensions.extension_elements) == 3 16 | assert ed.extensions 17 | assert len(ed.extensions.extension_elements) > 1 18 | assert any(e.tag is SPType.c_tag for e in ed.extensions.extension_elements) 19 | 20 | def test_sp_type_false(self): 21 | fil = "sp_mdext_conf.py" 22 | cnf = Config().load_file(fil) 23 | cnf.setattr("sp", "sp_type_in_metadata", False) 24 | ed = entity_descriptor(cnf) 25 | 26 | assert all(e.tag is not SPType.c_tag for e in ed.extensions.extension_elements) 27 | 28 | def test_entity_attributes(self): 29 | fil = "sp_mdext_conf.py" 30 | cnf = Config().load_file(fil) 31 | ed = entity_descriptor(cnf) 32 | 33 | entity_attributes = next(e for e in ed.extensions.extension_elements if e.tag == "EntityAttributes") 34 | attributes = [parse_str_as(Attribute, e.to_string()) for e in entity_attributes.children] 35 | assert all( 36 | a.name 37 | in [ 38 | "urn:oasis:names:tc:SAML:profiles:subject-id:req", 39 | "somename", 40 | ] 41 | for a in attributes 42 | ) 43 | 44 | import saml2.attribute_converter 45 | 46 | attrc = saml2.attribute_converter.ac_factory() 47 | 48 | import saml2.mdstore 49 | 50 | mds = saml2.mdstore.MetadataStore(attrc, cnf) 51 | 52 | mds.load("inline", ed.to_string()) 53 | entityid = ed.entity_id 54 | entity_attributes = mds.entity_attributes(entityid) 55 | assert entity_attributes == { 56 | "urn:oasis:names:tc:SAML:profiles:subject-id:req": ["any"], 57 | "somename": ["x", "y", "z"], 58 | } 59 | -------------------------------------------------------------------------------- /tests/test_88_nsprefix.py: -------------------------------------------------------------------------------- 1 | from saml2 import BINDING_HTTP_POST 2 | from saml2 import config 3 | from saml2 import saml 4 | from saml2 import samlp 5 | from saml2.client import Saml2Client 6 | from saml2.saml import NAMEID_FORMAT_TRANSIENT 7 | 8 | 9 | __author__ = "roland" 10 | 11 | 12 | def test_nsprefix(): 13 | status_message = samlp.StatusMessage() 14 | status_message.text = "OK" 15 | 16 | txt = f"{status_message}" 17 | 18 | assert "ns0:StatusMessage" in txt 19 | 20 | status_message.register_prefix({"saml2": saml.NAMESPACE, "saml2p": samlp.NAMESPACE}) 21 | 22 | txt = f"{status_message}" 23 | 24 | assert "saml2p:StatusMessage" in txt 25 | 26 | 27 | def test_nsprefix2(): 28 | conf = config.SPConfig() 29 | conf.load_file("servera_conf") 30 | client = Saml2Client(conf) 31 | 32 | selected_idp = "urn:mace:example.com:saml:roland:idp" 33 | 34 | destination = client._sso_location(selected_idp, BINDING_HTTP_POST) 35 | 36 | reqid, req = client.create_authn_request( 37 | destination, 38 | nameid_format=NAMEID_FORMAT_TRANSIENT, 39 | nsprefix={"saml2": saml.NAMESPACE, "saml2p": samlp.NAMESPACE}, 40 | ) 41 | 42 | txt = f"{req}" 43 | 44 | assert "saml2p:AuthnRequest" in txt 45 | assert "saml2:Issuer" in txt 46 | 47 | 48 | if __name__ == "__main__": 49 | test_nsprefix2() 50 | -------------------------------------------------------------------------------- /tests/test_92_aes.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from saml2.cryptography.symmetric import AESCipher 4 | 5 | 6 | class TestAES: 7 | def test_aes_defaults(self): 8 | original_msg = b"ToBeOrNotTobe W.S." 9 | key = os.urandom(16) 10 | aes = AESCipher(key) 11 | 12 | encrypted_msg = aes.encrypt(original_msg) 13 | decrypted_msg = aes.decrypt(encrypted_msg) 14 | assert decrypted_msg == original_msg 15 | 16 | def test_aes_128_cbc(self): 17 | original_msg = b"ToBeOrNotTobe W.S." 18 | key = os.urandom(16) 19 | aes = AESCipher(key) 20 | alg = "aes_128_cbc" 21 | 22 | encrypted_msg = aes.encrypt(original_msg, alg=alg) 23 | decrypted_msg = aes.decrypt(encrypted_msg, alg=alg) 24 | assert decrypted_msg == original_msg 25 | 26 | def test_aes_128_cfb(self): 27 | original_msg = b"ToBeOrNotTobe W.S." 28 | key = os.urandom(16) 29 | aes = AESCipher(key) 30 | alg = "aes_128_cfb" 31 | 32 | encrypted_msg = aes.encrypt(original_msg, alg=alg) 33 | decrypted_msg = aes.decrypt(encrypted_msg, alg=alg) 34 | assert decrypted_msg == original_msg 35 | 36 | def test_aes_192_cbc(self): 37 | original_msg = b"ToBeOrNotTobe W.S." 38 | key = os.urandom(24) 39 | aes = AESCipher(key) 40 | alg = "aes_192_cbc" 41 | 42 | encrypted_msg = aes.encrypt(original_msg, alg=alg) 43 | decrypted_msg = aes.decrypt(encrypted_msg, alg=alg) 44 | assert decrypted_msg == original_msg 45 | 46 | def test_aes_192_cfb(self): 47 | original_msg = b"ToBeOrNotTobe W.S." 48 | key = os.urandom(24) 49 | aes = AESCipher(key) 50 | alg = "aes_192_cfb" 51 | 52 | encrypted_msg = aes.encrypt(original_msg, alg=alg) 53 | decrypted_msg = aes.decrypt(encrypted_msg, alg=alg) 54 | assert decrypted_msg == original_msg 55 | 56 | def test_aes_256_cbc(self): 57 | original_msg = b"ToBeOrNotTobe W.S." 58 | key = os.urandom(32) 59 | aes = AESCipher(key) 60 | alg = "aes_256_cbc" 61 | 62 | encrypted_msg = aes.encrypt(original_msg, alg=alg) 63 | decrypted_msg = aes.decrypt(encrypted_msg, alg=alg) 64 | assert decrypted_msg == original_msg 65 | 66 | def test_aes_256_cfb(self): 67 | original_msg = b"ToBeOrNotTobe W.S." 68 | key = os.urandom(32) 69 | aes = AESCipher(key) 70 | alg = "aes_256_cfb" 71 | 72 | encrypted_msg = aes.encrypt(original_msg, alg=alg) 73 | decrypted_msg = aes.decrypt(encrypted_msg, alg=alg) 74 | assert decrypted_msg == original_msg 75 | -------------------------------------------------------------------------------- /tests/test_chain.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV 3 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX 4 | aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF 5 | MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 6 | ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB 7 | gQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8vQdzkihscQMXy 8 | 3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2PdTZcvDBKGbiaN 9 | efiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQABo4GnMIGkMB0G 10 | A1UdDgQWBBRePsKHKYJsiojE78ZWXccK9K4aJTB1BgNVHSMEbjBsgBRePsKHKYJs 11 | iojE78ZWXccK9K4aJaFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt 12 | U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrzqSSw 13 | mDY9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJSrKOEzHO7TL5cy6 14 | h3qh+3+JAk8HbGBW+cbX6KBCAw/mzU8flK25vnWwXS3dv2FF3Aod0/S7AWNfKib5 15 | U/SA9nJaz/mWeF9S0farz9AQFc8/NSzAzaVq7YbM4F6f6N2FRl7GikdXRCed45j6 16 | mrPzGzk3ECbupFnqyREH3+ZPSdk= 17 | -----END CERTIFICATE----- 18 | -----BEGIN CERTIFICATE----- 19 | MIIDnzCCAoegAwIBAgINSWITCHaai+Root+CAzANBgkqhkiG9w0BAQUFADBrMQsw 20 | CQYDVQQGEwJDSDFAMD4GA1UEChM3U3dpdGNoIC0gVGVsZWluZm9ybWF0aWtkaWVu 21 | c3RlIGZ1ZXIgTGVocmUgdW5kIEZvcnNjaHVuZzEaMBgGA1UEAxMRU1dJVENIYWFp 22 | IFJvb3QgQ0EwHhcNMDgwNTE1MDYzMDAwWhcNMjgwNTE1MDYyOTU5WjBrMQswCQYD 23 | VQQGEwJDSDFAMD4GA1UEChM3U3dpdGNoIC0gVGVsZWluZm9ybWF0aWtkaWVuc3Rl 24 | IGZ1ZXIgTGVocmUgdW5kIEZvcnNjaHVuZzEaMBgGA1UEAxMRU1dJVENIYWFpIFJv 25 | b3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUSWbn/rhWew/s 26 | LJRyciyRKDGyFXSgiDO/EohYuZLw6EAKLLlhZorNtEHQbbn0Oo13S33MclHMvGWT 27 | KJM0u1hG+6gLy78EPmJbqAE1Uv23wVEH4SX0VJfl3JVqIebiAH/CjuLubgMUspDI 28 | jOdQHNLS7pthTbm7Tgh7zMsiLPyMTZJep5CGbqv8NoK6bMaF0Z+Bt7e1JRlhHFCV 29 | iJJaR/+hfpzLsJ8NWVivvrpRGaGJ1XR+9FGsTkjNdMCirNJJZ6XvUOe5w7pHSd9M 30 | cppFP0eyLs02AMzMXI4iz6PK/w3EdzXGXpK+gSgvLxWYct4xHpv1e2NXhNgdJOSN 31 | 9ra/wJLVAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG 32 | MB0GA1UdDgQWBBTpmuIGWOsP14EDXVyXubG1k307hDANBgkqhkiG9w0BAQUFAAOC 33 | AQEAMV/eIW6pFB+mbk7rD7hUPTWDRaoca3kHqmFGFnHfuY8+c0/Mqjh8Y/jyX1yb 34 | f58crTSWrbyGbUZ3oxDGQ34tuZSkmeR32NqryiX3sP5qlNSozVguQKt8o4vhS1Qe 35 | WPsXALs3em2pdKuIGSOpbuDnopPcmU2g5Zi2R5P7qpKDKAKtNUEwV+LW7GBMEksO 36 | Nj7BFXk4AFBFBijaYJGgHmoKSImVgeNIvsV+BSv5HJ4q6vcxfnwuvvGHM0AGphYO 37 | 6f5qtHMUgvAblI8M/2QsBgethaGrirtKJ3aCRLdaR2R1QfaGRpck/Ron5/MpMxiJ 38 | wLT8YlW/zjx2yNABhPSAjfzeMw== 39 | -----END CERTIFICATE----- 40 | -------------------------------------------------------------------------------- /tests/test_chain_with_linebreaks.pem: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -----BEGIN CERTIFICATE----- 8 | MIICsDCCAhmgAwIBAgIJAJrzqSSwmDY9MA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV 9 | BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX 10 | aWRnaXRzIFB0eSBMdGQwHhcNMDkxMDA2MTk0OTQxWhcNMDkxMTA1MTk0OTQxWjBF 11 | MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50 12 | ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKB 13 | gQDJg2cms7MqjniT8Fi/XkNHZNPbNVQyMUMXE9tXOdqwYCA1cc8vQdzkihscQMXy 14 | 3iPw2cMggBu6gjMTOSOxECkuvX5ZCclKr8pXAJM5cY6gVOaVO2PdTZcvDBKGbiaN 15 | efiEw5hnoZomqZGp8wHNLAUkwtH9vjqqvxyS/vclc6k2ewIDAQABo4GnMIGkMB0G 16 | A1UdDgQWBBRePsKHKYJsiojE78ZWXccK9K4aJTB1BgNVHSMEbjBsgBRePsKHKYJs 17 | iojE78ZWXccK9K4aJaFJpEcwRTELMAkGA1UEBhMCQVUxEzARBgNVBAgTClNvbWUt 18 | U3RhdGUxITAfBgNVBAoTGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZIIJAJrzqSSw 19 | mDY9MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAJSrKOEzHO7TL5cy6 20 | h3qh+3+JAk8HbGBW+cbX6KBCAw/mzU8flK25vnWwXS3dv2FF3Aod0/S7AWNfKib5 21 | U/SA9nJaz/mWeF9S0farz9AQFc8/NSzAzaVq7YbM4F6f6N2FRl7GikdXRCed45j6 22 | mrPzGzk3ECbupFnqyREH3+ZPSdk= 23 | -----END CERTIFICATE----- 24 | 25 | 26 | 27 | -----BEGIN CERTIFICATE----- 28 | MIIDnzCCAoegAwIBAgINSWITCHaai+Root+CAzANBgkqhkiG9w0BAQUFADBrMQsw 29 | CQYDVQQGEwJDSDFAMD4GA1UEChM3U3dpdGNoIC0gVGVsZWluZm9ybWF0aWtkaWVu 30 | c3RlIGZ1ZXIgTGVocmUgdW5kIEZvcnNjaHVuZzEaMBgGA1UEAxMRU1dJVENIYWFp 31 | IFJvb3QgQ0EwHhcNMDgwNTE1MDYzMDAwWhcNMjgwNTE1MDYyOTU5WjBrMQswCQYD 32 | VQQGEwJDSDFAMD4GA1UEChM3U3dpdGNoIC0gVGVsZWluZm9ybWF0aWtkaWVuc3Rl 33 | IGZ1ZXIgTGVocmUgdW5kIEZvcnNjaHVuZzEaMBgGA1UEAxMRU1dJVENIYWFpIFJv 34 | b3QgQ0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDUSWbn/rhWew/s 35 | LJRyciyRKDGyFXSgiDO/EohYuZLw6EAKLLlhZorNtEHQbbn0Oo13S33MclHMvGWT 36 | KJM0u1hG+6gLy78EPmJbqAE1Uv23wVEH4SX0VJfl3JVqIebiAH/CjuLubgMUspDI 37 | jOdQHNLS7pthTbm7Tgh7zMsiLPyMTZJep5CGbqv8NoK6bMaF0Z+Bt7e1JRlhHFCV 38 | iJJaR/+hfpzLsJ8NWVivvrpRGaGJ1XR+9FGsTkjNdMCirNJJZ6XvUOe5w7pHSd9M 39 | cppFP0eyLs02AMzMXI4iz6PK/w3EdzXGXpK+gSgvLxWYct4xHpv1e2NXhNgdJOSN 40 | 9ra/wJLVAgMBAAGjQjBAMA8GA1UdEwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgEG 41 | MB0GA1UdDgQWBBTpmuIGWOsP14EDXVyXubG1k307hDANBgkqhkiG9w0BAQUFAAOC 42 | AQEAMV/eIW6pFB+mbk7rD7hUPTWDRaoca3kHqmFGFnHfuY8+c0/Mqjh8Y/jyX1yb 43 | f58crTSWrbyGbUZ3oxDGQ34tuZSkmeR32NqryiX3sP5qlNSozVguQKt8o4vhS1Qe 44 | WPsXALs3em2pdKuIGSOpbuDnopPcmU2g5Zi2R5P7qpKDKAKtNUEwV+LW7GBMEksO 45 | Nj7BFXk4AFBFBijaYJGgHmoKSImVgeNIvsV+BSv5HJ4q6vcxfnwuvvGHM0AGphYO 46 | 6f5qtHMUgvAblI8M/2QsBgethaGrirtKJ3aCRLdaR2R1QfaGRpck/Ron5/MpMxiJ 47 | wLT8YlW/zjx2yNABhPSAjfzeMw== 48 | -----END CERTIFICATE----- 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /tests/vo_metadata.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 8 | 10 | 11 | urn:mace:example.com:saml:aa 12 | 13 | 14 | urn:mace:example.com:saml:idp 15 | 16 | 17 | 18 | 19 | --------------------------------------------------------------------------------