✨(mailbox) send new mailbox confirmation email
send mailbox information upon creating a new mailbox
This commit is contained in:
committed by
Marie
parent
b69ce001c8
commit
5ded297df6
@@ -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
|
||||
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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"],
|
||||
)
|
||||
|
||||
@@ -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,
|
||||
)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
47
src/mail/mjml/new_mailbox.mjml
Normal file
47
src/mail/mjml/new_mailbox.mjml
Normal 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>
|
||||
|
||||
Reference in New Issue
Block a user