summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/saml2/client.py12
-rw-r--r--src/saml2/client_base.py9
-rw-r--r--src/saml2/entity.py20
-rw-r--r--src/saml2/mdstore.py66
-rw-r--r--tests/test_30_mdstore.py17
-rw-r--r--tests/test_30_mdstore_old.py17
-rw-r--r--tests/test_50_server.py24
-rw-r--r--tests/test_76_metadata_in_mdb.py4
8 files changed, 109 insertions, 60 deletions
diff --git a/src/saml2/client.py b/src/saml2/client.py
index e283420a..60b108ef 100644
--- a/src/saml2/client.py
+++ b/src/saml2/client.py
@@ -29,7 +29,7 @@ from saml2.client_base import Base
from saml2.client_base import SignOnError
from saml2.client_base import LogoutError
from saml2.client_base import NoServiceDefined
-from saml2.mdstore import destinations
+from saml2.mdstore import locations
import logging
@@ -209,7 +209,7 @@ class Saml2Client(Base):
logger.debug("No SLO '%s' service", binding)
continue
- destination = destinations(srvs)[0]
+ destination = next(locations(srvs), None)
logger.info("destination to provider: %s", destination)
try:
session_info = self.users.get_info_from(name_id,
@@ -374,7 +374,7 @@ class Saml2Client(Base):
name_qualifier=name_qualifier))
srvs = self.metadata.authz_service(entity_id, BINDING_SOAP)
- for dest in destinations(srvs):
+ for dest in locations(srvs):
resp = self._use_soap(dest, "authz_decision_query",
action=action, evidence=evidence,
resource=resource, subject=subject)
@@ -397,7 +397,7 @@ class Saml2Client(Base):
_id_refs = [AssertionIDRef(_id) for _id in assertion_ids]
- for destination in destinations(srvs):
+ for destination in locations(srvs):
res = self._use_soap(destination, "assertion_id_request",
assertion_id_refs=_id_refs, consent=consent,
extensions=extensions, sign=sign)
@@ -411,7 +411,7 @@ class Saml2Client(Base):
srvs = self.metadata.authn_request_service(entity_id, BINDING_SOAP)
- for destination in destinations(srvs):
+ for destination in locations(srvs):
resp = self._use_soap(destination, "authn_query", consent=consent,
extensions=extensions, sign=sign)
if resp:
@@ -461,7 +461,7 @@ class Saml2Client(Base):
if srvs is []:
raise SAMLError("No attribute service support at entity")
- destination = destinations(srvs)[0]
+ destination = next(locations(srvs), None)
if binding == BINDING_SOAP:
return self._use_soap(destination, "attribute_query",
diff --git a/src/saml2/client_base.py b/src/saml2/client_base.py
index 871f3f2c..51a3a574 100644
--- a/src/saml2/client_base.py
+++ b/src/saml2/client_base.py
@@ -12,7 +12,7 @@ import logging
from saml2.entity import Entity
-from saml2.mdstore import destinations
+from saml2.mdstore import locations
from saml2.profile import paos, ecp
from saml2.saml import NAMEID_FORMAT_PERSISTENT
from saml2.saml import NAMEID_FORMAT_TRANSIENT
@@ -212,7 +212,7 @@ class Base(Entity):
# verify that it's in the metadata
srvs = self.metadata.single_sign_on_service(entityid, binding)
if srvs:
- return destinations(srvs)[0]
+ return next(locations(srvs), None)
else:
logger.info("_sso_location: %s, %s", entityid, binding)
raise IdpUnspecified("No IdP to send to given the premises")
@@ -224,9 +224,8 @@ class Base(Entity):
raise IdpUnspecified("Too many IdPs to choose from: %s" % eids)
try:
- srvs = self.metadata.single_sign_on_service(list(eids.keys())[0],
- binding)
- return destinations(srvs)[0]
+ srvs = self.metadata.single_sign_on_service(list(eids.keys())[0], binding)
+ return next(locations(srvs), None)
except IndexError:
raise IdpUnspecified("No IdP to send to given the premises")
diff --git a/src/saml2/entity.py b/src/saml2/entity.py
index fad9326a..c9572aef 100644
--- a/src/saml2/entity.py
+++ b/src/saml2/entity.py
@@ -53,7 +53,7 @@ from saml2.samlp import ArtifactResponse
from saml2.samlp import Artifact
from saml2.samlp import LogoutRequest
from saml2.samlp import AttributeQuery
-from saml2.mdstore import destinations, response_destinations
+from saml2.mdstore import all_locations
from saml2 import BINDING_HTTP_POST
from saml2 import BINDING_HTTP_REDIRECT
from saml2 import BINDING_SOAP
@@ -249,8 +249,9 @@ class Entity(HTTPBase):
return info
- def pick_binding(self, service, bindings=None, descr_type="", request=None,
- entity_id="", response=False):
+ def pick_binding(
+ self, service, bindings=None, descr_type="", request=None, entity_id=""
+ ):
if request and not entity_id:
entity_id = request.issuer.text.strip()
@@ -284,10 +285,8 @@ class Entity(HTTPBase):
if srv["index"] == _index:
return binding, srv["location"]
else:
- if response:
- return binding, response_destinations(srvs)[0]
- else:
- return binding, destinations(srvs)[0]
+ destination = next(all_locations(srvs), None)
+ return binding, destination
except UnsupportedBinding:
pass
@@ -352,10 +351,9 @@ class Entity(HTTPBase):
else:
descr_type = "spsso"
- binding, destination = self.pick_binding(rsrv, bindings,
- descr_type=descr_type,
- request=message,
- response=True)
+ binding, destination = self.pick_binding(
+ rsrv, bindings, descr_type=descr_type, request=message
+ )
info["binding"] = binding
info["destination"] = destination
diff --git a/src/saml2/mdstore.py b/src/saml2/mdstore.py
index 41e521ec..3dfd0e5a 100644
--- a/src/saml2/mdstore.py
+++ b/src/saml2/mdstore.py
@@ -5,6 +5,8 @@ import json
import logging
import os
import sys
+from itertools import chain
+from warnings import warn as _warn
from hashlib import sha1
from os.path import isfile
@@ -26,7 +28,11 @@ from saml2 import BINDING_SOAP
from saml2.httpbase import HTTPBase
from saml2.extension.idpdisc import BINDING_DISCO
from saml2.extension.idpdisc import DiscoveryResponse
+from saml2.md import NAMESPACE as NS_MD
from saml2.md import EntitiesDescriptor
+from saml2.md import ArtifactResolutionService
+from saml2.md import NameIDMappingService
+from saml2.md import SingleSignOnService
from saml2.mdie import to_dict
from saml2.s_utils import UnsupportedBinding
from saml2.s_utils import UnknownSystemEntity
@@ -70,6 +76,9 @@ classnames = {
ns=NS_MDUI, tag=PrivacyStatementURL.c_tag
),
"mdui_uiinfo_logo": "{ns}&{tag}".format(ns=NS_MDUI, tag=Logo.c_tag),
+ "service_artifact_resolution": "{ns}&{tag}".format(ns=NS_MD, tag=ArtifactResolutionService.c_tag),
+ "service_single_sign_on": "{ns}&{tag}".format(ns=NS_MD, tag=SingleSignOnService.c_tag),
+ "service_nameid_mapping": "{ns}&{tag}".format(ns=NS_MD, tag=NameIDMappingService.c_tag),
}
ENTITY_CATEGORY = "http://macedir.org/entity-category"
@@ -79,8 +88,6 @@ ASSURANCE_CERTIFICATION = "urn:oasis:names:tc:SAML:attribute:assurance-certifica
SAML_METADATA_CONTENT_TYPE = "application/samlmetadata+xml"
DEFAULT_FRESHNESS_PERIOD = "P0Y0M0DT12H0M0S"
-
-
REQ2SRV = {
# IDP
"authn_request": "single_sign_on_service",
@@ -149,17 +156,54 @@ def metadata_modules():
return _res
-def response_destinations(srvs):
- _res = []
- for s in srvs:
- if "response_location" in s:
- _res.append(s["response_location"])
- else:
- _res.append(s["location"])
- return _res
+def response_locations(srvs):
+ """
+ Return the ResponseLocation attributes mapped to the services.
+
+ ArtifactResolutionService, SingleSignOnService and NameIDMappingService MUST omit
+ the ResponseLocation attribute. This is enforced here, but metadata with such
+ service declarations and such attributes should not have been part of the metadata
+ store in the first place.
+ """
+ values = (
+ s["response_location"]
+ for s in srvs
+ if "response_location" in s
+ if s["__class__"] not in [
+ classnames["service_artifact_resolution"],
+ classnames["service_single_sign_on"],
+ classnames["service_nameid_mapping"],
+ ]
+ )
+ return values
+
+
+def locations(srvs):
+ values = (
+ s["location"]
+ for s in srvs
+ if "location" in s
+ )
+ return values
+
def destinations(srvs):
- return [s["location"] for s in srvs]
+ warn_msg = (
+ "`saml2.mdstore.destinations` function is deprecated; "
+ "instead, use `saml2.mdstore.locations` or `saml2.mdstore.all_locations`."
+ )
+ logger.warning(warn_msg)
+ _warn(warn_msg)
+ values = list(locations(srvs))
+ return values
+
+
+def all_locations(srvs):
+ values = chain(
+ response_locations(srvs),
+ locations(srvs),
+ )
+ return values
def attribute_requirement(entity, index=None):
diff --git a/tests/test_30_mdstore.py b/tests/test_30_mdstore.py
index 59dc7da6..d712383f 100644
--- a/tests/test_30_mdstore.py
+++ b/tests/test_30_mdstore.py
@@ -15,7 +15,7 @@ from saml2.config import Config
from saml2.mdstore import MetadataStore, MetaDataExtern
from saml2.mdstore import MetaDataMDX
from saml2.mdstore import SAML_METADATA_CONTENT_TYPE
-from saml2.mdstore import destinations
+from saml2.mdstore import locations
from saml2.mdstore import name
from saml2 import sigver
from saml2.httpbase import HTTPBase
@@ -177,8 +177,9 @@ def test_swami_1():
assert idps.keys()
idpsso = mds.single_sign_on_service(UMU_IDP)
assert len(idpsso) == 1
- assert destinations(idpsso) == [
- 'https://idp.umu.se/saml2/idp/SSOService.php']
+ assert list(locations(idpsso)) == [
+ 'https://idp.umu.se/saml2/idp/SSOService.php'
+ ]
_name = name(mds[UMU_IDP])
assert _name == u'Umeå University (SAML2)'
@@ -219,8 +220,9 @@ def test_incommon_1():
idpsso = mds.single_sign_on_service('urn:mace:incommon:alaska.edu')
assert len(idpsso) == 1
print(idpsso)
- assert destinations(idpsso) == [
- 'https://idp.alaska.edu/idp/profile/SAML2/Redirect/SSO']
+ assert list(locations(idpsso)) == [
+ 'https://idp.alaska.edu/idp/profile/SAML2/Redirect/SSO'
+ ]
sps = mds.with_descriptor("spsso")
@@ -279,8 +281,9 @@ def test_switch_1():
'https://aai-demo-idp.switch.ch/idp/shibboleth')
assert len(idpsso) == 1
print(idpsso)
- assert destinations(idpsso) == [
- 'https://aai-demo-idp.switch.ch/idp/profile/SAML2/Redirect/SSO']
+ assert list(locations(idpsso)) == [
+ 'https://aai-demo-idp.switch.ch/idp/profile/SAML2/Redirect/SSO'
+ ]
assert len(idps) > 30
aas = mds.with_descriptor("attribute_authority")
print(aas.keys())
diff --git a/tests/test_30_mdstore_old.py b/tests/test_30_mdstore_old.py
index 7ceb6653..d9f400ed 100644
--- a/tests/test_30_mdstore_old.py
+++ b/tests/test_30_mdstore_old.py
@@ -6,7 +6,7 @@ import os
from unittest.mock import patch
from saml2.mdstore import MetadataStore, MetaDataMDX
-from saml2.mdstore import destinations
+from saml2.mdstore import locations
from saml2.mdstore import name
from saml2 import md
@@ -145,8 +145,9 @@ def test_swami_1():
assert idps.keys()
idpsso = mds.single_sign_on_service(UMU_IDP)
assert len(idpsso) == 1
- assert destinations(idpsso) == [
- 'https://idp.umu.se/saml2/idp/SSOService.php']
+ assert list(locations(idpsso)) == [
+ 'https://idp.umu.se/saml2/idp/SSOService.php'
+ ]
_name = name(mds[UMU_IDP])
assert _name == u'Umeå University (SAML2)'
@@ -187,8 +188,9 @@ def test_incommon_1():
idpsso = mds.single_sign_on_service('urn:mace:incommon:alaska.edu')
assert len(idpsso) == 1
print(idpsso)
- assert destinations(idpsso) == [
- 'https://idp.alaska.edu/idp/profile/SAML2/Redirect/SSO']
+ assert list(locations(idpsso)) == [
+ 'https://idp.alaska.edu/idp/profile/SAML2/Redirect/SSO'
+ ]
sps = mds.with_descriptor("spsso")
@@ -247,8 +249,9 @@ def test_switch_1():
'https://aai-demo-idp.switch.ch/idp/shibboleth')
assert len(idpsso) == 1
print(idpsso)
- assert destinations(idpsso) == [
- 'https://aai-demo-idp.switch.ch/idp/profile/SAML2/Redirect/SSO']
+ assert list(locations(idpsso)) == [
+ 'https://aai-demo-idp.switch.ch/idp/profile/SAML2/Redirect/SSO'
+ ]
assert len(idps) > 30
aas = mds.with_descriptor("attribute_authority")
print(aas.keys())
diff --git a/tests/test_50_server.py b/tests/test_50_server.py
index 93fbc6c6..2aee6d8a 100644
--- a/tests/test_50_server.py
+++ b/tests/test_50_server.py
@@ -2297,19 +2297,19 @@ def _logout_request(conf_file):
class TestServerLogout():
-
def test_1(self):
with closing(Server("idp_slo_redirect_conf")) as server:
req_id, request = _logout_request("sp_slo_redirect_conf")
print(request)
bindings = [BINDING_HTTP_REDIRECT]
response = server.create_logout_response(request, bindings)
- binding, destination = server.pick_binding("single_logout_service",
- bindings, "spsso",
- request, response=True)
- http_args = server.apply_binding(binding, "%s" % response, destination,
- "relay_state", response=True)
+ binding, destination = server.pick_binding(
+ "single_logout_service", bindings, "spsso", request
+ )
+ http_args = server.apply_binding(
+ binding, "%s" % response, destination, "relay_state", response=True
+ )
assert len(http_args) == 4
assert http_args["headers"][0][0] == "Location"
@@ -2322,18 +2322,20 @@ class TestServerLogout():
print(request)
bindings = [BINDING_HTTP_POST]
response = server.create_logout_response(request, bindings)
- binding, destination = server.pick_binding("single_logout_service",
- bindings, "spsso",
- request, response=True)
- http_args = server.apply_binding(binding, "%s" % response, destination,
- "relay_state", response=True)
+ binding, destination = server.pick_binding(
+ "single_logout_service", bindings, "spsso", request
+ )
+ http_args = server.apply_binding(
+ binding, "%s" % response, destination, "relay_state", response=True
+ )
assert len(http_args) == 4
assert len(http_args["data"]) > 0
assert http_args["method"] == "POST"
assert http_args['url'] == 'http://lingon.catalogix.se:8087/slo'
+
if __name__ == "__main__":
ts = TestServer1()
ts.setup_class()
diff --git a/tests/test_76_metadata_in_mdb.py b/tests/test_76_metadata_in_mdb.py
index f1376b17..60c1ff1c 100644
--- a/tests/test_76_metadata_in_mdb.py
+++ b/tests/test_76_metadata_in_mdb.py
@@ -5,7 +5,7 @@ from saml2.attribute_converter import ac_factory
from saml2.mongo_store import export_mdstore_to_mongo_db
from saml2.mongo_store import MetadataMDB
from saml2.mdstore import MetadataStore
-from saml2.mdstore import destinations
+from saml2.mdstore import locations
from saml2.mdstore import name
from saml2 import config
from pathutils import full_path
@@ -46,7 +46,7 @@ def test_metadata():
assert idps.keys()
idpsso = mds.single_sign_on_service(umu_idp)
assert len(idpsso) == 1
- assert destinations(idpsso) == [
+ assert list(locations(idpsso)) == [
'https://idp.umu.se/saml2/idp/SSOService.php']
_name = name(mds[umu_idp])