Skip to content

Commit d447776

Browse files
committed
IdP metadata parser tests: enrich for testing required_sso/sls_binding
1 parent b22f0a9 commit d447776

1 file changed

Lines changed: 193 additions & 4 deletions

File tree

tests/src/OneLogin/saml2_tests/idp_metadata_parser_test.py

Lines changed: 193 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,22 @@
44
# All rights reserved.
55

66

7+
from copy import deepcopy
78
import json
89
from os.path import dirname, join, exists
910
from lxml.etree import XMLSyntaxError
1011
import unittest
1112

1213
from onelogin.saml2.idp_metadata_parser import OneLogin_Saml2_IdPMetadataParser
14+
from onelogin.saml2.constants import OneLogin_Saml2_Constants
1315

1416

1517
class OneLogin_Saml2_IdPMetadataParser_Test(unittest.TestCase):
18+
# Instruct unittest to not hide diffs upon test failure, even for complex
19+
# dictionaries. This prevents the message "Diff is 907 characters long.
20+
# Set self.maxDiff to None to see it." from showing up.
21+
maxDiff = None
22+
1623
data_path = join(dirname(dirname(dirname(dirname(__file__)))), 'data')
1724
settings_path = join(dirname(dirname(dirname(dirname(__file__)))), 'settings')
1825

@@ -51,8 +58,22 @@ def testParseRemote(self):
5158

5259
data = OneLogin_Saml2_IdPMetadataParser.parse_remote('https://www.testshib.org/metadata/testshib-providers.xml')
5360
self.assertTrue(data is not None and data is not {})
54-
expected_data = {'sp': {'NameIDFormat': 'urn:mace:shibboleth:1.0:nameIdentifier'}, 'idp': {'singleSignOnService': {'url': 'https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO'}, 'entityId': 'https://idp.testshib.org/idp/shibboleth'}}
55-
self.assertEqual(expected_data, data)
61+
expected_settings_json = """
62+
{
63+
"sp": {
64+
"NameIDFormat": "urn:mace:shibboleth:1.0:nameIdentifier"
65+
},
66+
"idp": {
67+
"entityId": "https://idp.testshib.org/idp/shibboleth",
68+
"singleSignOnService": {
69+
"url": "https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO",
70+
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
71+
}
72+
}
73+
}
74+
"""
75+
expected_settings = json.loads(expected_settings_json)
76+
self.assertEqual(expected_settings, data)
5677

5778
def testParse(self):
5879
"""
@@ -67,13 +88,181 @@ def testParse(self):
6788

6889
xml_idp_metadata = self.file_contents(join(self.data_path, 'metadata', 'idp_metadata.xml'))
6990
data = OneLogin_Saml2_IdPMetadataParser.parse(xml_idp_metadata)
70-
expected_data = {'sp': {'NameIDFormat': 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress'}, 'idp': {'singleSignOnService': {'url': 'https://app.onelogin.com/trust/saml2/http-post/sso/383123'}, 'entityId': 'https://app.onelogin.com/saml/metadata/383123', 'x509cert': 'MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzET\nMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREwDwYD\nVQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbTAeFw0xMzA2\nMDUxNzE2MjBaFw0xODA2MDUxNzE2MjBaMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQI\nDApDYWxpZm9ybmlhMRUwEwYDVQQHDAxTYW50YSBNb25pY2ExETAPBgNVBAoMCE9u\nZUxvZ2luMRkwFwYDVQQDDBBhcHAub25lbG9naW4uY29tMIIBIjANBgkqhkiG9w0B\nAQEFAAOCAQ8AMIIBCgKCAQEAse8rnep4qL2GmhH10pMQyJ2Jae+AQHyfgVjaQZ7Z\n0QQog5jX91vcJRSMi0XWJnUtOr6lF0dq1+yckjZ92wyLrH+7fvngNO1aV4Mjk9sT\ngf+iqMrae6y6fRxDt9PXrEFVjvd3vv7QTJf2FuIPy4vVP06Dt8EMkQIr8rmLmU0m\nTr1k2DkrdtdlCuNFTXuAu3QqfvNCRrRwfNObn9MP6JeOUdcGLJsBjGF8exfcN1SF\nzRF0JFr3dmOlx761zK5liD0T1sYWnDquatj/JD9fZMbKecBKni1NglH/LVd+b6aJ\nUAr5LulERULUjLqYJRKW31u91/4Qazdo9tbvwqyFxaoUrwIDAQABo4HUMIHRMAwG\nA1UdEwEB/wQCMAAwHQYDVR0OBBYEFPWcXvQSlTXnzZD2xziuoUvrrDedMIGRBgNV\nHSMEgYkwgYaAFPWcXvQSlTXnzZD2xziuoUvrrDedoWukaTBnMQswCQYDVQQGEwJV\nUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREw\nDwYDVQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbYIBATAO\nBgNVHQ8BAf8EBAMCBPAwDQYJKoZIhvcNAQEFBQADggEBAB/8xe3rzqXQVxzHyAHu\nAuPa73ClDoL1cko0Fp8CGcqEIyj6Te9gx5z6wyfv+Lo8RFvBLlnB1lXqbC+fTGcV\ngG/4oKLJ5UwRFxInqpZPnOAudVNnd0PYOODn9FWs6u+OTIQIaIcPUv3MhB9lwHIJ\nsTk/bs9xcru5TPyLIxLLd6ib/pRceKH2mTkzUd0DYk9CQNXXeoGx/du5B9nh3ClP\nTbVakRzl3oswgI5MQIphYxkW70SopEh4kOFSRE1ND31NNIq1YrXlgtkguQBFsZWu\nQOPR6cEwFZzP0tHTYbI839WgxX6hfhIUTUz6mLqq4+3P4BG3+1OXeVDg63y8Uh78\n1sE='}}
71-
self.assertEqual(expected_data, data)
91+
92+
# W/o further specification, expect to get the redirect binding SSO
93+
# URL extracted.
94+
expected_settings_json = """
95+
{
96+
"idp": {
97+
"singleSignOnService": {
98+
"url": "https://app.onelogin.com/trust/saml2/http-post/sso/383123",
99+
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
100+
},
101+
"x509cert": "MIIEHjCCAwagAwIBAgIBATANBgkqhkiG9w0BAQUFADBnMQswCQYDVQQGEwJVUzET\\nMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREwDwYD\\nVQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbTAeFw0xMzA2\\nMDUxNzE2MjBaFw0xODA2MDUxNzE2MjBaMGcxCzAJBgNVBAYTAlVTMRMwEQYDVQQI\\nDApDYWxpZm9ybmlhMRUwEwYDVQQHDAxTYW50YSBNb25pY2ExETAPBgNVBAoMCE9u\\nZUxvZ2luMRkwFwYDVQQDDBBhcHAub25lbG9naW4uY29tMIIBIjANBgkqhkiG9w0B\\nAQEFAAOCAQ8AMIIBCgKCAQEAse8rnep4qL2GmhH10pMQyJ2Jae+AQHyfgVjaQZ7Z\\n0QQog5jX91vcJRSMi0XWJnUtOr6lF0dq1+yckjZ92wyLrH+7fvngNO1aV4Mjk9sT\\ngf+iqMrae6y6fRxDt9PXrEFVjvd3vv7QTJf2FuIPy4vVP06Dt8EMkQIr8rmLmU0m\\nTr1k2DkrdtdlCuNFTXuAu3QqfvNCRrRwfNObn9MP6JeOUdcGLJsBjGF8exfcN1SF\\nzRF0JFr3dmOlx761zK5liD0T1sYWnDquatj/JD9fZMbKecBKni1NglH/LVd+b6aJ\\nUAr5LulERULUjLqYJRKW31u91/4Qazdo9tbvwqyFxaoUrwIDAQABo4HUMIHRMAwG\\nA1UdEwEB/wQCMAAwHQYDVR0OBBYEFPWcXvQSlTXnzZD2xziuoUvrrDedMIGRBgNV\\nHSMEgYkwgYaAFPWcXvQSlTXnzZD2xziuoUvrrDedoWukaTBnMQswCQYDVQQGEwJV\\nUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEVMBMGA1UEBwwMU2FudGEgTW9uaWNhMREw\\nDwYDVQQKDAhPbmVMb2dpbjEZMBcGA1UEAwwQYXBwLm9uZWxvZ2luLmNvbYIBATAO\\nBgNVHQ8BAf8EBAMCBPAwDQYJKoZIhvcNAQEFBQADggEBAB/8xe3rzqXQVxzHyAHu\\nAuPa73ClDoL1cko0Fp8CGcqEIyj6Te9gx5z6wyfv+Lo8RFvBLlnB1lXqbC+fTGcV\\ngG/4oKLJ5UwRFxInqpZPnOAudVNnd0PYOODn9FWs6u+OTIQIaIcPUv3MhB9lwHIJ\\nsTk/bs9xcru5TPyLIxLLd6ib/pRceKH2mTkzUd0DYk9CQNXXeoGx/du5B9nh3ClP\\nTbVakRzl3oswgI5MQIphYxkW70SopEh4kOFSRE1ND31NNIq1YrXlgtkguQBFsZWu\\nQOPR6cEwFZzP0tHTYbI839WgxX6hfhIUTUz6mLqq4+3P4BG3+1OXeVDg63y8Uh78\\n1sE=",
102+
"entityId": "https://app.onelogin.com/saml/metadata/383123"
103+
},
104+
"sp": {
105+
"NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
106+
}
107+
}
108+
"""
109+
expected_settings = json.loads(expected_settings_json)
110+
self.assertEqual(expected_settings, data)
111+
112+
def test_parse_testshib_required_binding_sso_redirect(self):
113+
"""
114+
Test with testshib metadata.
115+
116+
Especially test extracting SSO with REDIRECT binding.
117+
118+
Note that the testshib metadata does not contain an SLO specification
119+
in the first <IDPSSODescriptor> tag.
120+
"""
121+
expected_settings_json = """
122+
{
123+
"sp": {
124+
"NameIDFormat": "urn:mace:shibboleth:1.0:nameIdentifier"
125+
},
126+
"idp": {
127+
"entityId": "https://idp.testshib.org/idp/shibboleth",
128+
"singleSignOnService": {
129+
"url": "https://idp.testshib.org/idp/profile/SAML2/Redirect/SSO",
130+
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
131+
}
132+
}
133+
}
134+
"""
135+
xmldoc = OneLogin_Saml2_IdPMetadataParser.get_metadata(
136+
'https://www.testshib.org/metadata/testshib-providers.xml')
137+
# Parse, require SSO REDIRECT binding, implicitly.
138+
settings1 = OneLogin_Saml2_IdPMetadataParser.parse(xmldoc)
139+
# Parse, require SSO REDIRECT binding, explicitly.
140+
settings2 = OneLogin_Saml2_IdPMetadataParser.parse(
141+
xmldoc,
142+
required_sso_binding=OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT
143+
)
144+
expected_settings = json.loads(expected_settings_json)
145+
self.assertEqual(expected_settings, settings1)
146+
self.assertEqual(expected_settings, settings2)
147+
148+
def test_parse_testshib_required_binding_sso_post(self):
149+
"""
150+
Test with testshib metadata.
151+
152+
Especially test extracting SSO with POST binding.
153+
"""
154+
expected_settings_json = """
155+
{
156+
"sp": {
157+
"NameIDFormat": "urn:mace:shibboleth:1.0:nameIdentifier"
158+
},
159+
"idp": {
160+
"entityId": "https://idp.testshib.org/idp/shibboleth",
161+
"singleSignOnService": {
162+
"url": "https://idp.testshib.org/idp/profile/SAML2/POST/SSO",
163+
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
164+
}
165+
}
166+
}
167+
"""
168+
xmldoc = OneLogin_Saml2_IdPMetadataParser.get_metadata(
169+
'https://www.testshib.org/metadata/testshib-providers.xml')
170+
# Parse, require POST binding.
171+
settings = OneLogin_Saml2_IdPMetadataParser.parse(
172+
xmldoc,
173+
required_sso_binding=OneLogin_Saml2_Constants.BINDING_HTTP_POST
174+
)
175+
expected_settings = json.loads(expected_settings_json)
176+
self.assertEqual(expected_settings, settings)
177+
178+
def test_parse_oktadev_required_binding_all(self):
179+
"""
180+
Test all combinations of the `require_slo_binding` and
181+
`require_sso_binding` parameters with the oktadev IdP metadata.
182+
183+
Note: Oktadev's dev IdP metadata contains a single logout (SLO)
184+
service and does not specify any endpoint for the POST binding.
185+
"""
186+
expected_settings_json = """
187+
{
188+
"sp": {
189+
"NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
190+
},
191+
"idp": {
192+
"entityId": "urn:example:idp",
193+
"x509cert": "MIIDPDCCAiQCCQDydJgOlszqbzANBgkqhkiG9w0BAQUFADBgMQswCQYDVQQGEwJVUzETMBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNU2FuIEZyYW5jaXNjbzEQMA4GA1UEChMHSmFua3lDbzESMBAGA1UEAxMJbG9jYWxob3N0MB4XDTE0MDMxMjE5NDYzM1oXDTI3MTExOTE5NDYzM1owYDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDVNhbiBGcmFuY2lzY28xEDAOBgNVBAoTB0phbmt5Q28xEjAQBgNVBAMTCWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMGvJpRTTasRUSPqcbqCG+ZnTAurnu0vVpIG9lzExnh11o/BGmzu7lB+yLHcEdwrKBBmpepDBPCYxpVajvuEhZdKFx/Fdy6j5mH3rrW0Bh/zd36CoUNjbbhHyTjeM7FN2yF3u9lcyubuvOzr3B3gX66IwJlU46+wzcQVhSOlMk2tXR+fIKQExFrOuK9tbX3JIBUqItpI+HnAow509CnM134svw8PTFLkR6/CcMqnDfDK1m993PyoC1Y+N4X9XkhSmEQoAlAHPI5LHrvuujM13nvtoVYvKYoj7ScgumkpWNEvX652LfXOnKYlkB8ZybuxmFfIkzedQrbJsyOhfL03cMECAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAeHwzqwnzGEkxjzSD47imXaTqtYyETZow7XwBc0ZaFS50qRFJUgKTAmKS1xQBP/qHpStsROT35DUxJAE6NY1Kbq3ZbCuhGoSlY0L7VzVT5tpu4EY8+Dq/u2EjRmmhoL7UkskvIZ2n1DdERtd+YUMTeqYl9co43csZwDno/IKomeN5qaPc39IZjikJ+nUC6kPFKeu/3j9rgHNlRtocI6S1FdtFz9OZMQlpr0JbUt2T3xS/YoQJn6coDmJL5GTiiKM6cOe+Ur1VwzS1JEDbSS2TWWhzq8ojLdrotYLGd9JOsoQhElmz+tMfCFQUFLExinPAyy7YHlSiVX13QH2XTu/iQQ==",
194+
"singleSignOnService": {
195+
"url": "http://idp.oktadev.com",
196+
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
197+
},
198+
"singleLogoutService": {
199+
"url": "http://idp.oktadev.com/logout",
200+
"binding": "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect"
201+
}
202+
}
203+
}
204+
"""
205+
xmldoc = OneLogin_Saml2_IdPMetadataParser.get_metadata(
206+
'http://idp.oktadev.com/metadata')
207+
expected_settings = json.loads(expected_settings_json)
208+
209+
# Parse, require SLO and SSO REDIRECT binding, implicitly.
210+
settings1 = OneLogin_Saml2_IdPMetadataParser.parse(xmldoc)
211+
212+
# Parse, require SLO and SSO REDIRECT binding, explicitly.
213+
settings2 = OneLogin_Saml2_IdPMetadataParser.parse(
214+
xmldoc,
215+
required_sso_binding=OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT,
216+
required_slo_binding=OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT
217+
)
218+
expected_settings1_2 = deepcopy(expected_settings)
219+
self.assertEqual(expected_settings1_2, settings1)
220+
self.assertEqual(expected_settings1_2, settings2)
221+
222+
settings3 = OneLogin_Saml2_IdPMetadataParser.parse(
223+
xmldoc,
224+
required_sso_binding=OneLogin_Saml2_Constants.BINDING_HTTP_POST,
225+
required_slo_binding=OneLogin_Saml2_Constants.BINDING_HTTP_POST
226+
)
227+
# Oktadev does not specify any POST binding endpoint.
228+
expected_settings3 = deepcopy(expected_settings)
229+
del expected_settings3['idp']['singleLogoutService']
230+
del expected_settings3['idp']['singleSignOnService']
231+
self.assertEqual(expected_settings3, settings3)
232+
233+
settings4 = OneLogin_Saml2_IdPMetadataParser.parse(
234+
xmldoc,
235+
required_sso_binding=OneLogin_Saml2_Constants.BINDING_HTTP_POST,
236+
required_slo_binding=OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT
237+
)
238+
settings5 = OneLogin_Saml2_IdPMetadataParser.parse(
239+
xmldoc,
240+
required_sso_binding=OneLogin_Saml2_Constants.BINDING_HTTP_POST
241+
)
242+
expected_settings4_5 = deepcopy(expected_settings)
243+
del expected_settings4_5['idp']['singleSignOnService']
244+
self.assertEqual(expected_settings4_5, settings4)
245+
self.assertEqual(expected_settings4_5, settings5)
246+
247+
settings6 = OneLogin_Saml2_IdPMetadataParser.parse(
248+
xmldoc,
249+
required_sso_binding=OneLogin_Saml2_Constants.BINDING_HTTP_REDIRECT,
250+
required_slo_binding=OneLogin_Saml2_Constants.BINDING_HTTP_POST
251+
)
252+
settings7 = OneLogin_Saml2_IdPMetadataParser.parse(
253+
xmldoc,
254+
required_slo_binding=OneLogin_Saml2_Constants.BINDING_HTTP_POST
255+
)
256+
expected_settings6_7 = deepcopy(expected_settings)
257+
del expected_settings6_7['idp']['singleLogoutService']
258+
self.assertEqual(expected_settings6_7, settings6)
259+
self.assertEqual(expected_settings6_7, settings7)
72260

73261
def testMergeSettings(self):
74262
"""
75263
Tests the merge_settings method of the OneLogin_Saml2_IdPMetadataParser
76264
"""
265+
77266
with self.assertRaises(AttributeError):
78267
settings_result = OneLogin_Saml2_IdPMetadataParser.merge_settings(None, {})
79268

0 commit comments

Comments
 (0)