Refactor email sending logic to use a group for QA email recipients (#21791)

* Refactor email sending logic to use a group for QA email recipients

* Add migration creating force send mail group
This commit is contained in:
Kevin Meinhardt 2024-02-02 16:07:12 +01:00 коммит произвёл GitHub
Родитель 8bcad61549
Коммит ceb064f3e9
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
8 изменённых файлов: 73 добавлений и 18 удалений

Просмотреть файл

@ -0,0 +1,29 @@
from django.db import migrations
from olympia.amo.mail import DevEmailBackend
def create_group(apps, schema_editor):
Group = apps.get_model('access', 'Group')
Group.objects.create(
name=DevEmailBackend.force_send_mail_group,
notes=(
'UserProfiles belonging to this group will have real emails sent'
' to them in dev/stage environments. This is useful for testing email flows.'
)
)
def delete_group(apps, schema_editor):
Group = apps.get_model('access', 'Group')
Group.objects.filter(name=DevEmailBackend.force_send_mail_group).delete()
class Migration(migrations.Migration):
dependencies = [
('access', '0002_give_api_bypass_throttling_permission'),
]
operations = [
migrations.RunPython(create_group, delete_group)
]

Просмотреть файл

@ -1,9 +1,9 @@
from django.conf import settings
from django.core import mail
from django.core.mail.backends.base import BaseEmailBackend
import olympia.core.logger
from olympia.amo.models import FakeEmail
from olympia.users.models import UserProfile
log = olympia.core.logger.getLogger('z.amo.mail')
@ -17,21 +17,26 @@ class DevEmailBackend(BaseEmailBackend):
`settings.SEND_REAL_EMAIL` is disabled.
BUT even if `settings.SEND_REAL_EMAIL` is disabled, if the targeted
email address is in the `settings.EMAIL_QA_ALLOW_LIST` list,
the email will be sent.
email address is in `dev_email` group users, the email will be sent.
"""
force_send_mail_group = 'Force Send Mail'
def send_messages(self, messages):
"""Save a `FakeEmail` object viewable within the admin.
If one of the target email addresses is in
`settings.EMAIL_QA_ALLOW_LIST`, it send a real email message.
group named `Group.force_send_mail_group`, it send a real email message.
"""
log.info('Sending dev mail messages.')
qa_messages = []
force_send_emails = UserProfile.objects.filter(
groups__name=self.force_send_mail_group
).values_list('email', flat=True)
for msg in messages:
FakeEmail.objects.create(message=msg.message().as_string())
qa_emails = set(msg.to).intersection(settings.EMAIL_QA_ALLOW_LIST)
qa_emails = set(msg.to).intersection(force_send_emails)
if qa_emails:
if len(msg.to) != len(qa_emails):
# We need to replace the recipients with the QA

Просмотреть файл

@ -96,6 +96,7 @@ class TestPositiveAutoField(TestCase):
assert extra == 'auto_increment'
def test_unsigned_int_limits(self):
self.ClassUsingPositiveAutoField.objects.all().delete()
self.ClassUsingPositiveAutoField.objects.create(id=1)
mysql_max_signed_int = 2147483647
self.ClassUsingPositiveAutoField.objects.create(id=mysql_max_signed_int + 10)

Просмотреть файл

@ -8,8 +8,7 @@ from olympia.access.models import Group
def test_admin_group(admin_group):
assert Group.objects.count() == 1
admin_group = Group.objects.get()
admin_group = Group.objects.get(name='Admins')
assert admin_group.name == 'Admins'
assert admin_group.rules == '*:*'

Просмотреть файл

@ -9,8 +9,13 @@ from django.utils import translation
from celery.exceptions import Retry
from olympia.access.models import Group
from olympia.amo.mail import DevEmailBackend
from olympia.amo.models import FakeEmail
from olympia.amo.tests import TestCase
from olympia.amo.tests import (
TestCase,
user_factory,
)
from olympia.amo.utils import send_html_mail_jinja, send_mail
from olympia.users import notifications
from olympia.users.models import UserNotification, UserProfile
@ -159,11 +164,23 @@ class TestSendMail(TestCase):
@mock.patch.object(settings, 'EMAIL_DENY_LIST', ())
@mock.patch.object(settings, 'SEND_REAL_EMAIL', False)
@mock.patch.object(settings, 'EMAIL_QA_ALLOW_LIST', ('nope@mozilla.org',))
def test_qa_allowed_list(self):
def test_success_fake_mail_missing_group(self):
Group.objects.all().delete()
assert Group.objects.all().count() == 0
assert send_mail(
'test subject', 'test body', recipient_list=['nope@mozilla.org']
'test subject', 'test body', recipient_list=['nobody@mozilla.org']
)
@mock.patch.object(settings, 'EMAIL_DENY_LIST', ())
@mock.patch.object(settings, 'SEND_REAL_EMAIL', False)
def test_qa_allowed_list(self):
nope_user = user_factory()
group = Group.objects.create(
name=DevEmailBackend.force_send_mail_group, rules='foo'
)
group.users.set([nope_user])
assert send_mail('test subject', 'test body', recipient_list=[nope_user.email])
assert len(mail.outbox) == 1
assert mail.outbox[0].subject.find('test subject') == 0
assert mail.outbox[0].body.find('test body') == 0
@ -172,15 +189,19 @@ class TestSendMail(TestCase):
@mock.patch.object(settings, 'EMAIL_DENY_LIST', ())
@mock.patch.object(settings, 'SEND_REAL_EMAIL', False)
@mock.patch.object(settings, 'EMAIL_QA_ALLOW_LIST', ('nope@mozilla.org',))
def test_qa_allowed_list_with_mixed_emails(self):
nope_user = user_factory()
group = Group.objects.create(
name=DevEmailBackend.force_send_mail_group, rules='foo'
)
group.users.set([nope_user])
assert send_mail(
'test subject',
'test body',
recipient_list=['nope@mozilla.org', 'b@example.fr'],
recipient_list=[nope_user.email, 'b@example.fr'],
)
assert len(mail.outbox) == 1
assert mail.outbox[0].to == ['nope@mozilla.org']
assert mail.outbox[0].to == [nope_user.email]
assert FakeEmail.objects.count() == 1
def test_dont_localize(self):

Просмотреть файл

@ -321,7 +321,9 @@ class GenerateAddonsSerializer(serializers.Serializer):
# Groups should have been created by loaddata initial.json at this
# point, we need our user to be part of a group allowed to submit
# extensions signed by Mozilla. Let's use Admins (pk=1) as a shortcut.
GroupUser.objects.get_or_create(user=user, group=Group.objects.get(pk=1))
GroupUser.objects.get_or_create(
user=user, group=Group.objects.get(name='Admins')
)
# generate a proper uploaded file that simulates what django requires
# as request.POST

Просмотреть файл

@ -7,7 +7,7 @@ from olympia.landfill.serializers import GenerateAddonsSerializer
class TestGenerateAddonsSerializer(TestCase):
def test_create_installable_addon(self):
Group.objects.get_or_create(pk=1, defaults={'name': 'Admins', 'rules': '*:*'})
Group.objects.create(name='Admins', rules='*:*')
AppVersion.objects.create(application=amo.FIREFOX.id, version='42.0')
AppVersion.objects.create(application=amo.FIREFOX.id, version='*')
AppVersion.objects.create(application=amo.ANDROID.id, version='48.0')

Просмотреть файл

@ -752,8 +752,6 @@ DEFAULT_FROM_EMAIL = ADDONS_EMAIL
# Email goes to the console by default. s/console/smtp/ for regular delivery
EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend'
# Please use all lowercase for the QA allow list.
EMAIL_QA_ALLOW_LIST = env.list('EMAIL_QA_ALLOW_LIST', default=())
# Please use all lowercase for the deny_list.
EMAIL_DENY_LIST = env.list('EMAIL_DENY_LIST', default=('nobody@mozilla.org',))