Skip to content

Commit 62f8775

Browse files
bajnokkc00kiemon5ter
authored andcommitted
Test for missing state and missing relay state
Signed-off-by: Ivan Kanakarakis <[email protected]>
1 parent d986464 commit 62f8775

File tree

2 files changed

+61
-31
lines changed

2 files changed

+61
-31
lines changed

src/satosa/backends/saml2.py

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -418,7 +418,8 @@ def authn_response(self, context, binding):
418418
logger.info(logline)
419419
raise SATOSAMissingStateError(msg)
420420

421-
if not context.request.get("SAMLResponse"):
421+
samlresponse = context.request.get("SAMLResponse")
422+
if not samlresponse:
422423
msg = {
423424
"message": "Authentication failed",
424425
"error": "SAML Response not found in context.request",
@@ -429,9 +430,7 @@ def authn_response(self, context, binding):
429430

430431
try:
431432
authn_response = self.sp.parse_authn_request_response(
432-
context.request["SAMLResponse"],
433-
binding,
434-
outstanding=self.outstanding_queries,
433+
samlresponse, binding, outstanding=self.outstanding_queries
435434
)
436435
except Exception as e:
437436
msg = {
@@ -456,7 +455,7 @@ def authn_response(self, context, binding):
456455
del self.outstanding_queries[req_id]
457456

458457
# check if the relay_state matches the cookie state
459-
if context.state[self.name]["relay_state"] != context.request["RelayState"]:
458+
if context.state[self.name].get("relay_state") != context.request["RelayState"]:
460459
msg = {
461460
"message": "Authentication failed",
462461
"error": "Response state query param did not match relay state for request",

tests/satosa/backends/test_saml2.py

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
from satosa.backends.saml2 import SAMLBackend
2323
from satosa.context import Context
24+
from satosa.exception import SATOSAAuthenticationError
25+
from satosa.exception import SATOSAMissingStateError
2426
from satosa.internal import InternalData
2527
from tests.users import USERS
2628
from tests.util import FakeIdP, create_metadata_from_config_dict, FakeSP
@@ -132,7 +134,7 @@ def test_full_flow(self, context, idp_conf, sp_conf):
132134
disco_resp = parse_qs(urlparse(resp.message).query)
133135
info = parse_qs(urlparse(disco_resp["return"][0]).query)
134136
info["entityID"] = idp_conf["entityid"]
135-
request_context = context
137+
request_context = Context()
136138
request_context.request = info
137139
request_context.state = context.state
138140

@@ -241,43 +243,72 @@ def test_unknown_or_no_hostname_selects_first_acs(
241243

242244
def test_authn_response(self, context, idp_conf, sp_conf):
243245
response_binding = BINDING_HTTP_REDIRECT
244-
fakesp = FakeSP(SPConfig().load(sp_conf))
245-
fakeidp = FakeIdP(USERS, config=IdPConfig().load(idp_conf))
246-
destination, request_params = fakesp.make_auth_req(idp_conf["entityid"])
247-
url, auth_resp = fakeidp.handle_auth_req(request_params["SAMLRequest"], request_params["RelayState"],
248-
BINDING_HTTP_REDIRECT,
249-
"testuser1", response_binding=response_binding)
250-
246+
request_params, auth_resp = self._perform_request_response(
247+
idp_conf, sp_conf, response_binding
248+
)
251249
context.request = auth_resp
252250
context.state[self.samlbackend.name] = {"relay_state": request_params["RelayState"]}
253251
self.samlbackend.authn_response(context, response_binding)
254252

255253
context, internal_resp = self.samlbackend.auth_callback_func.call_args[0]
256254
assert_authn_response(internal_resp)
257255

258-
@pytest.mark.skipif(
259-
saml2.__version__ < '4.6.1',
260-
reason="Optional NameID needs pysaml2 v4.6.1 or higher")
261-
def test_authn_response_no_name_id(self, context, idp_conf, sp_conf):
256+
def _perform_request_response(
257+
self, idp_conf, sp_conf, response_binding, receive_nameid=True
258+
):
259+
fakesp = FakeSP(SPConfig().load(sp_conf))
260+
fakeidp = FakeIdP(USERS, config=IdPConfig().load(idp_conf))
261+
destination, request_params = fakesp.make_auth_req(idp_conf["entityid"])
262+
auth_resp_func = (
263+
fakeidp.handle_auth_req
264+
if receive_nameid
265+
else fakeidp.handle_auth_req_no_name_id
266+
)
267+
url, auth_resp = auth_resp_func(
268+
request_params["SAMLRequest"],
269+
request_params["RelayState"],
270+
BINDING_HTTP_REDIRECT,
271+
"testuser1",
272+
response_binding=response_binding,
273+
)
274+
275+
return request_params, auth_resp
276+
277+
def test_no_state_raises_error(self, context, idp_conf, sp_conf):
262278
response_binding = BINDING_HTTP_REDIRECT
279+
request_params, auth_resp = self._perform_request_response(
280+
idp_conf, sp_conf, response_binding
281+
)
282+
context.request = auth_resp
283+
# not setting context.state[self.samlbackend.name]
284+
# to simulate a request with lost state
263285

264-
fakesp_conf = SPConfig().load(sp_conf)
265-
fakesp = FakeSP(fakesp_conf)
286+
with pytest.raises(SATOSAMissingStateError):
287+
self.samlbackend.authn_response(context, response_binding)
266288

267-
fakeidp_conf = IdPConfig().load(idp_conf)
268-
fakeidp = FakeIdP(USERS, config=fakeidp_conf)
289+
def test_no_relay_state_raises_error(self, context, idp_conf, sp_conf):
290+
response_binding = BINDING_HTTP_REDIRECT
291+
request_params, auth_resp = self._perform_request_response(
292+
idp_conf, sp_conf, response_binding
293+
)
294+
context.request = auth_resp
295+
# not setting context.state[self.samlbackend.name]["relay_state"]
296+
# to simulate a request without a relay state
297+
context.state[self.samlbackend.name] = {}
269298

270-
destination, request_params = fakesp.make_auth_req(
271-
idp_conf["entityid"])
299+
with pytest.raises(SATOSAAuthenticationError):
300+
self.samlbackend.authn_response(context, response_binding)
272301

273-
# Use the fake IdP to mock up an authentication request that has no
274-
# <NameID> element.
275-
url, auth_resp = fakeidp.handle_auth_req_no_name_id(
276-
request_params["SAMLRequest"],
277-
request_params["RelayState"],
278-
BINDING_HTTP_REDIRECT,
279-
"testuser1",
280-
response_binding=response_binding)
302+
@pytest.mark.skipif(
303+
saml2.__version__ < '4.6.1',
304+
reason="Optional NameID needs pysaml2 v4.6.1 or higher"
305+
)
306+
def test_authn_response_no_name_id(self, context, idp_conf, sp_conf):
307+
response_binding = BINDING_HTTP_REDIRECT
308+
309+
request_params, auth_resp = self._perform_request_response(
310+
idp_conf, sp_conf, response_binding, receive_nameid=False
311+
)
281312

282313
backend = self.samlbackend
283314

0 commit comments

Comments
 (0)