Skip to content

Commit f904996

Browse files
committed
Support single-label-domains as valid. New security parameter allowSingleLabelDomains
1 parent e0ffaf8 commit f904996

File tree

8 files changed

+43
-9
lines changed

8 files changed

+43
-9
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -461,6 +461,10 @@ In addition to the required settings data (idp, sp), extra settings can be defin
461461
// Provide the desired duration, for example PT518400S (6 days)
462462
"metadataCacheDuration": null,
463463

464+
// If enabled, URLs with single-label-domains will
465+
// be allowed and not rejected by the settings validator (Enable it under Docker/Kubernetes/testing env, not recommended on production)
466+
"allowSingleLabelDomains": false,
467+
464468
// Algorithm that the toolkit will use on signing process. Options:
465469
// 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
466470
// 'http://www.w3.org/2000/09/xmldsig#dsa-sha1'

demo-bottle/saml/advanced_settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"wantNameId" : true,
1111
"wantNameIdEncrypted": false,
1212
"wantAssertionsEncrypted": false,
13+
"allowSingleLabelDomains": false,
1314
"signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
1415
"digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256"
1516
},

demo-django/saml/advanced_settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"wantNameId" : true,
1111
"wantNameIdEncrypted": false,
1212
"wantAssertionsEncrypted": false,
13+
"allowSingleLabelDomains": false,
1314
"signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
1415
"digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256"
1516
},

demo-flask/saml/advanced_settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"wantNameId" : true,
1111
"wantNameIdEncrypted": false,
1212
"wantAssertionsEncrypted": false,
13+
"allowSingleLabelDomains": false,
1314
"signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
1415
"digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256"
1516
},

demo_pyramid/demo_pyramid/saml/advanced_settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"wantNameId" : true,
1111
"wantNameIdEncrypted": false,
1212
"wantAssertionsEncrypted": false,
13+
"allowSingleLabelDomains": false,
1314
"signatureAlgorithm": "http://www.w3.org/2001/04/xmldsig-more#rsa-sha256",
1415
"digestAlgorithm": "http://www.w3.org/2001/04/xmlenc#sha256"
1516
},

src/onelogin/saml2/settings.py

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,23 +31,38 @@
3131
r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
3232
r'(?::\d+)?' # optional port
3333
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
34+
url_regex_single_label_domain = re.compile(
35+
r'^(?:[a-z0-9\.\-]*)://' # scheme is validated separately
36+
r'(?:(?:[A-Z0-9_](?:[A-Z0-9-_]{0,61}[A-Z0-9_])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain...
37+
r'(?:[A-Z0-9_](?:[A-Z0-9-_]{0,61}[A-Z0-9_]))|' # single-label-domain
38+
r'localhost|' # localhost...
39+
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
40+
r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
41+
r'(?::\d+)?' # optional port
42+
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
3443
url_schemes = ['http', 'https', 'ftp', 'ftps']
3544

3645

37-
def validate_url(url):
46+
def validate_url(url, allow_single_label_domain=False):
3847
"""
3948
Auxiliary method to validate an urllib
4049
:param url: An url to be validated
4150
:type url: string
51+
:param allow_single_label_domain: In order to allow or not single label domain
52+
:type url: bool
4253
:returns: True if the url is valid
4354
:rtype: bool
4455
"""
4556

4657
scheme = url.split('://')[0].lower()
4758
if scheme not in url_schemes:
4859
return False
49-
if not bool(url_regex.search(url)):
50-
return False
60+
if allow_single_label_domain:
61+
if not bool(url_regex_single_label_domain.search(url)):
62+
return False
63+
else:
64+
if not bool(url_regex.search(url)):
65+
return False
5166
return True
5267

5368

@@ -351,17 +366,18 @@ def check_idp_settings(self, settings):
351366
if not settings.get('idp'):
352367
errors.append('idp_not_found')
353368
else:
369+
allow_single_domain_urls = self._get_allow_single_label_domain(settings)
354370
idp = settings['idp']
355371
if not idp.get('entityId'):
356372
errors.append('idp_entityId_not_found')
357373

358374
if not idp.get('singleSignOnService', {}).get('url'):
359375
errors.append('idp_sso_not_found')
360-
elif not validate_url(idp['singleSignOnService']['url']):
376+
elif not validate_url(idp['singleSignOnService']['url'], allow_single_domain_urls):
361377
errors.append('idp_sso_url_invalid')
362378

363379
slo_url = idp.get('singleLogoutService', {}).get('url')
364-
if slo_url and not validate_url(slo_url):
380+
if slo_url and not validate_url(slo_url, allow_single_domain_urls):
365381
errors.append('idp_slo_url_invalid')
366382

367383
if 'security' in settings:
@@ -408,6 +424,7 @@ def check_sp_settings(self, settings):
408424
if not settings.get('sp'):
409425
errors.append('sp_not_found')
410426
else:
427+
allow_single_domain_urls = self._get_allow_single_label_domain(settings)
411428
# check_sp_certs uses self.__sp so I add it
412429
old_sp = self.__sp
413430
self.__sp = settings['sp']
@@ -420,7 +437,7 @@ def check_sp_settings(self, settings):
420437

421438
if not sp.get('assertionConsumerService', {}).get('url'):
422439
errors.append('sp_acs_not_found')
423-
elif not validate_url(sp['assertionConsumerService']['url']):
440+
elif not validate_url(sp['assertionConsumerService']['url'], allow_single_domain_urls):
424441
errors.append('sp_acs_url_invalid')
425442

426443
if sp.get('attributeConsumingService'):
@@ -449,7 +466,7 @@ def check_sp_settings(self, settings):
449466
errors.append('sp_attributeConsumingService_serviceDescription_type_invalid')
450467

451468
slo_url = sp.get('singleLogoutService', {}).get('url')
452-
if slo_url and not validate_url(slo_url):
469+
if slo_url and not validate_url(slo_url, allow_single_domain_urls):
453470
errors.append('sp_sls_url_invalid')
454471

455472
if 'signMetadata' in security and isinstance(security['signMetadata'], dict):
@@ -840,3 +857,7 @@ def is_debug_active(self):
840857
:rtype: boolean
841858
"""
842859
return self.__debug
860+
861+
def _get_allow_single_label_domain(self, settings):
862+
security = settings.get('security', {})
863+
return 'allowSingleLabelDomains' in security.keys() and security['allowSingleLabelDomains']

tests/src/OneLogin/saml2_tests/response_test.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,6 @@ def testDoesNotAllowSignatureWrappingAttack(self):
714714
self.assertFalse(response.is_valid(self.get_request_data()))
715715
self.assertEqual('test@onelogin.com', response.get_nameid())
716716

717-
718717
def testDoesNotAllowSignatureWrappingAttack2(self):
719718
# Signature Wraping attack 2
720719
settings = OneLogin_Saml2_Settings(self.loadSettingsJSON())
@@ -724,7 +723,6 @@ def testDoesNotAllowSignatureWrappingAttack2(self):
724723
self.assertFalse(response.is_valid(self.get_request_data()))
725724
self.assertEquals("SAML Response must contain 1 assertion", response.get_error())
726725

727-
728726
def testNodeTextAttack(self):
729727
"""
730728
Tests the get_nameid and get_attributes methods of the OneLogin_Saml2_Response

tests/src/OneLogin/saml2_tests/settings_test.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,13 @@ def testLoadSettingsFromDict(self):
6464
with self.assertRaisesRegexp(Exception, 'Invalid dict settings: idp_sso_url_invalid'):
6565
OneLogin_Saml2_Settings(settings_info)
6666

67+
settings_info['idp']['singleSignOnService']['url'] = 'http://single-label-domain'
68+
settings_info['security'] = {}
69+
settings_info['security']['allowSingleLabelDomains'] = True
70+
settings = OneLogin_Saml2_Settings(settings_info)
71+
self.assertEqual(len(settings.get_errors()), 0)
72+
73+
del settings_info['security']
6774
del settings_info['sp']
6875
del settings_info['idp']
6976
with self.assertRaisesRegexp(Exception, 'Invalid dict settings: idp_not_found,sp_not_found'):

0 commit comments

Comments
 (0)