Skip to content

Commit 239bc38

Browse files
Merge pull request #541 from skoranda/no_saml_subject_name_id
Do not require a SAML authentication response to contain a NameID element - it is not required by the SAML 2.0 specification. Invoke add_information_about_person only when resp.assertion.subject.name_id is available.
2 parents a17f233 + c0828c8 commit 239bc38

File tree

2 files changed

+98
-42
lines changed

2 files changed

+98
-42
lines changed

src/saml2/client_base.py

Lines changed: 41 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -676,50 +676,49 @@ def parse_authn_request_response(self, xmlstr, binding, outstanding=None,
676676
:return: An response.AuthnResponse or None
677677
"""
678678

679-
try:
680-
_ = self.config.entityid
681-
except KeyError:
679+
if not getattr(self.config, 'entityid', None):
682680
raise SAMLError("Missing entity_id specification")
683681

684-
resp = None
685-
if xmlstr:
686-
kwargs = {
687-
"outstanding_queries": outstanding,
688-
"outstanding_certs": outstanding_certs,
689-
"allow_unsolicited": self.allow_unsolicited,
690-
"want_assertions_signed": self.want_assertions_signed,
691-
"want_response_signed": self.want_response_signed,
692-
"return_addrs": self.service_urls(binding=binding),
693-
"entity_id": self.config.entityid,
694-
"attribute_converters": self.config.attribute_converters,
695-
"allow_unknown_attributes":
696-
self.config.allow_unknown_attributes,
697-
'conv_info': conv_info
698-
}
699-
try:
700-
resp = self._parse_response(xmlstr, AuthnResponse,
701-
"assertion_consumer_service",
702-
binding, **kwargs)
703-
except StatusError as err:
704-
logger.error("SAML status error: %s", err)
705-
raise
706-
except UnravelError:
707-
return None
708-
except Exception as err:
709-
logger.error("XML parse error: %s", err)
710-
raise
711-
712-
if resp is None:
713-
return None
714-
elif isinstance(resp, AuthnResponse):
715-
if resp.assertion is not None and len(
716-
resp.response.encrypted_assertion) == 0:
717-
self.users.add_information_about_person(resp.session_info())
718-
logger.info("--- ADDED person info ----")
719-
pass
720-
else:
721-
logger.error("Response type not supported: %s",
722-
saml2.class_name(resp))
682+
if not xmlstr:
683+
return None
684+
685+
kwargs = {
686+
"outstanding_queries": outstanding,
687+
"outstanding_certs": outstanding_certs,
688+
"allow_unsolicited": self.allow_unsolicited,
689+
"want_assertions_signed": self.want_assertions_signed,
690+
"want_response_signed": self.want_response_signed,
691+
"return_addrs": self.service_urls(binding=binding),
692+
"entity_id": self.config.entityid,
693+
"attribute_converters": self.config.attribute_converters,
694+
"allow_unknown_attributes":
695+
self.config.allow_unknown_attributes,
696+
'conv_info': conv_info
697+
}
698+
699+
try:
700+
resp = self._parse_response(xmlstr, AuthnResponse,
701+
"assertion_consumer_service",
702+
binding, **kwargs)
703+
except StatusError as err:
704+
logger.error("SAML status error: %s", err)
705+
raise
706+
except UnravelError:
707+
return None
708+
except Exception as err:
709+
logger.error("XML parse error: %s", err)
710+
raise
711+
712+
if not isinstance(resp, AuthnResponse):
713+
logger.error("Response type not supported: %s",
714+
saml2.class_name(resp))
715+
return None
716+
717+
if (resp.assertion and len(resp.response.encrypted_assertion) == 0 and
718+
resp.assertion.subject.name_id):
719+
self.users.add_information_about_person(resp.session_info())
720+
logger.info("--- ADDED person info ----")
721+
723722
return resp
724723

725724
# ------------------------------------------------------------------------

tests/test_51_client.py

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -736,6 +736,63 @@ def test_response_8(self):
736736

737737
self.verify_authn_response(idp, authn_response, _client, ava_verify)
738738

739+
def test_response_no_name_id(self):
740+
""" Test that the SP client can parse an authentication response
741+
from an IdP that does not contain a <NameID> element."""
742+
743+
conf = config.SPConfig()
744+
conf.load_file("server_conf")
745+
client = Saml2Client(conf)
746+
747+
# Use the same approach as the other tests for mocking up
748+
# an authentication response to parse.
749+
idp, ava, ava_verify, nameid_policy = (
750+
self.setup_verify_authn_response()
751+
)
752+
753+
# Mock up an authentication response but do not encrypt it
754+
# nor sign it since below we will modify it directly. Note that
755+
# setting name_id to None still results in a response that includes
756+
# a <NameID> element.
757+
resp = self.server.create_authn_response(
758+
identity=ava,
759+
in_response_to="id1",
760+
destination="http://lingon.catalogix.se:8087/",
761+
sp_entity_id="urn:mace:example.com:saml:roland:sp",
762+
name_id=None,
763+
764+
authn=AUTHN,
765+
sign_response=False,
766+
sign_assertion=False,
767+
encrypt_assertion=False,
768+
encrypt_assertion_self_contained=False
769+
)
770+
771+
# The create_authn_response method above will return an instance
772+
# of saml2.samlp.Response when neither encrypting nor signing and
773+
# so we can remove the <NameID> element directly.
774+
resp.assertion.subject.name_id = None
775+
776+
# Assert that the response does not contain a NameID element so that
777+
# the parsing below is a fair test.
778+
assert str(resp).find("NameID") == -1
779+
780+
# Cast the response to a string and encode it to mock up the payload
781+
# the SP client is expected to receive via HTTP POST binding.
782+
resp_str = encode_fn(str(resp).encode())
783+
784+
# We do not need the client to verify a signature for this test.
785+
client.want_assertions_signed = False
786+
client.want_response_signed = False
787+
788+
# Parse the authentication response that does not include a <NameID>.
789+
authn_response = client.parse_authn_request_response(
790+
resp_str, BINDING_HTTP_POST,
791+
{"id1": "http://foo.example.com/service"})
792+
793+
# A successful test is parsing the response.
794+
assert authn_response is not None
795+
739796
def setup_verify_authn_response(self):
740797
idp = "urn:mace:example.com:saml:roland:idp"
741798
ava = {"givenName": ["Derek"], "sn": ["Jeter"],

0 commit comments

Comments
 (0)