diff options
-rw-r--r-- | CHANGELOG.md | 13 | ||||
-rw-r--r-- | README.rst | 3 | ||||
-rw-r--r-- | VERSION | 2 | ||||
-rw-r--r-- | docs/install.rst | 3 | ||||
-rw-r--r-- | example/idp2/idp_user.py | 4 | ||||
-rwxr-xr-x | example/sp-wsgi/sp.py | 33 | ||||
-rw-r--r-- | src/saml2/assertion.py | 7 | ||||
-rw-r--r-- | src/saml2/attributemaps/saml_uri.py | 8 | ||||
-rw-r--r-- | src/saml2/client_base.py | 4 | ||||
-rw-r--r-- | src/saml2/ident.py | 23 | ||||
-rw-r--r-- | src/saml2/mdstore.py | 2 | ||||
-rw-r--r-- | src/saml2/mongo_store.py | 23 | ||||
-rw-r--r-- | src/saml2/pack.py | 16 | ||||
-rw-r--r-- | src/saml2/response.py | 21 | ||||
-rw-r--r-- | tests/attribute_response.xml | 4 | ||||
-rw-r--r-- | tests/create_certs.sh | 6 | ||||
-rw-r--r-- | tests/entity_cat_rs.xml | 84 | ||||
-rw-r--r-- | tests/myentitycategory.py | 16 | ||||
-rw-r--r-- | tests/openssl.cnf | 6 | ||||
-rw-r--r-- | tests/pki/test_3.crt | 22 | ||||
-rw-r--r-- | tests/root_cert/localhost.ca.crt | 26 | ||||
-rw-r--r-- | tests/test_1.crt | 22 | ||||
-rw-r--r-- | tests/test_2.crt | 22 | ||||
-rw-r--r-- | tests/test_37_entity_categories.py | 39 | ||||
-rw-r--r-- | tests/test_50_server.py | 4 | ||||
-rw-r--r-- | tests/test_51_client.py | 4 |
26 files changed, 337 insertions, 80 deletions
diff --git a/CHANGELOG.md b/CHANGELOG.md index 05e78441..2de594b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ # Changelog +## 4.7.0 (2019-04-02) + +- Add support for MDQ signature verification +- Raise XmlsecError if xmlsec1 operations do not succeed +- Handle non standard response error status codes correctly +- Remove the hardcoded warning filter; pass -Wd to the python + interpreter to enable warnings +- Remove the python-future dependency and only use six +- Minor python2 and python3 compatibility fixes + (unicode strings and example code) +- Minor documentation fixes + + ## 4.6.5 (2018-12-04) - Fix for response status error case handling (introduced in v4.6.5) @@ -21,6 +21,9 @@ provider. The distribution contains examples of both. Originally written to work in a WSGI environment there are extensions that allow you to use it with other frameworks. +Install +======= +You can install with `pip install pysaml2` Testing ======= @@ -1 +1 @@ -4.6.5 +4.7.0 diff --git a/docs/install.rst b/docs/install.rst index 58910e29..4e23497b 100644 --- a/docs/install.rst +++ b/docs/install.rst @@ -50,7 +50,8 @@ After this you ought to be able to run the tests without an hitch. The tests are based on the pypy test environment, so:: cd tests - py.test + pip install -r test-requirements.txt + pytest is what you should use. If you don't have py.test, get it it's part of pypy! It's really good ! diff --git a/example/idp2/idp_user.py b/example/idp2/idp_user.py index e16e7069..82b91673 100644 --- a/example/idp2/idp_user.py +++ b/example/idp2/idp_user.py @@ -42,7 +42,7 @@ USERS = { "eduPersonScopedAffiliation": "student@example.com", "eduPersonPrincipalName": "test@example.com", "uid": "testuser", - "eduPersonTargetedID": "one!for!all", + "eduPersonTargetedID": ["one!for!all"], "c": "SE", "o": "Example Co.", "ou": "IT", @@ -64,7 +64,7 @@ USERS = { "eduPersonScopedAffiliation": "staff@example.com", "eduPersonPrincipalName": "rohe@example.com", "uid": "rohe", - "eduPersonTargetedID": "one!for!all", + "eduPersonTargetedID": ["one!for!all"], "c": "SE", "o": "Example Co.", "ou": "IT", diff --git a/example/sp-wsgi/sp.py b/example/sp-wsgi/sp.py index 485d802d..bec6747f 100755 --- a/example/sp-wsgi/sp.py +++ b/example/sp-wsgi/sp.py @@ -704,13 +704,6 @@ def main(environ, start_response, sp): ) body.append("<br><a href='/logout'>logout</a>") - body = [ - item - if isinstance(item, six.binary_type) - else item.encode("utf-8") - for item in body - ] - resp = Response(body) return resp(environ, start_response) @@ -831,7 +824,7 @@ def metadata(environ, start_response): _args.sign, ) start_response("200 OK", [("Content-Type", "text/xml")]) - return metadata + return [metadata] except Exception as ex: logger.error("An error occured while creating metadata: %s", ex.message) return not_found(environ, start_response) @@ -882,6 +875,28 @@ def application(environ, start_response): return resp(environ, start_response) +class ToBytesMiddleware(object): + """Converts a message to bytes to be sent by WSGI server.""" + + def __init__(self, app): + self.app = app + + def __call__(self, environ, start_response): + data = self.app(environ, start_response) + + if isinstance(data, list): + return ( + d + if isinstance(d, bytes) + else d.encode("utf-8") + for d in data + ) + elif isinstance(data, str): + return data.encode("utf-8") + + return data + + if __name__ == "__main__": try: from cheroot.wsgi import Server as WSGIServer @@ -966,7 +981,7 @@ if __name__ == "__main__": pass ds.DefaultSignature(sign_alg, digest_alg) - SRV = WSGIServer((HOST, PORT), application) + SRV = WSGIServer((HOST, PORT), ToBytesMiddleware(application)) _https = "" if service_conf.HTTPS: diff --git a/src/saml2/assertion.py b/src/saml2/assertion.py index 8984db59..ed043d9a 100644 --- a/src/saml2/assertion.py +++ b/src/saml2/assertion.py @@ -353,8 +353,11 @@ class Policy(object): else: ecs = [] for cat in items: - _mod = importlib.import_module( - "saml2.entity_category.%s" % cat) + try: + _mod = importlib.import_module(cat) + except ImportError: + _mod = importlib.import_module( + "saml2.entity_category.%s" % cat) _ec = {} for key, items in _mod.RELEASE.items(): alist = [k.lower() for k in items] diff --git a/src/saml2/attributemaps/saml_uri.py b/src/saml2/attributemaps/saml_uri.py index 40f7b778..608fcc28 100644 --- a/src/saml2/attributemaps/saml_uri.py +++ b/src/saml2/attributemaps/saml_uri.py @@ -23,6 +23,10 @@ OPENOSI_OID = 'urn:oid:1.3.6.1.4.1.27630.2.1.1.' EIDAS_NATURALPERSON = 'http://eidas.europa.eu/attributes/naturalperson/' EIDAS_LEGALPERSON = 'http://eidas.europa.eu/attributes/legalperson/' +# SAML subject id specification +# https://docs.oasis-open.org/security/saml-subject-id-attr/v1.0/cs01/saml-subject-id-attr-v1.0-cs01.html +SAML_SUBJECT_ID = 'urn:oasis:names:tc:SAML:attribute:' + MAP = { 'identifier': 'urn:oasis:names:tc:SAML:2.0:attrname-format:uri', 'fro': { @@ -109,6 +113,8 @@ MAP = { OPENOSI_OID+'109': 'osiOtherHomePhone', OPENOSI_OID+'120': 'osiWorkURL', PKCS_9+'1': 'email', + SAML_SUBJECT_ID+'subject-id': 'subject-id', + SAML_SUBJECT_ID+'pairwise-id': 'pairwise-id', SCHAC+'1': 'schacMotherTongue', SCHAC+'2': 'schacGender', SCHAC+'3': 'schacDateOfBirth', @@ -280,6 +286,7 @@ MAP = { 'osiWorkURL': OPENOSI_OID+'120', 'ou': X500ATTR_OID+'11', 'owner': X500ATTR_OID+'32', + 'pairwise-id': SAML_SUBJECT_ID+'pairwise-id', 'physicalDeliveryOfficeName': X500ATTR_OID+'19', 'postOfficeBox': X500ATTR_OID+'18', 'postalAddress': X500ATTR_OID+'16', @@ -337,6 +344,7 @@ MAP = { 'sn': X500ATTR_OID+'4', 'st': X500ATTR_OID+'8', 'street': X500ATTR_OID+'9', + 'subject-id': SAML_SUBJECT_ID+'subject-id', 'supportedAlgorithms': X500ATTR_OID+'52', 'supportedApplicationContext': X500ATTR_OID+'30', 'telephoneNumber': X500ATTR_OID+'20', diff --git a/src/saml2/client_base.py b/src/saml2/client_base.py index 39a7d0ed..15e3b0ec 100644 --- a/src/saml2/client_base.py +++ b/src/saml2/client_base.py @@ -339,6 +339,10 @@ class Base(Entity): # If no nameid_format has been set in the configuration # or passed in then transient is the default. if nameid_format is None: + # SAML 2.0 errata says AllowCreate MUST NOT be used for + # transient ids - to make a conservative change this is + # only applied for the default cause + allow_create = None nameid_format = NAMEID_FORMAT_TRANSIENT # If a list has been configured or passed in choose the diff --git a/src/saml2/ident.py b/src/saml2/ident.py index db8365bc..d6a6620a 100644 --- a/src/saml2/ident.py +++ b/src/saml2/ident.py @@ -155,6 +155,16 @@ class IdentDB(object): pass def get_nameid(self, userid, nformat, sp_name_qualifier, name_qualifier): + if nformat == NAMEID_FORMAT_PERSISTENT: + nameid = self.match_local_id(userid, sp_name_qualifier, name_qualifier) + if nameid: + logger.debug( + "Found existing persistent NameId {nid} for user {uid}".format( + nid=nameid, uid=userid + ) + ) + return nameid + _id = self.create_id(nformat, name_qualifier, sp_name_qualifier) if nformat == NAMEID_FORMAT_EMAILADDRESS: @@ -163,11 +173,12 @@ class IdentDB(object): _id = "%s@%s" % (_id, self.domain) - # if nformat == NAMEID_FORMAT_PERSISTENT: - # _id = userid - - nameid = NameID(format=nformat, sp_name_qualifier=sp_name_qualifier, - name_qualifier=name_qualifier, text=_id) + nameid = NameID( + format=nformat, + sp_name_qualifier=sp_name_qualifier, + name_qualifier=name_qualifier, + text=_id, + ) self.store(userid, nameid) return nameid @@ -236,7 +247,7 @@ class IdentDB(object): def construct_nameid(self, userid, local_policy=None, sp_name_qualifier=None, name_id_policy=None, name_qualifier=""): - """ Returns a name_id for the object. How the name_id is + """ Returns a name_id for the userid. How the name_id is constructed depends on the context. :param local_policy: The policy the server is configured to follow diff --git a/src/saml2/mdstore.py b/src/saml2/mdstore.py index f5ffbb41..a92414c5 100644 --- a/src/saml2/mdstore.py +++ b/src/saml2/mdstore.py @@ -720,7 +720,7 @@ class MetaDataLoader(MetaDataFile): class MetaDataExtern(InMemoryMetaData): """ Class that handles metadata store somewhere on the net. - Accessible but HTTP GET. + Accessible by HTTP GET. """ def __init__(self, attrc, url=None, security=None, cert=None, diff --git a/src/saml2/mongo_store.py b/src/saml2/mongo_store.py index 6a8f9f45..903dd481 100644 --- a/src/saml2/mongo_store.py +++ b/src/saml2/mongo_store.py @@ -1,3 +1,4 @@ +import datetime from hashlib import sha1 import logging @@ -5,6 +6,8 @@ from pymongo import MongoClient from pymongo.mongo_replica_set_client import MongoReplicaSetClient import pymongo.uri_parser import pymongo.errors +from saml2.saml import NAMEID_FORMAT_PERSISTENT + from saml2.eptid import Eptid from saml2.mdstore import InMemoryMetaData from saml2.mdstore import metadata_modules @@ -163,6 +166,23 @@ class IdentMDB(IdentDB): return item[self.mdb.primary_key] return None + def match_local_id(self, userid, sp_name_qualifier, name_qualifier): + """ + Match a local persistent identifier. + + Look for an existing persistent NameID matching userid, + sp_name_qualifier and name_qualifier. + """ + filter = { + "name_id.sp_name_qualifier": sp_name_qualifier, + "name_id.name_qualifier": name_qualifier, + "name_id.format": NAMEID_FORMAT_PERSISTENT, + } + res = self.mdb.get(value=userid, **filter) + if not res: + return None + return from_dict(res[0]["name_id"], ONTS, True) + def remove_remote(self, name_id): cnid = to_dict(name_id, MMODS, True) self.mdb.remove(name_id=cnid) @@ -192,6 +212,9 @@ class MDB(object): else: doc = {} doc.update(kwargs) + # Add timestamp to all documents to allow external garbage collecting + if "created_at" not in doc: + doc["created_at"] = datetime.datetime.utcnow() _ = self.db.insert(doc) def get(self, value=None, **kwargs): diff --git a/src/saml2/pack.py b/src/saml2/pack.py index b447be31..5a2534c2 100644 --- a/src/saml2/pack.py +++ b/src/saml2/pack.py @@ -8,7 +8,11 @@ Bindings normally consists of three parts: """ import base64 -import cgi +try: + import html +except: + import cgi as html + import logging import saml2 @@ -64,6 +68,10 @@ HTML_FORM_SPEC = """<!DOCTYPE html> </html>""" +def _html_escape(payload): + return html.escape(payload, quote=True) + + def http_form_post_message(message, location, relay_state="", typ="SAMLRequest", **kwargs): """The HTTP POST binding defines a mechanism by which SAML protocol @@ -87,15 +95,15 @@ def http_form_post_message(message, location, relay_state="", _msg = _msg.decode('ascii') saml_response_input = HTML_INPUT_ELEMENT_SPEC.format( - name=cgi.escape(typ), - val=cgi.escape(_msg), + name=_html_escape(typ), + val=_html_escape(_msg), type='hidden') relay_state_input = "" if relay_state: relay_state_input = HTML_INPUT_ELEMENT_SPEC.format( name='RelayState', - val=cgi.escape(relay_state), + val=_html_escape(relay_state), type='hidden') response = HTML_FORM_SPEC.format( diff --git a/src/saml2/response.py b/src/saml2/response.py index 26b0ab22..2660e738 100644 --- a/src/saml2/response.py +++ b/src/saml2/response.py @@ -944,21 +944,32 @@ class AuthnResponse(StatusResponse): resp = self.response decr_text = str(self.response) - while self.find_encrypt_data(resp): + decr_text_old = None + while self.find_encrypt_data(resp) and decr_text_old != decr_text: + decr_text_old = decr_text try: decr_text = self.sec.decrypt_keys(decr_text, keys) except DecryptError as e: continue else: resp = samlp.response_from_string(decr_text) + # check and prepare for comparison between str and unicode + if type(decr_text_old) != type(decr_text): + if isinstance(decr_text_old, six.binary_type): + decr_text_old = decr_text_old.decode("utf-8") + else: + decr_text_old = decr_text_old.encode("utf-8") _enc_assertions = self.decrypt_assertions( resp.encrypted_assertion, decr_text ) + + decr_text_old = None while ( self.find_encrypt_data(resp) or self.find_encrypt_data_assertion_list(_enc_assertions) - ): + ) and decr_text_old != decr_text: + decr_text_old = decr_text try: decr_text = self.sec.decrypt_keys(decr_text, keys) except DecryptError as e: @@ -968,6 +979,12 @@ class AuthnResponse(StatusResponse): _enc_assertions = self.decrypt_assertions( resp.encrypted_assertion, decr_text, verified=True ) + # check and prepare for comparison between str and unicode + if type(decr_text_old) != type(decr_text): + if isinstance(decr_text_old, six.binary_type): + decr_text_old = decr_text_old.decode("utf-8") + else: + decr_text_old = decr_text_old.encode("utf-8") all_assertions = _enc_assertions if resp.assertion: diff --git a/tests/attribute_response.xml b/tests/attribute_response.xml index d56817cf..9a7aa03b 100644 --- a/tests/attribute_response.xml +++ b/tests/attribute_response.xml @@ -32,13 +32,13 @@ Method="urn:oasis:names:tc:SAML:2.0:cm:bearer"> <saml2:SubjectConfirmationData Address="192.168.1.1" InResponseTo="id-f4d370f3d03650f3ec0da694e2348bfe" - NotOnOrAfter="2024-09-14T21:06:32.081Z" + NotOnOrAfter="2999-09-14T21:06:32.081Z" Recipient="https://myreviewroom.com/saml2/acs/" /> </saml2:SubjectConfirmation> </saml2:Subject> <saml2:Conditions NotBefore="2014-09-14T21:01:32.081Z" - NotOnOrAfter="2024-09-14T21:06:32.081Z" + NotOnOrAfter="2999-09-14T21:06:32.081Z" > <saml2:AudienceRestriction> <saml2:Audience>urn:mace:example.com:saml:roland:sp diff --git a/tests/create_certs.sh b/tests/create_certs.sh new file mode 100644 index 00000000..66914546 --- /dev/null +++ b/tests/create_certs.sh @@ -0,0 +1,6 @@ +#!/bin/sh +newcert="openssl req -x509 -new -days 365000 -sha256 -config openssl.cnf -set_serial 1" +$newcert -key test_1.key -out test_1.crt -subj '/C=zz/ST=zz/L=zzzz/O=Zzzzz/OU=Zzzzz/CN=test' +$newcert -key test_2.key -out test_2.crt -subj '/C=zz/ST=zz/L=zzzz/O=Zzzzz/OU=Zzzzz/CN=test' +$newcert -key pki/test_3.key -out pki/test_3.crt -subj '/C=zz/ST=zz/L=zzzz/O=Zzzzz/OU=Zzzzz/CN=test' +$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' diff --git a/tests/entity_cat_rs.xml b/tests/entity_cat_rs.xml new file mode 100644 index 00000000..5f3e00f8 --- /dev/null +++ b/tests/entity_cat_rs.xml @@ -0,0 +1,84 @@ +<?xml version='1.0' encoding='UTF-8'?> +<ns0:EntityDescriptor xmlns:ns0="urn:oasis:names:tc:SAML:2.0:metadata" + xmlns:xs="http://www.w3.org/2001/XMLSchema" + xmlns:ns1="urn:oasis:names:tc:SAML:metadata:attribute" + xmlns:ns2="urn:oasis:names:tc:SAML:2.0:assertion" + xmlns:ns5="http://www.w3.org/2000/09/xmldsig#" + xmlns:ns4="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + entityID="urn:mace:example.com:saml:roland:sp"> + <ns0:Extensions> + <ns1:EntityAttributes> + <ns2:Attribute Name="http://macedir.org/entity-category"> + <ns2:AttributeValue xsi:type="xs:string"> + http://refeds.org/category/research-and-scholarship + </ns2:AttributeValue> + </ns2:Attribute> + </ns1:EntityAttributes> + </ns0:Extensions> + <ns0:SPSSODescriptor AuthnRequestsSigned="false" WantAssertionsSigned="true" + protocolSupportEnumeration="urn:oasis:names:tc:SAML:2.0:protocol"> + <ns0:Extensions> + <ns4:DiscoveryResponse + Binding="urn:oasis:names:tc:SAML:profiles:SSO:idp-discovery-protocol" + Location="https://xenosmilus2.umdc.umu.se:8086/disco" + index="1"/> + </ns0:Extensions> + <ns0:KeyDescriptor use="encryption"> + <ns5:KeyInfo> + <ns5:X509Data> + <ns5:X509Certificate> + MIIC8jCCAlugAwIBAgIJAJHg2V5J31I8MA0GCSqGSIb3DQEBBQUAMFoxCzAJBgNV + BAYTAlNFMQ0wCwYDVQQHEwRVbWVhMRgwFgYDVQQKEw9VbWVhIFVuaXZlcnNpdHkx + EDAOBgNVBAsTB0lUIFVuaXQxEDAOBgNVBAMTB1Rlc3QgU1AwHhcNMDkxMDI2MTMz + MTE1WhcNMTAxMDI2MTMzMTE1WjBaMQswCQYDVQQGEwJTRTENMAsGA1UEBxMEVW1l + YTEYMBYGA1UEChMPVW1lYSBVbml2ZXJzaXR5MRAwDgYDVQQLEwdJVCBVbml0MRAw + DgYDVQQDEwdUZXN0IFNQMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkJWP7 + bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr6/ROgW96ZeQ57fzVy2MC + FiQRw2fzBs0n7leEmDJyVVtBTavYlhAVXDNa3stgvh43qCfLx+clUlOvtnsoMiiR + mo7qf0BoPKTj7c0uLKpDpEbAHQT4OF1HRYVxMwIDAQABo4G/MIG8MB0GA1UdDgQW + BBQ7RgbMJFDGRBu9o3tDQDuSoBy7JjCBjAYDVR0jBIGEMIGBgBQ7RgbMJFDGRBu9 + o3tDQDuSoBy7JqFepFwwWjELMAkGA1UEBhMCU0UxDTALBgNVBAcTBFVtZWExGDAW + BgNVBAoTD1VtZWEgVW5pdmVyc2l0eTEQMA4GA1UECxMHSVQgVW5pdDEQMA4GA1UE + AxMHVGVzdCBTUIIJAJHg2V5J31I8MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF + BQADgYEAMuRwwXRnsiyWzmRikpwinnhTmbooKm5TINPE7A7gSQ710RxioQePPhZO + zkM27NnHTrCe2rBVg0EGz7QTd1JIwLPvgoj4VTi/fSha/tXrYUaqc9AqU1kWI4WN + +vffBGQ09mo+6CffuFTZYeOhzP/2stAPwCTU4kxEoiy0KpZMANI= + </ns5:X509Certificate> + </ns5:X509Data> + </ns5:KeyInfo> + </ns0:KeyDescriptor> + <ns0:KeyDescriptor use="signing"> + <ns5:KeyInfo> + <ns5:X509Data> + <ns5:X509Certificate> + MIIC8jCCAlugAwIBAgIJAJHg2V5J31I8MA0GCSqGSIb3DQEBBQUAMFoxCzAJBgNV + BAYTAlNFMQ0wCwYDVQQHEwRVbWVhMRgwFgYDVQQKEw9VbWVhIFVuaXZlcnNpdHkx + EDAOBgNVBAsTB0lUIFVuaXQxEDAOBgNVBAMTB1Rlc3QgU1AwHhcNMDkxMDI2MTMz + MTE1WhcNMTAxMDI2MTMzMTE1WjBaMQswCQYDVQQGEwJTRTENMAsGA1UEBxMEVW1l + YTEYMBYGA1UEChMPVW1lYSBVbml2ZXJzaXR5MRAwDgYDVQQLEwdJVCBVbml0MRAw + DgYDVQQDEwdUZXN0IFNQMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDkJWP7 + bwOxtH+E15VTaulNzVQ/0cSbM5G7abqeqSNSs0l0veHr6/ROgW96ZeQ57fzVy2MC + FiQRw2fzBs0n7leEmDJyVVtBTavYlhAVXDNa3stgvh43qCfLx+clUlOvtnsoMiiR + mo7qf0BoPKTj7c0uLKpDpEbAHQT4OF1HRYVxMwIDAQABo4G/MIG8MB0GA1UdDgQW + BBQ7RgbMJFDGRBu9o3tDQDuSoBy7JjCBjAYDVR0jBIGEMIGBgBQ7RgbMJFDGRBu9 + o3tDQDuSoBy7JqFepFwwWjELMAkGA1UEBhMCU0UxDTALBgNVBAcTBFVtZWExGDAW + BgNVBAoTD1VtZWEgVW5pdmVyc2l0eTEQMA4GA1UECxMHSVQgVW5pdDEQMA4GA1UE + AxMHVGVzdCBTUIIJAJHg2V5J31I8MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQEF + BQADgYEAMuRwwXRnsiyWzmRikpwinnhTmbooKm5TINPE7A7gSQ710RxioQePPhZO + zkM27NnHTrCe2rBVg0EGz7QTd1JIwLPvgoj4VTi/fSha/tXrYUaqc9AqU1kWI4WN + +vffBGQ09mo+6CffuFTZYeOhzP/2stAPwCTU4kxEoiy0KpZMANI= + </ns5:X509Certificate> + </ns5:X509Data> + </ns5:KeyInfo> + </ns0:KeyDescriptor> + <ns0:AssertionConsumerService + Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" + Location="https://xenosmilus2.umdc.umu.se:8086/acs/sfs/re_nren/redirect" + index="1"/> + <ns0:AssertionConsumerService + Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" + Location="https://xenosmilus2.umdc.umu.se:8086/acs/sfs/re_nren/post" + index="2"/> + </ns0:SPSSODescriptor> +</ns0:EntityDescriptor> diff --git a/tests/myentitycategory.py b/tests/myentitycategory.py new file mode 100644 index 00000000..9ec55bf9 --- /dev/null +++ b/tests/myentitycategory.py @@ -0,0 +1,16 @@ +CUSTOM_R_AND_S = ['eduPersonTargetedID', + 'eduPersonPrincipalName', + 'mail', + 'displayName', + 'givenName', + 'sn', + 'eduPersonScopedAffiliation', + 'eduPersonUniqueId' + ] + +RESEARCH_AND_SCHOLARSHIP = "http://refeds.org/category/research-and-scholarship" + +RELEASE = { + "": ["eduPersonTargetedID"], + RESEARCH_AND_SCHOLARSHIP: CUSTOM_R_AND_S, +} diff --git a/tests/openssl.cnf b/tests/openssl.cnf new file mode 100644 index 00000000..405e8de2 --- /dev/null +++ b/tests/openssl.cnf @@ -0,0 +1,6 @@ +[req] +req_extensions = v3_req +distinguished_name = req_distinguished_name + +[req_distinguished_name] +[v3_req] diff --git a/tests/pki/test_3.crt b/tests/pki/test_3.crt index 5273869d..a67a9ae3 100644 --- a/tests/pki/test_3.crt +++ b/tests/pki/test_3.crt @@ -1,14 +1,14 @@ -----BEGIN CERTIFICATE----- -MIICHzCCAYgCAQEwDQYJKoZIhvcNAQELBQAwWDELMAkGA1UEBhMCenoxCzAJBgNV +MIICITCCAYoCAQEwDQYJKoZIhvcNAQELBQAwWDELMAkGA1UEBhMCenoxCzAJBgNV BAgMAnp6MQ0wCwYDVQQHDAR6enp6MQ4wDAYDVQQKDAVaenp6ejEOMAwGA1UECwwF -Wnp6enoxDTALBgNVBAMMBHRlc3QwHhcNMTUwNjAyMTEyMzQ2WhcNMjUwNTMwMTEy -MzQ2WjBYMQswCQYDVQQGEwJ6ejELMAkGA1UECAwCenoxDTALBgNVBAcMBHp6enox -DjAMBgNVBAoMBVp6enp6MQ4wDAYDVQQLDAVaenp6ejENMAsGA1UEAwwEdGVzdDCB -nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArxklvHr3cJJEHImPJCfsdId/yfhg -kZsbKzhoLTYX10gC9Qz1SbGxUJ4GKQn+IlaMWY4WTle5JUPibDIN5sYtF3GC5JPW -pI/sKeba7ni7JWBOCeAgcgQMGYvuX4VkR2HYuMkKOs/O3d1tAfQdEh8e0I1rXzyS -2TEiV2GECUec1JUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQCbgMfVhmLWasoguB43 -UtIc77T+AZwnxhPKpd8WNx6C03KFK+WoKViZuLR+8i6K/jwJhjiJ8nZzPDIomb4r -hnA/jvOVQKzzgZCr6FBWIVQjDFdIR6MCkGW+JKUVeTQTtsQ7DgL3QjnVDQLL96B7 -ZYA44pe1CSmcdFPmZJyRwFQm/g== +Wnp6enoxDTALBgNVBAMMBHRlc3QwIBcNMTkwNDEyMTk1MDM0WhgPMzAxODA4MTMx +OTUwMzRaMFgxCzAJBgNVBAYTAnp6MQswCQYDVQQIDAJ6ejENMAsGA1UEBwwEenp6 +ejEOMAwGA1UECgwFWnp6enoxDjAMBgNVBAsMBVp6enp6MQ0wCwYDVQQDDAR0ZXN0 +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCvGSW8evdwkkQciY8kJ+x0h3/J ++GCRmxsrOGgtNhfXSAL1DPVJsbFQngYpCf4iVoxZjhZOV7klQ+JsMg3mxi0XcYLk +k9akj+wp5trueLslYE4J4CByBAwZi+5fhWRHYdi4yQo6z87d3W0B9B0SHx7QjWtf +PJLZMSJXYYQJR5zUlQIDAQABMA0GCSqGSIb3DQEBCwUAA4GBABIjpaJbBYftDKCV +k53bv6ArWJgBkAxq0GUdwf+X7MELfctFRXpksUyqtGff7Whw4EDkTGSCHmdmLXXG +yGmc5sdEsAWGs68B7csJzznOPBlkPe5aIJFtlGRC0y1PHcXFM9H3Aof4rpAuJuby +7B8dlOXPEWoz+gLObD5bxWP8Qf+p -----END CERTIFICATE----- diff --git a/tests/root_cert/localhost.ca.crt b/tests/root_cert/localhost.ca.crt index c7faff99..fcbce845 100644 --- a/tests/root_cert/localhost.ca.crt +++ b/tests/root_cert/localhost.ca.crt @@ -1,15 +1,15 @@ -----BEGIN CERTIFICATE----- -MIICSTCCAbICAQEwDQYJKoZIhvcNAQELBQAwbTELMAkGA1UEBhMCc2UxCzAJBgNV -BAgTAmFjMQ0wCwYDVQQHEwR1bWVhMRwwGgYDVQQKExNJVFMgVW1lYSBVbml2ZXJz -aXR5MQ0wCwYDVQQLEwRESVJHMRUwEwYDVQQDEwxsb2NhbGhvc3QuY2EwHhcNMTQw -MzE0MDczNDIwWhcNMjQwMzExMDczNDIwWjBtMQswCQYDVQQGEwJzZTELMAkGA1UE -CBMCYWMxDTALBgNVBAcTBHVtZWExHDAaBgNVBAoTE0lUUyBVbWVhIFVuaXZlcnNp -dHkxDTALBgNVBAsTBERJUkcxFTATBgNVBAMTDGxvY2FsaG9zdC5jYTCBnzANBgkq -hkiG9w0BAQEFAAOBjQAwgYkCgYEAzJseOYg+hKGsnGWilv9FrfH2csQ9UZGwgwHz -zR2IquQg/+nONxd4MIwjOnQrLOJuhpu55ZTSpeT901GNDLj4xPQnFrWWyET8NxZg -w7Ilra55iQNaoUWpdi0JQSXI/9CY8t+Y170+7DfBJ6zo4y6+HKaOLNxZy4IbB0SE -aZBGsrUCAwEAATANBgkqhkiG9w0BAQsFAAOBgQAEOQJkD+5fb2mTtxwaZeRyQN9c -If0Xd2E7Z6BstGCUMWa/q4FrNee324kINVsFYg0GBTBwfyYPwR7I70LGjS3gXEzd -RjGx/Z8yvHJyavJj8iRCOflQ0fUlwasFYtrJesPOfM+aJ05Jb8GelUxpKh6BtPlE -IU0FLm//i9ucLQ9zBg== +MIICSzCCAbQCAQEwDQYJKoZIhvcNAQELBQAwbTELMAkGA1UEBhMCc2UxCzAJBgNV +BAgMAmFjMQ0wCwYDVQQHDAR1bWVhMRwwGgYDVQQKDBNJVFMgVW1lYSBVbml2ZXJz +aXR5MQ0wCwYDVQQLDARESVJHMRUwEwYDVQQDDAxsb2NhbGhvc3QuY2EwIBcNMTkw +NDEyMTk1MDM0WhgPMzAxODA4MTMxOTUwMzRaMG0xCzAJBgNVBAYTAnNlMQswCQYD +VQQIDAJhYzENMAsGA1UEBwwEdW1lYTEcMBoGA1UECgwTSVRTIFVtZWEgVW5pdmVy +c2l0eTENMAsGA1UECwwERElSRzEVMBMGA1UEAwwMbG9jYWxob3N0LmNhMIGfMA0G +CSqGSIb3DQEBAQUAA4GNADCBiQKBgQDMmx45iD6EoaycZaKW/0Wt8fZyxD1RkbCD +AfPNHYiq5CD/6c43F3gwjCM6dCss4m6Gm7nllNKl5P3TUY0MuPjE9CcWtZbIRPw3 +FmDDsiWtrnmJA1qhRal2LQlBJcj/0Jjy35jXvT7sN8EnrOjjLr4cpo4s3FnLghsH +RIRpkEaytQIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAHeVAl4z5gxyBCHrmlyfjR04 +Ndpt57FjBGARgULvCI3VFQpsXZvezVBfnbd/Ynith2mKRVsTx+h6aEBucWsSgLiG +gCR/ZPaX9bIbXNNadpe96J402CHpGlnA2nBtkZHN9TGEW8wCKe2D+RDjNMyi/1PJ +aiscKKlLdKqmRpTQZ7sy -----END CERTIFICATE----- diff --git a/tests/test_1.crt b/tests/test_1.crt index d43cc1d7..949c2f4f 100644 --- a/tests/test_1.crt +++ b/tests/test_1.crt @@ -1,14 +1,14 @@ -----BEGIN CERTIFICATE----- -MIICHzCCAYgCAQEwDQYJKoZIhvcNAQELBQAwWDELMAkGA1UEBhMCenoxCzAJBgNV +MIICITCCAYoCAQEwDQYJKoZIhvcNAQELBQAwWDELMAkGA1UEBhMCenoxCzAJBgNV BAgMAnp6MQ0wCwYDVQQHDAR6enp6MQ4wDAYDVQQKDAVaenp6ejEOMAwGA1UECwwF -Wnp6enoxDTALBgNVBAMMBHRlc3QwHhcNMTUwNjAyMDc0MjI2WhcNMjUwNTMwMDc0 -MjI2WjBYMQswCQYDVQQGEwJ6ejELMAkGA1UECAwCenoxDTALBgNVBAcMBHp6enox -DjAMBgNVBAoMBVp6enp6MQ4wDAYDVQQLDAVaenp6ejENMAsGA1UEAwwEdGVzdDCB -nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAx3I/NFlP1wbHfRZckJn4z1HX5nnY -QhQ3ekxEJmTTaj/1BvlZBmvgV40SBzH4nP1sT02xoQo7+vHItFAzaJlF2oBXsSxj -aZMGu/gkVbaHP9cYKvskhOjOJ4XArrUnKMTb1jZ+XkkOuot1NLE7/dTILF8ahHU2 -omYNASLnxHN3bnkCAwEAATANBgkqhkiG9w0BAQsFAAOBgQCQam1Oz7iQcD9+OurB -M5a+Hth53m5hbAFuguSvERPCuJ/CfP1+g7CIZN/GnsIsg9QW77NvdOyxjXxzoJJm -okl1qz/qy3FY3mJ0gIUxDyPD9DL3c9/03MDv5YmWsoP+HNqK8QtNJ/JDEOhBr/Eo -/MokRo4gtMNeLF/soveWNoNiUg== +Wnp6enoxDTALBgNVBAMMBHRlc3QwIBcNMTkwNDEyMTk1MDM0WhgPMzAxODA4MTMx +OTUwMzRaMFgxCzAJBgNVBAYTAnp6MQswCQYDVQQIDAJ6ejENMAsGA1UEBwwEenp6 +ejEOMAwGA1UECgwFWnp6enoxDjAMBgNVBAsMBVp6enp6MQ0wCwYDVQQDDAR0ZXN0 +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDHcj80WU/XBsd9FlyQmfjPUdfm +edhCFDd6TEQmZNNqP/UG+VkGa+BXjRIHMfic/WxPTbGhCjv68ci0UDNomUXagFex +LGNpkwa7+CRVtoc/1xgq+ySE6M4nhcCutScoxNvWNn5eSQ66i3U0sTv91MgsXxqE +dTaiZg0BIufEc3dueQIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAGUV5B+USHvaRa8k +gCNJSuNpo6ARlv0ekrk8bbdNRBiEUdCMyoGJFfuM9K0zybX6Vr25wai3nvaog294 +Vx/jWjX2g5SDbjItH6VGy6C9GCGf1A07VxFRCfJn5tA9HuJjPKiE+g/BmrV5N4Ce +alzFxPHWYkNOzoRU8qI7OqUai1kL -----END CERTIFICATE----- diff --git a/tests/test_2.crt b/tests/test_2.crt index 609a94b3..299e0a4c 100644 --- a/tests/test_2.crt +++ b/tests/test_2.crt @@ -1,14 +1,14 @@ -----BEGIN CERTIFICATE----- -MIICHzCCAYgCAQEwDQYJKoZIhvcNAQELBQAwWDELMAkGA1UEBhMCenoxCzAJBgNV +MIICITCCAYoCAQEwDQYJKoZIhvcNAQELBQAwWDELMAkGA1UEBhMCenoxCzAJBgNV BAgMAnp6MQ0wCwYDVQQHDAR6enp6MQ4wDAYDVQQKDAVaenp6ejEOMAwGA1UECwwF -Wnp6enoxDTALBgNVBAMMBHRlc3QwHhcNMTUwNjAyMDc0MzAxWhcNMjUwNTMwMDc0 -MzAxWjBYMQswCQYDVQQGEwJ6ejELMAkGA1UECAwCenoxDTALBgNVBAcMBHp6enox -DjAMBgNVBAoMBVp6enp6MQ4wDAYDVQQLDAVaenp6ejENMAsGA1UEAwwEdGVzdDCB -nzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA41tJCTPuG2lirbztuGbBlzbzSipM -EzM+zluWegUaoUjqtlgNHOTQqTJOqw/GdjkxRKJT6IxI3/HVcnfw7P4a4xSkL/ME -IG3VyzedWEyLIHeofoQSTvr84ZdD0+Gk+zNCSqOQC7UuqpOLbMKK1tgZ8Mr7BkgI -p8H3lreLf29Sd5MCAwEAATANBgkqhkiG9w0BAQsFAAOBgQB0EXxy5+hsB7Rid7Gy -CZrAObpaC4nbyPPW/vccFKmEkYtlygEPgky7D9AGsVSaTc/YxPZcanY+vKoRIsiR -6ZitIUU5b+NnHcdj6289tUQ0iHj5jgVyv8wYHvPntTnqH2S7he0talLER8ITYToh -2wz3u7waz/GypMeA/suhoEfxew== +Wnp6enoxDTALBgNVBAMMBHRlc3QwIBcNMTkwNDEyMTk1MDM0WhgPMzAxODA4MTMx +OTUwMzRaMFgxCzAJBgNVBAYTAnp6MQswCQYDVQQIDAJ6ejENMAsGA1UEBwwEenp6 +ejEOMAwGA1UECgwFWnp6enoxDjAMBgNVBAsMBVp6enp6MQ0wCwYDVQQDDAR0ZXN0 +MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDjW0kJM+4baWKtvO24ZsGXNvNK +KkwTMz7OW5Z6BRqhSOq2WA0c5NCpMk6rD8Z2OTFEolPojEjf8dVyd/Ds/hrjFKQv +8wQgbdXLN51YTIsgd6h+hBJO+vzhl0PT4aT7M0JKo5ALtS6qk4tsworW2BnwyvsG +SAinwfeWt4t/b1J3kwIDAQABMA0GCSqGSIb3DQEBCwUAA4GBAFtj7WArQQBugmh/ +KQjjlfTQ5A052QeXfgTyO9vv1S6MRIi7qgiaEv49cGXnJv/TWbySkMKObPMUApjg +6z8PqcxuShew5FCTkNvwhABFPiyu0fUj3e2FEPHfsBu76jz4ugtmhUqjqhzwFY9c +tnWRkkl6J0AjM3LnHOSgjNIclDZG -----END CERTIFICATE----- diff --git a/tests/test_37_entity_categories.py b/tests/test_37_entity_categories.py index 625caaa1..839030fd 100644 --- a/tests/test_37_entity_categories.py +++ b/tests/test_37_entity_categories.py @@ -152,5 +152,44 @@ def test_idp_policy_filter(): "eduPersonTargetedID"] # because no entity category +def test_entity_category_import_from_path(): + # The entity category module myentitycategory.py is in the tests + # directory which is on the standard module search path. + # The module uses a custom interpretation of the REFEDs R&S entity category + # by adding eduPersonUniqueId. + policy = Policy({ + "default": { + "lifetime": {"minutes": 15}, + "entity_categories": ["myentitycategory"] + } + }) + + mds = MetadataStore(ATTRCONV, sec_config, + disable_ssl_certificate_validation=True) + + # The file entity_cat_rs.xml contains the SAML metadata for an SP + # tagged with the REFEDs R&S entity category. + mds.imp([{"class": "saml2.mdstore.MetaDataFile", + "metadata": [(full_path("entity_cat_rs.xml"),)]}]) + + ava = {"givenName": ["Derek"], "sn": ["Jeter"], + "displayName": "Derek Jeter", + "mail": ["derek@nyy.mlb.com"], "c": ["USA"], + "eduPersonTargetedID": "foo!bar!xyz", + "eduPersonUniqueId": "R13ET7UD68K0HGR153KE@my.org", + "eduPersonScopedAffiliation": "member@my.org", + "eduPersonPrincipalName": "user01@my.org", + "norEduPersonNIN": "19800101134"} + + ava = policy.filter(ava, "urn:mace:example.com:saml:roland:sp", mds) + + # We expect c and norEduPersonNIN to be filtered out since they are not + # part of the custom entity category. + assert _eq(list(ava.keys()), + ["eduPersonTargetedID", "eduPersonPrincipalName", + "eduPersonUniqueId", "displayName", "givenName", + "eduPersonScopedAffiliation", "mail", "sn"]) + + if __name__ == "__main__": test_filter_ava3() diff --git a/tests/test_50_server.py b/tests/test_50_server.py index dc6cbf42..ecef319e 100644 --- a/tests/test_50_server.py +++ b/tests/test_50_server.py @@ -267,7 +267,7 @@ class TestServer1(): assert resp_args["destination"] == "http://lingon.catalogix.se:8087/" assert resp_args["in_response_to"] == "id1" name_id_policy = resp_args["name_id_policy"] - assert _eq(name_id_policy.keyswv(), ["format", "allow_create"]) + assert _eq(name_id_policy.keyswv(), ["format"]) assert name_id_policy.format == saml.NAMEID_FORMAT_TRANSIENT assert resp_args[ "sp_entity_id"] == "urn:mace:example.com:saml:roland:sp" @@ -1341,7 +1341,7 @@ class TestServer1NonAsciiAva(): assert resp_args["destination"] == "http://lingon.catalogix.se:8087/" assert resp_args["in_response_to"] == "id1" name_id_policy = resp_args["name_id_policy"] - assert _eq(name_id_policy.keyswv(), ["format", "allow_create"]) + assert _eq(name_id_policy.keyswv(), ["format"]) assert name_id_policy.format == saml.NAMEID_FORMAT_TRANSIENT assert resp_args[ "sp_entity_id"] == "urn:mace:example.com:saml:roland:sp" diff --git a/tests/test_51_client.py b/tests/test_51_client.py index 2e2b7f1c..75dd8f75 100644 --- a/tests/test_51_client.py +++ b/tests/test_51_client.py @@ -269,7 +269,7 @@ class TestClient: assert ar.provider_name == "urn:mace:example.com:saml:roland:sp" assert ar.issuer.text == "urn:mace:example.com:saml:roland:sp" nid_policy = ar.name_id_policy - assert nid_policy.allow_create == "false" + assert nid_policy.allow_create is None assert nid_policy.format == saml.NAMEID_FORMAT_TRANSIENT node_requested_attributes = None @@ -1757,7 +1757,7 @@ class TestClientNonAsciiAva: assert ar.provider_name == "urn:mace:example.com:saml:roland:sp" assert ar.issuer.text == "urn:mace:example.com:saml:roland:sp" nid_policy = ar.name_id_policy - assert nid_policy.allow_create == "false" + assert nid_policy.allow_create is None assert nid_policy.format == saml.NAMEID_FORMAT_TRANSIENT node_requested_attributes = None |