Skip to content

Commit

Permalink
✨(dimail) synchronize mailboxes from dimail to our db
Browse files Browse the repository at this point in the history
Synchronize mailboxes existing on dimail's api and not on our side,
on domains we are administrating.
  • Loading branch information
mjeammet committed Oct 9, 2024
1 parent 763eb25 commit 7376efc
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@ and this project adheres to

## [Unreleased]

### Added

- ✨(dimail) synchronize mailboxes from dimail to our db #453

### Fixed

- 🐛(frontend) fix update accesses form #448
Expand Down
97 changes: 97 additions & 0 deletions src/backend/mailbox_manager/tests/test_utils_dimail_client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""
Unit tests for dimail client
"""

import re

import pytest
import responses
from rest_framework import status

from mailbox_manager import enums, factories, models
from mailbox_manager.utils.dimail import DimailAPIClient

pytestmark = pytest.mark.django_db


def test_dimail_synchronization__already_sync():
"""
Nothing should be created when everything is already synced.
"""
domain = factories.MailDomainFactory(status=enums.MailDomainStatusChoices.ENABLED)
factories.MailboxFactory.create_batch(3, domain=domain)

pre_sync_mailboxes = models.Mailbox.objects.filter(domain=domain)
assert pre_sync_mailboxes.count() == 3

dimail_client = DimailAPIClient()
with responses.RequestsMock() as rsps:
# Ensure successful response using "responses":
rsps.add(
rsps.GET,
re.compile(r".*/token/"),
body='{"access_token": "dimail_people_token"}',
status=status.HTTP_200_OK,
content_type="application/json",
)
rsps.add(
rsps.GET,
re.compile(rf".*/domains/{domain.name}/mailboxes/"),
body=str(
[
{
"type": "mailbox",
"status": "broken",
"email": f"{mailbox.local_part}@{domain.name}",
"givenName": mailbox.first_name,
"surName": mailbox.last_name,
"displayName": f"{mailbox.first_name} {mailbox.last_name}",
}
for mailbox in pre_sync_mailboxes
]
),
status=status.HTTP_200_OK,
content_type="application/json",
)
dimail_client.synchronize_dimail_mailboxes(domain.name)

assert set(models.Mailbox.objects.filter(domain=domain)) == set(pre_sync_mailboxes)


def test_dimail_synchronization__synchronize_mailboxes():
"""A mailbox existing solely on dimail should be synchronized
upon calling sync function on its domain"""
domain = factories.MailDomainFactory(status=enums.MailDomainStatusChoices.ENABLED)
assert not models.Mailbox.objects.exists()

dimail_client = DimailAPIClient()
with responses.RequestsMock() as rsps:
# Ensure successful response using "responses":
rsps.add(
rsps.GET,
re.compile(r".*/token/"),
body='{"access_token": "dimail_people_token"}',
status=status.HTTP_200_OK,
content_type="application/json",
)
rsps.add(
rsps.GET,
re.compile(rf".*/domains/{domain.name}/mailboxes/"),
body=str(
[
{
"type": "mailbox",
"status": "broken",
"email": "[email protected]",
"givenName": "Admin",
"surName": "Context",
"displayName": "Context Admin",
}
]
),
status=status.HTTP_200_OK,
content_type="application/json",
)
dimail_client.synchronize_dimail_mailboxes(domain.name)

assert models.Mailbox.objects.exists()
52 changes: 52 additions & 0 deletions src/backend/mailbox_manager/utils/dimail.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""A minimalist client to synchronize with mailbox provisioning API."""

import ast
import smtplib
from logging import getLogger

Expand All @@ -13,6 +14,8 @@
from rest_framework import status
from urllib3.util import Retry

from mailbox_manager import models

logger = getLogger(__name__)

adapter = requests.adapters.HTTPAdapter(
Expand Down Expand Up @@ -163,3 +166,52 @@ def send_new_mailbox_notification(self, recipient, mailbox_data):
recipient,
exception,
)

def synchronize_dimail_mailboxes(self, domain_name):
"""Synchronize mailboxes from dimail - open xchange to our database.
This is useful in case of acquisition of a pre-existing mail domain.
This is not considered a new email and will not trigger any mail notification."""

domain = models.MailDomain.objects.get(name=domain_name)
people_mailboxes = models.Mailbox.objects.filter(domain=domain)

headers = self.get_headers()

try:
response = session.get(
f"{self.API_URL}/domains/{domain_name}/mailboxes/",
headers=headers,
verify=True,
timeout=10,
)
except requests.exceptions.ConnectionError as error:
logger.error(
"Connection error while trying to reach %s.",
self.API_URL,
exc_info=error,
)
raise error

if response.status_code != status.HTTP_200_OK:
return self.pass_dimail_unexpected_response(response)

content = ast.literal_eval(
response.content.decode("utf-8")
) # format output str to proper list

for mailbox in content:
local_part = mailbox["email"].split("@")[0]
if not mailbox["email"] in [str(mailbox) for mailbox in people_mailboxes]:
# creates a mailbox on our end
models.Mailbox.objects.create(
first_name=mailbox["givenName"],
last_name=mailbox["surName"],
local_part=local_part,
domain=domain,
secondary_email=mailbox[
"email"
], # secondary email is mandatory. Unfortunately, dimail doesn't store any.
# We temporarily give current email as secondary email.
)

return None

0 comments on commit 7376efc

Please sign in to comment.