✨(backend) domain accesses create API
Allow to create (POST) a new access for a domain. Role can be change only to a role available and depending to the authenticated user.
This commit is contained in:
@@ -19,6 +19,7 @@ and this project adheres to
|
|||||||
- ✨(frontend) allow group members filtering #363
|
- ✨(frontend) allow group members filtering #363
|
||||||
- ✨(mailbox) send new mailbox confirmation email #397
|
- ✨(mailbox) send new mailbox confirmation email #397
|
||||||
- ✨(domains) domain accesses update API #423
|
- ✨(domains) domain accesses update API #423
|
||||||
|
- ✨(backend) domain accesses create API #428
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ import json
|
|||||||
from rest_framework import exceptions, serializers
|
from rest_framework import exceptions, serializers
|
||||||
|
|
||||||
from core.api.serializers import UserSerializer
|
from core.api.serializers import UserSerializer
|
||||||
|
from core.models import User
|
||||||
|
|
||||||
from mailbox_manager import models
|
from mailbox_manager import enums, models
|
||||||
from mailbox_manager.utils.dimail import DimailAPIClient
|
from mailbox_manager.utils.dimail import DimailAPIClient
|
||||||
|
|
||||||
|
|
||||||
@@ -85,13 +86,13 @@ class MailDomainAccessSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
class Meta:
|
class Meta:
|
||||||
model = models.MailDomainAccess
|
model = models.MailDomainAccess
|
||||||
fields = [
|
fields = ["id", "user", "role", "can_set_role_to"]
|
||||||
"id",
|
read_only_fields = ["id", "can_set_role_to"]
|
||||||
"user",
|
|
||||||
"role",
|
def update(self, instance, validated_data):
|
||||||
"can_set_role_to",
|
"""Make "user" field is readonly but only on update."""
|
||||||
]
|
validated_data.pop("user", None)
|
||||||
read_only_fields = ["id", "user", "can_set_role_to"]
|
return super().update(instance, validated_data)
|
||||||
|
|
||||||
def get_can_set_role_to(self, access):
|
def get_can_set_role_to(self, access):
|
||||||
"""Return roles available to set for the authenticated user"""
|
"""Return roles available to set for the authenticated user"""
|
||||||
@@ -99,8 +100,9 @@ class MailDomainAccessSerializer(serializers.ModelSerializer):
|
|||||||
|
|
||||||
def validate(self, attrs):
|
def validate(self, attrs):
|
||||||
"""
|
"""
|
||||||
Check access rights specific to writing (update)
|
Check access rights specific to writing (update/create)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
request = self.context.get("request")
|
request = self.context.get("request")
|
||||||
authenticated_user = getattr(request, "user", None)
|
authenticated_user = getattr(request, "user", None)
|
||||||
role = attrs.get("role")
|
role = attrs.get("role")
|
||||||
@@ -116,6 +118,45 @@ class MailDomainAccessSerializer(serializers.ModelSerializer):
|
|||||||
else "You are not allowed to modify role for this user."
|
else "You are not allowed to modify role for this user."
|
||||||
)
|
)
|
||||||
raise exceptions.PermissionDenied(message)
|
raise exceptions.PermissionDenied(message)
|
||||||
|
# Create
|
||||||
|
else:
|
||||||
|
# A domain slug has to be set to create a new access
|
||||||
|
try:
|
||||||
|
domain_slug = self.context["domain_slug"]
|
||||||
|
except KeyError as exc:
|
||||||
|
raise exceptions.ValidationError(
|
||||||
|
"You must set a domain slug in kwargs to create a new domain access."
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
try:
|
||||||
|
access = authenticated_user.mail_domain_accesses.get(
|
||||||
|
domain__slug=domain_slug
|
||||||
|
)
|
||||||
|
except models.MailDomainAccess.DoesNotExist as exc:
|
||||||
|
raise exceptions.PermissionDenied(
|
||||||
|
"You are not allowed to manage accesses for this domain."
|
||||||
|
) from exc
|
||||||
|
|
||||||
|
# Authenticated user must be owner or admin of current domain to set new roles
|
||||||
|
if access.role not in [
|
||||||
|
enums.MailDomainRoleChoices.OWNER,
|
||||||
|
enums.MailDomainRoleChoices.ADMIN,
|
||||||
|
]:
|
||||||
|
raise exceptions.PermissionDenied(
|
||||||
|
"You are not allowed to manage accesses for this domain."
|
||||||
|
)
|
||||||
|
# only an owner can set an owner role to another user
|
||||||
|
if (
|
||||||
|
role == enums.MailDomainRoleChoices.OWNER
|
||||||
|
and access.role != enums.MailDomainRoleChoices.OWNER
|
||||||
|
):
|
||||||
|
raise exceptions.PermissionDenied(
|
||||||
|
"Only owners of a domain can assign other users as owners."
|
||||||
|
)
|
||||||
|
attrs["user"] = User.objects.get(pk=self.initial_data["user"])
|
||||||
|
attrs["domain"] = models.MailDomain.objects.get(
|
||||||
|
slug=self.context["domain_slug"]
|
||||||
|
)
|
||||||
return attrs
|
return attrs
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -58,6 +58,7 @@ class MailDomainViewSet(
|
|||||||
class MailDomainAccessViewSet(
|
class MailDomainAccessViewSet(
|
||||||
viewsets.GenericViewSet,
|
viewsets.GenericViewSet,
|
||||||
mixins.ListModelMixin,
|
mixins.ListModelMixin,
|
||||||
|
mixins.CreateModelMixin,
|
||||||
mixins.UpdateModelMixin,
|
mixins.UpdateModelMixin,
|
||||||
mixins.RetrieveModelMixin,
|
mixins.RetrieveModelMixin,
|
||||||
):
|
):
|
||||||
@@ -68,6 +69,11 @@ class MailDomainAccessViewSet(
|
|||||||
Return list of all domain accesses related to the logged-in user and one
|
Return list of all domain accesses related to the logged-in user and one
|
||||||
domain access if an id is provided.
|
domain access if an id is provided.
|
||||||
|
|
||||||
|
POST /api/v1.0/mail-domains/<domain_slug>/accesses/ with expected data:
|
||||||
|
- user: str
|
||||||
|
- role: str [owner|admin|viewer]
|
||||||
|
Return newly created mail domain access
|
||||||
|
|
||||||
PUT /api/v1.0/mail-domains/<domain_slug>/accesses/<domain_access_id>/ with expected data:
|
PUT /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 updated domain access
|
Return updated domain access
|
||||||
|
|||||||
@@ -0,0 +1,173 @@
|
|||||||
|
"""
|
||||||
|
Test for mail domain accesses API endpoints in People's core app : create
|
||||||
|
"""
|
||||||
|
|
||||||
|
import random
|
||||||
|
|
||||||
|
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_create_anonymous():
|
||||||
|
"""Anonymous users should not be allowed to create mail domain accesses."""
|
||||||
|
user = core_factories.UserFactory()
|
||||||
|
mail_domain = factories.MailDomainFactory()
|
||||||
|
for role in [role[0] for role in enums.MailDomainRoleChoices.choices]:
|
||||||
|
response = APIClient().post(
|
||||||
|
f"/api/v1.0/mail-domains/{mail_domain.slug}/accesses/",
|
||||||
|
{
|
||||||
|
"user": str(user.id),
|
||||||
|
"role": role,
|
||||||
|
},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
assert response.status_code == status.HTTP_401_UNAUTHORIZED
|
||||||
|
assert response.json() == {
|
||||||
|
"detail": "Authentication credentials were not provided."
|
||||||
|
}
|
||||||
|
assert models.MailDomainAccess.objects.exists() is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_mail_domain__accesses_create_authenticated_unrelated():
|
||||||
|
"""
|
||||||
|
Authenticated users should not be allowed to create domain accesses for a domain to
|
||||||
|
which they are not related.
|
||||||
|
"""
|
||||||
|
user = core_factories.UserFactory()
|
||||||
|
other_user = core_factories.UserFactory()
|
||||||
|
domain = factories.MailDomainFactory()
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(user)
|
||||||
|
for role in [role[0] for role in enums.MailDomainRoleChoices.choices]:
|
||||||
|
response = client.post(
|
||||||
|
f"/api/v1.0/mail-domains/{domain.slug}/accesses/",
|
||||||
|
{
|
||||||
|
"user": str(other_user.id),
|
||||||
|
"role": role,
|
||||||
|
},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||||
|
assert response.json() == {
|
||||||
|
"detail": "You are not allowed to manage accesses for this domain."
|
||||||
|
}
|
||||||
|
assert not models.MailDomainAccess.objects.filter(user=other_user).exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_mail_domain__accesses_create_authenticated_viewer():
|
||||||
|
"""Viewer of a mail domain should not be allowed to create mail domain accesses."""
|
||||||
|
authenticated_user = core_factories.UserFactory()
|
||||||
|
mail_domain = factories.MailDomainFactory(
|
||||||
|
users=[(authenticated_user, enums.MailDomainRoleChoices.VIEWER)]
|
||||||
|
)
|
||||||
|
other_user = core_factories.UserFactory()
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(authenticated_user)
|
||||||
|
for role in [role[0] for role in enums.MailDomainRoleChoices.choices]:
|
||||||
|
response = client.post(
|
||||||
|
f"/api/v1.0/mail-domains/{mail_domain.slug}/accesses/",
|
||||||
|
{
|
||||||
|
"user": str(other_user.id),
|
||||||
|
"role": role,
|
||||||
|
},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||||
|
assert response.json() == {
|
||||||
|
"detail": "You are not allowed to manage accesses for this domain."
|
||||||
|
}
|
||||||
|
|
||||||
|
assert not models.MailDomainAccess.objects.filter(user=other_user).exists()
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_mail_domain__accesses_create_authenticated_administrator():
|
||||||
|
"""
|
||||||
|
Administrators of a domain should be able to create mail domain accesses
|
||||||
|
except for the "owner" role.
|
||||||
|
"""
|
||||||
|
authenticated_user = core_factories.UserFactory()
|
||||||
|
mail_domain = factories.MailDomainFactory(
|
||||||
|
users=[(authenticated_user, enums.MailDomainRoleChoices.ADMIN)]
|
||||||
|
)
|
||||||
|
other_user = core_factories.UserFactory()
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(authenticated_user)
|
||||||
|
|
||||||
|
# It should not be allowed to create an owner access
|
||||||
|
response = client.post(
|
||||||
|
f"/api/v1.0/mail-domains/{mail_domain.slug}/accesses/",
|
||||||
|
{
|
||||||
|
"user": str(other_user.id),
|
||||||
|
"role": enums.MailDomainRoleChoices.OWNER,
|
||||||
|
},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_403_FORBIDDEN
|
||||||
|
assert response.json() == {
|
||||||
|
"detail": "Only owners of a domain can assign other users as owners."
|
||||||
|
}
|
||||||
|
|
||||||
|
# It should be allowed to create a lower access
|
||||||
|
for role in [enums.MailDomainRoleChoices.ADMIN, enums.MailDomainRoleChoices.VIEWER]:
|
||||||
|
other_user = core_factories.UserFactory()
|
||||||
|
response = client.post(
|
||||||
|
f"/api/v1.0/mail-domains/{mail_domain.slug}/accesses/",
|
||||||
|
{
|
||||||
|
"user": str(other_user.id),
|
||||||
|
"role": role,
|
||||||
|
},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
assert response.status_code == status.HTTP_201_CREATED
|
||||||
|
new_mail_domain_access = models.MailDomainAccess.objects.filter(
|
||||||
|
user=other_user
|
||||||
|
).last()
|
||||||
|
|
||||||
|
assert response.json()["id"] == str(new_mail_domain_access.id)
|
||||||
|
assert response.json()["role"] == role
|
||||||
|
assert models.MailDomainAccess.objects.filter(domain=mail_domain).count() == 3
|
||||||
|
|
||||||
|
|
||||||
|
def test_api_mail_domain__accesses_create_authenticated_owner():
|
||||||
|
"""
|
||||||
|
Owners of a mail domain should be able to create mail domain accesses whatever the role.
|
||||||
|
"""
|
||||||
|
authenticated_user = core_factories.UserFactory()
|
||||||
|
mail_domain = factories.MailDomainFactory(
|
||||||
|
users=[(authenticated_user, enums.MailDomainRoleChoices.OWNER)]
|
||||||
|
)
|
||||||
|
other_user = core_factories.UserFactory()
|
||||||
|
|
||||||
|
role = random.choice([role[0] for role in enums.MailDomainRoleChoices.choices])
|
||||||
|
|
||||||
|
client = APIClient()
|
||||||
|
client.force_login(authenticated_user)
|
||||||
|
response = client.post(
|
||||||
|
f"/api/v1.0/mail-domains/{mail_domain.slug}/accesses/",
|
||||||
|
{
|
||||||
|
"user": str(other_user.id),
|
||||||
|
"role": role,
|
||||||
|
},
|
||||||
|
format="json",
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == status.HTTP_201_CREATED
|
||||||
|
assert models.MailDomainAccess.objects.filter(user=other_user).count() == 1
|
||||||
|
new_mail_domain_access = models.MailDomainAccess.objects.filter(
|
||||||
|
user=other_user
|
||||||
|
).get()
|
||||||
|
assert response.json()["id"] == str(new_mail_domain_access.id)
|
||||||
|
assert response.json()["role"] == role
|
||||||
Reference in New Issue
Block a user