(dimail) check domain health

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.
This commit is contained in:
Sabrina Demagny
2024-12-27 20:07:18 +01:00
parent 5d2e63fc18
commit 0abfd49fee
4 changed files with 134 additions and 3 deletions

View File

@@ -27,6 +27,7 @@ and this project adheres to
### Added ### Added
- ✨(dimail) check domain health
- ✨(frontend) disable mailbox and allow to create pending mailbox - ✨(frontend) disable mailbox and allow to create pending mailbox
- ✨(organizations) add siret to name conversion #584 - ✨(organizations) add siret to name conversion #584
- 💄(frontend) redirect home according to abilities #588 - 💄(frontend) redirect home according to abilities #588

View File

@@ -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)"}
],
},
}

View File

@@ -2,6 +2,7 @@
Unit tests for dimail client Unit tests for dimail client
""" """
import json
import re import re
from email.errors import HeaderParseError, NonASCIILocalPartDefect from email.errors import HeaderParseError, NonASCIILocalPartDefect
from logging import Logger from logging import Logger
@@ -11,9 +12,11 @@ import pytest
import responses import responses
from rest_framework import status 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 mailbox_manager.utils.dimail import DimailAPIClient
from .fixtures.dimail import CHECK_DOMAIN_BROKEN
pytestmark = pytest.mark.django_db pytestmark = pytest.mark.django_db
@@ -154,3 +157,22 @@ def test_dimail_synchronization__synchronize_mailboxes(mock_warning):
mailbox = models.Mailbox.objects.get() mailbox = models.Mailbox.objects.get()
assert mailbox.local_part == "oxadmin" assert mailbox.local_part == "oxadmin"
assert imported_mailboxes == [mailbox_valid["email"]] 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

View File

@@ -17,7 +17,7 @@ import requests
from rest_framework import status from rest_framework import status
from urllib3.util import Retry from urllib3.util import Retry
from mailbox_manager import models from mailbox_manager import enums, models
logger = getLogger(__name__) logger = getLogger(__name__)
@@ -249,7 +249,8 @@ class DimailAPIClient:
"[DIMAIL] unexpected error : %s %s", response.status_code, error_content "[DIMAIL] unexpected error : %s %s", response.status_code, error_content
) )
raise requests.exceptions.HTTPError( 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): def notify_mailbox_creation(self, recipient, mailbox_data):
@@ -394,3 +395,28 @@ class DimailAPIClient:
) )
return response return response
return self.raise_exception_for_unexpected_response(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)