(mailbox) send new mailbox confirmation email

send mailbox information upon creating a new mailbox
This commit is contained in:
Marie PUPO JEAMMET
2024-09-20 18:08:02 +02:00
committed by Marie
parent b69ce001c8
commit 5ded297df6
10 changed files with 222 additions and 12 deletions

View File

@@ -17,6 +17,7 @@ and this project adheres to
- ✨(frontend) show username on AccountDropDown #412
- 🥅(frontend) improve add & update group forms error handling #387
- ✨(frontend) allow group members filtering #363
- ✨(mailbox) send new mailbox confirmation email #397
### Changed

View File

@@ -1,5 +1,7 @@
"""Client serializers for People's mailbox manager app."""
import json
from rest_framework import serializers
from core.api.serializers import UserSerializer
@@ -21,9 +23,24 @@ class MailboxSerializer(serializers.ModelSerializer):
"""
Override create function to fire a request on mailbox creation.
"""
# send new mailbox request to dimail
client = DimailAPIClient()
client.send_mailbox_request(validated_data, self.context["request"].user.sub)
return models.Mailbox.objects.create(**validated_data)
response = client.send_mailbox_request(
validated_data, self.context["request"].user.sub
)
# fix format to have actual json, and remove uuid
mailbox_data = json.loads(response.content.decode("utf-8").replace("'", '"'))
del mailbox_data["uuid"]
# actually save mailbox on our database
instance = models.Mailbox.objects.create(**validated_data)
# send confirmation email
client.send_new_mailbox_notification(
recipient=validated_data["secondary_email"], mailbox_data=mailbox_data
)
return instance
class MailDomainSerializer(serializers.ModelSerializer):

View File

@@ -8,6 +8,7 @@ from logging import Logger
from unittest import mock
from django.test.utils import override_settings
from django.utils.translation import gettext_lazy as _
import pytest
import responses
@@ -290,6 +291,53 @@ def test_api_mailboxes__domain_viewer_provisioning_api_not_called():
assert response.status_code == status.HTTP_403_FORBIDDEN
@mock.patch.object(Logger, "error")
def test_api_mailboxes__dimail_unauthorized(mock_error):
"""
Wrong secret but a secret corresponding to another user/domain
i.e. secret from another domain/user on dimail API.
"""
# creating all needed objects
access = factories.MailDomainAccessFactory(role=enums.MailDomainRoleChoices.OWNER)
client = APIClient()
client.force_login(access.user)
mailbox_data = serializers.MailboxSerializer(
factories.MailboxFactory.build(domain=access.domain)
).data
with responses.RequestsMock() as rsps:
# Ensure successful response using "responses":
rsps.add(
rsps.GET,
re.compile(r".*/token/"),
body='{"access_token": "domain_owner_token"}',
status=status.HTTP_200_OK,
content_type="application/json",
)
rsp = rsps.add(
rsps.POST,
re.compile(rf".*/domains/{access.domain.name}/mailboxes/"),
status=status.HTTP_403_FORBIDDEN,
content_type="application/json",
)
response = client.post(
f"/api/v1.0/mail-domains/{access.domain.slug}/mailboxes/",
mailbox_data,
format="json",
)
assert mock_error.call_count == 1
assert mock_error.call_args_list[0][0] == (
"[DIMAIL] 403 Forbidden: please check the mail domain secret of %s",
access.domain.name,
)
assert rsp.call_count == 1
assert response.status_code == status.HTTP_403_FORBIDDEN
@pytest.mark.parametrize(
"role",
[enums.MailDomainRoleChoices.ADMIN, enums.MailDomainRoleChoices.OWNER],
@@ -542,7 +590,7 @@ def test_api_mailboxes__send_correct_logger_infos(mock_info, mock_error):
# Logger
assert not mock_error.called
assert mock_info.call_count == 3
assert mock_info.call_count == 4
assert mock_info.call_args_list[0][0] == (
"Token succesfully granted by mail-provisioning API.",
)
@@ -551,3 +599,58 @@ def test_api_mailboxes__send_correct_logger_infos(mock_info, mock_error):
str(access.domain),
access.user.sub,
)
@mock.patch.object(Logger, "info")
def test_api_mailboxes__sends_new_mailbox_notification(mock_info):
"""
Creating a new mailbox should send confirmation email
to secondary email.
"""
access = factories.MailDomainAccessFactory(role=enums.MailDomainRoleChoices.OWNER)
client = APIClient()
client.force_login(access.user)
mailbox_data = serializers.MailboxSerializer(
factories.MailboxFactory.build(domain=access.domain)
).data
with responses.RequestsMock() as rsps:
# Ensure successful response using "responses":
rsps.add(
rsps.GET,
re.compile(r".*/token/"),
body='{"access_token": "domain_owner_token"}',
status=status.HTTP_200_OK,
content_type="application/json",
)
rsps.add(
rsps.POST,
re.compile(rf".*/domains/{access.domain.name}/mailboxes/"),
body=str(
{
"email": f"{mailbox_data['local_part']}@{access.domain.name}",
"password": "newpass",
"uuid": "uuid",
}
),
status=status.HTTP_201_CREATED,
content_type="application/json",
)
with mock.patch("django.core.mail.send_mail") as mock_send:
client.post(
f"/api/v1.0/mail-domains/{access.domain.slug}/mailboxes/",
mailbox_data,
format="json",
)
assert mock_send.call_count == 1
assert mock_send.mock_calls[0][1][0] == "Your new mailbox information"
assert mock_send.mock_calls[0][1][3][0] == mailbox_data["secondary_email"]
assert mock_info.call_count == 4
assert mock_info.call_args_list[2][0] == (
"Information for mailbox %s sent to %s.",
f"{mailbox_data['local_part']}@{access.domain.name}",
mailbox_data["secondary_email"],
)

View File

@@ -1,9 +1,13 @@
"""A minimalist client to synchronize with mailbox provisioning API."""
import smtplib
from logging import getLogger
from django.conf import settings
from django.core import exceptions
from django.contrib.sites.models import Site
from django.core import exceptions, mail
from django.template.loader import render_to_string
from django.utils.translation import gettext_lazy as _
import requests
from rest_framework import status
@@ -94,15 +98,10 @@ class DimailAPIClient:
raise error
if response.status_code == status.HTTP_201_CREATED:
extra = {"response": response.content.decode("utf-8")}
# This a temporary broken solution. Password will soon be sent
# from OX servers but their prod is not ready.
# In the meantime, we log mailbox info (including password !)
logger.info(
"Mailbox successfully created on domain %s by user %s",
str(mailbox["domain"]),
user_sub,
extra=extra,
)
return response
@@ -119,3 +118,40 @@ class DimailAPIClient:
logger.error("[DIMAIL] unexpected error : %s", error_content)
raise SystemError(f"Unexpected response from dimail: {error_content}")
def send_new_mailbox_notification(self, recipient, mailbox_data):
"""
Send email to confirm mailbox creation
and send new mailbox information.
"""
template_vars = {
"title": _("Your new mailbox information"),
"site": Site.objects.get_current(),
"webmail_url": settings.WEBMAIL_URL,
"mailbox_data": mailbox_data,
}
msg_html = render_to_string("mail/html/new_mailbox.html", template_vars)
msg_plain = render_to_string("mail/text/new_mailbox.txt", template_vars)
try:
mail.send_mail(
template_vars["title"],
msg_plain,
settings.EMAIL_FROM,
[recipient],
html_message=msg_html,
fail_silently=False,
)
logger.info(
"Information for mailbox %s sent to %s.",
mailbox_data["email"],
recipient,
)
except smtplib.SMTPException as exception:
logger.error(
"Mailbox confirmation email to %s was not sent: %s",
recipient,
exception,
)

View File

@@ -265,10 +265,7 @@ class Base(Configuration):
# Mail
EMAIL_BACKEND = values.Value("django.core.mail.backends.smtp.EmailBackend")
EMAIL_HOST = values.Value(None)
EMAIL_HOST_USER = values.Value(None)
EMAIL_HOST_PASSWORD = values.Value(None)
EMAIL_PORT = values.PositiveIntegerValue(None)
EMAIL_USE_TLS = values.BooleanValue(False)
EMAIL_USE_SSL = values.BooleanValue(False)
EMAIL_FROM = values.Value("from@example.com")
@@ -416,6 +413,11 @@ class Base(Configuration):
OIDC_TIMEOUT = values.Value(None, environ_name="OIDC_TIMEOUT", environ_prefix=None)
# MAILBOX-PROVISIONING API
WEBMAIL_URL = values.Value(
default=None,
environ_name="WEBMAIL_URL",
environ_prefix=None,
)
MAIL_PROVISIONING_API_URL = values.Value(
default="http://host.docker.internal:8001",
environ_name="MAIL_PROVISIONING_API_URL",

View File

@@ -50,6 +50,7 @@ backend:
POSTGRES_USER: dinum
POSTGRES_PASSWORD: pass
REDIS_URL: redis://default:pass@redis-master:6379/1
WEBMAIL_URL: "https://onestendev.yapasdewebmail.fr"
MAIL_PROVISIONING_API_URL: "http://host.docker.internal:8001"
MAIL_PROVISIONING_API_CREDENTIALS:
secretKeyRef:

View File

@@ -84,6 +84,7 @@ backend:
secretKeyRef:
name: redis.redis.libre.sh
key: url
WEBMAIL_URL: "https://webmail.test.ox.numerique.gouv.fr"
MAIL_PROVISIONING_API_URL: "https://api.dev.ox.numerique.gouv.fr"
MAIL_PROVISIONING_API_CREDENTIALS:
secretKeyRef:

View File

@@ -84,6 +84,7 @@ backend:
secretKeyRef:
name: redis.redis.libre.sh
key: url
WEBMAIL_URL: "https://webmail.numerique.gouv.fr"
MAIL_PROVISIONING_API_URL: "https://api.dev.ox.numerique.gouv.fr"
MAIL_PROVISIONING_API_CREDENTIALS:
secretKeyRef:

View File

@@ -98,6 +98,7 @@ backend:
secretKeyRef:
name: redis.redis.libre.sh
key: url
WEBMAIL_URL: "https://webmail.test.ox.numerique.gouv.fr"
MAIL_PROVISIONING_API_URL: "https://api.dev.ox.numerique.gouv.fr"
MAIL_PROVISIONING_API_CREDENTIALS:
secretKeyRef:

View File

@@ -0,0 +1,47 @@
<mjml>
<mj-include path="./partial/header.mjml" />
<mj-body mj-class="bg--blue-100">
<mj-wrapper css-class="wrapper" padding="0 40px 40px 40px">
<mj-section background-url="{% base64_static 'images/mail-header-background.png' %}" background-size="cover" background-repeat="no-repeat" background-position="0 -30px">
<mj-column>
<mj-image align="center" src="{% base64_static 'images/logo-suite-numerique.png' %}" width="250px" align="left" alt="{% trans 'La Suite Numérique' %}" />
</mj-column>
</mj-section>
<mj-section mj-class="bg--white-100" padding="30px 20px 60px 20px">
<mj-column>
<mj-text font-size="14px">
<p>{{ title }}</p>
</mj-text>
<!-- Welcome Message -->
<mj-text>
<h1>{% trans "Your new mailbox is ready !" %}</h1>
</mj-text>
<mj-divider border-width="1px" border-style="solid" border-color="#DDDDDD" width="30%" align="left"/>
<mj-image src="{% base64_static 'images/logo.png' %}" width="157px" align="left" alt="{% trans 'Logo' %}" />
<!-- Main Message -->
<mj-text>{% trans "Here are your credentials ! " %}</mj-text>
<mj-text>{% trans "Email address : "%}{{ mailbox_data.email }}</mj-text>
<mj-text>{% trans "Temporary password : "%}{{ mailbox_data.password }}</mj-text>
<mj-text>{% trans "You can access your mails on " %}<a href="//{{ webmail_url }}"</a>.</mj-text>
<mj-button href="//{{ site.domain }}" background-color="#000091" color="white" padding-bottom="30px">
{% trans "Visit La Régie" %}
</mj-button>
<!-- Signature -->
<mj-text>
<p>{% trans "Sincerely," %}</p>
<p>{% trans "The La Suite Numérique Team" %}</p>
</mj-text>
</mj-column>
</mj-section>
</mj-wrapper>
</mj-body>
<mj-include path="./partial/footer.mjml" />
</mjml>