summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CHANGELOG.md13
-rw-r--r--README.rst3
-rw-r--r--VERSION2
-rw-r--r--docs/install.rst3
-rw-r--r--example/idp2/idp_user.py4
-rwxr-xr-xexample/sp-wsgi/sp.py33
-rw-r--r--src/saml2/assertion.py7
-rw-r--r--src/saml2/attributemaps/saml_uri.py8
-rw-r--r--src/saml2/client_base.py4
-rw-r--r--src/saml2/ident.py23
-rw-r--r--src/saml2/mdstore.py2
-rw-r--r--src/saml2/mongo_store.py23
-rw-r--r--src/saml2/pack.py16
-rw-r--r--src/saml2/response.py21
-rw-r--r--tests/attribute_response.xml4
-rw-r--r--tests/create_certs.sh6
-rw-r--r--tests/entity_cat_rs.xml84
-rw-r--r--tests/myentitycategory.py16
-rw-r--r--tests/openssl.cnf6
-rw-r--r--tests/pki/test_3.crt22
-rw-r--r--tests/root_cert/localhost.ca.crt26
-rw-r--r--tests/test_1.crt22
-rw-r--r--tests/test_2.crt22
-rw-r--r--tests/test_37_entity_categories.py39
-rw-r--r--tests/test_50_server.py4
-rw-r--r--tests/test_51_client.py4
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)
diff --git a/README.rst b/README.rst
index 51a5e096..d67d980f 100644
--- a/README.rst
+++ b/README.rst
@@ -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
=======
diff --git a/VERSION b/VERSION
index f18045d6..f6cdf409 100644
--- a/VERSION
+++ b/VERSION
@@ -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