diff --git a/zerver/lib/send_email.py b/zerver/lib/send_email.py index a00e23c5a6..010da47522 100644 --- a/zerver/lib/send_email.py +++ b/zerver/lib/send_email.py @@ -15,6 +15,7 @@ import os from typing import Any, Dict, Iterable, List, Mapping, Optional from zerver.lib.logging_util import log_to_file +from confirmation.models import generate_key ## Logging setup ## @@ -25,6 +26,13 @@ class FromAddress: SUPPORT = parseaddr(settings.ZULIP_ADMINISTRATOR)[1] NOREPLY = parseaddr(settings.NOREPLY_EMAIL_ADDRESS)[1] + # Generates an unpredictable noreply address. + @staticmethod + def tokenized_no_reply_address() -> str: + if settings.ADD_TOKENS_TO_NOREPLY_ADDRESS: + return parseaddr(settings.TOKENIZED_NOREPLY_EMAIL_ADDRESS)[1].format(token=generate_key()) + return FromAddress.NOREPLY + def build_email(template_prefix: str, to_user_id: Optional[int]=None, to_email: Optional[str]=None, from_name: Optional[str]=None, from_address: Optional[str]=None, reply_to_email: Optional[str]=None, diff --git a/zerver/lib/test_classes.py b/zerver/lib/test_classes.py index a7ae7a2f5b..7c3e652f8e 100644 --- a/zerver/lib/test_classes.py +++ b/zerver/lib/test_classes.py @@ -104,6 +104,7 @@ class ZulipTestCase(TestCase): ''' DEFAULT_SUBDOMAIN = "zulip" DEFAULT_REALM = Realm.objects.get(string_id='zulip') + TOKENIZED_NOREPLY_REGEX = settings.TOKENIZED_NOREPLY_EMAIL_ADDRESS.format(token="[a-z0-9_]{24}") def set_http_host(self, kwargs: Dict[str, Any]) -> None: if 'subdomain' in kwargs: diff --git a/zerver/tests/test_signup.py b/zerver/tests/test_signup.py index 64ac769938..b96048dadf 100644 --- a/zerver/tests/test_signup.py +++ b/zerver/tests/test_signup.py @@ -2937,6 +2937,13 @@ class FollowupEmailTest(ZulipTestCase): user_profile.date_joined = datetime.datetime(2018, 1, 5, 1, 0, 0, 0, pytz.UTC) self.assertEqual(followup_day2_email_delay(user_profile), datetime.timedelta(days=1, hours=-1)) +class NoReplyEmailTest(ZulipTestCase): + def test_noreply_email_address(self) -> None: + self.assertTrue(re.search(self.TOKENIZED_NOREPLY_REGEX, FromAddress.tokenized_no_reply_address())) + + with self.settings(ADD_TOKENS_TO_NOREPLY_ADDRESS=False): + self.assertEqual(FromAddress.tokenized_no_reply_address(), "noreply@testserver") + class TwoFactorAuthTest(ZulipTestCase): @patch('two_factor.models.totp') def test_two_factor_login(self, mock_totp): diff --git a/zproject/settings.py b/zproject/settings.py index 24e9d9622e..02ea509fd2 100644 --- a/zproject/settings.py +++ b/zproject/settings.py @@ -122,6 +122,8 @@ DEFAULT_SETTINGS = { # Basic email settings 'NOREPLY_EMAIL_ADDRESS': "noreply@" + EXTERNAL_HOST.split(":")[0], + 'ADD_TOKENS_TO_NOREPLY_ADDRESS': True, + 'TOKENIZED_NOREPLY_EMAIL_ADDRESS': "noreply-{token}@" + EXTERNAL_HOST.split(":")[0], 'PHYSICAL_ADDRESS': '', # SMTP settings