Skip to content

Commit f32f4f5

Browse files
author
Rebecka Gulliksson
committed
Support 'publish_metadata' in saml backend.
Has the same behavior as the same parameter in the saml frontend.
1 parent be6934b commit f32f4f5

File tree

5 files changed

+65
-39
lines changed

5 files changed

+65
-39
lines changed

example/plugins/frontends/saml2_frontend.yaml.example

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ config:
3232

3333
state_id: <name>
3434
base: <base_url>
35+
publish_metadata: <base_url>/<name>/metadata
3536
endpoints:
3637
single_sign_on_service: {'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST': sso/post,
3738
'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect': sso/redirect}

src/satosa/backends/saml2.py

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -24,29 +24,12 @@
2424
from satosa.logging_util import satosa_logging
2525
from satosa.metadata_creation.description import MetadataDescription, OrganizationDesc, \
2626
ContactPersonDesc, UIInfoDesc
27-
from satosa.response import SeeOther, Response
27+
from satosa.response import SeeOther, Response, MetadataResponse
2828
from satosa.util import rndstr, get_saml_name_id_format
2929

3030
LOGGER = logging.getLogger(__name__)
3131

3232

33-
class MetadataResponse(Response):
34-
"""
35-
A response containing metadata for the saml backend
36-
"""
37-
38-
def __init__(self, config):
39-
"""
40-
Creates a response containing the metadata generated from the SP config.
41-
:type config: dict[str, Any]
42-
:param config: The SP config
43-
"""
44-
metadata_string = create_metadata_string(None, config, 4, None, None, None, None,
45-
None).decode("utf-8")
46-
resp = {"content": "text/xml"}
47-
super(MetadataResponse, self).__init__(message=metadata_string, **resp)
48-
49-
5033
class SamlBackend(BackendModule):
5134
"""
5235
A saml2 backend module

src/satosa/frontends/saml2.py

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from satosa.frontends.base import FrontendModule
2222
from satosa.internal_data import InternalRequest, DataConverter, UserIdHashType
2323
from satosa.logging_util import satosa_logging
24+
from satosa.response import MetadataResponse
2425
from satosa.util import response, get_saml_name_id_format, saml_name_format_to_hash_type
2526

2627
LOGGER = logging.getLogger(__name__)
@@ -35,7 +36,8 @@ def __init__(self, auth_req_callback_func, internal_attributes, conf):
3536
self._validate_config(conf)
3637

3738
super(SamlFrontend, self).__init__(auth_req_callback_func, internal_attributes)
38-
self.config = conf["idp_config"]
39+
self.config = conf
40+
self.idp_config = conf["idp_config"]
3941
self.endpoints = conf["endpoints"]
4042
self.base = conf["base"]
4143
self.state_id = conf["state_id"]
@@ -82,9 +84,9 @@ def register_endpoints(self, providers):
8284
:rtype: list[(str, ((satosa.context.Context, Any) -> satosa.response.Response, Any))]
8385
"""
8486
self._validate_providers(providers)
85-
self.config = self._build_idp_config_endpoints(self.config, providers)
87+
self.idp_config = self._build_idp_config_endpoints(self.idp_config, providers)
8688
# Create the idp
87-
idp_config = IdPConfig().load(copy.deepcopy(self.config), metadata_construction=False)
89+
idp_config = IdPConfig().load(copy.deepcopy(self.idp_config), metadata_construction=False)
8890
self.idp = Server(config=idp_config)
8991
return self._register_endpoints(providers)
9092

@@ -446,6 +448,18 @@ def _validate_providers(self, providers):
446448
LOGGER.error(msg)
447449
raise TypeError(msg)
448450

451+
def _metadata(self, context):
452+
"""
453+
Endpoint for retrieving the backend metadata
454+
:type context: satosa.context.Context
455+
:rtype: satosa.backends.saml2.MetadataResponse
456+
457+
:param context: The current context
458+
:return: response with metadata
459+
"""
460+
satosa_logging(LOGGER, logging.DEBUG, "Sending metadata response", context.state)
461+
return MetadataResponse(self.idp.config)
462+
449463
def _register_endpoints(self, providers):
450464
"""
451465
Register methods to endpoints
@@ -468,6 +482,10 @@ def _register_endpoints(self, providers):
468482
url_map.append(("(%s)/%s/(.*)$" % (valid_providers, parsed_endp.path),
469483
(self.handle_authn_request, binding)))
470484

485+
if "publish_metadata" in self.config:
486+
metadata_path = urlparse(self.config["publish_metadata"])
487+
url_map.append(("^%s$" % metadata_path.path[1:], self._metadata))
488+
471489
return url_map
472490

473491
def _build_idp_config_endpoints(self, config, providers):
@@ -573,7 +591,7 @@ def _load_idp_dynamic_endpoints(self, context):
573591
"""
574592
target_entity_id = self._get_target_entity_id(context)
575593
context.internal_data["mirror.target_entity_id"] = target_entity_id
576-
idp_conf_file = self._load_endpoints_to_config(self.config, self.endpoints, self.base,
594+
idp_conf_file = self._load_endpoints_to_config(self.idp_config, self.endpoints, self.base,
577595
context.target_backend, target_entity_id)
578596
idp_config = IdPConfig().load(idp_conf_file, metadata_construction=False)
579597
return Server(config=idp_config)
@@ -632,7 +650,7 @@ def handle_backend_error(self, exception):
632650
:type exception: satosa.exception.SATOSAAuthenticationError
633651
:rtype: satosa.response.Response
634652
"""
635-
idp = self._load_idp_dynamic_entity_id(self.config, exception.state)
653+
idp = self._load_idp_dynamic_entity_id(self.idp_config, exception.state)
636654
return self._handle_backend_error(exception, idp)
637655

638656
def handle_authn_response(self, context, internal_response):
@@ -642,7 +660,7 @@ def handle_authn_response(self, context, internal_response):
642660
:param internal_response:
643661
:return:
644662
"""
645-
idp = self._load_idp_dynamic_entity_id(self.config, context.state)
663+
idp = self._load_idp_dynamic_entity_id(self.idp_config, context.state)
646664
return self._handle_authn_response(context, internal_response, idp)
647665

648666
def register_endpoints(self, providers):

src/satosa/response.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from urllib.parse import quote
55

66
import six
7+
from saml2.metadata import create_metadata_string
78

89
__author__ = 'mathiashedstrom'
910

@@ -151,6 +152,23 @@ def __call__(self, environ, start_response):
151152
return self.to_list()
152153

153154

155+
class MetadataResponse(Response):
156+
"""
157+
A response containing metadata for the saml backend
158+
"""
159+
160+
def __init__(self, config):
161+
"""
162+
Creates a response containing the metadata generated from the SP config.
163+
:type config: dict[str, Any]
164+
:param config: The SP config
165+
"""
166+
metadata_string = create_metadata_string(None, config, 4, None, None, None, None,
167+
None).decode("utf-8")
168+
resp = {"content": "text/xml"}
169+
super(MetadataResponse, self).__init__(message=metadata_string, **resp)
170+
171+
154172
def geturl(environ, query=True, path=True, use_server_name=False):
155173
"""Rebuilds a request URL (from PEP 333).
156174
You may want to chose to use the environment variables

tests/satosa/frontends/test_saml2.py

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -51,14 +51,14 @@ def setup_for_authn_req(self, idp_conf, sp_conf, nameid_format):
5151
INTERNAL_ATTRIBUTES, config)
5252
samlfrontend.register_endpoints(["saml"])
5353

54-
idp_metadata_str = create_metadata_from_config_dict(samlfrontend.config)
54+
idp_metadata_str = create_metadata_from_config_dict(samlfrontend.idp_config)
5555
sp_conf["metadata"]["inline"].append(idp_metadata_str)
5656

5757
fakesp = FakeSP(None, config=SPConfig().load(sp_conf, metadata_construction=False))
5858
context = Context()
5959
context.state = State()
6060
context.request = parse.parse_qs(
61-
urlparse(fakesp.make_auth_req(samlfrontend.config["entityid"], nameid_format)).query)
61+
urlparse(fakesp.make_auth_req(samlfrontend.idp_config["entityid"], nameid_format)).query)
6262
tmp_dict = {}
6363
for val in context.request:
6464
if isinstance(context.request[val], list):
@@ -83,23 +83,29 @@ def test_config_error_handling(self, conf):
8383
SamlFrontend(lambda ctx, req: None, INTERNAL_ATTRIBUTES, conf)
8484

8585
def test_register_endpoints(self, idp_conf):
86+
"""
87+
Tests the method register_endpoints
88+
"""
89+
def get_path_from_url(url):
90+
return urlparse(url).path.lstrip("/")
91+
92+
metadata_url = "http://example.com/SAML2IDP/metadata"
8693
config = {"idp_config": idp_conf, "endpoints": ENDPOINTS,
8794
"base": self.construct_base_url_from_entity_id(idp_conf["entityid"]),
88-
"state_id": "state_id"}
95+
"state_id": "state_id",
96+
"publish_metadata": metadata_url}
97+
8998
samlfrontend = SamlFrontend(lambda context, internal_req: (context, internal_req),
90-
INTERNAL_ATTRIBUTES, config)
99+
INTERNAL_ATTRIBUTES, config)
91100

92101
providers = ["foo", "bar"]
93102
url_map = samlfrontend.register_endpoints(providers)
94-
for k, v in idp_conf["service"]["idp"]["endpoints"].items():
95-
for endp in v:
96-
match = False
97-
for regex in url_map:
98-
p = re.compile(regex[0])
99-
if p.match(urlparse(endp[0]).path.lstrip("/")):
100-
match = True
101-
break
102-
assert match, "Not correct regular expression for endpoint: %s" % endp[0]
103+
all_idp_endpoints = [get_path_from_url(v[0][0]) for v in idp_conf["service"]["idp"]["endpoints"].values()]
104+
compiled_regex = [re.compile(regex) for regex, _ in url_map]
105+
for endp in all_idp_endpoints:
106+
assert any(p.match(endp) for p in compiled_regex)
107+
108+
assert any(p.match(get_path_from_url(metadata_url)) for p in compiled_regex)
103109

104110
def test_handle_authn_request(self, idp_conf, sp_conf):
105111
"""
@@ -200,7 +206,7 @@ def test_acr_mapping_in_authn_response(self, idp_conf, sp_conf):
200206
samlfrontend = SamlFrontend(None, INTERNAL_ATTRIBUTES, conf)
201207
samlfrontend.register_endpoints(["foo"])
202208

203-
idp_metadata_str = create_metadata_from_config_dict(samlfrontend.config)
209+
idp_metadata_str = create_metadata_from_config_dict(samlfrontend.idp_config)
204210
sp_conf["metadata"]["inline"].append(idp_metadata_str)
205211
fakesp = FakeSP(None, config=SPConfig().load(sp_conf, metadata_construction=False))
206212

@@ -241,7 +247,7 @@ def test_acr_mapping_per_idp_in_authn_response(self, idp_conf, sp_conf):
241247
samlfrontend = SamlFrontend(None, INTERNAL_ATTRIBUTES, conf)
242248
samlfrontend.register_endpoints(["foo"])
243249

244-
idp_metadata_str = create_metadata_from_config_dict(samlfrontend.config)
250+
idp_metadata_str = create_metadata_from_config_dict(samlfrontend.idp_config)
245251
sp_conf["metadata"]["inline"].append(idp_metadata_str)
246252
fakesp = FakeSP(None, config=SPConfig().load(sp_conf, metadata_construction=False))
247253

0 commit comments

Comments
 (0)