(mailbox) allow to disable mailbox

We send a request to dimail API and change mailbox status to disabled.
A disabled mailbox can no longer be used thus access to webmail
is disabled for user.
This commit is contained in:
Sabrina Demagny
2024-11-22 19:49:27 +01:00
parent 3469764697
commit ccb06b3abf
8 changed files with 147 additions and 15 deletions

View File

@@ -10,6 +10,7 @@ and this project adheres to
### Added ### Added
- ✨(mailbox) allow to disable mailbox
- ✨(backend) add ServiceProvider #522 - ✨(backend) add ServiceProvider #522
- 💄(admin) allow header color customization #552 - 💄(admin) allow header color customization #552

View File

@@ -519,8 +519,8 @@ msgid "Mailboxes"
msgstr "" msgstr ""
#: mailbox_manager/models.py:224 #: mailbox_manager/models.py:224
msgid "You can't create a mailbox for a disabled domain." msgid "You can't create or update a mailbox for a disabled domain."
msgstr "Vous ne pouvez pas créer de boîte mail pour un domain désactivé." msgstr "Vous ne pouvez pas créer ou modifier une boîte mail pour un domain désactivé."
#: mailbox_manager/utils/dimail.py:183 #: mailbox_manager/utils/dimail.py:183
msgid "Your new mailbox information" msgid "Your new mailbox information"

View File

@@ -3,12 +3,15 @@
from django.db.models import Subquery from django.db.models import Subquery
from rest_framework import exceptions, filters, mixins, viewsets from rest_framework import exceptions, filters, mixins, viewsets
from rest_framework.decorators import action
from rest_framework.response import Response
from core import models as core_models from core import models as core_models
from mailbox_manager import enums, models from mailbox_manager import enums, models
from mailbox_manager.api import permissions from mailbox_manager.api import permissions
from mailbox_manager.api.client import serializers from mailbox_manager.api.client import serializers
from mailbox_manager.utils.dimail import DimailAPIClient
# pylint: disable=too-many-ancestors # pylint: disable=too-many-ancestors
@@ -186,15 +189,18 @@ class MailBoxViewSet(
): ):
"""MailBox ViewSet """MailBox ViewSet
GET /api/<version>/mail-domains/<domain-slug>/mailboxes/ GET /api/<version>/mail-domains/<domain_slug>/mailboxes/
Return a list of mailboxes on the domain Return a list of mailboxes on the domain
POST /api/<version>/mail-domains/<domain-slug>/mailboxes/ with expected data: POST /api/<version>/mail-domains/<domain_slug>/mailboxes/ with expected data:
- first_name: str - first_name: str
- last_name: str - last_name: str
- local_part: str - local_part: str
- secondary_email: str - secondary_email: str
Sends request to email provisioning API and returns newly created mailbox Sends request to email provisioning API and returns newly created mailbox
POST /api/<version>/mail-domains/<domain_slug>/mailboxes/<mailbox_id>/disable/
Send a request to dimail to disable mailbox and change status of the mailbox in our DB
""" """
permission_classes = [permissions.MailBoxPermission] permission_classes = [permissions.MailBoxPermission]
@@ -218,3 +224,13 @@ class MailBoxViewSet(
slug=domain_slug slug=domain_slug
) )
super().perform_create(serializer) super().perform_create(serializer)
@action(detail=True, methods=["post"])
def disable(self, request, domain_slug, pk=None): # pylint: disable=unused-argument
"""Disable mailbox. Send a request to dimail and change status in our DB"""
mailbox = self.get_object()
client = DimailAPIClient()
client.disable_mailbox(mailbox, request.user.sub)
mailbox.status = enums.MailboxStatusChoices.DISABLED
mailbox.save()
return Response(serializers.MailboxSerializer(mailbox).data)

View File

@@ -215,17 +215,12 @@ class Mailbox(BaseModel):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
""" """
Modification is forbidden for now. Override save function to not allow to create or update mailbox of a disabled domain.
""" """
self.full_clean() self.full_clean()
if self.domain.status == MailDomainStatusChoices.DISABLED: if self.domain.status == MailDomainStatusChoices.DISABLED:
raise exceptions.ValidationError( raise exceptions.ValidationError(
_("You can't create a mailbox for a disabled domain.") _("You can't create or update a mailbox for a disabled domain.")
) )
return super().save(*args, **kwargs)
if self._state.adding:
return super().save(*args, **kwargs)
# Update is not implemented for now
raise NotImplementedError()

View File

@@ -264,7 +264,9 @@ def test_api_mailboxes__cannot_create_on_disabled_domain(role):
) )
assert response.status_code == status.HTTP_400_BAD_REQUEST assert response.status_code == status.HTTP_400_BAD_REQUEST
assert not models.Mailbox.objects.exists() assert not models.Mailbox.objects.exists()
assert response.json() == ["You can't create a mailbox for a disabled domain."] assert response.json() == [
"You can't create or update a mailbox for a disabled domain."
]
@pytest.mark.parametrize( @pytest.mark.parametrize(

View File

@@ -0,0 +1,99 @@
"""
Unit tests for the mailbox API
"""
import re
import pytest
import responses
from rest_framework import status
from rest_framework.test import APIClient
from core import factories as core_factories
from mailbox_manager import enums, factories, models
pytestmark = pytest.mark.django_db
def test_api_mailboxes__disable_anonymous_forbidden():
"""Anonymous users should not be able to disable a mailbox via the API."""
mailbox = factories.MailboxEnabledFactory()
response = APIClient().post(
f"/api/v1.0/mail-domains/{mailbox.domain.slug}/mailboxes/{mailbox.pk}/disable/",
)
assert response.status_code == status.HTTP_401_UNAUTHORIZED
assert models.Mailbox.objects.get().status == enums.MailboxStatusChoices.ENABLED
def test_api_mailboxes__disable_authenticated_failure():
"""Authenticated users should not be able to disable mailbox
without specific role on mail domain."""
user = core_factories.UserFactory()
client = APIClient()
client.force_login(user)
mailbox = factories.MailboxEnabledFactory()
response = client.post(
f"/api/v1.0/mail-domains/{mailbox.domain.slug}/mailboxes/{mailbox.pk}/disable/",
)
assert response.status_code == status.HTTP_403_FORBIDDEN
assert models.Mailbox.objects.get().status == enums.MailboxStatusChoices.ENABLED
def test_api_mailboxes__disable_viewer_failure():
"""Users with viewer role should not be able to disable mailbox on the mail domain."""
mailbox = factories.MailboxEnabledFactory()
access = factories.MailDomainAccessFactory(
role=enums.MailDomainRoleChoices.VIEWER, domain=mailbox.domain
)
client = APIClient()
client.force_login(access.user)
response = client.post(
f"/api/v1.0/mail-domains/{mailbox.domain.slug}/mailboxes/{mailbox.pk}/disable/",
)
assert response.status_code == status.HTTP_403_FORBIDDEN
assert models.Mailbox.objects.get().status == enums.MailboxStatusChoices.ENABLED
@pytest.mark.parametrize(
"role",
[enums.MailDomainRoleChoices.OWNER, enums.MailDomainRoleChoices.ADMIN],
)
def test_api_mailboxes__disable_roles_success(role):
"""Users with owner or admin role should be able to disable mailbox on the mail domain."""
mailbox = factories.MailboxEnabledFactory()
access = factories.MailDomainAccessFactory(role=role, domain=mailbox.domain)
client = APIClient()
client.force_login(access.user)
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.PATCH,
re.compile(
rf".*/domains/{mailbox.domain.name}/mailboxes/{mailbox.local_part}"
),
status=status.HTTP_200_OK,
content_type="application/json",
)
response = client.post(
f"/api/v1.0/mail-domains/{mailbox.domain.slug}/mailboxes/{mailbox.pk}/disable/",
)
assert response.status_code == status.HTTP_200_OK
mailbox = models.Mailbox.objects.get()
assert mailbox.status == enums.MailboxStatusChoices.DISABLED

View File

@@ -116,7 +116,7 @@ def test_models_mailboxes__cannot_create_mailboxes_on_disabled_domain():
A disabled status for the mail domain raises an error.""" A disabled status for the mail domain raises an error."""
with pytest.raises( with pytest.raises(
exceptions.ValidationError, exceptions.ValidationError,
match="You can't create a mailbox for a disabled domain.", match="You can't create or update a mailbox for a disabled domain.",
): ):
factories.MailboxFactory( factories.MailboxFactory(
domain=factories.MailDomainFactory( domain=factories.MailDomainFactory(

View File

@@ -211,7 +211,7 @@ class DimailAPIClient:
) )
def import_mailboxes(self, domain): def import_mailboxes(self, domain):
"""Synchronize mailboxes from dimail - open xchange to our database. """Import mailboxes from dimail - open xchange in our database.
This is useful in case of acquisition of a pre-existing mail domain. This is useful in case of acquisition of a pre-existing mail domain.
Mailboxes created here are not new mailboxes and will not trigger mail notification.""" Mailboxes created here are not new mailboxes and will not trigger mail notification."""
@@ -272,3 +272,22 @@ class DimailAPIClient:
err, err,
) )
return imported_mailboxes return imported_mailboxes
def disable_mailbox(self, mailbox, user_sub=None):
"""Send a request to disable a mailbox to dimail API"""
response = session.patch(
f"{self.API_URL}/domains/{mailbox.domain.name}/mailboxes/{mailbox.local_part}",
json={"active": "no"},
headers=self.get_headers(user_sub),
verify=True,
timeout=10,
)
if response.status_code == status.HTTP_200_OK:
logger.info(
"Mailbox %s successfully desactivated on domain %s by user %s",
str(mailbox),
str(mailbox.domain),
user_sub,
)
return response
return self.raise_exception_for_unexpected_response(response)