From 0abfd49feeb115a54942b7dd950941dfff974eb2 Mon Sep 17 00:00:00 2001 From: Sabrina Demagny Date: Fri, 27 Dec 2024 20:07:18 +0100 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8(dimail)=20check=20domain=20health?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Call dimail to check if a domain still works. Turn domain into failure status if dimail returns broken state. And enable domain if dimail returns ok state. --- CHANGELOG.md | 1 + .../mailbox_manager/tests/fixtures/dimail.py | 82 +++++++++++++++++++ .../tests/test_utils_dimail_client.py | 24 +++++- src/backend/mailbox_manager/utils/dimail.py | 30 ++++++- 4 files changed, 134 insertions(+), 3 deletions(-) create mode 100644 src/backend/mailbox_manager/tests/fixtures/dimail.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 693c9f7..4f4fb9a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ and this project adheres to ### Added +- ✨(dimail) check domain health - ✨(frontend) disable mailbox and allow to create pending mailbox - ✨(organizations) add siret to name conversion #584 - 💄(frontend) redirect home according to abilities #588 diff --git a/src/backend/mailbox_manager/tests/fixtures/dimail.py b/src/backend/mailbox_manager/tests/fixtures/dimail.py new file mode 100644 index 0000000..25fe4b7 --- /dev/null +++ b/src/backend/mailbox_manager/tests/fixtures/dimail.py @@ -0,0 +1,82 @@ +# pylint: disable=line-too-long +"""Define here some fake data from dimail, useful to mock dimail response""" + +CHECK_DOMAIN_BROKEN = { + "name": "example.fr", + "state": "broken", + "valid": False, + "delivery": "virtual", + "features": ["webmail", "mailbox"], + "webmail_domain": None, + "imap_domain": None, + "smtp_domain": None, + "context_name": "example.fr", + "transport": None, + "domain_exist": {"ok": True, "internal": False, "errors": []}, + "mx": { + "ok": False, + "internal": False, + "errors": [ + { + "code": "wrong_mx", + "detail": "Je veux que le MX du domaine soit mx.ox.numerique.gouv.fr., or je trouve example-fr.mail.protection.outlook.com.", + } + ], + }, + "cname_imap": { + "ok": False, + "internal": False, + "errors": [ + { + "code": "no_cname_imap", + "detail": "Il faut un CNAME 'imap.example.fr' qui renvoie vers 'imap.ox.numerique.gouv.fr.'", + } + ], + }, + "cname_smtp": { + "ok": False, + "internal": False, + "errors": [ + { + "code": "wrong_cname_smtp", + "detail": "Le CNAME pour 'smtp.example.fr' n'est pas bon, il renvoie vers 'ns0.ovh.net.' et je veux 'smtp.ox.numerique.gouv.fr.'", + } + ], + }, + "cname_webmail": { + "ok": False, + "internal": False, + "errors": [ + { + "code": "no_cname_webmail", + "detail": "Il faut un CNAME 'webmail.example.fr' qui renvoie vers 'webmail.ox.numerique.gouv.fr.'", + } + ], + }, + "spf": { + "ok": False, + "internal": False, + "errors": [ + { + "code": "wrong_spf", + "detail": "Le SPF record ne contient pas include:_spf.ox.numerique.gouv.fr", + } + ], + }, + "dkim": { + "ok": False, + "internal": False, + "errors": [ + {"code": "no_dkim", "detail": "Il faut un DKIM record, avec la bonne clef"} + ], + }, + "postfix": {"ok": True, "internal": True, "errors": []}, + "ox": {"ok": True, "internal": True, "errors": []}, + "cert": { + "ok": False, + "internal": True, + "errors": [ + {"code": "no_cert", "detail": "Pas de certificat pour ce domaine (ls)"} + ], + }, +} diff --git a/src/backend/mailbox_manager/tests/test_utils_dimail_client.py b/src/backend/mailbox_manager/tests/test_utils_dimail_client.py index 5aa59c9..970cdaa 100644 --- a/src/backend/mailbox_manager/tests/test_utils_dimail_client.py +++ b/src/backend/mailbox_manager/tests/test_utils_dimail_client.py @@ -2,6 +2,7 @@ Unit tests for dimail client """ +import json import re from email.errors import HeaderParseError, NonASCIILocalPartDefect from logging import Logger @@ -11,9 +12,11 @@ import pytest import responses from rest_framework import status -from mailbox_manager import factories, models +from mailbox_manager import enums, factories, models from mailbox_manager.utils.dimail import DimailAPIClient +from .fixtures.dimail import CHECK_DOMAIN_BROKEN + pytestmark = pytest.mark.django_db @@ -154,3 +157,22 @@ def test_dimail_synchronization__synchronize_mailboxes(mock_warning): mailbox = models.Mailbox.objects.get() assert mailbox.local_part == "oxadmin" assert imported_mailboxes == [mailbox_valid["email"]] + + +def test_dimail__fetch_domain_status_from_dimail(): + """Request to dimail health status of a domain""" + domain = factories.MailDomainEnabledFactory() + with responses.RequestsMock() as rsps: + body_content_domain = CHECK_DOMAIN_BROKEN.copy() + body_content_domain["name"] = domain.name + rsps.add( + rsps.GET, + re.compile(rf".*/domains/{domain.name}/check/"), + body=json.dumps(body_content_domain), + status=status.HTTP_200_OK, + content_type="application/json", + ) + dimail_client = DimailAPIClient() + response = dimail_client.fetch_domain_status(domain) + assert response.status_code == status.HTTP_200_OK + assert domain.status == enums.MailDomainStatusChoices.FAILED diff --git a/src/backend/mailbox_manager/utils/dimail.py b/src/backend/mailbox_manager/utils/dimail.py index 207aa4d..c4ec5f4 100644 --- a/src/backend/mailbox_manager/utils/dimail.py +++ b/src/backend/mailbox_manager/utils/dimail.py @@ -17,7 +17,7 @@ import requests from rest_framework import status from urllib3.util import Retry -from mailbox_manager import models +from mailbox_manager import enums, models logger = getLogger(__name__) @@ -249,7 +249,8 @@ class DimailAPIClient: "[DIMAIL] unexpected error : %s %s", response.status_code, error_content ) raise requests.exceptions.HTTPError( - f"Unexpected response from dimail: {response.status_code} {error_content}" + f"Unexpected response from dimail: {response.status_code} " + f"{error_content.get('detail') or error_content}" ) def notify_mailbox_creation(self, recipient, mailbox_data): @@ -394,3 +395,28 @@ class DimailAPIClient: ) return response return self.raise_exception_for_unexpected_response(response) + + def fetch_domain_status(self, domain): + """Send a request to check domain and update status of our domain.""" + response = session.get( + f"{self.API_URL}/domains/{domain.name}/check/", + headers={"Authorization": f"Basic {self.API_CREDENTIALS}"}, + verify=True, + timeout=10, + ) + if response.status_code == status.HTTP_200_OK: + dimail_status = response.json()["state"] + if ( + domain.status != enums.MailDomainStatusChoices.ENABLED + and dimail_status == "ok" + ): + domain.status = enums.MailDomainStatusChoices.ENABLED + domain.save() + elif ( + domain.status != enums.MailDomainStatusChoices.FAILED + and dimail_status == "broken" + ): + domain.status = enums.MailDomainStatusChoices.FAILED + domain.save() + return response + return self.raise_exception_for_unexpected_response(response)