From 27c09043da52ca1f02605bf28600bfd5ace95ae4 Mon Sep 17 00:00:00 2001 From: David Smith Date: Wed, 17 Jun 2020 07:35:09 +0100 Subject: [PATCH] Refs #31670 -- Renamed whitelist argument and attribute of EmailValidator. --- django/core/validators.py | 41 ++++++++++++++++++++++++++----- docs/internals/deprecation.txt | 3 +++ docs/ref/validators.txt | 26 +++++++++++++------- docs/releases/3.2.txt | 6 +++++ docs/spelling_wordlist | 2 +- tests/validators/tests.py | 44 ++++++++++++++++++++++++++++++++-- 6 files changed, 104 insertions(+), 18 deletions(-) diff --git a/django/core/validators.py b/django/core/validators.py index a37f3416e9..84b4f31ec7 100644 --- a/django/core/validators.py +++ b/django/core/validators.py @@ -1,10 +1,12 @@ import ipaddress import re +import warnings from pathlib import Path from urllib.parse import urlsplit, urlunsplit from django.core.exceptions import ValidationError from django.utils.deconstruct import deconstructible +from django.utils.deprecation import RemovedInDjango41Warning from django.utils.encoding import punycode from django.utils.ipv6 import is_valid_ipv6_address from django.utils.regex_helper import _lazy_re_compile @@ -167,15 +169,42 @@ class EmailValidator: # literal form, ipv4 or ipv6 address (SMTP 4.1.3) r'\[([A-f0-9:.]+)\]\Z', re.IGNORECASE) - domain_whitelist = ['localhost'] + domain_allowlist = ['localhost'] - def __init__(self, message=None, code=None, whitelist=None): + @property + def domain_whitelist(self): + warnings.warn( + 'The domain_whitelist attribute is deprecated in favor of ' + 'domain_allowlist.', + RemovedInDjango41Warning, + stacklevel=2, + ) + return self.domain_allowlist + + @domain_whitelist.setter + def domain_whitelist(self, allowlist): + warnings.warn( + 'The domain_whitelist attribute is deprecated in favor of ' + 'domain_allowlist.', + RemovedInDjango41Warning, + stacklevel=2, + ) + self.domain_allowlist = allowlist + + def __init__(self, message=None, code=None, allowlist=None, *, whitelist=None): + if whitelist is not None: + allowlist = whitelist + warnings.warn( + 'The whitelist argument is deprecated in favor of allowlist.', + RemovedInDjango41Warning, + stacklevel=2, + ) if message is not None: self.message = message if code is not None: self.code = code - if whitelist is not None: - self.domain_whitelist = whitelist + if allowlist is not None: + self.domain_allowlist = allowlist def __call__(self, value): if not value or '@' not in value: @@ -186,7 +215,7 @@ class EmailValidator: if not self.user_regex.match(user_part): raise ValidationError(self.message, code=self.code) - if (domain_part not in self.domain_whitelist and + if (domain_part not in self.domain_allowlist and not self.validate_domain_part(domain_part)): # Try for possible IDN domain-part try: @@ -215,7 +244,7 @@ class EmailValidator: def __eq__(self, other): return ( isinstance(other, EmailValidator) and - (self.domain_whitelist == other.domain_whitelist) and + (self.domain_allowlist == other.domain_allowlist) and (self.message == other.message) and (self.code == other.code) ) diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index dd80acf833..d6becd31c1 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -21,6 +21,9 @@ details on these changes. * ``BaseCommand.requires_system_checks`` won't support boolean values. +* The ``whitelist`` argument and ``domain_whitelist`` attribute of + ``django.core.validators.EmailValidator`` will be removed. + .. _deprecation-removed-in-4.0: 4.0 diff --git a/docs/ref/validators.txt b/docs/ref/validators.txt index 6374f4e8cd..4669ba28d7 100644 --- a/docs/ref/validators.txt +++ b/docs/ref/validators.txt @@ -119,11 +119,11 @@ to, or in lieu of custom ``field.clean()`` methods. ``EmailValidator`` ------------------ -.. class:: EmailValidator(message=None, code=None, whitelist=None) +.. class:: EmailValidator(message=None, code=None, allowlist=None) :param message: If not ``None``, overrides :attr:`.message`. :param code: If not ``None``, overrides :attr:`code`. - :param whitelist: If not ``None``, overrides :attr:`whitelist`. + :param allowlist: If not ``None``, overrides :attr:`allowlist`. .. attribute:: message @@ -136,14 +136,22 @@ to, or in lieu of custom ``field.clean()`` methods. The error code used by :exc:`~django.core.exceptions.ValidationError` if validation fails. Defaults to ``"invalid"``. - .. attribute:: whitelist + .. attribute:: allowlist - Whitelist of email domains to allow. By default, a regular expression - (the ``domain_regex`` attribute) is used to validate whatever appears - after the @ sign. However, if that string appears in the whitelist, this - validation is bypassed. If not provided, the default whitelist is - ``['localhost']``. Other domains that don't contain a dot won't pass - validation, so you'd need to whitelist them as necessary. + Allowlist of email domains. By default, a regular expression (the + ``domain_regex`` attribute) is used to validate whatever appears after + the ``@`` sign. However, if that string appears in the ``allowlist``, + this validation is bypassed. If not provided, the default ``allowlist`` + is ``['localhost']``. Other domains that don't contain a dot won't pass + validation, so you'd need to add them to the ``allowlist`` as + necessary. + + .. deprecated:: 3.2 + + The ``whitelist`` parameter is deprecated. Use :attr:`allowlist` + instead. + The undocumented ``domain_whitelist`` attribute is deprecated. Use + ``domain_allowlist`` instead. ``URLValidator`` ---------------- diff --git a/docs/releases/3.2.txt b/docs/releases/3.2.txt index 9b87484335..a9e9ff662d 100644 --- a/docs/releases/3.2.txt +++ b/docs/releases/3.2.txt @@ -351,3 +351,9 @@ Miscellaneous * Using a boolean value in :attr:`.BaseCommand.requires_system_checks` is deprecated. Use ``'__all__'`` instead of ``True``, and ``[]`` (an empty list) instead of ``False``. + +* The ``whitelist`` argument and ``domain_whitelist`` attribute of + :class:`~django.core.validators.EmailValidator` are deprecated. Use + ``allowlist`` instead of ``whitelist``, and ``domain_allowlist`` instead of + ``domain_whitelist``. You may need to rename ``whitelist`` in existing + migrations. diff --git a/docs/spelling_wordlist b/docs/spelling_wordlist index 06c2f7399d..ead96de4cf 100644 --- a/docs/spelling_wordlist +++ b/docs/spelling_wordlist @@ -10,6 +10,7 @@ affordances aggregator Ai Alchin +allowlist alphanumerics amet analytics @@ -780,7 +781,6 @@ vertices viewable virtualized Weblog -whitelist whitespace whitespaces whizbang diff --git a/tests/validators/tests.py b/tests/validators/tests.py index 5127bfecf5..c94ec251d6 100644 --- a/tests/validators/tests.py +++ b/tests/validators/tests.py @@ -16,7 +16,8 @@ from django.core.validators import ( validate_ipv4_address, validate_ipv6_address, validate_ipv46_address, validate_slug, validate_unicode_slug, ) -from django.test import SimpleTestCase +from django.test import SimpleTestCase, ignore_warnings +from django.utils.deprecation import RemovedInDjango41Warning try: from PIL import Image # noqa @@ -50,7 +51,7 @@ TEST_DATA = [ (validate_email, 'example@valid-with-hyphens.com', None), (validate_email, 'test@domain.with.idn.tld.उदाहरण.परीक्षा', None), (validate_email, 'email@localhost', None), - (EmailValidator(whitelist=['localdomain']), 'email@localdomain', None), + (EmailValidator(allowlist=['localdomain']), 'email@localdomain', None), (validate_email, '"test@test"@example.com', None), (validate_email, 'example@atm.%s' % ('a' * 63), None), (validate_email, 'example@%s.atm' % ('a' * 63), None), @@ -510,3 +511,42 @@ class TestValidatorEquality(TestCase): ProhibitNullCharactersValidator(message='message', code='code1'), ProhibitNullCharactersValidator(message='message', code='code2') ) + + +class DeprecationTests(SimpleTestCase): + @ignore_warnings(category=RemovedInDjango41Warning) + def test_whitelist(self): + validator = EmailValidator(whitelist=['localdomain']) + self.assertEqual(validator.domain_allowlist, ['localdomain']) + self.assertIsNone(validator('email@localdomain')) + self.assertEqual(validator.domain_allowlist, validator.domain_whitelist) + + def test_whitelist_warning(self): + msg = "The whitelist argument is deprecated in favor of allowlist." + with self.assertRaisesMessage(RemovedInDjango41Warning, msg): + EmailValidator(whitelist='localdomain') + + @ignore_warnings(category=RemovedInDjango41Warning) + def test_domain_whitelist(self): + validator = EmailValidator() + validator.domain_whitelist = ['mydomain'] + self.assertEqual(validator.domain_allowlist, ['mydomain']) + self.assertEqual(validator.domain_allowlist, validator.domain_whitelist) + + def test_domain_whitelist_access_warning(self): + validator = EmailValidator() + msg = ( + 'The domain_whitelist attribute is deprecated in favor of ' + 'domain_allowlist.' + ) + with self.assertRaisesMessage(RemovedInDjango41Warning, msg): + validator.domain_whitelist + + def test_domain_whitelist_set_warning(self): + validator = EmailValidator() + msg = ( + 'The domain_whitelist attribute is deprecated in favor of ' + 'domain_allowlist.' + ) + with self.assertRaisesMessage(RemovedInDjango41Warning, msg): + validator.domain_whitelist = ['mydomain']