We want to be able to control who can access a template via roles. I added this feature on the TeamAccess model assuming that the teams to which a user belongs can be retrieved via a `get_teams` method on the user model. The idea is that this method will get the teams either via a call to an external API or directly from the OIDC token upon user login. This list of teams will probably have to be cached for each user.
129 lines
4.3 KiB
Python
129 lines
4.3 KiB
Python
"""Client serializers for the publish core app."""
|
|
from django.db.models import Q
|
|
from django.utils.translation import gettext_lazy as _
|
|
|
|
from rest_framework import exceptions, serializers
|
|
from timezone_field.rest_framework import TimeZoneSerializerField
|
|
|
|
from core import models
|
|
|
|
|
|
class UserSerializer(serializers.ModelSerializer):
|
|
"""Serialize users."""
|
|
|
|
timezone = TimeZoneSerializerField(use_pytz=False, required=True)
|
|
|
|
class Meta:
|
|
model = models.User
|
|
fields = [
|
|
"id",
|
|
"language",
|
|
"timezone",
|
|
"is_device",
|
|
"is_staff",
|
|
]
|
|
read_only_fields = ["id", "is_device", "is_staff"]
|
|
|
|
|
|
class TemplateAccessSerializer(serializers.ModelSerializer):
|
|
"""Serialize template accesses."""
|
|
|
|
abilities = serializers.SerializerMethodField(read_only=True)
|
|
|
|
class Meta:
|
|
model = models.TemplateAccess
|
|
fields = ["id", "user", "team", "role", "abilities"]
|
|
read_only_fields = ["id", "abilities"]
|
|
|
|
def update(self, instance, validated_data):
|
|
"""Make "user" field is readonly but only on update."""
|
|
validated_data.pop("user", None)
|
|
return super().update(instance, validated_data)
|
|
|
|
def get_abilities(self, access) -> dict:
|
|
"""Return abilities of the logged-in user on the instance."""
|
|
request = self.context.get("request")
|
|
if request:
|
|
return access.get_abilities(request.user)
|
|
return {}
|
|
|
|
def validate(self, attrs):
|
|
"""
|
|
Check access rights specific to writing (create/update)
|
|
"""
|
|
request = self.context.get("request")
|
|
user = getattr(request, "user", None)
|
|
role = attrs.get("role")
|
|
|
|
# Update
|
|
if self.instance:
|
|
can_set_role_to = self.instance.get_abilities(user)["set_role_to"]
|
|
|
|
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 set this role for this template."
|
|
)
|
|
raise exceptions.PermissionDenied(message)
|
|
|
|
# Create
|
|
else:
|
|
teams = user.get_teams()
|
|
try:
|
|
template_id = self.context["template_id"]
|
|
except KeyError as exc:
|
|
raise exceptions.ValidationError(
|
|
"You must set a template ID in kwargs to create a new template access."
|
|
) from exc
|
|
|
|
if not models.TemplateAccess.objects.filter(
|
|
Q(user=user) | Q(team__in=teams),
|
|
template=template_id,
|
|
role__in=[models.RoleChoices.OWNER, models.RoleChoices.ADMIN],
|
|
).exists():
|
|
raise exceptions.PermissionDenied(
|
|
"You are not allowed to manage accesses for this template."
|
|
)
|
|
|
|
if (
|
|
role == models.RoleChoices.OWNER
|
|
and not models.TemplateAccess.objects.filter(
|
|
Q(user=user) | Q(team__in=teams),
|
|
template=template_id,
|
|
role=models.RoleChoices.OWNER,
|
|
).exists()
|
|
):
|
|
raise exceptions.PermissionDenied(
|
|
"Only owners of a template can assign other users as owners."
|
|
)
|
|
|
|
attrs["template_id"] = self.context["template_id"]
|
|
return attrs
|
|
|
|
|
|
class TemplateSerializer(serializers.ModelSerializer):
|
|
"""Serialize templates."""
|
|
|
|
abilities = serializers.SerializerMethodField(read_only=True)
|
|
accesses = TemplateAccessSerializer(many=True, read_only=True)
|
|
|
|
class Meta:
|
|
model = models.Template
|
|
fields = ["id", "title", "accesses", "abilities"]
|
|
read_only_fields = ["id", "accesses", "abilities"]
|
|
|
|
def get_abilities(self, template) -> dict:
|
|
"""Return abilities of the logged-in user on the instance."""
|
|
request = self.context.get("request")
|
|
if request:
|
|
return template.get_abilities(request.user)
|
|
return {}
|
|
|
|
|
|
# pylint: disable=abstract-method
|
|
class DocumentGenerationSerializer(serializers.Serializer):
|
|
"""Serializer to receive a request to generate a document on a template."""
|
|
|
|
body = serializers.CharField(label=_("Markdown Body"))
|