Skip to content

Commit 0771b41

Browse files
author
Rebecka Gulliksson
committed
Handle request without nameid_policy in SAML frontend.
Don't assume the request will contain a nameid policy, but default to requesting a transient identifier from the backing provider.
1 parent 4628c99 commit 0771b41

File tree

3 files changed

+69
-33
lines changed

3 files changed

+69
-33
lines changed

src/satosa/frontends/saml2.py

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
from saml2.server import Server
2020

2121
from satosa.frontends.base import FrontendModule
22-
from satosa.internal_data import InternalRequest, DataConverter
22+
from satosa.internal_data import InternalRequest, DataConverter, UserIdHashType
2323
from satosa.logging_util import satosa_logging
2424
from satosa.util import response, get_saml_name_id_format, saml_name_format_to_hash_type
2525

@@ -102,7 +102,8 @@ def save_state(self, context, resp_args, relay_state):
102102
:param relay_state: Request relay state
103103
:return: A state as a dict
104104
"""
105-
resp_args["name_id_policy"] = resp_args["name_id_policy"].to_string().decode("utf-8")
105+
if "name_id_policy" in resp_args and resp_args["name_id_policy"] is not None:
106+
resp_args["name_id_policy"] = resp_args["name_id_policy"].to_string().decode("utf-8")
106107
return {"resp_args": resp_args,
107108
"relay_state": relay_state}
108109

@@ -262,12 +263,17 @@ def _handle_authn_request(self, context, binding_in, idp):
262263
except IndexError:
263264
pass
264265

265-
internal_req = InternalRequest(
266-
saml_name_format_to_hash_type(extracted_request['req_args']
267-
['name_id_policy'].format),
268-
extracted_request["resp_args"]["sp_entity_id"],
269-
requester_name
270-
)
266+
name_format = None
267+
if 'name_id_policy' in extracted_request['req_args']:
268+
name_format = saml_name_format_to_hash_type(
269+
extracted_request['req_args']['name_id_policy'].format)
270+
if name_format is None:
271+
# default to requesting transient name id
272+
name_format = UserIdHashType.transient
273+
274+
internal_req = InternalRequest(name_format,
275+
extracted_request["resp_args"]["sp_entity_id"],
276+
requester_name)
271277

272278
# Get attribute filter
273279
idp_policy = idp.config.getattr("policy", "idp")

tests/satosa/frontends/test_saml2.py

Lines changed: 50 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,35 @@ class TestSamlFrontend:
4040
def construct_base_url_from_entity_id(self, entity_id):
4141
return "{parsed.scheme}://{parsed.netloc}".format(parsed=urlparse(entity_id))
4242

43+
def setup_for_authn_req(self, idp_conf, sp_conf, nameid_format):
44+
base = self.construct_base_url_from_entity_id(idp_conf["entityid"])
45+
config = {"idp_config": idp_conf, "endpoints": ENDPOINTS, "base": base,
46+
"state_id": "state_id"}
47+
sp_metadata_str = create_metadata_from_config_dict(sp_conf)
48+
idp_conf["metadata"]["inline"] = [sp_metadata_str]
49+
50+
samlfrontend = SamlFrontend(lambda context, internal_req: (context, internal_req),
51+
INTERNAL_ATTRIBUTES, config)
52+
samlfrontend.register_endpoints(["saml"])
53+
54+
idp_metadata_str = create_metadata_from_config_dict(samlfrontend.config)
55+
sp_conf["metadata"]["inline"].append(idp_metadata_str)
56+
57+
fakesp = FakeSP(None, config=SPConfig().load(sp_conf, metadata_construction=False))
58+
context = Context()
59+
context.state = State()
60+
context.request = parse.parse_qs(
61+
urlparse(fakesp.make_auth_req(samlfrontend.config["entityid"], nameid_format)).query)
62+
tmp_dict = {}
63+
for val in context.request:
64+
if isinstance(context.request[val], list):
65+
tmp_dict[val] = context.request[val][0]
66+
else:
67+
tmp_dict[val] = context.request[val]
68+
context.request = tmp_dict
69+
70+
return context, samlfrontend
71+
4372
@pytest.mark.parametrize("conf", [
4473
None,
4574
{"idp_config_notok": {}, "endpoints": {}, "base": "base",
@@ -76,31 +105,29 @@ def test_handle_authn_request(self, idp_conf, sp_conf):
76105
"""
77106
Performs a complete test for the module. The flow should be accepted.
78107
"""
79-
base = self.construct_base_url_from_entity_id(idp_conf["entityid"])
80-
config = {"idp_config": idp_conf, "endpoints": ENDPOINTS, "base": base,
81-
"state_id": "state_id"}
82-
sp_metadata_str = create_metadata_from_config_dict(sp_conf)
83-
idp_conf["metadata"]["inline"] = [sp_metadata_str]
108+
context, samlfrontend = self.setup_for_authn_req(idp_conf, sp_conf, None)
109+
_, internal_req = samlfrontend.handle_authn_request(context, BINDING_HTTP_REDIRECT)
110+
assert internal_req.requestor == sp_conf["entityid"]
84111

85-
samlfrontend = SamlFrontend(lambda context, internal_req: (context, internal_req),
86-
INTERNAL_ATTRIBUTES, config)
87-
samlfrontend.register_endpoints(["saml"])
112+
auth_info = AuthenticationInformation(PASSWORD, "2015-09-30T12:21:37Z", "unittest_idp.xml")
113+
internal_response = InternalResponse(auth_info=auth_info)
114+
internal_response.set_user_id_hash_type(internal_req.user_id_hash_type)
115+
internal_response.add_attributes(USERS["testuser1"])
116+
117+
resp = samlfrontend.handle_authn_response(context, internal_response)
118+
resp_dict = parse_qs(urlparse(resp.message).query)
88119

89-
idp_metadata_str = create_metadata_from_config_dict(samlfrontend.config)
90-
sp_conf["metadata"]["inline"].append(idp_metadata_str)
91120
fakesp = FakeSP(None, config=SPConfig().load(sp_conf, metadata_construction=False))
92-
context = Context()
93-
context.state = State()
94-
context.request = parse.parse_qs(
95-
urlparse(fakesp.make_auth_req(samlfrontend.config["entityid"])).query)
96-
tmp_dict = {}
97-
for val in context.request:
98-
if isinstance(context.request[val], list):
99-
tmp_dict[val] = context.request[val][0]
100-
else:
101-
tmp_dict[val] = context.request[val]
102-
context.request = tmp_dict
121+
resp = fakesp.parse_authn_request_response(resp_dict['SAMLResponse'][0],
122+
BINDING_HTTP_REDIRECT)
123+
for key in resp.ava:
124+
assert USERS["testuser1"][key] == resp.ava[key]
103125

126+
def test_handle_authn_request_without_name_id_policy(self, idp_conf, sp_conf):
127+
"""
128+
Performs a complete test for the module. The flow should be accepted.
129+
"""
130+
context, samlfrontend = self.setup_for_authn_req(idp_conf, sp_conf, "")
104131
_, internal_req = samlfrontend.handle_authn_request(context, BINDING_HTTP_REDIRECT)
105132
assert internal_req.requestor == sp_conf["entityid"]
106133

@@ -111,6 +138,8 @@ def test_handle_authn_request(self, idp_conf, sp_conf):
111138

112139
resp = samlfrontend.handle_authn_response(context, internal_response)
113140
resp_dict = parse_qs(urlparse(resp.message).query)
141+
142+
fakesp = FakeSP(None, config=SPConfig().load(sp_conf, metadata_construction=False))
114143
resp = fakesp.parse_authn_request_response(resp_dict['SAMLResponse'][0],
115144
BINDING_HTTP_REDIRECT)
116145
for key in resp.ava:

tests/util.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def __init__(self, config_module, config=None):
3737
config = config_factory('sp', config_module)
3838
Saml2Client.__init__(self, config)
3939

40-
def make_auth_req(self, entity_id):
40+
def make_auth_req(self, entity_id, nameid_format=None):
4141
"""
4242
:type entity_id: str
4343
:rtype: str
@@ -62,7 +62,7 @@ def make_auth_req(self, entity_id):
6262
break
6363

6464
req_id, req = self.create_authn_request(destination,
65-
binding=return_binding)
65+
binding=return_binding, nameid_format=nameid_format)
6666
ht_args = self.apply_binding(_binding, '%s' % req, destination,
6767
relay_state='hello')
6868

@@ -86,7 +86,8 @@ def __init__(self, user_db, config):
8686
server.Server.__init__(self, config=config)
8787
self.user_db = user_db
8888

89-
def handle_auth_req(self, saml_request, relay_state, binding, userid, response_binding=BINDING_HTTP_POST):
89+
def handle_auth_req(self, saml_request, relay_state, binding, userid,
90+
response_binding=BINDING_HTTP_POST):
9091
"""
9192
Handles a SAML request, validates and creates a SAML response.
9293
:type saml_request: str
@@ -124,7 +125,7 @@ def handle_auth_req(self, saml_request, relay_state, binding, userid, response_b
124125
resp = {'SAMLResponse': saml_response, 'RelayState': relay_state}
125126
elif response_binding == BINDING_HTTP_REDIRECT:
126127
http_args = self.apply_binding(response_binding, '%s' % _resp,
127-
destination, relay_state, response=True)
128+
destination, relay_state, response=True)
128129
resp = dict(parse_qsl(urlparse(dict(http_args["headers"])["Location"]).query))
129130

130131
return destination, resp

0 commit comments

Comments
 (0)