✨(mail) manage mailboxes permissions
Manage create and list permissions for all roles.
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
|
||||
from core.api import permissions as core_permissions
|
||||
|
||||
from mailbox_manager import models
|
||||
|
||||
|
||||
class AccessPermission(core_permissions.IsAuthenticated):
|
||||
"""Permission class for access objects."""
|
||||
@@ -10,3 +12,12 @@ class AccessPermission(core_permissions.IsAuthenticated):
|
||||
"""Check permission for a given object."""
|
||||
abilities = obj.get_abilities(request.user)
|
||||
return abilities.get(request.method.lower(), False)
|
||||
|
||||
|
||||
class MailBoxPermission(core_permissions.IsAuthenticated):
|
||||
"""Permission class to manage mailboxes for a mail domain"""
|
||||
|
||||
def has_permission(self, request, view):
|
||||
domain = models.MailDomain.objects.get(slug=view.kwargs.get("domain_slug", ""))
|
||||
abilities = domain.get_abilities(request.user)
|
||||
return abilities.get(request.method.lower(), False)
|
||||
|
||||
@@ -16,6 +16,8 @@ class MailboxSerializer(serializers.ModelSerializer):
|
||||
class MailDomainSerializer(serializers.ModelSerializer):
|
||||
"""Serialize mail domain."""
|
||||
|
||||
abilities = serializers.SerializerMethodField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = models.MailDomain
|
||||
lookup_field = "slug"
|
||||
@@ -23,16 +25,25 @@ class MailDomainSerializer(serializers.ModelSerializer):
|
||||
"id",
|
||||
"name",
|
||||
"slug",
|
||||
"abilities",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
read_only_fields = [
|
||||
"id",
|
||||
"slug",
|
||||
"abilities",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
]
|
||||
|
||||
def get_abilities(self, domain) -> dict:
|
||||
"""Return abilities of the logged-in user on the instance."""
|
||||
request = self.context.get("request")
|
||||
if request:
|
||||
return domain.get_abilities(request.user)
|
||||
return {}
|
||||
|
||||
|
||||
class MailDomainAccessSerializer(serializers.ModelSerializer):
|
||||
"""Serialize mail domain accesses."""
|
||||
|
||||
@@ -76,8 +76,10 @@ class MailBoxViewSet(
|
||||
):
|
||||
"""MailBox ViewSet"""
|
||||
|
||||
permission_classes = [drf_permissions.IsAuthenticated]
|
||||
permission_classes = [permissions.MailBoxPermission]
|
||||
serializer_class = serializers.MailboxSerializer
|
||||
filter_backends = [filters.OrderingFilter]
|
||||
ordering = ["-created_at"]
|
||||
queryset = models.Mailbox.objects.all()
|
||||
|
||||
def get_queryset(self):
|
||||
|
||||
@@ -66,6 +66,7 @@ class MailDomain(BaseModel):
|
||||
"get": bool(role),
|
||||
"patch": is_owner_or_admin,
|
||||
"put": is_owner_or_admin,
|
||||
"post": is_owner_or_admin,
|
||||
"delete": role == MailDomainRoleChoices.OWNER,
|
||||
"manage_accesses": is_owner_or_admin,
|
||||
}
|
||||
|
||||
@@ -68,4 +68,5 @@ def test_api_mail_domains__retrieve_authenticated_related():
|
||||
"slug": domain.slug,
|
||||
"created_at": domain.created_at.isoformat().replace("+00:00", "Z"),
|
||||
"updated_at": domain.updated_at.isoformat().replace("+00:00", "Z"),
|
||||
"abilities": domain.get_abilities(user),
|
||||
}
|
||||
|
||||
@@ -8,7 +8,8 @@ from rest_framework.test import APIClient
|
||||
|
||||
from core import factories as core_factories
|
||||
|
||||
from mailbox_manager import factories, models
|
||||
from mailbox_manager import enums, factories, models
|
||||
from mailbox_manager.api import serializers
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
@@ -16,86 +17,132 @@ pytestmark = pytest.mark.django_db
|
||||
def test_api_mailboxes__create_anonymous_forbidden():
|
||||
"""Anonymous users should not be able to create a new mailbox via the API."""
|
||||
mail_domain = factories.MailDomainEnabledFactory()
|
||||
|
||||
mailbox_values = serializers.MailboxSerializer(
|
||||
factories.MailboxFactory.build()
|
||||
).data
|
||||
response = APIClient().post(
|
||||
f"/api/v1.0/mail-domains/{mail_domain.slug}/mailboxes/",
|
||||
{
|
||||
"first_name": "jean",
|
||||
"last_name": "doe",
|
||||
"local_part": "jean.doe",
|
||||
"secondary_email": "jean.doe@gmail.com",
|
||||
"phone_number": "+33150142700",
|
||||
},
|
||||
mailbox_values,
|
||||
)
|
||||
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||
assert not models.Mailbox.objects.exists()
|
||||
|
||||
|
||||
def test_api_mailboxes__create_authenticated_missing_fields():
|
||||
"""
|
||||
Authenticated users should not be able to create mailboxes
|
||||
without local part or secondary mail.
|
||||
"""
|
||||
user = core_factories.UserFactory(email="tester@ministry.fr", name="john doe")
|
||||
def test_api_mailboxes__create_authenticated_failure():
|
||||
"""Authenticated users should not be able to create mailbox
|
||||
without specific role on mail domain."""
|
||||
user = core_factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
mailbox_values = serializers.MailboxSerializer(
|
||||
factories.MailboxFactory.build()
|
||||
).data
|
||||
mail_domain = factories.MailDomainEnabledFactory()
|
||||
response = client.post(
|
||||
f"/api/v1.0/mail-domains/{mail_domain.slug}/mailboxes/",
|
||||
{
|
||||
"first_name": "jean",
|
||||
"last_name": "doe",
|
||||
"secondary_email": "jean.doe@gmail.com",
|
||||
"phone_number": "+33150142700",
|
||||
},
|
||||
mailbox_values,
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
assert models.Mailbox.objects.exists() is False
|
||||
assert response.json() == {"local_part": ["This field is required."]}
|
||||
|
||||
response = client.post(
|
||||
f"/api/v1.0/mail-domains/{mail_domain.slug}/mailboxes/",
|
||||
{
|
||||
"first_name": "jean",
|
||||
"last_name": "doe",
|
||||
"local_part": "jean.doe",
|
||||
"phone_number": "+33150142700",
|
||||
},
|
||||
format="json",
|
||||
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||
assert not models.Mailbox.objects.exists()
|
||||
|
||||
|
||||
def test_api_mailboxes__create_viewer_failure():
|
||||
"""Users with viewer role should not be able to create mailbox on the mail domain."""
|
||||
mail_domain = factories.MailDomainEnabledFactory()
|
||||
access = factories.MailDomainAccessFactory(
|
||||
role=enums.MailDomainRoleChoices.VIEWER, domain=mail_domain
|
||||
)
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
assert models.Mailbox.objects.exists() is False
|
||||
assert response.json() == {"secondary_email": ["This field is required."]}
|
||||
|
||||
|
||||
def test_api_mailboxes__create_authenticated_successful():
|
||||
"""Authenticated users should be able to create mailbox."""
|
||||
user = core_factories.UserFactory(email="tester@ministry.fr", name="john doe")
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
client.force_login(access.user)
|
||||
|
||||
mail_domain = factories.MailDomainEnabledFactory(name="saint-jean.collectivite.fr")
|
||||
mailbox_values = serializers.MailboxSerializer(
|
||||
factories.MailboxFactory.build()
|
||||
).data
|
||||
response = client.post(
|
||||
f"/api/v1.0/mail-domains/{mail_domain.slug}/mailboxes/",
|
||||
{
|
||||
"first_name": "jean",
|
||||
"last_name": "doe",
|
||||
"local_part": "jean.doe",
|
||||
"secondary_email": "jean.doe@gmail.com",
|
||||
"phone_number": "+33150142700",
|
||||
},
|
||||
mailbox_values,
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||
assert not models.Mailbox.objects.exists()
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"role",
|
||||
[
|
||||
enums.MailDomainRoleChoices.OWNER,
|
||||
enums.MailDomainRoleChoices.ADMIN,
|
||||
],
|
||||
)
|
||||
def test_api_mailboxes__create_roles_success(role):
|
||||
"""Users with owner or admin role should be able to create mailbox on the mail domain."""
|
||||
mail_domain = factories.MailDomainEnabledFactory()
|
||||
access = factories.MailDomainAccessFactory(role=role, domain=mail_domain)
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(access.user)
|
||||
|
||||
mailbox_values = serializers.MailboxSerializer(
|
||||
factories.MailboxFactory.build()
|
||||
).data
|
||||
response = client.post(
|
||||
f"/api/v1.0/mail-domains/{mail_domain.slug}/mailboxes/",
|
||||
mailbox_values,
|
||||
format="json",
|
||||
)
|
||||
|
||||
assert response.status_code == status.HTTP_201_CREATED
|
||||
mailbox = models.Mailbox.objects.get()
|
||||
assert mailbox.local_part == "jean.doe"
|
||||
assert mailbox.secondary_email == "jean.doe@gmail.com"
|
||||
|
||||
assert mailbox.local_part == mailbox_values["local_part"]
|
||||
assert mailbox.secondary_email == mailbox_values["secondary_email"]
|
||||
assert response.json() == {
|
||||
"id": str(mailbox.id),
|
||||
"local_part": str(mailbox.local_part),
|
||||
"secondary_email": str(mailbox.secondary_email),
|
||||
}
|
||||
|
||||
|
||||
def test_api_mailboxes__create_administrator_missing_fields():
|
||||
"""
|
||||
Administrator users should not be able to create mailboxes
|
||||
without local part or secondary mail.
|
||||
"""
|
||||
mail_domain = factories.MailDomainEnabledFactory()
|
||||
access = factories.MailDomainAccessFactory(
|
||||
role=enums.MailDomainRoleChoices.ADMIN, domain=mail_domain
|
||||
)
|
||||
client = APIClient()
|
||||
client.force_login(access.user)
|
||||
|
||||
mailbox_values = serializers.MailboxSerializer(
|
||||
factories.MailboxFactory.build()
|
||||
).data
|
||||
del mailbox_values["local_part"]
|
||||
response = client.post(
|
||||
f"/api/v1.0/mail-domains/{mail_domain.slug}/mailboxes/",
|
||||
mailbox_values,
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
assert not models.Mailbox.objects.exists()
|
||||
assert response.json() == {"local_part": ["This field is required."]}
|
||||
|
||||
mailbox_values = serializers.MailboxSerializer(
|
||||
factories.MailboxFactory.build()
|
||||
).data
|
||||
del mailbox_values["secondary_email"]
|
||||
response = client.post(
|
||||
f"/api/v1.0/mail-domains/{mail_domain.slug}/mailboxes/",
|
||||
mailbox_values,
|
||||
format="json",
|
||||
)
|
||||
assert response.status_code == status.HTTP_400_BAD_REQUEST
|
||||
assert not models.Mailbox.objects.exists()
|
||||
assert response.json() == {"secondary_email": ["This field is required."]}
|
||||
|
||||
@@ -8,7 +8,7 @@ from rest_framework.test import APIClient
|
||||
|
||||
from core import factories as core_factories
|
||||
|
||||
from mailbox_manager import factories
|
||||
from mailbox_manager import enums, factories
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
@@ -25,28 +25,52 @@ def test_api_mailboxes__list_anonymous():
|
||||
}
|
||||
|
||||
|
||||
def test_api_mailboxes__list_authenticated_no_query():
|
||||
"""Authenticated users should be able to list mailboxes without applying a query."""
|
||||
user = core_factories.UserFactory(email="tester@ministry.fr", name="john doe")
|
||||
def test_api_mailboxes__list_authenticated():
|
||||
"""Authenticated users should not be able to list mailboxes"""
|
||||
user = core_factories.UserFactory()
|
||||
|
||||
client = APIClient()
|
||||
client.force_login(user)
|
||||
|
||||
mail_domain = factories.MailDomainEnabledFactory()
|
||||
factories.MailboxFactory.create_batch(2, domain=mail_domain)
|
||||
|
||||
response = client.get(f"/api/v1.0/mail-domains/{mail_domain.slug}/mailboxes/")
|
||||
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||
assert response.json() == {
|
||||
"detail": "You do not have permission to perform this action."
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"role",
|
||||
[
|
||||
enums.MailDomainRoleChoices.OWNER,
|
||||
enums.MailDomainRoleChoices.ADMIN,
|
||||
enums.MailDomainRoleChoices.VIEWER,
|
||||
],
|
||||
)
|
||||
def test_api_mailboxes__list_roles(role):
|
||||
"""Owner, admin and viewer users should be able to list mailboxes"""
|
||||
mail_domain = factories.MailDomainEnabledFactory()
|
||||
mailbox1 = factories.MailboxFactory(domain=mail_domain)
|
||||
mailbox2 = factories.MailboxFactory(domain=mail_domain)
|
||||
|
||||
access = factories.MailDomainAccessFactory(role=role, domain=mail_domain)
|
||||
client = APIClient()
|
||||
client.force_login(access.user)
|
||||
|
||||
response = client.get(f"/api/v1.0/mail-domains/{mail_domain.slug}/mailboxes/")
|
||||
assert response.status_code == status.HTTP_200_OK
|
||||
assert response.json()["results"] == [
|
||||
{
|
||||
"id": str(mailbox1.id),
|
||||
"local_part": str(mailbox1.local_part),
|
||||
"secondary_email": str(mailbox1.secondary_email),
|
||||
},
|
||||
{
|
||||
"id": str(mailbox2.id),
|
||||
"local_part": str(mailbox2.local_part),
|
||||
"secondary_email": str(mailbox2.secondary_email),
|
||||
},
|
||||
{
|
||||
"id": str(mailbox1.id),
|
||||
"local_part": str(mailbox1.local_part),
|
||||
"secondary_email": str(mailbox1.secondary_email),
|
||||
},
|
||||
]
|
||||
|
||||
@@ -2,12 +2,15 @@
|
||||
Unit tests for the MailDomain model
|
||||
"""
|
||||
|
||||
from django.contrib.auth.models import AnonymousUser
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.text import slugify
|
||||
|
||||
import pytest
|
||||
|
||||
from mailbox_manager import factories
|
||||
from core import factories as core_factories
|
||||
|
||||
from mailbox_manager import enums, factories
|
||||
|
||||
pytestmark = pytest.mark.django_db
|
||||
|
||||
@@ -32,3 +35,76 @@ def test_models_mail_domain__slug_inferred_from_name():
|
||||
name = "N3w_D0main-Name$.com"
|
||||
domain = factories.MailDomainFactory(name=name, slug="something else")
|
||||
assert domain.slug == slugify(name)
|
||||
|
||||
|
||||
# get_abilities
|
||||
|
||||
|
||||
def test_models_maildomains_get_abilities_anonymous():
|
||||
"""Check abilities returned for an anonymous user."""
|
||||
maildomain = factories.MailDomainFactory()
|
||||
abilities = maildomain.get_abilities(AnonymousUser())
|
||||
assert abilities == {
|
||||
"delete": False,
|
||||
"get": False,
|
||||
"patch": False,
|
||||
"put": False,
|
||||
"post": False,
|
||||
"manage_accesses": False,
|
||||
}
|
||||
|
||||
|
||||
def test_models_maildomains_get_abilities_authenticated():
|
||||
"""Check abilities returned for an authenticated user."""
|
||||
maildomain = factories.MailDomainFactory()
|
||||
abilities = maildomain.get_abilities(core_factories.UserFactory())
|
||||
assert abilities == {
|
||||
"delete": False,
|
||||
"get": False,
|
||||
"patch": False,
|
||||
"put": False,
|
||||
"post": False,
|
||||
"manage_accesses": False,
|
||||
}
|
||||
|
||||
|
||||
def test_models_maildomains_get_abilities_owner():
|
||||
"""Check abilities returned for the owner of a maildomain."""
|
||||
access = factories.MailDomainAccessFactory(role=enums.MailDomainRoleChoices.OWNER)
|
||||
abilities = access.domain.get_abilities(access.user)
|
||||
assert abilities == {
|
||||
"delete": True,
|
||||
"get": True,
|
||||
"patch": True,
|
||||
"put": True,
|
||||
"post": True,
|
||||
"manage_accesses": True,
|
||||
}
|
||||
|
||||
|
||||
def test_models_maildomains_get_abilities_administrator():
|
||||
"""Check abilities returned for the administrator of a maildomain."""
|
||||
access = factories.MailDomainAccessFactory(role=enums.MailDomainRoleChoices.ADMIN)
|
||||
abilities = access.domain.get_abilities(access.user)
|
||||
assert abilities == {
|
||||
"delete": False,
|
||||
"get": True,
|
||||
"patch": True,
|
||||
"put": True,
|
||||
"post": True,
|
||||
"manage_accesses": True,
|
||||
}
|
||||
|
||||
|
||||
def test_models_maildomains_get_abilities_viewer():
|
||||
"""Check abilities returned for the member of a mail domain. It's a viewer role."""
|
||||
access = factories.MailDomainAccessFactory(role=enums.MailDomainRoleChoices.VIEWER)
|
||||
abilities = access.domain.get_abilities(access.user)
|
||||
assert abilities == {
|
||||
"delete": False,
|
||||
"get": True,
|
||||
"patch": False,
|
||||
"put": False,
|
||||
"post": False,
|
||||
"manage_accesses": False,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user