From 1e07cd2365185fd1beb3297ce9ee9685677faf9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Casaj=C3=BAs?= Date: Tue, 11 Apr 2023 17:04:42 +0200 Subject: [PATCH] Use the alias domain for contacts --- app/dashboard/views/alias_contact_manager.py | 2 +- app/email_utils.py | 12 ++++++--- app/models.py | 15 ++++++++--- email_handler.py | 6 ++--- tests/test_email_utils.py | 27 ++++++++++++-------- tests/test_models.py | 2 +- 6 files changed, 41 insertions(+), 23 deletions(-) diff --git a/app/dashboard/views/alias_contact_manager.py b/app/dashboard/views/alias_contact_manager.py index 709556a4..0e7d8624 100644 --- a/app/dashboard/views/alias_contact_manager.py +++ b/app/dashboard/views/alias_contact_manager.py @@ -90,7 +90,7 @@ def create_contact(user: User, alias: Alias, contact_address: str) -> Contact: alias_id=alias.id, website_email=contact_email, name=contact_name, - reply_email=generate_reply_email(contact_email, user), + reply_email=generate_reply_email(contact_email, alias), ) LOG.d( diff --git a/app/email_utils.py b/app/email_utils.py index 0535373b..63f2dc74 100644 --- a/app/email_utils.py +++ b/app/email_utils.py @@ -1043,7 +1043,7 @@ def replace(msg: Union[Message, str], old, new) -> Union[Message, str]: return msg -def generate_reply_email(contact_email: str, user: User) -> str: +def generate_reply_email(contact_email: str, alias: Alias) -> str: """ generate a reply_email (aka reverse-alias), make sure it isn't used by any contact """ @@ -1054,6 +1054,7 @@ def generate_reply_email(contact_email: str, user: User) -> str: include_sender_in_reverse_alias = False + user = alias.user # user has set this option explicitly if user.include_sender_in_reverse_alias is not None: include_sender_in_reverse_alias = user.include_sender_in_reverse_alias @@ -1068,6 +1069,7 @@ def generate_reply_email(contact_email: str, user: User) -> str: contact_email = contact_email.replace(".", "_") contact_email = convert_to_alphanumeric(contact_email) + reply_domain = alias.get_domain() # not use while to avoid infinite loop for _ in range(1000): if include_sender_in_reverse_alias and contact_email: @@ -1075,15 +1077,17 @@ def generate_reply_email(contact_email: str, user: User) -> str: reply_email = ( # do not use the ra+ anymore # f"ra+{contact_email}+{random_string(random_length)}@{config.EMAIL_DOMAIN}" - f"{contact_email}_{random_string(random_length)}@{config.EMAIL_DOMAIN}" + f"{contact_email}_{random_string(random_length)}@{reply_domain}" ) else: random_length = random.randint(20, 50) # do not use the ra+ anymore # reply_email = f"ra+{random_string(random_length)}@{config.EMAIL_DOMAIN}" - reply_email = f"{random_string(random_length)}@{config.EMAIL_DOMAIN}" + reply_email = f"{random_string(random_length)}@{reply_domain}" - if not Contact.get_by(reply_email=reply_email): + if not Contact.get_by(reply_email=reply_email) and not Alias.get_by( + email=reply_email + ): return reply_email raise Exception("Cannot generate reply email") diff --git a/app/models.py b/app/models.py index e5b07ce2..da91c9e2 100644 --- a/app/models.py +++ b/app/models.py @@ -1297,12 +1297,15 @@ def generate_email( scheme: int = AliasGeneratorEnum.word.value, in_hex: bool = False, alias_domain=config.FIRST_ALIAS_DOMAIN, + retries: int = 5, ) -> str: """generate an email address that does not exist before :param alias_domain: the domain used to generate the alias. :param scheme: int, value of AliasGeneratorEnum, indicate how the email is generated :type in_hex: bool, if the generate scheme is uuid, is hex favorable? """ + if retries <= 0: + raise Exception("Cannot generate alias after many retries") if scheme == AliasGeneratorEnum.uuid.value: name = uuid.uuid4().hex if in_hex else uuid.uuid4().__str__() random_email = name + "@" + alias_domain @@ -1312,15 +1315,17 @@ def generate_email( random_email = random_email.lower().strip() # check that the client does not exist yet - if not Alias.get_by(email=random_email) and not DeletedAlias.get_by( - email=random_email + if ( + not Alias.get_by(email=random_email) + and not DeletedAlias.get_by(email=random_email) + and not Contact.get_by(reply_email=random_email) ): LOG.d("generate email %s", random_email) return random_email # Rerun the function LOG.w("email %s already exists, generate a new email", random_email) - return generate_email(scheme=scheme, in_hex=in_hex) + return generate_email(scheme=scheme, in_hex=in_hex, retries=retries - 1) class Alias(Base, ModelMixin): @@ -1581,6 +1586,10 @@ class Alias(Base, ModelMixin): else: return self.user.email + def get_domain(self) -> str: + splitPos = self.email.find("@") + return self.email[splitPos + 1 :] + def __repr__(self): return f"" diff --git a/email_handler.py b/email_handler.py index 8e1d1258..a00192d9 100644 --- a/email_handler.py +++ b/email_handler.py @@ -243,7 +243,7 @@ def get_or_create_contact(from_header: str, mail_from: str, alias: Alias) -> Con website_email=contact_email, name=contact_name, mail_from=mail_from, - reply_email=generate_reply_email(contact_email, alias.user) + reply_email=generate_reply_email(contact_email, alias) if is_valid_email(contact_email) else NOREPLY, automatic_created=True, @@ -304,7 +304,7 @@ def get_or_create_reply_to_contact( alias_id=alias.id, website_email=contact_address, name=contact_name, - reply_email=generate_reply_email(contact_address, alias.user), + reply_email=generate_reply_email(contact_address, alias), automatic_created=True, ) Session.commit() @@ -372,7 +372,7 @@ def replace_header_when_forward(msg: Message, alias: Alias, header: str): alias_id=alias.id, website_email=contact_email, name=full_address.display_name, - reply_email=generate_reply_email(contact_email, alias.user), + reply_email=generate_reply_email(contact_email, alias), is_cc=header.lower() == "cc", automatic_created=True, ) diff --git a/tests/test_email_utils.py b/tests/test_email_utils.py index c802de6f..1137df49 100644 --- a/tests/test_email_utils.py +++ b/tests/test_email_utils.py @@ -48,6 +48,7 @@ from app.models import ( IgnoreBounceSender, InvalidMailboxDomain, VerpType, + AliasGeneratorEnum, ) # flake8: noqa: E101, W191 @@ -469,33 +470,37 @@ def test_replace_str(): def test_generate_reply_email(flask_client): user = create_new_user() - reply_email = generate_reply_email("test@example.org", user) - assert reply_email.endswith(EMAIL_DOMAIN) + alias = Alias.create_new_random(user, AliasGeneratorEnum.uuid.value) + Session.commit() + reply_email = generate_reply_email("test@example.org", alias) + assert reply_email.endswith(alias.get_domain()) - reply_email = generate_reply_email("", user) - assert reply_email.endswith(EMAIL_DOMAIN) + reply_email = generate_reply_email("", alias) + assert reply_email.endswith(alias.get_domain()) def test_generate_reply_email_include_sender_in_reverse_alias(flask_client): # user enables include_sender_in_reverse_alias user = create_new_user() + alias = Alias.create_new_random(user, AliasGeneratorEnum.uuid.value) + Session.commit() user.include_sender_in_reverse_alias = True - reply_email = generate_reply_email("test@example.org", user) + reply_email = generate_reply_email("test@example.org", alias) assert reply_email.startswith("test_at_example_org") - assert reply_email.endswith(EMAIL_DOMAIN) + assert reply_email.endswith(alias.get_domain()) - reply_email = generate_reply_email("", user) - assert reply_email.endswith(EMAIL_DOMAIN) + reply_email = generate_reply_email("", alias) + assert reply_email.endswith(alias.get_domain()) - reply_email = generate_reply_email("👌汉字@example.org", user) + reply_email = generate_reply_email("👌汉字@example.org", alias) assert reply_email.startswith("yizi_at_example_org") # make sure reply_email only contain lowercase - reply_email = generate_reply_email("TEST@example.org", user) + reply_email = generate_reply_email("TEST@example.org", alias) assert reply_email.startswith("test_at_example_org") - reply_email = generate_reply_email("test.dot@example.org", user) + reply_email = generate_reply_email("test.dot@example.org", alias) assert reply_email.startswith("test_dot_at_example_org") diff --git a/tests/test_models.py b/tests/test_models.py index 01b3c4d1..1037afec 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -312,6 +312,6 @@ def test_create_contact_for_noreply(flask_client): user_id=user.id, alias_id=alias.id, website_email=NOREPLY, - reply_email=generate_reply_email(NOREPLY, user), + reply_email=generate_reply_email(NOREPLY, alias), ) assert contact.website_email == NOREPLY