Skip to content

Commit b4c7cb9

Browse files
committed
Escape ampersands when creating AuthnRequests
In some cases, the IdP singleSignOnService or singleLogoutService url values will contain a query string and that query string will contain an ampersand ("&"). If not escaped, this ampersand will prevent valid XML creation by python3-saml and thus the IdP will not be able to correctly parse the received AuthnRequest.
1 parent c660f6f commit b4c7cb9

File tree

3 files changed

+77
-1
lines changed

3 files changed

+77
-1
lines changed

src/onelogin/saml2/authn_request.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ def __init__(self, settings, force_authn=False, is_passive=False, set_nameid_pol
5151
self.__id = uid
5252
issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now())
5353

54-
destination = idp_data['singleSignOnService']['url']
54+
destination = idp_data['singleSignOnService']['url'].replace("&", "&")
5555

5656
provider_name_str = ''
5757
organization_data = settings.get_organization()

tests/settings/settings10.json

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
{
2+
"strict": false,
3+
"debug": false,
4+
"custom_base_path": "../../../tests/data/customPath/",
5+
"sp": {
6+
"entityId": "http://stuff.com/endpoints/metadata.php",
7+
"assertionConsumerService": {
8+
"url": "http://stuff.com/endpoints/endpoints/acs.php"
9+
},
10+
"singleLogoutService": {
11+
"url": "http://stuff.com/endpoints/endpoints/sls.php"
12+
},
13+
"NameIDFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified"
14+
},
15+
"idp": {
16+
"entityId": "http://idp.example.com/",
17+
"singleSignOnService": {
18+
"url": "http://idp.example.com/SSOService.php?arg1=a&arg2=b"
19+
},
20+
"singleLogoutService": {
21+
"url": "http://idp.example.com/SingleLogoutService.php?arg1=a&arg2=b"
22+
},
23+
"x509cert": "MIICgTCCAeoCCQCbOlrWDdX7FTANBgkqhkiG9w0BAQUFADCBhDELMAkGA1UEBhMCTk8xGDAWBgNVBAgTD0FuZHJlYXMgU29sYmVyZzEMMAoGA1UEBxMDRm9vMRAwDgYDVQQKEwdVTklORVRUMRgwFgYDVQQDEw9mZWlkZS5lcmxhbmcubm8xITAfBgkqhkiG9w0BCQEWEmFuZHJlYXNAdW5pbmV0dC5ubzAeFw0wNzA2MTUxMjAxMzVaFw0wNzA4MTQxMjAxMzVaMIGEMQswCQYDVQQGEwJOTzEYMBYGA1UECBMPQW5kcmVhcyBTb2xiZXJnMQwwCgYDVQQHEwNGb28xEDAOBgNVBAoTB1VOSU5FVFQxGDAWBgNVBAMTD2ZlaWRlLmVybGFuZy5ubzEhMB8GCSqGSIb3DQEJARYSYW5kcmVhc0B1bmluZXR0Lm5vMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDivbhR7P516x/S3BqKxupQe0LONoliupiBOesCO3SHbDrl3+q9IbfnfmE04rNuMcPsIxB161TdDpIesLCn7c8aPHISKOtPlAeTZSnb8QAu7aRjZq3+PbrP5uW3TcfCGPtKTytHOge/OlJbo078dVhXQ14d1EDwXJW1rRXuUt4C8QIDAQABMA0GCSqGSIb3DQEBBQUAA4GBACDVfp86HObqY+e8BUoWQ9+VMQx1ASDohBjwOsg2WykUqRXF+dLfcUH9dWR63CtZIKFDbStNomPnQz7nbK+onygwBspVEbnHuUihZq3ZUdmumQqCw4Uvs/1Uvq3orOo/WJVhTyvLgFVK2QarQ4/67OZfHd7R+POBXhophSMv1ZOo"
24+
},
25+
"security": {
26+
"authnRequestsSigned": false,
27+
"wantAssertionsSigned": false,
28+
"signMetadata": false
29+
},
30+
"contactPerson": {
31+
"technical": {
32+
"givenName": "technical_name",
33+
"emailAddress": "[email protected]"
34+
},
35+
"support": {
36+
"givenName": "support_name",
37+
"emailAddress": "[email protected]"
38+
}
39+
},
40+
"organization": {
41+
"en-US": {
42+
"name": "sp_test",
43+
"displayname": "SP test",
44+
"url": "http://sp.example.com"
45+
}
46+
}
47+
}

tests/src/OneLogin/saml2_tests/authn_request_test.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import json
77
from os.path import dirname, join, exists
88
import unittest
9+
import xml.etree.ElementTree as ET
910

1011
from onelogin.saml2 import compat
1112
from onelogin.saml2.authn_request import OneLogin_Saml2_Authn_Request
@@ -305,6 +306,34 @@ def testCreateDeflatedSAMLRequestURLParameter(self):
305306
inflated = compat.to_string(OneLogin_Saml2_Utils.decode_base64_and_inflate(payload))
306307
self.assertRegex(inflated, '^<samlp:AuthnRequest')
307308

309+
def testSAMLRequestURLParameterWithAmpersand(self):
310+
"""
311+
Tests the OneLogin_Saml2_Authn_Request Constructor.
312+
Test that ampersand
313+
"""
314+
settings = OneLogin_Saml2_Settings(self.loadSettingsJSON(name='settings10.json'))
315+
authn_request = OneLogin_Saml2_Authn_Request(settings)
316+
parameters = {
317+
'SAMLRequest': authn_request.get_request()
318+
}
319+
auth_url = OneLogin_Saml2_Utils.redirect('http://idp.example.com/SSOService.php', parameters, True)
320+
self.assertRegex(auth_url, r'^http://idp\.example\.com\/SSOService\.php\?SAMLRequest=')
321+
exploded = urlparse(auth_url)
322+
exploded = parse_qs(exploded[4])
323+
payload = exploded['SAMLRequest'][0]
324+
inflated = compat.to_string(OneLogin_Saml2_Utils.decode_base64_and_inflate(payload))
325+
self.assertRegex(inflated, '^<samlp:AuthnRequest')
326+
327+
try:
328+
root = ET.fromstring(inflated)
329+
except ET.ParseError:
330+
self.fail('Unable to parse AuthnRequest')
331+
332+
destination = root.get('Destination')
333+
# This URL includes an un-escaped ampersand as should be the case after parsing the XML.
334+
url = 'http://idp.example.com/SSOService.php?arg1=a&arg2=b'
335+
self.assertEqual(url, destination)
336+
308337
def testCreateEncSAMLRequest(self):
309338
"""
310339
Tests the OneLogin_Saml2_Authn_Request Constructor.

0 commit comments

Comments
 (0)