✨(aliases) import existing aliases from dimail
import existing aliases from dimail, making sure usernames don't clash with existing mailboxes. Convenient when people fall out of sync with dimail or for domains partially operated outside people.
This commit is contained in:
committed by
Marie
parent
befcecf33c
commit
040f949e5e
@@ -8,6 +8,7 @@ and this project adheres to
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
- ✨(aliases) import existing aliases from dimail
|
||||||
- 🛂(permissions) return 404 to users with no access to domain #985
|
- 🛂(permissions) return 404 to users with no access to domain #985
|
||||||
- ✨(aliases) can create, list and delete aliases #974
|
- ✨(aliases) can create, list and delete aliases #974
|
||||||
|
|
||||||
|
|||||||
@@ -10,8 +10,6 @@ from requests import exceptions
|
|||||||
from mailbox_manager import enums, models
|
from mailbox_manager import enums, models
|
||||||
from mailbox_manager.utils.dimail import DimailAPIClient
|
from mailbox_manager.utils.dimail import DimailAPIClient
|
||||||
|
|
||||||
# Prevent Ruff complaining about mark_safe below
|
|
||||||
|
|
||||||
|
|
||||||
@admin.action(description=_("Import emails from dimail"))
|
@admin.action(description=_("Import emails from dimail"))
|
||||||
def sync_mailboxes_from_dimail(modeladmin, request, queryset): # pylint: disable=unused-argument
|
def sync_mailboxes_from_dimail(modeladmin, request, queryset): # pylint: disable=unused-argument
|
||||||
@@ -50,6 +48,50 @@ def sync_mailboxes_from_dimail(modeladmin, request, queryset): # pylint: disabl
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@admin.action(description=_("Import aliases from dimail"))
|
||||||
|
def sync_aliases_from_dimail(modeladmin, request, queryset): # pylint: disable=unused-argument
|
||||||
|
"""
|
||||||
|
Admin action to import existing aliases from dimail.
|
||||||
|
Checks alias is not a duplicate and that usernames don't clash with existing mailboxes.
|
||||||
|
"""
|
||||||
|
excluded_domains = []
|
||||||
|
|
||||||
|
client = DimailAPIClient()
|
||||||
|
|
||||||
|
for domain in queryset:
|
||||||
|
if domain.status != enums.MailDomainStatusChoices.ENABLED:
|
||||||
|
excluded_domains.append(domain.name)
|
||||||
|
continue
|
||||||
|
|
||||||
|
try:
|
||||||
|
imported_aliases = client.import_aliases(domain)
|
||||||
|
except exceptions.HTTPError as err:
|
||||||
|
messages.error(
|
||||||
|
request,
|
||||||
|
_("Synchronisation failed for %(domain)s with message: %(err)s")
|
||||||
|
% {"domain": domain.name, "err": err},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
messages.success(
|
||||||
|
request,
|
||||||
|
_(
|
||||||
|
"Synchronisation succeed for %(domain)s. %(imported_aliases)\
|
||||||
|
imported aliases: %(mailboxes)s"
|
||||||
|
)
|
||||||
|
% {
|
||||||
|
"domain": domain.name,
|
||||||
|
"number_imported": len(imported_aliases),
|
||||||
|
"mailboxes": ", ".join(imported_aliases),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if excluded_domains:
|
||||||
|
messages.warning(
|
||||||
|
request,
|
||||||
|
_("Sync require enabled domains. Excluded domains: %(domains)s")
|
||||||
|
% {"domains": ", ".join(excluded_domains)},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@admin.action(description=_("Check and update status from dimail"))
|
@admin.action(description=_("Check and update status from dimail"))
|
||||||
def fetch_domain_status_from_dimail(modeladmin, request, queryset): # pylint: disable=unused-argument
|
def fetch_domain_status_from_dimail(modeladmin, request, queryset): # pylint: disable=unused-argument
|
||||||
"""Admin action to check domain health with dimail and update domain status."""
|
"""Admin action to check domain health with dimail and update domain status."""
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
Unit tests for dimail client
|
Unit tests for dimail client
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import json
|
# pylint: disable=W0613
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
import re
|
import re
|
||||||
from email.errors import HeaderParseError, NonASCIILocalPartDefect
|
from email.errors import HeaderParseError, NonASCIILocalPartDefect
|
||||||
@@ -21,14 +22,14 @@ from .fixtures.dimail import (
|
|||||||
CHECK_DOMAIN_BROKEN_EXTERNAL,
|
CHECK_DOMAIN_BROKEN_EXTERNAL,
|
||||||
CHECK_DOMAIN_BROKEN_INTERNAL,
|
CHECK_DOMAIN_BROKEN_INTERNAL,
|
||||||
CHECK_DOMAIN_OK,
|
CHECK_DOMAIN_OK,
|
||||||
TOKEN_OK,
|
|
||||||
response_mailbox_created,
|
response_mailbox_created,
|
||||||
)
|
)
|
||||||
|
|
||||||
pytestmark = pytest.mark.django_db
|
pytestmark = pytest.mark.django_db
|
||||||
|
|
||||||
|
|
||||||
def test_dimail_synchronization__already_sync():
|
@responses.activate
|
||||||
|
def test_dimail_synchronization__already_sync(dimail_token_ok):
|
||||||
"""
|
"""
|
||||||
No mailbox should be created when everything is already synced.
|
No mailbox should be created when everything is already synced.
|
||||||
"""
|
"""
|
||||||
@@ -39,35 +40,26 @@ def test_dimail_synchronization__already_sync():
|
|||||||
assert pre_sync_mailboxes.count() == 3
|
assert pre_sync_mailboxes.count() == 3
|
||||||
|
|
||||||
dimail_client = DimailAPIClient()
|
dimail_client = DimailAPIClient()
|
||||||
with responses.RequestsMock() as rsps:
|
|
||||||
# Ensure successful response using "responses":
|
# Ensure successful response using "responses":
|
||||||
rsps.add(
|
# token response in fixtures
|
||||||
rsps.GET,
|
responses.get(
|
||||||
re.compile(r".*/token/"),
|
re.compile(rf".*/domains/{domain.name}/mailboxes/"),
|
||||||
body='{"access_token": "dimail_people_token"}',
|
json=[
|
||||||
status=status.HTTP_200_OK,
|
{
|
||||||
content_type="application/json",
|
"type": "mailbox",
|
||||||
)
|
"status": "broken",
|
||||||
rsps.add(
|
"email": f"{mailbox.local_part}@{domain.name}",
|
||||||
rsps.GET,
|
"givenName": mailbox.first_name,
|
||||||
re.compile(rf".*/domains/{domain.name}/mailboxes/"),
|
"surName": mailbox.last_name,
|
||||||
body=json.dumps(
|
"displayName": f"{mailbox.first_name} {mailbox.last_name}",
|
||||||
[
|
}
|
||||||
{
|
for mailbox in pre_sync_mailboxes
|
||||||
"type": "mailbox",
|
],
|
||||||
"status": "broken",
|
status=status.HTTP_200_OK,
|
||||||
"email": f"{mailbox.local_part}@{domain.name}",
|
content_type="application/json",
|
||||||
"givenName": mailbox.first_name,
|
)
|
||||||
"surName": mailbox.last_name,
|
imported_mailboxes = dimail_client.import_mailboxes(domain)
|
||||||
"displayName": f"{mailbox.first_name} {mailbox.last_name}",
|
|
||||||
}
|
|
||||||
for mailbox in pre_sync_mailboxes
|
|
||||||
]
|
|
||||||
),
|
|
||||||
status=status.HTTP_200_OK,
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
imported_mailboxes = dimail_client.import_mailboxes(domain)
|
|
||||||
|
|
||||||
post_sync_mailboxes = models.Mailbox.objects.filter(domain=domain)
|
post_sync_mailboxes = models.Mailbox.objects.filter(domain=domain)
|
||||||
assert post_sync_mailboxes.count() == 3
|
assert post_sync_mailboxes.count() == 3
|
||||||
@@ -75,97 +67,138 @@ def test_dimail_synchronization__already_sync():
|
|||||||
assert set(models.Mailbox.objects.filter(domain=domain)) == set(pre_sync_mailboxes)
|
assert set(models.Mailbox.objects.filter(domain=domain)) == set(pre_sync_mailboxes)
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
@mock.patch.object(Logger, "warning")
|
@mock.patch.object(Logger, "warning")
|
||||||
def test_dimail_synchronization__synchronize_mailboxes(mock_warning):
|
def test_dimail_synchronization__synchronize_mailboxes(mock_warning, dimail_token_ok):
|
||||||
"""A mailbox existing solely on dimail should be synchronized
|
"""A mailbox existing solely on dimail should be synchronized
|
||||||
upon calling sync function on its domain"""
|
upon calling sync function on its domain"""
|
||||||
domain = factories.MailDomainEnabledFactory()
|
domain = factories.MailDomainEnabledFactory()
|
||||||
assert not models.Mailbox.objects.exists()
|
assert not models.Mailbox.objects.exists()
|
||||||
|
|
||||||
dimail_client = DimailAPIClient()
|
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",
|
|
||||||
)
|
|
||||||
|
|
||||||
mailbox_valid = {
|
# Ensure successful response using "responses":
|
||||||
"type": "mailbox",
|
# token response in fixtures
|
||||||
"status": "broken",
|
mailbox_valid = {
|
||||||
"email": f"oxadmin@{domain.name}",
|
"type": "mailbox",
|
||||||
"givenName": "Admin",
|
"status": "broken",
|
||||||
"surName": "Context",
|
"email": f"oxadmin@{domain.name}",
|
||||||
"displayName": "Context Admin",
|
"givenName": "Admin",
|
||||||
}
|
"surName": "Context",
|
||||||
mailbox_with_wrong_domain = {
|
"displayName": "Context Admin",
|
||||||
"type": "mailbox",
|
}
|
||||||
"status": "broken",
|
mailbox_with_wrong_domain = {
|
||||||
"email": "johndoe@wrongdomain.com",
|
"type": "mailbox",
|
||||||
"givenName": "John",
|
"status": "broken",
|
||||||
"surName": "Doe",
|
"email": "johndoe@wrongdomain.com",
|
||||||
"displayName": "John Doe",
|
"givenName": "John",
|
||||||
}
|
"surName": "Doe",
|
||||||
mailbox_with_invalid_domain = {
|
"displayName": "John Doe",
|
||||||
"type": "mailbox",
|
}
|
||||||
"status": "broken",
|
mailbox_with_invalid_domain = {
|
||||||
"email": f"naw@ake@{domain.name}",
|
"type": "mailbox",
|
||||||
"givenName": "Joe",
|
"status": "broken",
|
||||||
"surName": "Doe",
|
"email": f"naw@ake@{domain.name}",
|
||||||
"displayName": "Joe Doe",
|
"givenName": "Joe",
|
||||||
}
|
"surName": "Doe",
|
||||||
mailbox_with_invalid_local_part = {
|
"displayName": "Joe Doe",
|
||||||
"type": "mailbox",
|
}
|
||||||
"status": "broken",
|
mailbox_with_invalid_local_part = {
|
||||||
"email": f"obalmaské@{domain.name}",
|
"type": "mailbox",
|
||||||
"givenName": "Jean",
|
"status": "broken",
|
||||||
"surName": "Vang",
|
"email": f"obalmaské@{domain.name}",
|
||||||
"displayName": "Jean Vang",
|
"givenName": "Jean",
|
||||||
}
|
"surName": "Vang",
|
||||||
|
"displayName": "Jean Vang",
|
||||||
|
}
|
||||||
|
|
||||||
rsps.add(
|
responses.get(
|
||||||
rsps.GET,
|
re.compile(rf".*/domains/{domain.name}/mailboxes/"),
|
||||||
re.compile(rf".*/domains/{domain.name}/mailboxes/"),
|
json=[
|
||||||
body=json.dumps(
|
mailbox_valid,
|
||||||
[
|
mailbox_with_wrong_domain,
|
||||||
mailbox_valid,
|
mailbox_with_invalid_domain,
|
||||||
mailbox_with_wrong_domain,
|
mailbox_with_invalid_local_part,
|
||||||
mailbox_with_invalid_domain,
|
],
|
||||||
mailbox_with_invalid_local_part,
|
status=status.HTTP_200_OK,
|
||||||
]
|
content_type="application/json",
|
||||||
),
|
)
|
||||||
status=status.HTTP_200_OK,
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
|
|
||||||
imported_mailboxes = dimail_client.import_mailboxes(domain)
|
imported_mailboxes = dimail_client.import_mailboxes(domain)
|
||||||
|
|
||||||
# 3 imports failed: wrong domain, HeaderParseError, NonASCIILocalPartDefect
|
# 3 imports failed: wrong domain, HeaderParseError, NonASCIILocalPartDefect
|
||||||
assert mock_warning.call_count == 3
|
assert mock_warning.call_count == 3
|
||||||
|
|
||||||
# first we try to import email with a wrong domain
|
# first we try to import email with a wrong domain
|
||||||
assert mock_warning.call_args_list[0][0] == (
|
assert mock_warning.call_args_list[0][0] == (
|
||||||
"Import of email %s failed because of a wrong domain",
|
"Import of email %s failed because of a wrong domain",
|
||||||
mailbox_with_wrong_domain["email"],
|
mailbox_with_wrong_domain["email"],
|
||||||
)
|
)
|
||||||
|
|
||||||
# then we try to import email with invalid domain
|
# then we try to import email with invalid domain
|
||||||
invalid_mailbox_log = mock_warning.call_args_list[1][0]
|
invalid_mailbox_log = mock_warning.call_args_list[1][0]
|
||||||
assert invalid_mailbox_log[1] == mailbox_with_invalid_domain["email"]
|
assert invalid_mailbox_log[1] == mailbox_with_invalid_domain["email"]
|
||||||
assert isinstance(invalid_mailbox_log[2], HeaderParseError)
|
assert isinstance(invalid_mailbox_log[2], HeaderParseError)
|
||||||
|
|
||||||
# finally we try to import email with non ascii local part
|
# finally we try to import email with non ascii local part
|
||||||
non_ascii_mailbox_log = mock_warning.call_args_list[2][0]
|
non_ascii_mailbox_log = mock_warning.call_args_list[2][0]
|
||||||
assert non_ascii_mailbox_log[1] == mailbox_with_invalid_local_part["email"]
|
assert non_ascii_mailbox_log[1] == mailbox_with_invalid_local_part["email"]
|
||||||
assert isinstance(non_ascii_mailbox_log[2], NonASCIILocalPartDefect)
|
assert isinstance(non_ascii_mailbox_log[2], NonASCIILocalPartDefect)
|
||||||
|
|
||||||
mailbox = models.Mailbox.objects.get()
|
mailbox = models.Mailbox.objects.get()
|
||||||
assert mailbox.local_part == "oxadmin"
|
assert mailbox.local_part == "oxadmin"
|
||||||
assert mailbox.status == enums.MailboxStatusChoices.ENABLED
|
assert mailbox.status == enums.MailboxStatusChoices.ENABLED
|
||||||
assert imported_mailboxes == [mailbox_valid["email"]]
|
assert imported_mailboxes == [mailbox_valid["email"]]
|
||||||
|
|
||||||
|
|
||||||
|
@responses.activate
|
||||||
|
def test_dimail_synchronization__synchronize_aliases(dimail_token_ok): # pylint: disable=unused-argument
|
||||||
|
"""Should import aliases from dimail if they don't already exist
|
||||||
|
and if username is not already used for mailbox"""
|
||||||
|
alias = factories.AliasFactory()
|
||||||
|
dimail_client = DimailAPIClient()
|
||||||
|
|
||||||
|
existing_mailbox = factories.MailboxFactory(domain=alias.domain)
|
||||||
|
|
||||||
|
# Ensure successful response using "responses":
|
||||||
|
# token response in fixtures
|
||||||
|
incoming_aliases = [
|
||||||
|
{
|
||||||
|
"username": "contact",
|
||||||
|
"domain": alias.domain.name,
|
||||||
|
"destination": alias.destination, # same destination
|
||||||
|
"allow_to_send": False,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"username": alias.local_part, # same username
|
||||||
|
"domain": alias.domain.name,
|
||||||
|
"destination": "maheius.endorecles@somethingelse.com",
|
||||||
|
"allow_to_send": False,
|
||||||
|
},
|
||||||
|
{ # same username + same destination = big nono
|
||||||
|
"username": alias.local_part,
|
||||||
|
"domain": alias.domain.name,
|
||||||
|
"destination": alias.destination,
|
||||||
|
"allow_to_send": False,
|
||||||
|
},
|
||||||
|
{ # username already used for a mailbox
|
||||||
|
"username": existing_mailbox.local_part,
|
||||||
|
"domain": alias.domain.name,
|
||||||
|
"destination": existing_mailbox.secondary_email,
|
||||||
|
"allow_to_send": False,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
responses.get(
|
||||||
|
re.compile(rf".*/domains/{alias.domain.name}/aliases/"),
|
||||||
|
json=incoming_aliases,
|
||||||
|
status=status.HTTP_200_OK,
|
||||||
|
content_type="application/json",
|
||||||
|
)
|
||||||
|
|
||||||
|
imported_aliases = dimail_client.import_aliases(alias.domain)
|
||||||
|
|
||||||
|
assert len(imported_aliases) == 2
|
||||||
|
assert models.Alias.objects.count() == 3
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
@@ -183,10 +216,9 @@ def test_dimail__fetch_domain_status__switch_to_enabled(domain_status):
|
|||||||
domain = factories.MailDomainFactory(status=domain_status)
|
domain = factories.MailDomainFactory(status=domain_status)
|
||||||
body_content = CHECK_DOMAIN_OK.copy()
|
body_content = CHECK_DOMAIN_OK.copy()
|
||||||
body_content["name"] = domain.name
|
body_content["name"] = domain.name
|
||||||
responses.add(
|
responses.get(
|
||||||
responses.GET,
|
|
||||||
re.compile(rf".*/domains/{domain.name}/check/"),
|
re.compile(rf".*/domains/{domain.name}/check/"),
|
||||||
body=json.dumps(body_content),
|
json=body_content,
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
content_type="application/json",
|
content_type="application/json",
|
||||||
)
|
)
|
||||||
@@ -221,10 +253,9 @@ def test_dimail__fetch_domain_status__switch_to_action_required(
|
|||||||
domain = factories.MailDomainFactory(status=domain_status)
|
domain = factories.MailDomainFactory(status=domain_status)
|
||||||
body_domain_broken = CHECK_DOMAIN_BROKEN_EXTERNAL.copy()
|
body_domain_broken = CHECK_DOMAIN_BROKEN_EXTERNAL.copy()
|
||||||
body_domain_broken["name"] = domain.name
|
body_domain_broken["name"] = domain.name
|
||||||
responses.add(
|
responses.get(
|
||||||
responses.GET,
|
|
||||||
re.compile(rf".*/domains/{domain.name}/check/"),
|
re.compile(rf".*/domains/{domain.name}/check/"),
|
||||||
body=json.dumps(body_domain_broken),
|
json=body_domain_broken,
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
content_type="application/json",
|
content_type="application/json",
|
||||||
)
|
)
|
||||||
@@ -238,10 +269,9 @@ def test_dimail__fetch_domain_status__switch_to_action_required(
|
|||||||
# Now domain is OK again
|
# Now domain is OK again
|
||||||
body_domain_ok = CHECK_DOMAIN_OK.copy()
|
body_domain_ok = CHECK_DOMAIN_OK.copy()
|
||||||
body_domain_ok["name"] = domain.name
|
body_domain_ok["name"] = domain.name
|
||||||
responses.add(
|
responses.get(
|
||||||
responses.GET,
|
|
||||||
re.compile(rf".*/domains/{domain.name}/check/"),
|
re.compile(rf".*/domains/{domain.name}/check/"),
|
||||||
body=json.dumps(body_domain_ok),
|
json=body_domain_ok,
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
content_type="application/json",
|
content_type="application/json",
|
||||||
)
|
)
|
||||||
@@ -267,18 +297,16 @@ def test_dimail__fetch_domain_status__switch_to_failed(domain_status):
|
|||||||
# nothing can be done by support team, domain should be in failed
|
# nothing can be done by support team, domain should be in failed
|
||||||
body_domain_broken = CHECK_DOMAIN_BROKEN_INTERNAL.copy()
|
body_domain_broken = CHECK_DOMAIN_BROKEN_INTERNAL.copy()
|
||||||
body_domain_broken["name"] = domain.name
|
body_domain_broken["name"] = domain.name
|
||||||
responses.add(
|
responses.get(
|
||||||
responses.GET,
|
|
||||||
re.compile(rf".*/domains/{domain.name}/check/"),
|
re.compile(rf".*/domains/{domain.name}/check/"),
|
||||||
body=json.dumps(body_domain_broken),
|
json=body_domain_broken,
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
content_type="application/json",
|
content_type="application/json",
|
||||||
)
|
)
|
||||||
# the endpoint fix is called and still returns KO for internal checks
|
# the endpoint fix is called and still returns KO for internal checks
|
||||||
responses.add(
|
responses.get(
|
||||||
responses.GET,
|
|
||||||
re.compile(rf".*/domains/{domain.name}/fix/"),
|
re.compile(rf".*/domains/{domain.name}/fix/"),
|
||||||
body=json.dumps(body_domain_broken),
|
json=body_domain_broken,
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
content_type="application/json",
|
content_type="application/json",
|
||||||
)
|
)
|
||||||
@@ -305,10 +333,9 @@ def test_dimail__fetch_domain_status__full_fix_scenario(domain_status):
|
|||||||
# with all checks KO, domain should be in action required
|
# with all checks KO, domain should be in action required
|
||||||
body_domain_broken = CHECK_DOMAIN_BROKEN.copy()
|
body_domain_broken = CHECK_DOMAIN_BROKEN.copy()
|
||||||
body_domain_broken["name"] = domain.name
|
body_domain_broken["name"] = domain.name
|
||||||
responses.add(
|
responses.get(
|
||||||
responses.GET,
|
|
||||||
re.compile(rf".*/domains/{domain.name}/check/"),
|
re.compile(rf".*/domains/{domain.name}/check/"),
|
||||||
body=json.dumps(body_domain_broken),
|
json=body_domain_broken,
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
content_type="application/json",
|
content_type="application/json",
|
||||||
)
|
)
|
||||||
@@ -324,20 +351,18 @@ def test_dimail__fetch_domain_status__full_fix_scenario(domain_status):
|
|||||||
# the fetch_domain_status call
|
# the fetch_domain_status call
|
||||||
body_domain_broken_internal = CHECK_DOMAIN_BROKEN_INTERNAL.copy()
|
body_domain_broken_internal = CHECK_DOMAIN_BROKEN_INTERNAL.copy()
|
||||||
body_domain_broken_internal["name"] = domain.name
|
body_domain_broken_internal["name"] = domain.name
|
||||||
responses.add(
|
responses.get(
|
||||||
responses.GET,
|
|
||||||
re.compile(rf".*/domains/{domain.name}/check/"),
|
re.compile(rf".*/domains/{domain.name}/check/"),
|
||||||
body=json.dumps(body_domain_broken_internal),
|
json=body_domain_broken_internal,
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
content_type="application/json",
|
content_type="application/json",
|
||||||
)
|
)
|
||||||
# the endpoint fix is called and returns OK. Hooray!
|
# the endpoint fix is called and returns OK. Hooray!
|
||||||
body_domain_ok = CHECK_DOMAIN_OK.copy()
|
body_domain_ok = CHECK_DOMAIN_OK.copy()
|
||||||
body_domain_ok["name"] = domain.name
|
body_domain_ok["name"] = domain.name
|
||||||
responses.add(
|
responses.get(
|
||||||
responses.GET,
|
|
||||||
re.compile(rf".*/domains/{domain.name}/fix/"),
|
re.compile(rf".*/domains/{domain.name}/fix/"),
|
||||||
body=json.dumps(body_domain_ok),
|
json=body_domain_ok,
|
||||||
status=status.HTTP_200_OK,
|
status=status.HTTP_200_OK,
|
||||||
content_type="application/json",
|
content_type="application/json",
|
||||||
)
|
)
|
||||||
@@ -348,7 +373,8 @@ def test_dimail__fetch_domain_status__full_fix_scenario(domain_status):
|
|||||||
assert domain.last_check_details == body_domain_ok
|
assert domain.last_check_details == body_domain_ok
|
||||||
|
|
||||||
|
|
||||||
def test_dimail__send_pending_mailboxes(caplog):
|
@responses.activate
|
||||||
|
def test_dimail__send_pending_mailboxes(caplog, dimail_token_ok):
|
||||||
"""Status of pending mailboxes should switch to "enabled"
|
"""Status of pending mailboxes should switch to "enabled"
|
||||||
when calling send_pending_mailboxes."""
|
when calling send_pending_mailboxes."""
|
||||||
caplog.set_level(logging.INFO)
|
caplog.set_level(logging.INFO)
|
||||||
@@ -365,22 +391,16 @@ def test_dimail__send_pending_mailboxes(caplog):
|
|||||||
)
|
)
|
||||||
|
|
||||||
dimail_client = DimailAPIClient()
|
dimail_client = DimailAPIClient()
|
||||||
with responses.RequestsMock() as rsps:
|
|
||||||
rsps.add(
|
# Ensure successful response using "responses":
|
||||||
rsps.GET,
|
# token response in fixtures
|
||||||
re.compile(r".*/token/"),
|
responses.post(
|
||||||
body=TOKEN_OK,
|
re.compile(rf".*/domains/{domain.name}/mailboxes/"),
|
||||||
status=status.HTTP_200_OK,
|
body=response_mailbox_created(f"mock@{domain.name}"),
|
||||||
content_type="application/json",
|
status=status.HTTP_201_CREATED,
|
||||||
)
|
content_type="application/json",
|
||||||
rsps.add(
|
)
|
||||||
rsps.POST,
|
dimail_client.send_pending_mailboxes(domain=domain)
|
||||||
re.compile(rf".*/domains/{domain.name}/mailboxes/"),
|
|
||||||
body=response_mailbox_created(f"mock@{domain.name}"),
|
|
||||||
status=status.HTTP_201_CREATED,
|
|
||||||
content_type="application/json",
|
|
||||||
)
|
|
||||||
dimail_client.send_pending_mailboxes(domain=domain)
|
|
||||||
|
|
||||||
mailbox1.refresh_from_db()
|
mailbox1.refresh_from_db()
|
||||||
mailbox2.refresh_from_db()
|
mailbox2.refresh_from_db()
|
||||||
|
|||||||
@@ -375,12 +375,16 @@ class DimailAPIClient:
|
|||||||
return self.raise_exception_for_unexpected_response(response)
|
return self.raise_exception_for_unexpected_response(response)
|
||||||
|
|
||||||
dimail_mailboxes = response.json()
|
dimail_mailboxes = response.json()
|
||||||
people_mailboxes = models.Mailbox.objects.filter(domain=domain)
|
known_mailboxes = models.Mailbox.objects.filter(domain=domain)
|
||||||
|
known_aliases = [
|
||||||
|
known_alias.local_part
|
||||||
|
for known_alias in models.Alias.objects.filter(domain=domain)
|
||||||
|
]
|
||||||
imported_mailboxes = []
|
imported_mailboxes = []
|
||||||
for dimail_mailbox in dimail_mailboxes:
|
for dimail_mailbox in dimail_mailboxes:
|
||||||
if not dimail_mailbox["email"] in [
|
if dimail_mailbox["email"] not in [
|
||||||
str(people_mailbox) for people_mailbox in people_mailboxes
|
str(known_mailboxes) for known_mailboxes in known_mailboxes
|
||||||
]:
|
] and dimail_mailbox['email'].split('@')[0] not in known_aliases:
|
||||||
try:
|
try:
|
||||||
# sometimes dimail api returns email from another domain,
|
# sometimes dimail api returns email from another domain,
|
||||||
# so we decide to exclude this kind of email
|
# so we decide to exclude this kind of email
|
||||||
@@ -776,3 +780,58 @@ class DimailAPIClient:
|
|||||||
return response
|
return response
|
||||||
|
|
||||||
return self.raise_exception_for_unexpected_response(response)
|
return self.raise_exception_for_unexpected_response(response)
|
||||||
|
|
||||||
|
def import_aliases(self, domain):
|
||||||
|
"""Import aliases from dimail. Useful if people fall out of sync with dimail."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = session.get(
|
||||||
|
f"{self.API_URL}/domains/{domain.name}/aliases/",
|
||||||
|
headers=self.get_headers(),
|
||||||
|
verify=True,
|
||||||
|
timeout=self.API_TIMEOUT,
|
||||||
|
)
|
||||||
|
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.raise_exception_for_unexpected_response(response)
|
||||||
|
|
||||||
|
incoming_aliases = response.json()
|
||||||
|
known_aliases = [
|
||||||
|
(known_alias.local_part, known_alias.destination)
|
||||||
|
for known_alias in models.Alias.objects.filter(domain=domain)
|
||||||
|
]
|
||||||
|
known_mailboxes = [
|
||||||
|
known_mailbox.local_part
|
||||||
|
for known_mailbox in models.Mailbox.objects.filter(domain=domain)
|
||||||
|
]
|
||||||
|
imported_aliases = []
|
||||||
|
for incoming_alias in incoming_aliases:
|
||||||
|
if (
|
||||||
|
incoming_alias["username"],
|
||||||
|
incoming_alias["destination"],
|
||||||
|
) not in known_aliases and incoming_alias[
|
||||||
|
"username"
|
||||||
|
] not in known_mailboxes:
|
||||||
|
try:
|
||||||
|
new_alias = models.Alias.objects.create(
|
||||||
|
local_part=incoming_alias["username"],
|
||||||
|
destination=incoming_alias["destination"],
|
||||||
|
domain=domain,
|
||||||
|
)
|
||||||
|
except (HeaderParseError, NonASCIILocalPartDefect) as err:
|
||||||
|
logger.warning(
|
||||||
|
"Import of alias %s to %s failed with error %s",
|
||||||
|
incoming_alias["username"],
|
||||||
|
incoming_alias["destination"],
|
||||||
|
err,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
imported_aliases.append(str(new_alias))
|
||||||
|
return imported_aliases
|
||||||
|
|||||||
Reference in New Issue
Block a user