Skip to content

Commit 78e753c

Browse files
committed
Minimal changes to enforce safeinput assertions & repair email validation.
The wiring of the pre-existing safeinput tests into the automated suite was executing them but did not raise assertion errors upon test strings not being handled correctly thus did not enforce correctness. Break the test values into pass validation/fail value lists. Loop these lists and use a similar approach to testcore wherein a non-zero exit is triggered for if a test string passes is allowed/rejected when it should not be and wire these exits to trip AssertionError when under test. In doing so it was discovered that email validation was not behaving the same across versions and this turns out to be a combination of dependency versioning problems in combination with overly broad assertion handling in the suite masking issues. Address this here.
1 parent bbe9bea commit 78e753c

File tree

3 files changed

+101
-32
lines changed

3 files changed

+101
-32
lines changed

mig/shared/safeinput.py

Lines changed: 79 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -884,8 +884,14 @@ def valid_email_address(addr, allow_real_name=True):
884884
raise InputException("Only plain addresses without name allowed")
885885
if not silent_email_validator(plain_addr):
886886
raise InputException("No actual email address included")
887-
merged = formataddr(name_address_pair)
888-
if merged != addr:
887+
raise_error = False
888+
try:
889+
merged = formataddr(name_address_pair)
890+
if merged != addr:
891+
raise InputException("")
892+
except (InputException, UnicodeEncodeError) as exc:
893+
raise_error = True
894+
if raise_error:
889895
raise InputException("Invalid email format")
890896

891897

@@ -2281,24 +2287,49 @@ def __str__(self):
22812287
return force_utf8(force_unicode(self.value))
22822288

22832289

2284-
def main(_print=print):
2290+
def main(_exit=sys.exit, _print=print):
22852291
print = _print # workaround print as reserved word on PY2
22862292

22872293
for test_org in ('UCPH', 'Some University, Some Dept.', 'Green Shoes Ltd.',
2288-
u'Unicode Org', "Invalid R+D", "Invalid R/D",
2294+
u'Unicode Org'):
2295+
try:
2296+
print('Testing valid_organization: %r' % test_org)
2297+
print('Filtered organization: %s' % filter_organization(test_org))
2298+
valid_organization(test_org)
2299+
print('Accepted raw organization!')
2300+
except InputException as exc:
2301+
print('Rejected raw organization %r: %s' % (test_org, exc))
2302+
_exit(1)
2303+
2304+
for test_org in (
2305+
"Invalid R+D", "Invalid R/D",
22892306
"Invalid R@D", 'Test HTML Invalid <code/>'):
22902307
try:
22912308
print('Testing valid_organization: %r' % test_org)
22922309
print('Filtered organization: %s' % filter_organization(test_org))
22932310
valid_organization(test_org)
22942311
print('Accepted raw organization!')
2295-
except Exception as exc:
2312+
_exit(1)
2313+
except InputException as exc:
22962314
print('Rejected raw organization %r: %s' % (test_org, exc))
22972315

22982316
for test_path in ('test.txt', 'Test Æøå', 'Test Überh4x0r',
22992317
'Test valid Jean-Luc Géraud', 'Test valid Źacãŕ',
23002318
'Test valid special%&()!$¶â€', 'Test look-alike-å å',
2301-
'Test north Atlantic Barður Ðýþ', 'Test exotic لرحيم',
2319+
'Test north Atlantic Barður Ðýþ'):
2320+
try:
2321+
print('Testing valid_path: %s' % test_path)
2322+
print('Filtered path: %s' % filter_path(test_path))
2323+
# print 'DEBUG %s only in %s' % ([test_path],
2324+
# [VALID_PATH_CHARACTERS])
2325+
valid_path(test_path)
2326+
print('Accepted raw path!')
2327+
except InputException as exc:
2328+
print('Rejected raw path %r: %s' % (test_path, exc))
2329+
_exit(1)
2330+
2331+
for test_path in (
2332+
'Test exotic لرحيم',
23022333
'Test Invalid ?', 'Test Invalid `',
23032334
'Test invalid <', 'Test Invalid >',
23042335
'Test Invalid *', 'Test Invalid "'):
@@ -2309,14 +2340,26 @@ def main(_print=print):
23092340
# [VALID_PATH_CHARACTERS])
23102341
valid_path(test_path)
23112342
print('Accepted raw path!')
2312-
except Exception as exc:
2343+
_exit(1)
2344+
except InputException as exc:
23132345
print('Rejected raw path %r: %s' % (test_path, exc))
23142346

2315-
for test_addr in ('', 'invalid', 'abc@dk', 'abc@def.org', 'abc@def.gh.org',
2316-
'aBc@Def.org', '<invalid@def.org>',
2317-
'Test <abc@def.org>', 'Test Æøå <æøå@abc.org>',
2347+
for test_addr in ('abc@def.org', 'abc@def.gh.org',
2348+
'Test <abc@def.org>',
23182349
'Test <abc.def@ghi.org>', 'Test <abc+def@ghi.org>',
23192350
'A valid-name <abc@ghi.org>',
2351+
):
2352+
try:
2353+
print('Testing valid_email_address: %s' % test_addr)
2354+
valid_email_address(test_addr)
2355+
print('Accepted raw address! %s' % [parseaddr(test_addr)])
2356+
except InputException as exc:
2357+
print('Rejected raw address %r: %s' % (test_addr, exc))
2358+
_exit(1)
2359+
2360+
for test_addr in (
2361+
'', 'invalid', 'abc@dk', 'Test Æøå <æøå@abc.org>',
2362+
'aBc@Def.org', '<invalid@def.org>',
23202363
' false-positive@ghi.org',
23212364
'False Positive <abc@ghi.org>',
23222365
' Another False Positive <abc@ghi.org>',
@@ -2331,7 +2374,8 @@ def main(_print=print):
23312374
print('Testing valid_email_address: %s' % test_addr)
23322375
valid_email_address(test_addr)
23332376
print('Accepted raw address! %s' % [parseaddr(test_addr)])
2334-
except Exception as exc:
2377+
_exit(1)
2378+
except InputException as exc:
23352379
print('Rejected raw address %r: %s' % (test_addr, exc))
23362380

23372381
# OpenID 2.0 version
@@ -2378,19 +2422,24 @@ def main(_print=print):
23782422
print("Accepted:")
23792423
for (key, val) in accepted.items():
23802424
print("\t%s: %s" % (key, val))
2381-
print("Rejected:")
2382-
for (key, val) in rejected.items():
2383-
print("\t%s: %s" % (key, val))
2425+
if rejected:
2426+
print("Rejected:")
2427+
for (key, val) in rejected.items():
2428+
print("\t%s: %s" % (key, val))
2429+
_exit(1)
2430+
23842431
user_arguments_dict['openid.sreg.fullname'] = [
23852432
force_unicode('Jonas Æøå Bardino')]
23862433
(accepted, rejected) = validated_input(
23872434
user_arguments_dict, autocreate_defaults)
23882435
print("Accepted:")
23892436
for (key, val) in accepted.items():
23902437
print("\t%s: %s" % (key, val))
2391-
print("Rejected:")
2392-
for (key, val) in rejected.items():
2393-
print("\t%s: %s" % (key, val))
2438+
if rejected:
2439+
print("Rejected:")
2440+
for (key, val) in rejected.items():
2441+
print("\t%s: %s" % (key, val))
2442+
_exit(1)
23942443

23952444
# OpenID Connect version
23962445
autocreate_defaults = {
@@ -2437,9 +2486,12 @@ def main(_print=print):
24372486
print("Accepted:")
24382487
for (key, val) in accepted.items():
24392488
print("\t%s: %s" % (key, val))
2440-
print("Rejected:")
2441-
for (key, val) in rejected.items():
2442-
print("\t%s: %s" % (key, val))
2489+
if rejected:
2490+
print("Rejected:")
2491+
for (key, val) in rejected.items():
2492+
print("\t%s: %s" % (key, val))
2493+
_exit(1)
2494+
24432495
user_arguments_dict = {'oidc.claim.aud': ['http://somedomain.org'],
24442496
'oidc.claim.country': ['DK'],
24452497
'oidc.claim.email': ['bardino@nbi.ku.dk,bardino@science.ku.dk'],
@@ -2464,9 +2516,13 @@ def main(_print=print):
24642516
print("Accepted:")
24652517
for (key, val) in accepted.items():
24662518
print("\t%s: %s" % (key, val))
2467-
print("Rejected:")
2468-
for (key, val) in rejected.items():
2469-
print("\t%s: %s" % (key, val))
2519+
if rejected:
2520+
print("Rejected:")
2521+
for (key, val) in rejected.items():
2522+
print("\t%s: %s" % (key, val))
2523+
_exit(1)
2524+
2525+
_exit(0)
24702526

24712527
if __name__ == '__main__':
24722528
main()

requirements.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,11 @@ future
44
pyotp;python_version >= "3"
55
pyotp<2.4;python_version < "3"
66
pyyaml
7-
email-validator
7+
dnspython;python_version >= "3"
8+
dnspython<2;python_version < "3"
9+
email-validator<2.1;python_version >= "3.7"
10+
email-validator<2.0;python_version >= "3" and python_version < "3.7"
11+
email-validator<1.3;python_version < "3"
812

913
# NOTE: additional optional dependencies depending on site conf are listed
1014
# in recommended.txt and can be installed in the same manner by pointing

tests/test_mig_shared_safeinput.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
sys.path.append(os.path.realpath(os.path.join(os.path.dirname(__file__), ".")))
3737

3838
from support import MigTestCase, testmain
39-
from mig.shared.safeinput import \
39+
from mig.shared.safeinput import main as safeinput_main, InputException, \
4040
filter_commonname, valid_commonname
4141

4242
PY2 = sys.version_info[0] == 2
@@ -56,12 +56,21 @@ def is_string_of_unicode(value):
5656

5757
class MigSharedSafeinput(MigTestCase):
5858

59-
def test_basic_import(self):
60-
safeimport = importlib.import_module("mig.shared.safeinput")
61-
6259
def test_existing_main(self):
63-
safeimport = importlib.import_module("mig.shared.safeinput")
64-
safeimport.main(_print=lambda _: None)
60+
def raise_on_error_exit(exit_code):
61+
if exit_code != 0:
62+
if raise_on_error_exit.last_print is not None:
63+
identifying_message = raise_on_error_exit.last_print
64+
else:
65+
identifying_message = 'unknown'
66+
raise AssertionError(
67+
'failure in unittest/testcore: %s' % (identifying_message,))
68+
raise_on_error_exit.last_print = None
69+
70+
def record_last_print(value):
71+
raise_on_error_exit.last_print = value
72+
73+
safeinput_main(_exit=raise_on_error_exit, _print=record_last_print)
6574

6675
COMMONNAME_PERMITTED = (
6776
'Firstname Lastname',
@@ -81,15 +90,15 @@ def test_commonname_valid(self):
8190
saw_raise = False
8291
try:
8392
valid_commonname(test_cn)
84-
except Exception:
93+
except InputException:
8594
saw_raise = True
8695
self.assertFalse(saw_raise)
8796

8897
for test_cn in self.COMMONNAME_PROHIBITED:
8998
saw_raise = False
9099
try:
91100
valid_commonname(test_cn)
92-
except Exception:
101+
except InputException:
93102
saw_raise = True
94103
self.assertTrue(saw_raise)
95104

0 commit comments

Comments
 (0)