diff --git a/demo-django/saml/settings.json b/demo-django/saml/settings.json index cafd090b..082e248f 100644 --- a/demo-django/saml/settings.json +++ b/demo-django/saml/settings.json @@ -11,7 +11,7 @@ "url": "https:///?sls", "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" }, - "NameIDFormat": "urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified", + "NameIDFormats": ["urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified"], "x509cert": "", "privateKey": "" }, @@ -25,6 +25,8 @@ "url": "https://app.onelogin.com/trust/saml2/http-redirect/slo/", "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" }, + "NameIDPolicyFormat": "urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified", + "NameIDPolicyAllowCreate": true, "x509cert": "" } -} \ No newline at end of file +} diff --git a/demo-flask/saml/settings.json b/demo-flask/saml/settings.json index 142911f1..082e248f 100644 --- a/demo-flask/saml/settings.json +++ b/demo-flask/saml/settings.json @@ -11,7 +11,7 @@ "url": "https:///?sls", "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" }, - "NameIDFormat": "urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified", + "NameIDFormats": ["urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified"], "x509cert": "", "privateKey": "" }, @@ -25,6 +25,8 @@ "url": "https://app.onelogin.com/trust/saml2/http-redirect/slo/", "binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" }, + "NameIDPolicyFormat": "urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified", + "NameIDPolicyAllowCreate": true, "x509cert": "" } } diff --git a/src/onelogin/saml2/authn_request.py b/src/onelogin/saml2/authn_request.py index 8e8495e7..a506cb38 100644 --- a/src/onelogin/saml2/authn_request.py +++ b/src/onelogin/saml2/authn_request.py @@ -49,9 +49,19 @@ def __init__(self, settings, force_authn=False, is_passive=False): destination = idp_data['singleSignOnService']['url'] - name_id_policy_format = sp_data['NameIDFormat'] + # NameIDPolicy element: + name_id_policy_str = '' + name_id_policy_attrs = '' if 'wantNameIdEncrypted' in security and security['wantNameIdEncrypted']: name_id_policy_format = OneLogin_Saml2_Constants.NAMEID_ENCRYPTED + else: + name_id_policy_format = idp_data.get('NameIDPolicyFormat') + if name_id_policy_format: + name_id_policy_attrs += 'Format="%s" ' % name_id_policy_format + if idp_data['NameIDPolicyAllowCreate']: + name_id_policy_attrs += 'AllowCreate="true" ' + if name_id_policy_attrs: + name_id_policy_str = '' % name_id_policy_attrs provider_name_str = '' organization_data = settings.get_organization() @@ -97,9 +107,7 @@ def __init__(self, settings, force_authn=False, is_passive=False): ProtocolBinding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" AssertionConsumerServiceURL="%(assertion_url)s"> %(entity_id)s - + %(name_id_policy)s %(requested_authn_context_str)s """ % \ { @@ -111,7 +119,7 @@ def __init__(self, settings, force_authn=False, is_passive=False): 'destination': destination, 'assertion_url': sp_data['assertionConsumerService']['url'], 'entity_id': sp_data['entityId'], - 'name_id_policy': name_id_policy_format, + 'name_id_policy': name_id_policy_str, 'requested_authn_context_str': requested_authn_context_str, } diff --git a/src/onelogin/saml2/logout_request.py b/src/onelogin/saml2/logout_request.py index 92af8d7b..36543cdc 100644 --- a/src/onelogin/saml2/logout_request.py +++ b/src/onelogin/saml2/logout_request.py @@ -64,7 +64,7 @@ def __init__(self, settings, request=None, name_id=None, session_index=None): cert = idp_data['x509cert'] if name_id is not None: - nameIdFormat = sp_data['NameIDFormat'] + nameIdFormat = idp_data['NameIDPolicyFormat'] else: name_id = idp_data['entityId'] nameIdFormat = OneLogin_Saml2_Constants.NAMEID_ENTITY diff --git a/src/onelogin/saml2/metadata.py b/src/onelogin/saml2/metadata.py index 2dcb3efc..da45b209 100644 --- a/src/onelogin/saml2/metadata.py +++ b/src/onelogin/saml2/metadata.py @@ -119,13 +119,18 @@ def builder(sp, authnsign=False, wsign=False, valid_until=None, cache_duration=N contacts_info.append(contact) str_contacts = '\n'.join(contacts_info) + str_nameid_formats = '' + for name_id_format in sp['NameIDFormats']: + str_nameid_formats += ' %s\n' % name_id_format + metadata = """ -%(sls)s %(name_id_format)s + %(sls)s + %(str_nameid_formats)s @@ -139,7 +144,7 @@ def builder(sp, authnsign=False, wsign=False, valid_until=None, cache_duration=N 'entity_id': sp['entityId'], 'authnsign': str_authnsign, 'wsign': str_wsign, - 'name_id_format': sp['NameIDFormat'], + 'str_nameid_formats': str_nameid_formats, 'binding': sp['assertionConsumerService']['binding'], 'location': sp['assertionConsumerService']['url'], 'sls': sls, diff --git a/src/onelogin/saml2/response.py b/src/onelogin/saml2/response.py index c41e3cf0..4d73a726 100644 --- a/src/onelogin/saml2/response.py +++ b/src/onelogin/saml2/response.py @@ -287,7 +287,7 @@ def get_nameid_data(self): if nameid_nodes: nameid = nameid_nodes[0] if nameid is None: - raise Exception('Not NameID found in the assertion of the Response') + return {'Value': None} nameid_data = {'Value': nameid.text} for attr in ['Format', 'SPNameQualifier', 'NameQualifier']: diff --git a/src/onelogin/saml2/settings.py b/src/onelogin/saml2/settings.py index 213d078e..8a01a254 100644 --- a/src/onelogin/saml2/settings.py +++ b/src/onelogin/saml2/settings.py @@ -261,8 +261,19 @@ def __add_default_values(self): self.__sp['singleLogoutService']['binding'] = OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT # Related to nameID - if 'NameIDFormat' not in self.__sp: - self.__sp['NameIDFormat'] = OneLogin_Saml2_Constants.NAMEID_PERSISTENT + if 'NameIDFormats' not in self.__sp: + # Check if the older config setting, single NameIDFormat is present: + if 'NameIDFormat' in self.__sp: + self.__sp['NameIDFormats'] = [self.__sp['NameIDFormat']] + else: + self.__sp['NameIDFormats'] = [OneLogin_Saml2_Constants.NAMEID_PERSISTENT] + if 'NameIDPolicyFormat' not in self.__idp: + # Check for the old-style setting 'NameIDFormat' which set both NameIDFormats and NameIDPolicyFormat: + if 'NameIDFormat' in self.__sp: + self.__idp['NameIDPolicyFormat'] = self.__sp.pop('NameIDFormat') + self.__idp['NameIDPolicyAllowCreate'] = True + if 'NameIDPolicyAllowCreate' not in self.__idp: + self.__idp['NameIDPolicyAllowCreate'] = False # False is the default according to the spec if 'nameIdEncrypted' not in self.__security: self.__security['nameIdEncrypted'] = False diff --git a/tests/settings/settings1.json b/tests/settings/settings1.json index d809b7d6..d67f56e5 100644 --- a/tests/settings/settings1.json +++ b/tests/settings/settings1.json @@ -10,7 +10,7 @@ "singleLogoutService": { "url": "http://stuff.com/endpoints/endpoints/sls.php" }, - "NameIDFormat": "urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified" + "NameIDFormats": ["urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified"] }, "idp": { "entityId": "http://idp.example.com/", @@ -20,6 +20,8 @@ "singleLogoutService": { "url": "http://idp.example.com/SingleLogoutService.php" }, + "NameIDPolicyFormat": "urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified", + "NameIDPolicyAllowCreate": true, "x509cert": "MIICgTCCAeoCCQCbOlrWDdX7FTANBgkqhkiG9w0BAQUFADCBhDELMAkGA1UEBhMCTk8xGDAWBgNVBAgTD0FuZHJlYXMgU29sYmVyZzEMMAoGA1UEBxMDRm9vMRAwDgYDVQQKEwdVTklORVRUMRgwFgYDVQQDEw9mZWlkZS5lcmxhbmcubm8xITAfBgkqhkiG9w0BCQEWEmFuZHJlYXNAdW5pbmV0dC5ubzAeFw0wNzA2MTUxMjAxMzVaFw0wNzA4MTQxMjAxMzVaMIGEMQswCQYDVQQGEwJOTzEYMBYGA1UECBMPQW5kcmVhcyBTb2xiZXJnMQwwCgYDVQQHEwNGb28xEDAOBgNVBAoTB1VOSU5FVFQxGDAWBgNVBAMTD2ZlaWRlLmVybGFuZy5ubzEhMB8GCSqGSIb3DQEJARYSYW5kcmVhc0B1bmluZXR0Lm5vMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDivbhR7P516x/S3BqKxupQe0LONoliupiBOesCO3SHbDrl3+q9IbfnfmE04rNuMcPsIxB161TdDpIesLCn7c8aPHISKOtPlAeTZSnb8QAu7aRjZq3+PbrP5uW3TcfCGPtKTytHOge/OlJbo078dVhXQ14d1EDwXJW1rRXuUt4C8QIDAQABMA0GCSqGSIb3DQEBBQUAA4GBACDVfp86HObqY+e8BUoWQ9+VMQx1ASDohBjwOsg2WykUqRXF+dLfcUH9dWR63CtZIKFDbStNomPnQz7nbK+onygwBspVEbnHuUihZq3ZUdmumQqCw4Uvs/1Uvq3orOo/WJVhTyvLgFVK2QarQ4/67OZfHd7R+POBXhophSMv1ZOo" }, "security": { diff --git a/tests/src/OneLogin/saml2_tests/response_test.py b/tests/src/OneLogin/saml2_tests/response_test.py index 9ed46dd8..b48065b1 100644 --- a/tests/src/OneLogin/saml2_tests/response_test.py +++ b/tests/src/OneLogin/saml2_tests/response_test.py @@ -74,11 +74,7 @@ def testReturnNameId(self): xml_4 = self.file_contents(join(self.data_path, 'responses', 'invalids', 'no_nameid.xml.base64')) response_4 = OneLogin_Saml2_Response(settings, xml_4) - try: - response_4.get_nameid() - self.assertTrue(False) - except Exception as e: - self.assertIn('Not NameID found in the assertion of the Response', e.message) + self.assertIsNone(response_4.get_nameid()) def testGetNameIdData(self): """ @@ -116,11 +112,8 @@ def testGetNameIdData(self): xml_4 = self.file_contents(join(self.data_path, 'responses', 'invalids', 'no_nameid.xml.base64')) response_4 = OneLogin_Saml2_Response(settings, xml_4) - try: - response_4.get_nameid_data() - self.assertTrue(False) - except Exception as e: - self.assertIn('Not NameID found in the assertion of the Response', e.message) + data = response_4.get_nameid_data() + self.assertEqual(data, {'Value': None}) def testCheckStatus(self): """ diff --git a/tests/src/OneLogin/saml2_tests/settings_test.py b/tests/src/OneLogin/saml2_tests/settings_test.py index 6af09960..275029f0 100644 --- a/tests/src/OneLogin/saml2_tests/settings_test.py +++ b/tests/src/OneLogin/saml2_tests/settings_test.py @@ -49,7 +49,9 @@ def testLoadSettingsFromDict(self): settings = OneLogin_Saml2_Settings(settings_info) self.assertEqual(len(settings.get_errors()), 0) - del settings_info['sp']['NameIDFormat'] + del settings_info['sp']['NameIDFormats'] + del settings_info['idp']['NameIDPolicyFormat'] + del settings_info['idp']['NameIDPolicyAllowCreate'] del settings_info['idp']['x509cert'] settings_info['idp']['certFingerprint'] = 'afe71c28ef740bc87425be13a2263d37971daA1f9' settings = OneLogin_Saml2_Settings(settings_info) @@ -507,11 +509,15 @@ def testGetIdPData(self): self.assertIn('entityId', idp_data) self.assertIn('singleSignOnService', idp_data) self.assertIn('singleLogoutService', idp_data) + self.assertIn('NameIDPolicyFormat', idp_data) + self.assertIn('NameIDPolicyAllowCreate', idp_data) self.assertIn('x509cert', idp_data) self.assertEqual('http://idp.example.com/', idp_data['entityId']) self.assertEqual('http://idp.example.com/SSOService.php', idp_data['singleSignOnService']['url']) self.assertEqual('http://idp.example.com/SingleLogoutService.php', idp_data['singleLogoutService']['url']) + self.assertEqual('urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified', idp_data['NameIDPolicyFormat']) + self.assertTrue(idp_data['NameIDPolicyAllowCreate']) x509cert = 'MIICgTCCAeoCCQCbOlrWDdX7FTANBgkqhkiG9w0BAQUFADCBhDELMAkGA1UEBhMCTk8xGDAWBgNVBAgTD0FuZHJlYXMgU29sYmVyZzEMMAoGA1UEBxMDRm9vMRAwDgYDVQQKEwdVTklORVRUMRgwFgYDVQQDEw9mZWlkZS5lcmxhbmcubm8xITAfBgkqhkiG9w0BCQEWEmFuZHJlYXNAdW5pbmV0dC5ubzAeFw0wNzA2MTUxMjAxMzVaFw0wNzA4MTQxMjAxMzVaMIGEMQswCQYDVQQGEwJOTzEYMBYGA1UECBMPQW5kcmVhcyBTb2xiZXJnMQwwCgYDVQQHEwNGb28xEDAOBgNVBAoTB1VOSU5FVFQxGDAWBgNVBAMTD2ZlaWRlLmVybGFuZy5ubzEhMB8GCSqGSIb3DQEJARYSYW5kcmVhc0B1bmluZXR0Lm5vMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDivbhR7P516x/S3BqKxupQe0LONoliupiBOesCO3SHbDrl3+q9IbfnfmE04rNuMcPsIxB161TdDpIesLCn7c8aPHISKOtPlAeTZSnb8QAu7aRjZq3+PbrP5uW3TcfCGPtKTytHOge/OlJbo078dVhXQ14d1EDwXJW1rRXuUt4C8QIDAQABMA0GCSqGSIb3DQEBBQUAA4GBACDVfp86HObqY+e8BUoWQ9+VMQx1ASDohBjwOsg2WykUqRXF+dLfcUH9dWR63CtZIKFDbStNomPnQz7nbK+onygwBspVEbnHuUihZq3ZUdmumQqCw4Uvs/1Uvq3orOo/WJVhTyvLgFVK2QarQ4/67OZfHd7R+POBXhophSMv1ZOo' formated_x509_cert = OneLogin_Saml2_Utils.format_cert(x509cert) @@ -528,12 +534,12 @@ def testGetSPData(self): self.assertIn('entityId', sp_data) self.assertIn('assertionConsumerService', sp_data) self.assertIn('singleLogoutService', sp_data) - self.assertIn('NameIDFormat', sp_data) + self.assertIn('NameIDFormats', sp_data) self.assertEqual('http://stuff.com/endpoints/metadata.php', sp_data['entityId']) self.assertEqual('http://stuff.com/endpoints/endpoints/acs.php', sp_data['assertionConsumerService']['url']) self.assertEqual('http://stuff.com/endpoints/endpoints/sls.php', sp_data['singleLogoutService']['url']) - self.assertEqual('urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified', sp_data['NameIDFormat']) + self.assertEqual(['urn:oasis:names:tc:SAML:2.0:nameid-format:unspecified'], sp_data['NameIDFormats']) def testGetSecurityData(self): """