diff --git a/README.md b/README.md index 2237019..fc2302b 100644 --- a/README.md +++ b/README.md @@ -261,6 +261,15 @@ TODO_MAIL_TRACKERS = { } ``` +Optionally, the email addresses of incoming emails can be mapped back to django users. If a user emails the test_tracker, and also is a registered User in your application, the user will show up as having created the task or comment. By default, only the email address will show up. + +This isn't enabled by default, as some domains are misconfigured and do not prevent impersonation. If this option is enabled and your setup doesn't properly authenticate emails, malicious incoming emails might mistakenly be attributed to users. + +Settings: +```python +TODO_MAIL_USER_MAPPER = None # Set to True if you would like to match users. If you do not have authentication setup, do not set this to True. +``` + A mail worker can be started with: ```sh diff --git a/test_settings.py b/test_settings.py index 9180bdb..e84a566 100644 --- a/test_settings.py +++ b/test_settings.py @@ -69,3 +69,5 @@ "django.request": {"handlers": ["console"], "level": "DEBUG", "propagate": True}, }, } + +TODO_MAIL_USER_MAPPER = None diff --git a/todo/mail/consumers/tracker.py b/todo/mail/consumers/tracker.py index 4ba141e..2d158f2 100644 --- a/todo/mail/consumers/tracker.py +++ b/todo/mail/consumers/tracker.py @@ -4,7 +4,10 @@ from email.charset import Charset as EMailCharset from django.db import transaction from django.db.models import Count +from django.contrib.auth import get_user_model +from django.conf import settings from html2text import html2text +from email.utils import parseaddr from todo.models import Comment, Task, TaskList logger = logging.getLogger(__name__) @@ -128,6 +131,7 @@ def insert_message(task_list, message, priority, task_title_format): priority=priority, title=format_task_title(task_title_format, message), task_list=task_list, + created_by=match_user(message_from), ) logger.info("using task: %r", best_task) @@ -135,6 +139,7 @@ def insert_message(task_list, message, priority, task_title_format): task=best_task, email_message_id=message_id, defaults={"email_from": message_from, "body": text}, + author=match_user(message_from), # TODO: Write test for this ) logger.info("created comment: %r", comment) @@ -149,3 +154,18 @@ def tracker_consumer( except Exception: # ignore exceptions during insertion, in order to avoid logger.exception("got exception while inserting message") + + +def match_user(email): + """ This function takes an email and checks for a registered user.""" + + if not settings.TODO_MAIL_USER_MAPPER: + user = None + else: + try: + # Find the first user that matches the email + user = get_user_model().objects.get(email=parseaddr(email)[1]) + except get_user_model().DoesNotExist: + user = None + + return user diff --git a/todo/tests/test_tracker.py b/todo/tests/test_tracker.py index 5ea5ad0..9f96102 100644 --- a/todo/tests/test_tracker.py +++ b/todo/tests/test_tracker.py @@ -58,3 +58,59 @@ def test_tracker_task_creation(todo_setup, django_user_model): Comment.objects.get( task=task, body__contains="test3 content", email_message_id="" ) + +def test_tracker_email_match(todo_setup, django_user_model, settings): + """ + Ensure that a user is added to new lists when sent from registered email + """ + settings.TODO_MAIL_USER_MAPPER = True + + u1 = django_user_model.objects.get(username="u1") + + msg = make_message("test1 subject", "test1 content") + msg["From"] = u1.email + msg["Message-ID"] = "" + + # test task creation + task_count = Task.objects.count() + consumer([msg]) + + assert task_count + 1 == Task.objects.count(), "task wasn't created" + task = Task.objects.filter(title="[TEST] test1 subject").first() + assert task is not None, "task was created with the wrong name" + assert task.created_by == u1 + + # Check no match + msg = make_message("test2 subject", "test2 content") + msg["From"] = "no-match-email@example.com" + msg["Message-ID"] = "" + + # test task creation + task_count = Task.objects.count() + consumer([msg]) + + assert task_count + 1 == Task.objects.count(), "task wasn't created" + task = Task.objects.filter(title="[TEST] test2 subject").first() + assert task.created_by == None + + +def test_tracker_match_users_false(todo_setup, django_user_model, settings): + """ + Do not match users on incoming mail if TODO_MAIL_USER_MAPPER is False + """ + settings.TODO_MAIL_USER_MAPPER = None + + u1 = django_user_model.objects.get(username="u1") + + msg = make_message("test1 subject", "test1 content") + msg["From"] = u1.email + msg["Message-ID"] = "" + + # test task creation + task_count = Task.objects.count() + consumer([msg]) + + assert task_count + 1 == Task.objects.count(), "task wasn't created" + task = Task.objects.filter(title="[TEST] test1 subject").first() + assert task is not None, "task was created with the wrong name" + assert task.created_by == None