From 49d22359307842caa1bff0f5049c91b601a86cc0 Mon Sep 17 00:00:00 2001 From: Juan Castro Date: Mon, 11 Dec 2017 19:05:39 -0600 Subject: [PATCH] AN-53 - Frontier SMTP yahoo validation - As per @horkhe recommendations, left address in-tact, updated the contract for each plugin to consume EmailAddress object, then taking mailbox from that object to satisfy existing. - Fixing email address object name reference --- flanker/addresslib/address.py | 4 +-- flanker/addresslib/plugins/aol.py | 5 +++- flanker/addresslib/plugins/gmail.py | 5 +++- flanker/addresslib/plugins/google.py | 5 +++- flanker/addresslib/plugins/hotmail.py | 5 +++- flanker/addresslib/plugins/icloud.py | 5 +++- flanker/addresslib/plugins/yahoo.py | 34 +++++++++++++++++------ tests/addresslib/validator_test.py | 40 +++++++++++++++++++++++++++ 8 files changed, 88 insertions(+), 15 deletions(-) diff --git a/flanker/addresslib/address.py b/flanker/addresslib/address.py index 9a751ae2..10dcdddb 100644 --- a/flanker/addresslib/address.py +++ b/flanker/addresslib/address.py @@ -312,7 +312,7 @@ def validate_address(addr_spec, metrics=False): bstart = time() plugin = plugin_for_esp(exchanger) mtimes['custom_grammar'] = time() - bstart - if plugin and plugin.validate(addr_parts[0]) is False: + if plugin and plugin.validate(paddr) is False: _log.warning('failed custom grammer check for %s/%s', addr_spec, plugin.__name__) return None, mtimes @@ -369,7 +369,7 @@ def validate_list(addr_list, as_tuple=False, metrics=False): # lookup custom local-part grammar if it exists plugin = plugin_for_esp(exchanger) bstart = time() - if plugin and plugin.validate(paddr.mailbox) is False: + if plugin and plugin.validate(paddr) is False: ulist.append(paddr.full_spec()) continue mtimes['custom_grammar'] = time() - bstart diff --git a/flanker/addresslib/plugins/aol.py b/flanker/addresslib/plugins/aol.py index 05acc2e2..4555e2ac 100644 --- a/flanker/addresslib/plugins/aol.py +++ b/flanker/addresslib/plugins/aol.py @@ -42,7 +42,10 @@ ''', re.MULTILINE | re.VERBOSE) -def validate(localpart): +def validate(email_addr): + # Setup for handling EmailAddress type instead of literal string + localpart = email_addr.mailbox + # check string exists and not empty if not localpart: return False diff --git a/flanker/addresslib/plugins/gmail.py b/flanker/addresslib/plugins/gmail.py index 232692fd..58597a1d 100644 --- a/flanker/addresslib/plugins/gmail.py +++ b/flanker/addresslib/plugins/gmail.py @@ -48,7 +48,10 @@ ''', re.MULTILINE | re.VERBOSE) -def validate(localpart): +def validate(email_addr): + # Setup for handling EmailAddress type instead of literal string + localpart = email_addr.mailbox + # check string exists and not empty if not localpart: return False diff --git a/flanker/addresslib/plugins/google.py b/flanker/addresslib/plugins/google.py index 4725a0f2..fb3d0c81 100644 --- a/flanker/addresslib/plugins/google.py +++ b/flanker/addresslib/plugins/google.py @@ -61,7 +61,10 @@ ''', re.MULTILINE | re.VERBOSE) -def validate(localpart): +def validate(email_addr): + # Setup for handling EmailAddress type instead of literal string + localpart = email_addr.mailbox + # check string exists and not empty if not localpart: return False diff --git a/flanker/addresslib/plugins/hotmail.py b/flanker/addresslib/plugins/hotmail.py index af4c1dc5..60f41173 100644 --- a/flanker/addresslib/plugins/hotmail.py +++ b/flanker/addresslib/plugins/hotmail.py @@ -53,7 +53,10 @@ ''', re.MULTILINE | re.VERBOSE) -def validate(localpart): +def validate(email_addr): + # Setup for handling EmailAddress type instead of literal string + localpart = email_addr.mailbox + # check string exists and not empty if not localpart: return False diff --git a/flanker/addresslib/plugins/icloud.py b/flanker/addresslib/plugins/icloud.py index aa8b1f90..f0d04a3b 100644 --- a/flanker/addresslib/plugins/icloud.py +++ b/flanker/addresslib/plugins/icloud.py @@ -61,7 +61,10 @@ ''', re.MULTILINE | re.VERBOSE) -def validate(localpart): +def validate(email_addr): + # Setup for handling EmailAddress type instead of literal string + localpart = email_addr.mailbox + # check string exists and not empty if not localpart: return False diff --git a/flanker/addresslib/plugins/yahoo.py b/flanker/addresslib/plugins/yahoo.py index f68d48a3..24d59654 100644 --- a/flanker/addresslib/plugins/yahoo.py +++ b/flanker/addresslib/plugins/yahoo.py @@ -68,14 +68,20 @@ \- ''', re.MULTILINE | re.VERBOSE) +YAHOO_MANAGED = ['yahoo.com', 'ymail.com', 'rocketmail.com'] + + +def validate(email_addr): + # Setup for handling EmailAddress type instead of literal string + localpart = email_addr.mailbox + managed = managed_email(email_addr.hostname) -def validate(localpart): # check string exists and not empty if not localpart: return False # must start with letter - if len(localpart) < 1 or ALPHA.match(localpart[0]) is None: + if len(localpart) < 1 or (ALPHA.match(localpart[0]) is None and managed): return False # must end with letter or digit @@ -84,13 +90,17 @@ def validate(localpart): # only disposable addresses may contain hyphens if HYPHEN.search(localpart): - return _validate_disposable(localpart) + return _validate_disposable(email_addr) # otherwise, normal validation - return _validate_primary(localpart) + return _validate_primary(email_addr) + +def _validate_primary(email_addr): + # Setup for handling EmailAddress type instead of literal string + localpart = email_addr.mailbox + managed = managed_email(email_addr.hostname) -def _validate_primary(localpart): # length check l = len(localpart) if l < 4 or l > 32: @@ -105,7 +115,7 @@ def _validate_primary(localpart): # local-part must being with alpha alpa = stream.get_token(ALPHA) - if alpa is None: + if alpa is None and managed: return False while True: @@ -123,7 +133,11 @@ def _validate_primary(localpart): return True -def _validate_disposable(localpart): +def _validate_disposable(email_addr): + # Setup for handling EmailAddress type instead of literal string + localpart = email_addr.mailbox + managed = managed_email(email_addr.hostname) + # length check (base + hyphen + keyword) l = len(localpart) if l < 3 or l > 65: @@ -145,7 +159,7 @@ def _validate_disposable(localpart): # must being with alpha begin = stream.get_token(ALPHA) - if begin is None: + if begin is None and managed: return False while True: @@ -167,3 +181,7 @@ def _validate_disposable(localpart): return False return True + + +def managed_email(hostname): + return hostname in YAHOO_MANAGED diff --git a/tests/addresslib/validator_test.py b/tests/addresslib/validator_test.py index 48bbe93d..eaab4c5a 100644 --- a/tests/addresslib/validator_test.py +++ b/tests/addresslib/validator_test.py @@ -253,3 +253,43 @@ def test_validate_address_metrics(): # assert_equal(addr.full_spec(), 'foo@[1.2.3.4]') # mock_lookup_domain.assert_not_called() # mock_connect_to_mail_exchanger.assert_called_once_with(['1.2.3.4']) + +@patch('flanker.addresslib.validate.connect_to_mail_exchanger') +@patch('flanker.addresslib.validate.lookup_domain') +def test_mx_yahoo_dual_lookup(ld, cmx): + ld.return_value = ['mta7.am0.yahoodns.net', 'mta6.am0.yahoodns.net'] + cmx.return_value = 'mta5.am0.yahoodns.net' + + # Invalidate managed email response out of pattern + mailbox = '1testuser@yahoo.com' + addr = address.validate_address(mailbox) + assert_equal(type(addr), type(None)) + + # Same test but with validate_list + addr = address.validate_list([mailbox]) + expected = 'flanker.addresslib.address:' + assert_equal(addr, []) + + # Allow Yahoo MX unmanaged mailboxes to pass remaining patterns + mailbox = '8testuser@frontier.com' + addr = address.validate_address(mailbox) + assert_equal(addr, mailbox) + + # Same test but with validate_list + expected = 'flanker.addresslib.address:' + addr = address.validate_list([mailbox]) + assert_equal(addr, mailbox) + + +def test_mx_yahoo_manage_flag_toggle(): + # Just checking if the domain provided from the sub is managed + mailbox = '1testuser@yahoo.com' + addr_obj = address.parse(mailbox) + managed = validate.yahoo.managed_email(addr_obj.hostname) + assert_equal(managed, True) + + # Same but inversed, unmanaged yahoo mailbox + mailbox = '1testuser@frontier.com' + addr_obj = address.parse(mailbox) + managed = validate.yahoo.managed_email(addr_obj.hostname) + assert_equal(managed, False)