diff --git a/app/admin_model.py b/app/admin_model.py index e9336bc8..a97d41e8 100644 --- a/app/admin_model.py +++ b/app/admin_model.py @@ -1,3 +1,4 @@ +from __future__ import annotations from typing import Optional import arrow @@ -30,6 +31,8 @@ from app.models import ( Newsletter, PADDLE_SUBSCRIPTION_GRACE_DAYS, Mailbox, + DeletedAlias, + DomainDeletedAlias, ) from app.newsletter_utils import send_newsletter_to_user, send_newsletter_to_address @@ -729,6 +732,67 @@ class InvalidMailboxDomainAdmin(SLModelView): can_delete = True +class EmailSearchResult: + no_match: bool = True + alias: Optional[Alias] = None + mailbox: Optional[Mailbox] = None + deleted_alias: Optional[DeletedAlias] = None + deleted_custom_alias: Optional[DomainDeletedAlias] = None + user: Optional[User] = None + + @staticmethod + def from_email(email: str) -> EmailSearchResult: + output = EmailSearchResult() + alias = Alias.get_by(email=email) + if alias: + output.alias = alias + output.no_match = False + return output + user = User.get_by(email=email) + if user: + output.user = user + output.no_match = False + return output + mailbox = Mailbox.get_by(email=email) + if mailbox: + output.mailbox = mailbox + output.no_match = False + return output + deleted_alias = DeletedAlias.get_by(email=email) + if deleted_alias: + output.deleted_alias = deleted_alias + output.no_match = False + return output + domain_deleted_alias = DomainDeletedAlias.get_by(email=email) + if domain_deleted_alias: + output.domain_deleted_alias = domain_deleted_alias + output.no_match = False + return output + + +class EmailSearchHelpers: + @staticmethod + def mailbox_list(user: User) -> list[Mailbox]: + return ( + Mailbox.filter_by(user_id=user.id) + .order_by(Mailbox.id.asc()) + .limit(10) + .all() + ) + + @staticmethod + def mailbox_count(user: User) -> int: + return Mailbox.filter_by(user_id=user.id).order_by(Mailbox.id.asc()).count() + + @staticmethod + def alias_list(user: User) -> list[Alias]: + return Alias.filter_by(user_id=user.id).order_by(Alias.id.asc()).limit(10).all() + + @staticmethod + def alias_count(user: User) -> int: + return Alias.filter_by(user_id=user.id).count() + + class EmailSearchAdmin(BaseView): def is_accessible(self): return current_user.is_authenticated and current_user.is_admin @@ -740,50 +804,16 @@ class EmailSearchAdmin(BaseView): @expose("/", methods=["GET", "POST"]) def index(self): - alias = None - user = None - mailbox = None - no_match = False - email = None + search = EmailSearchResult() + email = "" if request.form and request.form["email"]: email = request.form["email"] - alias = Alias.get_by(email=email) - user = User.get_by(email=email) - mailbox = Mailbox.get_by(email=email) - if not alias and not user and not mailbox: - no_match = True - - def user_mailboxes(user_id: int) -> list[Mailbox]: - return ( - Mailbox.filter_by(user_id=user_id) - .order_by(Mailbox.id.asc()) - .limit(10) - .all() - ) - - def user_mailboxes_count(user_id: int) -> int: - return Mailbox.filter_by(user_id=user_id).order_by(Mailbox.id.asc()).count() - - def user_aliases(user_id: int) -> list[Alias]: - return ( - Alias.filter_by(user_id=user_id) - .order_by(Alias.id.asc()) - .limit(10) - .all() - ) - - def user_aliases_count(user_id: int) -> int: - return Alias.filter_by(user_id=user_id).count() + email = email.strip() + search = EmailSearchResult.from_email(email) return self.render( "admin/email_search.html", email=email, - no_match=no_match, - alias=alias, - mailbox=mailbox, - user=user, - user_aliases=user_aliases, - user_aliases_count=user_aliases_count, - user_mailboxes=user_mailboxes, - user_mailboxes_count=user_mailboxes_count, + data=search, + helper=EmailSearchHelpers, ) diff --git a/app/alias_utils.py b/app/alias_utils.py index e0aeb6ae..f4ff0d3b 100644 --- a/app/alias_utils.py +++ b/app/alias_utils.py @@ -63,12 +63,16 @@ def get_user_if_alias_would_auto_create( # Prevent addresses with unicode characters (🤯) in them for now. validate_email(address, check_deliverability=False, allow_smtputf8=False) except EmailNotValidError: + LOG.i(f"Not creating alias for {address} because email is invalid") return None domain_and_rule = check_if_alias_can_be_auto_created_for_custom_domain( address, notify_user=notify_user ) if DomainDeletedAlias.get_by(email=address): + LOG.i( + f"Not creating alias for {address} because it was previously deleted for this domain" + ) return None if domain_and_rule: return domain_and_rule[0].user @@ -93,6 +97,9 @@ def check_if_alias_can_be_auto_created_for_custom_domain( custom_domain: CustomDomain = CustomDomain.get_by(domain=alias_domain) if not custom_domain: + LOG.i( + f"Cannot auto-create custom domain alias for {address} because there's no custom domain for {alias_domain}" + ) return None user: User = custom_domain.user @@ -108,6 +115,9 @@ def check_if_alias_can_be_auto_created_for_custom_domain( if not custom_domain.catch_all: if len(custom_domain.auto_create_rules) == 0: + LOG.i( + f"Cannot create alias {address} for domain {custom_domain} because it has no catch-all and no rules" + ) return None local = get_email_local_part(address) @@ -121,7 +131,7 @@ def check_if_alias_can_be_auto_created_for_custom_domain( ) return custom_domain, rule else: # no rule passes - LOG.d("no rule passed to create %s", local) + LOG.d(f"No rule matches auto-create {address} for domain {custom_domain}") return None LOG.d("Create alias via catchall") @@ -148,6 +158,7 @@ def check_if_alias_can_be_auto_created_for_a_directory( sep = "#" else: # if there's no directory separator in the alias, no way to auto-create it + LOG.info(f"Cannot auto-create {address} since it has no directory separator") return None directory_name = address[: address.find(sep)] @@ -155,6 +166,9 @@ def check_if_alias_can_be_auto_created_for_a_directory( directory = Directory.get_by(name=directory_name) if not directory: + LOG.info( + f"Cannot auto-create {address} because there is no directory for {directory_name}" + ) return None user: User = directory.user @@ -163,12 +177,17 @@ def check_if_alias_can_be_auto_created_for_a_directory( return None if not user.can_create_new_alias(): - LOG.d(f"{user} can't create new directory alias {address}") + LOG.d( + f"{user} can't create new directory alias {address} because user cannot create aliases" + ) if notify_user: send_cannot_create_directory_alias(user, address, directory_name) return None if directory.disabled: + LOG.d( + f"{user} can't create new directory alias {address} bcause directory is disabled" + ) if notify_user: send_cannot_create_directory_alias_disabled(user, address, directory_name) return None diff --git a/app/email_utils.py b/app/email_utils.py index 6048a99e..5ff34d06 100644 --- a/app/email_utils.py +++ b/app/email_utils.py @@ -548,7 +548,9 @@ def can_create_directory_for_address(email_address: str) -> bool: for domain in config.ALIAS_DOMAINS: if email_address.endswith("@" + domain): return True - + LOG.i( + f"Cannot create address in directory for {email_address} since it does not belong to a valid directory domain" + ) return False diff --git a/templates/admin/email_search.html b/templates/admin/email_search.html index 97a77955..537d03cb 100644 --- a/templates/admin/email_search.html +++ b/templates/admin/email_search.html @@ -50,7 +50,7 @@ {% endmacro %} -{% macro list_alias(alias_count, aliases) %} +{% macro list_alias(alias_count, aliases) %}

{{ alias_count }} Aliases found. {% if alias_count>10 %}Showing only the first 10.{% endif %} @@ -79,6 +79,100 @@ {% endmacro %} +{% macro show_deleted_alias(deleted_alias) -%} +

+ Deleted Alias {{ deleted_alias.email }} with ID {{ deleted_alias.id }}. +

+ + + + + + + + + + + + + + + + + +
+ Deleted Alias ID + + Email + + Deleted At + + Reason +
+ {{ deleted_alias.id }} + + {{ deleted_alias.email }} + + {{ deleted_alias.created_at }} + + {{ deleted_alias.reason }} +
+{%- endmacro %} +{% macro show_domain_deleted_alias(dom_deleted_alias) -%} +

+ Domain Deleted Alias {{ dom_deleted_alias.email }} with ID {{ dom_deleted_alias.id }} for domain {{ dom_deleted_alias.domain.domain }} +

+ + + + + + + + + + + + + + + + + + + + + + +
+ Deleted Alias ID + + Email + + Domain + + Domain ID + + Domain owner user ID + + Domain owner user email + + Deleted At +
+ {{ dom_deleted_alias.id }} + + {{ dom_deleted_alias.email }} + + {{ dom_deleted_alias.domain.domain }} + + {{ dom_deleted_alias.domain.id }} + + {{ dom_deleted_alias.domain.user_id }} + + {{ dom_deleted_alias.created_at }} +
+ {{ show_user(data.domain_deleted_alias.domain.user) }} +{%- endmacro %} {% block body %}
@@ -104,36 +198,54 @@ No user, alias or mailbox found for {{ email }}
{% endif %} - {% if alias %} + {% if data.alias %}

- Found Alias {{ alias.email }} + Found Alias {{ data.alias.email }}

- {{ list_alias(1,[alias]) }} - {{ show_user(alias.user) }} - {{ list_mailboxes(user_mailboxes_count(alias.user.id), user_mailboxes(alias.user.id) ) }} + {{ list_alias(1,[data.alias]) }} + {{ show_user(data.alias.user) }} + {{ list_mailboxes(helper.mailbox_count(data.alias.user), helper.mailbox_list(data.alias.user) ) }}
{% endif %} - {% if user %} + {% if data.user %}

- Found User {{ user.email }} + Found User {{ data.user.email }}

- {{ show_user(user) }} - {{ list_mailboxes(user_mailboxes_count(user.id), user_mailboxes(user.id) ) }} - {{ list_alias(user_aliases_count(user.id),user_aliases(user.id)) }} + {{ show_user(data.user) }} + {{ list_mailboxes(helper.mailbox_count(data.user), helper.mailbox_list(data.user) ) }} + {{ list_alias(helper.alias_count(data.user),helper.alias_list(data.user)) }}
{% endif %} - {% if mailbox %} + {% if data.mailbox %}

- Found Mailbox {{ mailbox.email }} + Found Mailbox {{ data.mailbox.email }}

- {{ list_mailboxes(1, [mailbox] ) }} - {{ show_user(mailbox.user) }} + {{ list_mailboxes(1, [data.mailbox] ) }} + {{ show_user(data.mailbox.user) }} +
+ {% endif %} + {% if data.deleted_alias %} + +
+

+ Found DeletedAlias {{ data.deleted_alias.email }} +

+ {{ show_deleted_alias(data.deleted_alias) }} +
+ {% endif %} + {% if data.domain_deleted_alias %} + +
+

+ Found DomainDeletedAlias {{ data.domain_deleted_alias.email }} +

+ {{ show_domain_deleted_alias(data.domain_deleted_alias) }}
{% endif %} {% endblock %}