✨(backend) domain accesses delete API
Allow to delete a access for a domain.
This commit is contained in:
@@ -22,6 +22,7 @@ and this project adheres to
|
|||||||
- ✨(domains) domain accesses update API #423
|
- ✨(domains) domain accesses update API #423
|
||||||
- ✨(backend) domain accesses create API #428
|
- ✨(backend) domain accesses create API #428
|
||||||
- 🥅(frontend) catch new errors on mailbox creation #392
|
- 🥅(frontend) catch new errors on mailbox creation #392
|
||||||
|
- ✨(api) domain accesses delete API #433
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
@@ -21,3 +21,12 @@ class MailBoxPermission(core_permissions.IsAuthenticated):
|
|||||||
domain = models.MailDomain.objects.get(slug=view.kwargs.get("domain_slug", ""))
|
domain = models.MailDomain.objects.get(slug=view.kwargs.get("domain_slug", ""))
|
||||||
abilities = domain.get_abilities(request.user)
|
abilities = domain.get_abilities(request.user)
|
||||||
return abilities.get(request.method.lower(), False)
|
return abilities.get(request.method.lower(), False)
|
||||||
|
|
||||||
|
|
||||||
|
class MailDomainAccessRolePermission(core_permissions.IsAuthenticated):
|
||||||
|
"""Permission class to manage mailboxes for a mail domain"""
|
||||||
|
|
||||||
|
def has_object_permission(self, request, view, obj):
|
||||||
|
"""Check permission for a given object."""
|
||||||
|
abilities = obj.get_abilities(request.user)
|
||||||
|
return abilities.get(request.method.lower(), False)
|
||||||
|
|||||||
@@ -3,7 +3,6 @@
|
|||||||
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 import permissions as drf_permissions
|
|
||||||
|
|
||||||
from core import models as core_models
|
from core import models as core_models
|
||||||
|
|
||||||
@@ -61,6 +60,7 @@ class MailDomainAccessViewSet(
|
|||||||
mixins.CreateModelMixin,
|
mixins.CreateModelMixin,
|
||||||
mixins.UpdateModelMixin,
|
mixins.UpdateModelMixin,
|
||||||
mixins.RetrieveModelMixin,
|
mixins.RetrieveModelMixin,
|
||||||
|
mixins.DestroyModelMixin,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
API ViewSet for all interactions with mail domain accesses.
|
API ViewSet for all interactions with mail domain accesses.
|
||||||
@@ -81,9 +81,12 @@ class MailDomainAccessViewSet(
|
|||||||
PATCH /api/v1.0/mail-domains/<domain_slug>/accesses/<domain_access_id>/ with expected data:
|
PATCH /api/v1.0/mail-domains/<domain_slug>/accesses/<domain_access_id>/ with expected data:
|
||||||
- role: str [owner|admin|viewer]
|
- role: str [owner|admin|viewer]
|
||||||
Return partially updated domain access
|
Return partially updated domain access
|
||||||
|
|
||||||
|
DELETE /api/v1.0/mail-domains/<domain_slug>/accesses/<domain_access_id>/
|
||||||
|
Delete targeted domain access
|
||||||
"""
|
"""
|
||||||
|
|
||||||
permission_classes = [drf_permissions.IsAuthenticated]
|
permission_classes = [permissions.MailDomainAccessRolePermission]
|
||||||
serializer_class = serializers.MailDomainAccessSerializer
|
serializer_class = serializers.MailDomainAccessSerializer
|
||||||
filter_backends = [filters.OrderingFilter]
|
filter_backends = [filters.OrderingFilter]
|
||||||
ordering_fields = ["role", "user__email", "user__name"]
|
ordering_fields = ["role", "user__email", "user__name"]
|
||||||
@@ -156,6 +159,22 @@ class MailDomainAccessViewSet(
|
|||||||
raise exceptions.PermissionDenied({"role": message})
|
raise exceptions.PermissionDenied({"role": message})
|
||||||
serializer.save()
|
serializer.save()
|
||||||
|
|
||||||
|
def destroy(self, request, *args, **kwargs):
|
||||||
|
"""Forbid deleting the last owner access"""
|
||||||
|
instance = self.get_object()
|
||||||
|
domain = instance.domain
|
||||||
|
|
||||||
|
# Check if the access being deleted is the last owner access for the domain
|
||||||
|
if (
|
||||||
|
instance.role == enums.MailDomainRoleChoices.OWNER
|
||||||
|
and domain.accesses.filter(role=enums.MailDomainRoleChoices.OWNER).count()
|
||||||
|
== 1
|
||||||
|
):
|
||||||
|
message = "Cannot delete the last owner access for the domain."
|
||||||
|
raise exceptions.PermissionDenied({"detail": message})
|
||||||
|
|
||||||
|
return super().destroy(request, *args, **kwargs)
|
||||||
|
|
||||||
|
|
||||||
class MailBoxViewSet(
|
class MailBoxViewSet(
|
||||||
mixins.CreateModelMixin,
|
mixins.CreateModelMixin,
|
||||||
|
|||||||
@@ -136,6 +136,31 @@ class MailDomainAccess(BaseModel):
|
|||||||
roles.remove(self.role)
|
roles.remove(self.role)
|
||||||
return sorted(roles)
|
return sorted(roles)
|
||||||
|
|
||||||
|
def get_abilities(self, user):
|
||||||
|
"""
|
||||||
|
Compute and return abilities for a given user on the domain access.
|
||||||
|
"""
|
||||||
|
role = None
|
||||||
|
|
||||||
|
if user.is_authenticated:
|
||||||
|
try:
|
||||||
|
role = user.mail_domain_accesses.filter(domain=self.domain).get().role
|
||||||
|
except (MailDomainAccess.DoesNotExist, IndexError):
|
||||||
|
role = None
|
||||||
|
|
||||||
|
is_owner_or_admin = role in [
|
||||||
|
MailDomainRoleChoices.OWNER,
|
||||||
|
MailDomainRoleChoices.ADMIN,
|
||||||
|
]
|
||||||
|
|
||||||
|
return {
|
||||||
|
"get": bool(role),
|
||||||
|
"patch": is_owner_or_admin,
|
||||||
|
"put": is_owner_or_admin,
|
||||||
|
"post": is_owner_or_admin,
|
||||||
|
"delete": is_owner_or_admin,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
class Mailbox(BaseModel):
|
class Mailbox(BaseModel):
|
||||||
"""Mailboxes for users from mail domain."""
|
"""Mailboxes for users from mail domain."""
|
||||||
|
|||||||
@@ -0,0 +1,139 @@
|
|||||||
|
"""
|
||||||
|
Test for mail_domain accesses API endpoints in People's core app : delete
|
||||||
|
"""
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
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_mail_domain__accesses_delete_anonymous():
|
||||||
|
"""Anonymous users should not be allowed to destroy a mail domain access."""
|
||||||
|
access = factories.MailDomainAccessFactory()
|
||||||
|
|
||||||
|
response = APIClient().delete(
|
||||||
|
f"/api/v1.0/mail-domains/{access.domain.slug}/accesses/{access.id!s}/",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||||
|
assert models.MailDomainAccess.objects.count() == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_mail_domain__accesses_delete_authenticated():
|
||||||
|
"""
|
||||||
|
Authenticated users should not be allowed to delete a mail domain access for a
|
||||||
|
mail domain to which they are not related.
|
||||||
|
"""
|
||||||
|
authenticated_user = core_factories.UserFactory()
|
||||||
|
access = factories.MailDomainAccessFactory()
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(authenticated_user)
|
||||||
|
response = client.delete(
|
||||||
|
f"/api/v1.0/mail-domains/{access.domain.slug}/accesses/{access.id!s}/",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||||
|
assert models.MailDomainAccess.objects.count() == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_mail_domain__accesses_delete_viewer():
|
||||||
|
"""
|
||||||
|
Authenticated users should not be allowed to delete a mail domain access for a
|
||||||
|
mail domain in which they are a simple viewer.
|
||||||
|
"""
|
||||||
|
authenticated_user = core_factories.UserFactory()
|
||||||
|
mail_domain = factories.MailDomainFactory(
|
||||||
|
users=[(authenticated_user, enums.MailDomainRoleChoices.VIEWER)]
|
||||||
|
)
|
||||||
|
access = factories.MailDomainAccessFactory(domain=mail_domain)
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(authenticated_user)
|
||||||
|
response = client.delete(
|
||||||
|
f"/api/v1.0/mail-domains/{mail_domain.slug}/accesses/{access.id!s}/",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||||
|
assert models.MailDomainAccess.objects.count() == 2
|
||||||
|
assert models.MailDomainAccess.objects.filter(user=access.user).exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_mail_domain__accesses_delete_administrators():
|
||||||
|
"""
|
||||||
|
Administrators of a mail domain should be allowed to delete accesses excepted owner accesses.
|
||||||
|
"""
|
||||||
|
authenticated_user = core_factories.UserFactory()
|
||||||
|
mail_domain = factories.MailDomainFactory(
|
||||||
|
users=[(authenticated_user, enums.MailDomainRoleChoices.ADMIN)]
|
||||||
|
)
|
||||||
|
for role in [enums.MailDomainRoleChoices.VIEWER, enums.MailDomainRoleChoices.ADMIN]:
|
||||||
|
access = factories.MailDomainAccessFactory(domain=mail_domain, role=role)
|
||||||
|
|
||||||
|
assert models.MailDomainAccess.objects.count() == 2
|
||||||
|
assert models.MailDomainAccess.objects.filter(user=access.user).exists()
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(authenticated_user)
|
||||||
|
response = client.delete(
|
||||||
|
f"/api/v1.0/mail-domains/{mail_domain.slug}/accesses/{access.id!s}/",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_204_NO_CONTENT
|
||||||
|
assert models.MailDomainAccess.objects.count() == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_mail_domain__accesses_delete_owners():
|
||||||
|
"""
|
||||||
|
An owner should be able to delete the mail domain access of another user including
|
||||||
|
a owner access.
|
||||||
|
"""
|
||||||
|
authenticated_user = core_factories.UserFactory()
|
||||||
|
mail_domain = factories.MailDomainFactory(
|
||||||
|
users=[(authenticated_user, enums.MailDomainRoleChoices.OWNER)]
|
||||||
|
)
|
||||||
|
for role in [role[0] for role in enums.MailDomainRoleChoices.choices]:
|
||||||
|
access = factories.MailDomainAccessFactory(domain=mail_domain, role=role)
|
||||||
|
|
||||||
|
assert models.MailDomainAccess.objects.count() == 2
|
||||||
|
assert models.MailDomainAccess.objects.filter(user=access.user).exists()
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(authenticated_user)
|
||||||
|
response = client.delete(
|
||||||
|
f"/api/v1.0/mail-domains/{mail_domain.slug}/accesses/{access.id!s}/",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_204_NO_CONTENT
|
||||||
|
assert models.MailDomainAccess.objects.count() == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_mail_domain__accesses_delete_owners_last_owner():
|
||||||
|
"""
|
||||||
|
It should not be possible to delete the last owner access from a mail domain
|
||||||
|
"""
|
||||||
|
authenticated_user = core_factories.UserFactory()
|
||||||
|
mail_domain = factories.MailDomainFactory()
|
||||||
|
access = factories.MailDomainAccessFactory(
|
||||||
|
domain=mail_domain,
|
||||||
|
user=authenticated_user,
|
||||||
|
role=enums.MailDomainRoleChoices.OWNER,
|
||||||
|
)
|
||||||
|
factories.MailDomainAccessFactory.create_batch(9)
|
||||||
|
assert models.MailDomainAccess.objects.count() == 10
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(authenticated_user)
|
||||||
|
|
||||||
|
response = client.delete(
|
||||||
|
f"/api/v1.0/mail-domains/{mail_domain.slug}/accesses/{access.id!s}/",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||||
|
assert models.MailDomainAccess.objects.count() == 10
|
||||||
Reference in New Issue
Block a user