Skip to content

Commit aef5af9

Browse files
authored
Merge pull request #164 from open-craft/mtyaka/non-ascii-support
Add support for non-ascii fields in settings.
2 parents 6f36e59 + 4e73f17 commit aef5af9

File tree

6 files changed

+109
-22
lines changed

6 files changed

+109
-22
lines changed

src/onelogin/saml2/metadata.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,7 @@ def builder(sp, authnsign=False, wsign=False, valid_until=None, cache_duration=N
173173
contacts_info.append(contact)
174174
str_contacts = '\n'.join(contacts_info) + '\n'
175175

176-
metadata = """<?xml version="1.0"?>
176+
metadata = u"""<?xml version="1.0"?>
177177
<md:EntityDescriptor xmlns:md="urn:oasis:names:tc:SAML:2.0:metadata"
178178
%(valid)s
179179
%(cache)s
@@ -241,7 +241,7 @@ def add_x509_key_descriptors(metadata, cert=None):
241241
if cert is None or cert == '':
242242
return metadata
243243
try:
244-
xml = parseString(metadata)
244+
xml = parseString(metadata.encode('utf-8'))
245245
except Exception as e:
246246
raise Exception('Error parsing metadata. ' + e.message)
247247

src/onelogin/saml2/utils.py

Lines changed: 16 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ def decode_base64_and_inflate(value):
7575
:rtype: string
7676
"""
7777

78-
return zlib.decompress(base64.b64decode(value), -15)
78+
return zlib.decompress(base64.b64decode(value), -15).decode('utf-8')
7979

8080
@staticmethod
8181
def deflate_and_base64_encode(value):
@@ -86,7 +86,7 @@ def deflate_and_base64_encode(value):
8686
:returns: The deflated and encoded string
8787
:rtype: string
8888
"""
89-
return base64.b64encode(zlib.compress(value)[2:-4])
89+
return base64.b64encode(zlib.compress(value.encode('utf-8'))[2:-4])
9090

9191
@staticmethod
9292
def validate_xml(xml, schema, debug=False):
@@ -107,11 +107,11 @@ def validate_xml(xml, schema, debug=False):
107107
if isinstance(xml, Document):
108108
xml = xml.toxml()
109109
elif isinstance(xml, etree._Element):
110-
xml = tostring(xml)
110+
xml = tostring(xml, encoding='unicode')
111111

112112
# Switch to lxml for schema validation
113113
try:
114-
dom = fromstring(str(xml))
114+
dom = fromstring(xml.encode('utf-8'))
115115
except Exception:
116116
return 'unloaded_xml'
117117

@@ -130,7 +130,7 @@ def validate_xml(xml, schema, debug=False):
130130

131131
return 'invalid_xml'
132132

133-
return parseString(etree.tostring(dom))
133+
return parseString(etree.tostring(dom, encoding='unicode').encode('utf-8'))
134134

135135
@staticmethod
136136
def format_cert(cert, heads=True):
@@ -655,7 +655,7 @@ def generate_name_id(value, sp_nq, sp_format, cert=None, debug=False, nq=None):
655655

656656
edata = enc_ctx.encryptXml(enc_data, elem[0])
657657

658-
newdoc = parseString(etree.tostring(edata))
658+
newdoc = parseString(etree.tostring(edata, encoding='unicode').encode('utf-8'))
659659

660660
if newdoc.hasChildNodes():
661661
child = newdoc.firstChild
@@ -791,7 +791,7 @@ def add_sign(xml, key, cert, debug=False, sign_algorithm=OneLogin_Saml2_Constant
791791
elem = xml
792792
elif isinstance(xml, Document):
793793
xml = xml.toxml()
794-
elem = fromstring(str(xml))
794+
elem = fromstring(xml.encode('utf-8'))
795795
elif isinstance(xml, Element):
796796
xml.setAttributeNS(
797797
unicode(OneLogin_Saml2_Constants.NS_SAMLP),
@@ -804,9 +804,9 @@ def add_sign(xml, key, cert, debug=False, sign_algorithm=OneLogin_Saml2_Constant
804804
unicode(OneLogin_Saml2_Constants.NS_SAML)
805805
)
806806
xml = xml.toxml()
807-
elem = fromstring(str(xml))
807+
elem = fromstring(xml.encode('utf-8'))
808808
elif isinstance(xml, basestring):
809-
elem = fromstring(str(xml))
809+
elem = fromstring(xml.encode('utf-8'))
810810
else:
811811
raise Exception('Error parsing xml string')
812812

@@ -849,7 +849,7 @@ def add_sign(xml, key, cert, debug=False, sign_algorithm=OneLogin_Saml2_Constant
849849
dsig_ctx.signKey = sign_key
850850
dsig_ctx.sign(signature)
851851

852-
newdoc = parseString(etree.tostring(elem))
852+
newdoc = parseString(etree.tostring(elem, encoding='unicode').encode('utf-8'))
853853

854854
signature_nodes = newdoc.getElementsByTagName("Signature")
855855

@@ -895,7 +895,7 @@ def validate_sign(xml, cert=None, fingerprint=None, fingerprintalg='sha1', valid
895895
elem = xml
896896
elif isinstance(xml, Document):
897897
xml = xml.toxml()
898-
elem = fromstring(str(xml))
898+
elem = fromstring(xml.encode('utf-8'))
899899
elif isinstance(xml, Element):
900900
xml.setAttributeNS(
901901
unicode(OneLogin_Saml2_Constants.NS_SAMLP),
@@ -908,9 +908,9 @@ def validate_sign(xml, cert=None, fingerprint=None, fingerprintalg='sha1', valid
908908
unicode(OneLogin_Saml2_Constants.NS_SAML)
909909
)
910910
xml = xml.toxml()
911-
elem = fromstring(str(xml))
911+
elem = fromstring(xml.encode('utf-8'))
912912
elif isinstance(xml, basestring):
913-
elem = fromstring(str(xml))
913+
elem = fromstring(xml.encode('utf-8'))
914914
else:
915915
raise Exception('Error parsing xml string')
916916

@@ -963,17 +963,17 @@ def validate_metadata_sign(xml, cert=None, fingerprint=None, fingerprintalg='sha
963963
elem = xml
964964
elif isinstance(xml, Document):
965965
xml = xml.toxml()
966-
elem = fromstring(str(xml))
966+
elem = fromstring(xml.encode('utf-8'))
967967
elif isinstance(xml, Element):
968968
xml.setAttributeNS(
969969
unicode(OneLogin_Saml2_Constants.NS_MD),
970970
'xmlns:md',
971971
unicode(OneLogin_Saml2_Constants.NS_MD)
972972
)
973973
xml = xml.toxml()
974-
elem = fromstring(str(xml))
974+
elem = fromstring(xml.encode('utf-8'))
975975
elif isinstance(xml, basestring):
976-
elem = fromstring(str(xml))
976+
elem = fromstring(xml.encode('utf-8'))
977977
else:
978978
raise Exception('Error parsing xml string')
979979

tests/settings/settings6.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"
19+
},
20+
"singleLogoutService": {
21+
"url": "http://idp.example.com/SingleLogoutService.php"
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": "Téçhnïçäl Nämé",
33+
"emailAddress": "[email protected]"
34+
},
35+
"support": {
36+
"givenName": "Süppört Nämé",
37+
"emailAddress": "[email protected]"
38+
}
39+
},
40+
"organization": {
41+
"en-US": {
42+
"name": "sp_test",
43+
"displayname": "Sérvïçé prövïdér",
44+
"url": "http://sp.example.com"
45+
}
46+
}
47+
}

tests/src/OneLogin/saml2_tests/auth_test.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,8 +22,8 @@ class OneLogin_Saml2_Auth_Test(unittest.TestCase):
2222
data_path = join(dirname(dirname(dirname(dirname(__file__)))), 'data')
2323
settings_path = join(dirname(dirname(dirname(dirname(__file__)))), 'settings')
2424

25-
def loadSettingsJSON(self):
26-
filename = join(self.settings_path, 'settings1.json')
25+
def loadSettingsJSON(self, name='settings1.json'):
26+
filename = join(self.settings_path, name)
2727
if exists(filename):
2828
stream = open(filename, 'r')
2929
settings = json.load(stream)
@@ -566,6 +566,20 @@ def testLogin(self):
566566
hostname = OneLogin_Saml2_Utils.get_self_host(request_data)
567567
self.assertIn(u'http://%s/index.html' % hostname, parsed_query['RelayState'])
568568

569+
def testLoginWithUnicodeSettings(self):
570+
"""
571+
Tests the login method of the OneLogin_Saml2_Auth class
572+
Case Login with unicode settings. An AuthnRequest is built an redirect executed
573+
"""
574+
settings_info = self.loadSettingsJSON('settings6.json')
575+
request_data = self.get_request()
576+
auth = OneLogin_Saml2_Auth(request_data, old_settings=settings_info)
577+
578+
target_url = auth.login()
579+
parsed_query = parse_qs(urlparse(target_url)[4])
580+
hostname = OneLogin_Saml2_Utils.get_self_host(request_data)
581+
self.assertIn(u'http://%s/index.html' % hostname, parsed_query['RelayState'])
582+
569583
def testLoginWithRelayState(self):
570584
"""
571585
Tests the login method of the OneLogin_Saml2_Auth class

tests/src/OneLogin/saml2_tests/settings_test.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,8 @@ class OneLogin_Saml2_Settings_Test(unittest.TestCase):
1818
data_path = join(dirname(dirname(dirname(dirname(__file__)))), 'data')
1919
settings_path = join(dirname(dirname(dirname(dirname(__file__)))), 'settings')
2020

21-
def loadSettingsJSON(self):
22-
filename = join(self.settings_path, 'settings1.json')
21+
def loadSettingsJSON(self, name='settings1.json'):
22+
filename = join(self.settings_path, name)
2323
if exists(filename):
2424
stream = open(filename, 'r')
2525
settings = json.load(stream)
@@ -397,6 +397,20 @@ def testGetSPMetadata(self):
397397
self.assertIn('<md:SingleLogoutService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" Location="http://stuff.com/endpoints/endpoints/sls.php"/>', metadata)
398398
self.assertIn('<md:NameIDFormat>urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified</md:NameIDFormat>', metadata)
399399

400+
def testGetUnicodeSPMetadata(self):
401+
"""
402+
Tests the getSPMetadata method of the OneLogin_Saml2_Settings
403+
Case unicode metadata
404+
"""
405+
settings = OneLogin_Saml2_Settings(self.loadSettingsJSON('settings6.json'))
406+
metadata = settings.get_sp_metadata()
407+
408+
self.assertIn('<md:SPSSODescriptor', metadata)
409+
self.assertIn('<md:AssertionConsumerService Binding="urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" Location="http://stuff.com/endpoints/endpoints/acs.php" index="1"/>', metadata)
410+
self.assertIn(u'<md:OrganizationDisplayName xml:lang="en-US">Sérvïçé prövïdér</md:OrganizationDisplayName>', metadata)
411+
self.assertIn(u'<md:GivenName>Téçhnïçäl Nämé</md:GivenName>', metadata)
412+
self.assertIn(u'<md:GivenName>Süppört Nämé</md:GivenName>', metadata)
413+
400414
def testGetSPMetadataSigned(self):
401415
"""
402416
Tests the getSPMetadata method of the OneLogin_Saml2_Settings

tests/src/OneLogin/saml2_tests/utils_test.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,18 @@ def file_contents(self, filename):
3939
f.close()
4040
return content
4141

42+
def testDeflateBase64Roundtrip(self):
43+
"""
44+
Tests deflate_and_base64_encode and decode_base64_and_inflate methods of OneLogin_Saml2_Utils
45+
"""
46+
body = 'Some random string.'
47+
encoded = OneLogin_Saml2_Utils.deflate_and_base64_encode(body)
48+
self.assertEqual(OneLogin_Saml2_Utils.decode_base64_and_inflate(encoded), body)
49+
50+
unicode_body = u'Sömé rändöm nön-äsçïï strïng.'
51+
unicode_encoded = OneLogin_Saml2_Utils.deflate_and_base64_encode(unicode_body)
52+
self.assertEqual(OneLogin_Saml2_Utils.decode_base64_and_inflate(unicode_encoded), unicode_body)
53+
4254
def testValidateXML(self):
4355
"""
4456
Tests the validate_xml method of the OneLogin_Saml2_Utils

0 commit comments

Comments
 (0)