From 97a810612480dfc5b5e88ee077178c7a858e9731 Mon Sep 17 00:00:00 2001 From: uttam12331 Date: Fri, 24 Jul 2026 22:17:08 +0530 Subject: [PATCH] Make EmailValidator allowed_domains actually restrict domains The domain check was 'domain_part not in allowed_domains and not _validate_domain_part(domain_part)', so rejection required both operands. Any syntactically valid domain made the second operand False, meaning the allowlist could only ever widen acceptance and never restrict: EmailValidator(allowed_domains=['example.com']) accepted attacker@evil.com, contradicting the documented behaviour that 'only emails from these domains will be accepted'. Branch on allowed_domains instead, so a configured allowlist is enforced while allowlisted entries still bypass the domain syntax check (e.g. allowed_domains=['localhost']). Also drops a leftover debug print() in _domain_regex that wrote to stdout on first use. --- tests/test_validators.py | 15 +++++++++++++++ tortoise/validators.py | 6 ++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/tests/test_validators.py b/tests/test_validators.py index 57073b934..b20c82d01 100644 --- a/tests/test_validators.py +++ b/tests/test_validators.py @@ -263,6 +263,21 @@ def test_email_validator_invalid_allowed_domains(): validator("user@invalid..com") +def test_email_validator_rejects_domain_outside_allowed_domains(): + # a well-formed address whose domain is not allowlisted must be rejected + validator = EmailValidator(allowed_domains=["example.com"]) + with pytest.raises(InvalidEmailAddress): + validator("attacker@evil.com") + with pytest.raises(InvalidEmailAddress): + validator("user@totally-unrelated.co.uk") + + +def test_email_validator_allowed_domains_bypass_domain_syntax(): + # an allowlisted domain is accepted even if it is not a valid public domain + validator = EmailValidator(allowed_domains=["localhost"]) + validator("user@localhost") + + @pytest.mark.parametrize( "value", [ diff --git a/tortoise/validators.py b/tortoise/validators.py index d2d0ae055..6083e9965 100644 --- a/tortoise/validators.py +++ b/tortoise/validators.py @@ -324,7 +324,6 @@ def _user_regex(self) -> re.Pattern[str]: @cached_property def _domain_regex(self) -> re.Pattern[str]: - print("evaluating domain regex!!!") return re.compile( r"^" + HOSTNAME_REGEX + DOMAIN_REGEX + TLD_NO_FQDN_REGEX + r"\Z", re.IGNORECASE ) @@ -358,7 +357,10 @@ def __call__(self, value: str) -> None: if not self._user_regex.match(user_part): raise InvalidEmailAddress() - if domain_part not in self.allowed_domains and not self._validate_domain_part(domain_part): + if self.allowed_domains: + if domain_part not in self.allowed_domains: + raise InvalidEmailAddress() + elif not self._validate_domain_part(domain_part): raise InvalidEmailAddress()