Skip to content

Commit c512616

Browse files
committed
Remove NameId requirement on SAMLResponse
1 parent e4c8f37 commit c512616

8 files changed

Lines changed: 127 additions & 13 deletions

File tree

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,10 @@ In addition to the required settings data (idp, sp), there is extra information
327327
// this SP to be signed. [Metadata of the SP will offer this info]
328328
"wantAssertionsSigned": false,
329329

330+
// Indicates a requirement for the NameID element on the SAMLResponse
331+
// received by this SP to be present.
332+
"wantNameId": true,
333+
330334
// Indicates a requirement for the NameID received by
331335
// this SP to be encrypted.
332336
"wantNameIdEncrypted": false,

demo-django/saml/advanced_settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"signMetadata": false,
88
"wantMessagesSigned": false,
99
"wantAssertionsSigned": false,
10+
"wantNameId" : true,
1011
"wantNameIdEncrypted": false,
1112
"signatureAlgorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
1213
},

demo-flask/saml/advanced_settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
"signMetadata": false,
88
"wantMessagesSigned": false,
99
"wantAssertionsSigned": false,
10+
"wantNameId" : true,
1011
"wantNameIdEncrypted": false,
1112
"signatureAlgorithm": "http://www.w3.org/2000/09/xmldsig#rsa-sha1"
1213
},

src/onelogin/saml2/auth.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -210,7 +210,7 @@ def get_nameid(self):
210210
Returns the nameID.
211211
212212
:returns: NameID
213-
:rtype: string
213+
:rtype: string|None
214214
"""
215215
return self.__nameid
216216

@@ -226,7 +226,7 @@ def get_session_expiration(self):
226226
"""
227227
Returns the SessionNotOnOrAfter from the AuthnStatement.
228228
:returns: The SessionNotOnOrAfter of the assertion
229-
:rtype: DateTime|null
229+
:rtype: DateTime|None
230230
"""
231231
return self.__session_expiration
232232

src/onelogin/saml2/response.py

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,8 @@ def get_nameid_data(self):
273273
:rtype: dict
274274
"""
275275
nameid = None
276+
nameid_data = {}
277+
276278
encrypted_id_data_nodes = self.__query_assertion('/saml:Subject/saml:EncryptedID/xenc:EncryptedData')
277279
if encrypted_id_data_nodes:
278280
encrypted_data = encrypted_id_data_nodes[0]
@@ -283,24 +285,29 @@ def get_nameid_data(self):
283285
if nameid_nodes:
284286
nameid = nameid_nodes[0]
285287
if nameid is None:
286-
raise Exception('Not NameID found in the assertion of the Response')
287-
288-
nameid_data = {'Value': nameid.text}
289-
for attr in ['Format', 'SPNameQualifier', 'NameQualifier']:
290-
value = nameid.get(attr, None)
291-
if value:
292-
nameid_data[attr] = value
288+
security = self.__settings.get_security_data()
289+
if security.get('wantNameId', True):
290+
raise Exception('Not NameID found in the assertion of the Response')
291+
else:
292+
nameid_data = {'Value': nameid.text}
293+
for attr in ['Format', 'SPNameQualifier', 'NameQualifier']:
294+
value = nameid.get(attr, None)
295+
if value:
296+
nameid_data[attr] = value
293297
return nameid_data
294298

295299
def get_nameid(self):
296300
"""
297301
Gets the NameID provided by the SAML Response from the IdP
298302
299303
:returns: NameID (value)
300-
:rtype: string
304+
:rtype: string|None
301305
"""
306+
nameid_value = None
302307
nameid_data = self.get_nameid_data()
303-
return nameid_data['Value']
308+
if nameid_data and 'Value' in nameid_data.keys():
309+
nameid_value = nameid_data['Value']
310+
return nameid_value
304311

305312
def get_session_not_on_or_after(self):
306313
"""

src/onelogin/saml2/settings.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,10 @@ def __add_default_values(self):
302302
if 'wantAssertionsSigned' not in self.__security:
303303
self.__security['wantAssertionsSigned'] = False
304304

305+
# NameID element expected
306+
if 'wantNameId' not in self.__security.keys():
307+
self.__security['wantNameId'] = True
308+
305309
# Encrypt expected
306310
if 'wantAssertionsEncrypted' not in self.__security:
307311
self.__security['wantAssertionsEncrypted'] = False

tests/src/OneLogin/saml2_tests/response_test.py

Lines changed: 58 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ def testReturnNameId(self):
6363
"""
6464
Tests the get_nameid method of the OneLogin_Saml2_Response
6565
"""
66-
settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
66+
json_settings = self.loadSettingsJSON()
67+
settings = OneLogin_Saml2_Settings(json_settings)
6768
xml = self.file_contents(join(self.data_path, 'responses', 'response1.xml.base64'))
6869
response = OneLogin_Saml2_Response(settings, xml)
6970
self.assertEqual('support@onelogin.com', response.get_nameid())
@@ -84,11 +85,39 @@ def testReturnNameId(self):
8485
except Exception as e:
8586
self.assertIn('Not NameID found in the assertion of the Response', str(e))
8687

88+
json_settings['security']['wantNameId'] = True
89+
settings = OneLogin_Saml2_Settings(json_settings)
90+
91+
response_5 = OneLogin_Saml2_Response(settings, xml_4)
92+
try:
93+
response_5.get_nameid()
94+
self.assertTrue(False)
95+
except Exception as e:
96+
self.assertIn('Not NameID found in the assertion of the Response', e.message)
97+
98+
json_settings['security']['wantNameId'] = False
99+
settings = OneLogin_Saml2_Settings(json_settings)
100+
101+
response_6 = OneLogin_Saml2_Response(settings, xml_4)
102+
nameid_6 = response_6.get_nameid()
103+
self.assertIsNone(nameid_6)
104+
105+
del json_settings['security']['wantNameId']
106+
settings = OneLogin_Saml2_Settings(json_settings)
107+
108+
response_7 = OneLogin_Saml2_Response(settings, xml_4)
109+
try:
110+
response_7.get_nameid()
111+
self.assertTrue(False)
112+
except Exception as e:
113+
self.assertIn('Not NameID found in the assertion of the Response', e.message)
114+
87115
def testGetNameIdData(self):
88116
"""
89117
Tests the get_nameid_data method of the OneLogin_Saml2_Response
90118
"""
91-
settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
119+
json_settings = self.loadSettingsJSON()
120+
settings = OneLogin_Saml2_Settings(json_settings)
92121
xml = self.file_contents(join(self.data_path, 'responses', 'response1.xml.base64'))
93122
response = OneLogin_Saml2_Response(settings, xml)
94123
expected_nameid_data = {
@@ -126,6 +155,33 @@ def testGetNameIdData(self):
126155
except Exception as e:
127156
self.assertIn('Not NameID found in the assertion of the Response', str(e))
128157

158+
json_settings['security']['wantNameId'] = True
159+
settings = OneLogin_Saml2_Settings(json_settings)
160+
161+
response_5 = OneLogin_Saml2_Response(settings, xml_4)
162+
try:
163+
response_5.get_nameid_data()
164+
self.assertTrue(False)
165+
except Exception as e:
166+
self.assertIn('Not NameID found in the assertion of the Response', e.message)
167+
168+
json_settings['security']['wantNameId'] = False
169+
settings = OneLogin_Saml2_Settings(json_settings)
170+
171+
response_6 = OneLogin_Saml2_Response(settings, xml_4)
172+
nameid_data_6 = response_6.get_nameid_data()
173+
self.assertEqual({}, nameid_data_6)
174+
175+
del json_settings['security']['wantNameId']
176+
settings = OneLogin_Saml2_Settings(json_settings)
177+
178+
response_7 = OneLogin_Saml2_Response(settings, xml_4)
179+
try:
180+
response_7.get_nameid_data()
181+
self.assertTrue(False)
182+
except Exception as e:
183+
self.assertIn('Not NameID found in the assertion of the Response', e.message)
184+
129185
def testCheckStatus(self):
130186
"""
131187
Tests the check_status method of the OneLogin_Saml2_Response

tests/src/OneLogin/saml2_tests/settings_test.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -590,8 +590,49 @@ def testGetSecurityData(self):
590590
self.assertIn('signMetadata', security)
591591
self.assertIn('wantMessagesSigned', security)
592592
self.assertIn('wantAssertionsSigned', security)
593+
self.assertIn('requestedAuthnContext', security)
594+
self.assertIn('wantNameId', security)
593595
self.assertIn('wantNameIdEncrypted', security)
594596

597+
def testGetDefaultSecurityValues(self):
598+
"""
599+
Tests default values of Security advanced sesettings
600+
"""
601+
settings_json = self.loadSettingsJSON()
602+
del settings_json['security']
603+
settings = OneLogin_Saml2_Settings(settings_json)
604+
security = settings.get_security_data()
605+
606+
self.assertIn('nameIdEncrypted', security)
607+
self.assertFalse(security.get('nameIdEncrypted'))
608+
609+
self.assertIn('authnRequestsSigned', security)
610+
self.assertFalse(security.get('authnRequestsSigned'))
611+
612+
self.assertIn('logoutRequestSigned', security)
613+
self.assertFalse(security.get('logoutRequestSigned'))
614+
615+
self.assertIn('logoutResponseSigned', security)
616+
self.assertFalse(security.get('logoutResponseSigned'))
617+
618+
self.assertIn('signMetadata', security)
619+
self.assertFalse(security.get('signMetadata'))
620+
621+
self.assertIn('wantMessagesSigned', security)
622+
self.assertFalse(security.get('wantMessagesSigned'))
623+
624+
self.assertIn('wantAssertionsSigned', security)
625+
self.assertFalse(security.get('wantAssertionsSigned'))
626+
627+
self.assertIn('requestedAuthnContext', security)
628+
self.assertTrue(security.get('requestedAuthnContext'))
629+
630+
self.assertIn('wantNameId', security)
631+
self.assertTrue(security.get('wantNameId'))
632+
633+
self.assertIn('wantNameIdEncrypted', security)
634+
self.assertFalse(security.get('wantNameIdEncrypted'))
635+
595636
def testGetContacts(self):
596637
"""
597638
Tests the getContacts method of the OneLogin_Saml2_Settings

0 commit comments

Comments
 (0)