Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨(backend) domain accesses update API #423

Merged
merged 1 commit into from
Sep 27, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ and this project adheres to
- 🥅(frontend) improve add & update group forms error handling #387
- ✨(frontend) allow group members filtering #363
- ✨(mailbox) send new mailbox confirmation email #397
- ✨(domains) domain accesses update API #423

### Fixed

Expand Down
44 changes: 25 additions & 19 deletions src/backend/mailbox_manager/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import json

from rest_framework import serializers
from rest_framework import exceptions, serializers

from core.api.serializers import UserSerializer

from mailbox_manager import enums, models
from mailbox_manager import models
from mailbox_manager.utils.dimail import DimailAPIClient


Expand Down Expand Up @@ -94,23 +94,29 @@ class Meta:
read_only_fields = ["id", "user", "can_set_role_to"]

def get_can_set_role_to(self, access):
"""Return roles available to set"""
roles = list(enums.MailDomainRoleChoices)
# get role of authenticated user
authenticated_user_role = access.user_role
if authenticated_user_role != enums.MailDomainRoleChoices.OWNER:
roles.remove(enums.MailDomainRoleChoices.OWNER)
# if the user authenticated is a viewer, they can't modify role
# and only an owner can change role of an owner
if authenticated_user_role == enums.MailDomainRoleChoices.VIEWER or (
authenticated_user_role != enums.MailDomainRoleChoices.OWNER
and access.role == enums.MailDomainRoleChoices.OWNER
):
return []
# we only want to return other roles available to change,
# so we remove the current role of current access.
roles.remove(access.role)
return sorted(roles)
"""Return roles available to set for the authenticated user"""
return access.get_can_set_role_to(self.context.get("request").user)

def validate(self, attrs):
"""
Check access rights specific to writing (update)
"""
request = self.context.get("request")
authenticated_user = getattr(request, "user", None)
role = attrs.get("role")

# Update
if self.instance:
can_set_role_to = self.instance.get_can_set_role_to(authenticated_user)

if role and role not in can_set_role_to:
message = (
f"You are only allowed to set role to {', '.join(can_set_role_to)}"
if can_set_role_to
else "You are not allowed to modify role for this user."
)
raise exceptions.PermissionDenied(message)
return attrs


class MailDomainAccessReadOnlySerializer(MailDomainAccessSerializer):
Expand Down
36 changes: 34 additions & 2 deletions src/backend/mailbox_manager/api/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@

from django.db.models import Subquery

from rest_framework import 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 mailbox_manager import models
from mailbox_manager import enums, models
from mailbox_manager.api import permissions, serializers


Expand Down Expand Up @@ -58,6 +58,7 @@ def perform_create(self, serializer):
class MailDomainAccessViewSet(
viewsets.GenericViewSet,
mixins.ListModelMixin,
mixins.UpdateModelMixin,
mixins.RetrieveModelMixin,
):
"""
Expand All @@ -66,6 +67,14 @@ class MailDomainAccessViewSet(
GET /api/v1.0/mail-domains/<domain_slug>/accesses/:<domain_access_id>
Return list of all domain accesses related to the logged-in user and one
domain access if an id is provided.

PUT /api/v1.0/mail-domains/<domain_slug>/accesses/<domain_access_id>/ with expected data:
- role: str [owner|admin|viewer]
Return updated domain access

PATCH /api/v1.0/mail-domains/<domain_slug>/accesses/<domain_access_id>/ with expected data:
- role: str [owner|admin|viewer]
Return partially updated domain access
"""

permission_classes = [drf_permissions.IsAuthenticated]
Expand All @@ -90,6 +99,7 @@ def get_serializer_context(self):
"""Extra context provided to the serializer class."""
context = super().get_serializer_context()
context["domain_slug"] = self.kwargs["domain_slug"]
context["authenticated_user"] = self.request.user
return context

def get_queryset(self):
Expand Down Expand Up @@ -118,6 +128,28 @@ def get_queryset(self):
)
return queryset

def perform_update(self, serializer):
"""Check that we don't change the role if it leads to losing the last owner."""
instance = serializer.instance

# Check if the role is being updated and the new role is not "owner"
if (
"role" in self.request.data
and self.request.data["role"] != enums.MailDomainRoleChoices.OWNER
):
domain = instance.domain
# Check if the access being updated 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 change the role to a non-owner role for the last owner access."
raise exceptions.PermissionDenied({"role": message})
serializer.save()


class MailBoxViewSet(
mixins.CreateModelMixin,
Expand Down
35 changes: 34 additions & 1 deletion src/backend/mailbox_manager/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,6 @@ def get_abilities(self, user):
"""
Compute and return abilities for a given user on the domain.
"""
is_owner_or_admin = False
role = None

if user.is_authenticated:
Expand Down Expand Up @@ -103,6 +102,40 @@ class Meta:
def __str__(self):
return f"Access of user {self.user} on domain {self.domain}."

def get_can_set_role_to(self, user):
"""Return roles available to set"""
if not user.is_authenticated:
return []
roles = list(MailDomainRoleChoices)
authenticated_user_role = None

# get role of authenticated user
if hasattr(self, "user_role"):
authenticated_user_role = self.user_role
else:
try:
authenticated_user_role = user.mail_domain_accesses.get(
domain=self.domain
).role
except (MailDomainAccess.DoesNotExist, IndexError):
return []

# only an owner can set an owner role
if authenticated_user_role != MailDomainRoleChoices.OWNER:
roles.remove(MailDomainRoleChoices.OWNER)

# if the user authenticated is a viewer, they can't modify role
# and only an owner can change role of an owner
if authenticated_user_role == MailDomainRoleChoices.VIEWER or (
authenticated_user_role != MailDomainRoleChoices.OWNER
and self.role == MailDomainRoleChoices.OWNER
):
return []
# we only want to return other roles available to change,
# so we remove the current role of current access.
roles.remove(self.role)
return sorted(roles)


class Mailbox(BaseModel):
"""Mailboxes for users from mail domain."""
Expand Down
Loading
Loading