Skip to content

Commit a1e1f8b

Browse files
committed
Close #217. Support single-label-domains as valid. New security parameter allowSingleLabelDomains
1 parent 3aa3c57 commit a1e1f8b

File tree

8 files changed

+47
-14
lines changed

8 files changed

+47
-14
lines changed

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -430,6 +430,10 @@ In addition to the required settings data (idp, sp), extra settings can be defin
430430
// Provide the desire Duration, for example PT518400S (6 days)
431431
"metadataCacheDuration": null,
432432

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

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-tornado/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/logout_response.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,6 @@ def build(self, in_response_to):
158158
:type in_response_to: string
159159
"""
160160
sp_data = self.__settings.get_sp_data()
161-
idp_data = self.__settings.get_idp_data()
162161

163162
uid = OneLogin_Saml2_Utils.generate_unique_id()
164163
issue_instant = OneLogin_Saml2_Utils.parse_time_to_SAML(OneLogin_Saml2_Utils.now())

src/onelogin/saml2/settings.py

Lines changed: 26 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,19 @@
3939
r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
4040
r'(?::\d+)?' # optional port
4141
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
42+
url_regex_single_label_domain = re.compile(
43+
r'^(?:[a-z0-9\.\-]*)://' # scheme is validated separately
44+
r'(?:(?:[A-Z0-9_](?:[A-Z0-9-_]{0,61}[A-Z0-9_])?\.)+(?:[A-Z]{2,6}\.?|[A-Z0-9-]{2,}\.?)|' # domain...
45+
r'(?:[A-Z0-9_](?:[A-Z0-9-_]{0,61}[A-Z0-9_]))|' # single-label-domain
46+
r'localhost|' # localhost...
47+
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|' # ...or ipv4
48+
r'\[?[A-F0-9]*:[A-F0-9:]+\]?)' # ...or ipv6
49+
r'(?::\d+)?' # optional port
50+
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
4251
url_schemes = ['http', 'https', 'ftp', 'ftps']
4352

4453

45-
def validate_url(url):
54+
def validate_url(url, allow_single_label_domain=False):
4655
"""
4756
Auxiliary method to validate an urllib
4857
:param url: An url to be validated
@@ -54,8 +63,12 @@ def validate_url(url):
5463
scheme = url.split('://')[0].lower()
5564
if scheme not in url_schemes:
5665
return False
57-
if not bool(url_regex.search(url)):
58-
return False
66+
if allow_single_label_domain:
67+
if not bool(url_regex_single_label_domain.search(url)):
68+
return False
69+
else:
70+
if not bool(url_regex.search(url)):
71+
return False
5972
return True
6073

6174

@@ -353,17 +366,18 @@ def check_idp_settings(self, settings):
353366
if not settings.get('idp'):
354367
errors.append('idp_not_found')
355368
else:
369+
allow_single_domain_urls = self._get_allow_single_label_domain(settings)
356370
idp = settings['idp']
357371
if not idp.get('entityId'):
358372
errors.append('idp_entityId_not_found')
359373

360374
if not idp.get('singleSignOnService', {}).get('url'):
361375
errors.append('idp_sso_not_found')
362-
elif not validate_url(idp['singleSignOnService']['url']):
376+
elif not validate_url(idp['singleSignOnService']['url'], allow_single_domain_urls):
363377
errors.append('idp_sso_url_invalid')
364378

365379
slo_url = idp.get('singleLogoutService', {}).get('url')
366-
if slo_url and not validate_url(slo_url):
380+
if slo_url and not validate_url(slo_url, allow_single_domain_urls):
367381
errors.append('idp_slo_url_invalid')
368382

369383
if 'security' in settings:
@@ -407,6 +421,7 @@ def check_sp_settings(self, settings):
407421
if not settings.get('sp'):
408422
errors.append('sp_not_found')
409423
else:
424+
allow_single_domain_urls = self._get_allow_single_label_domain(settings)
410425
# check_sp_certs uses self.__sp so I add it
411426
old_sp = self.__sp
412427
self.__sp = settings['sp']
@@ -419,7 +434,7 @@ def check_sp_settings(self, settings):
419434

420435
if not sp.get('assertionConsumerService', {}).get('url'):
421436
errors.append('sp_acs_not_found')
422-
elif not validate_url(sp['assertionConsumerService']['url']):
437+
elif not validate_url(sp['assertionConsumerService']['url'], allow_single_domain_urls):
423438
errors.append('sp_acs_url_invalid')
424439

425440
if sp.get('attributeConsumingService'):
@@ -448,7 +463,7 @@ def check_sp_settings(self, settings):
448463
errors.append('sp_attributeConsumingService_serviceDescription_type_invalid')
449464

450465
slo_url = sp.get('singleLogoutService', {}).get('url')
451-
if slo_url and not validate_url(slo_url):
466+
if slo_url and not validate_url(slo_url, allow_single_domain_urls):
452467
errors.append('sp_sls_url_invalid')
453468

454469
if 'signMetadata' in security and isinstance(security['signMetadata'], dict):
@@ -833,3 +848,7 @@ def is_debug_active(self):
833848
:rtype: boolean
834849
"""
835850
return self.__debug
851+
852+
def _get_allow_single_label_domain(self, settings):
853+
security = settings.get('security', {})
854+
return 'allowSingleLabelDomains' in security.keys() and security['allowSingleLabelDomains']

tests/src/OneLogin/saml2_tests/settings_test.py

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,18 @@ def testLoadSettingsFromDict(self):
7171
except Exception as e:
7272
self.assertIn('Invalid dict settings: idp_sso_url_invalid', str(e))
7373

74+
settings_info['idp']['singleSignOnService']['url'] = 'http://single-label-domain'
75+
settings_info['security'] = {}
76+
settings_info['security']['allowSingleLabelDomains'] = True
77+
settings_4 = OneLogin_Saml2_Settings(settings_info)
78+
self.assertEqual(len(settings_4.get_errors()), 0)
79+
80+
del settings_info['security']
7481
del settings_info['sp']
7582
del settings_info['idp']
7683
try:
77-
settings_4 = OneLogin_Saml2_Settings(settings_info)
78-
self.assertNotEqual(len(settings_4.get_errors()), 0)
84+
settings_5 = OneLogin_Saml2_Settings(settings_info)
85+
self.assertNotEqual(len(settings_5.get_errors()), 0)
7986
except Exception as e:
8087
self.assertIn('Invalid dict settings', str(e))
8188
self.assertIn('idp_not_found', str(e))
@@ -85,17 +92,17 @@ def testLoadSettingsFromDict(self):
8592
settings_info['security']['authnRequestsSigned'] = True
8693
settings_info['custom_base_path'] = dirname(__file__)
8794
try:
88-
settings_5 = OneLogin_Saml2_Settings(settings_info)
89-
self.assertNotEqual(len(settings_5.get_errors()), 0)
95+
settings_6 = OneLogin_Saml2_Settings(settings_info)
96+
self.assertNotEqual(len(settings_6.get_errors()), 0)
9097
except Exception as e:
9198
self.assertIn('Invalid dict settings: sp_cert_not_found_and_required', str(e))
9299

93100
settings_info = self.loadSettingsJSON()
94101
settings_info['security']['nameIdEncrypted'] = True
95102
del settings_info['idp']['x509cert']
96103
try:
97-
settings_6 = OneLogin_Saml2_Settings(settings_info)
98-
self.assertNotEqual(len(settings_6.get_errors()), 0)
104+
settings_7 = OneLogin_Saml2_Settings(settings_info)
105+
self.assertNotEqual(len(settings_7.get_errors()), 0)
99106
except Exception as e:
100107
self.assertIn('Invalid dict settings: idp_cert_not_found_and_required', str(e))
101108

0 commit comments

Comments
 (0)