✨(models/api) add RBAC on templates linking accesses to a team name
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.
This commit is contained in:
committed by
Samuel Paccoud
parent
a23118bee4
commit
f581eb8abd
@@ -1,4 +1,5 @@
|
||||
"""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
|
||||
@@ -31,7 +32,7 @@ class TemplateAccessSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = models.TemplateAccess
|
||||
fields = ["id", "user", "role", "abilities"]
|
||||
fields = ["id", "user", "team", "role", "abilities"]
|
||||
read_only_fields = ["id", "abilities"]
|
||||
|
||||
def update(self, instance, validated_data):
|
||||
@@ -68,6 +69,7 @@ class TemplateAccessSerializer(serializers.ModelSerializer):
|
||||
|
||||
# Create
|
||||
else:
|
||||
teams = user.get_teams()
|
||||
try:
|
||||
template_id = self.context["template_id"]
|
||||
except KeyError as exc:
|
||||
@@ -76,8 +78,8 @@ class TemplateAccessSerializer(serializers.ModelSerializer):
|
||||
) from exc
|
||||
|
||||
if not models.TemplateAccess.objects.filter(
|
||||
Q(user=user) | Q(team__in=teams),
|
||||
template=template_id,
|
||||
user=user,
|
||||
role__in=[models.RoleChoices.OWNER, models.RoleChoices.ADMIN],
|
||||
).exists():
|
||||
raise exceptions.PermissionDenied(
|
||||
@@ -87,8 +89,8 @@ class TemplateAccessSerializer(serializers.ModelSerializer):
|
||||
if (
|
||||
role == models.RoleChoices.OWNER
|
||||
and not models.TemplateAccess.objects.filter(
|
||||
Q(user=user) | Q(team__in=teams),
|
||||
template=template_id,
|
||||
user=user,
|
||||
role=models.RoleChoices.OWNER,
|
||||
).exists()
|
||||
):
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""API endpoints"""
|
||||
from io import BytesIO
|
||||
|
||||
from django.contrib.postgres.aggregates import ArrayAgg
|
||||
from django.db.models import (
|
||||
OuterRef,
|
||||
Q,
|
||||
@@ -156,14 +157,22 @@ class TemplateViewSet(
|
||||
if not self.request.user.is_authenticated:
|
||||
return models.Template.objects.filter(is_public=True)
|
||||
|
||||
user_role_query = models.TemplateAccess.objects.filter(
|
||||
user=self.request.user, template=OuterRef("pk")
|
||||
).values("role")[:1]
|
||||
user = self.request.user
|
||||
teams = user.get_teams()
|
||||
|
||||
user_roles_query = (
|
||||
models.TemplateAccess.objects.filter(
|
||||
Q(user=user) | Q(team__in=teams), template=OuterRef("pk")
|
||||
)
|
||||
.values("template")
|
||||
.annotate(roles_array=ArrayAgg("role"))
|
||||
.values("roles_array")
|
||||
)
|
||||
return (
|
||||
models.Template.objects.filter(
|
||||
Q(accesses__user=self.request.user) | Q(is_public=True)
|
||||
Q(accesses__user=user) | Q(accesses__team__in=teams) | Q(is_public=True)
|
||||
)
|
||||
.annotate(user_role=Subquery(user_role_query))
|
||||
.annotate(user_roles=Subquery(user_roles_query))
|
||||
.distinct()
|
||||
)
|
||||
|
||||
@@ -263,19 +272,30 @@ class TemplateAccessViewSet(
|
||||
queryset = queryset.filter(template=self.kwargs["template_id"])
|
||||
|
||||
if self.action == "list":
|
||||
user = self.request.user
|
||||
teams = user.get_teams()
|
||||
|
||||
user_roles_query = (
|
||||
models.TemplateAccess.objects.filter(
|
||||
Q(user=user) | Q(team__in=teams),
|
||||
template=self.kwargs["template_id"],
|
||||
)
|
||||
.values("template")
|
||||
.annotate(roles_array=ArrayAgg("role"))
|
||||
.values("roles_array")
|
||||
)
|
||||
|
||||
# Limit to template access instances related to a template THAT also has
|
||||
# a template access
|
||||
# instance for the logged-in user (we don't want to list only the template
|
||||
# access instances pointing to the logged-in user)
|
||||
user_role_query = models.TemplateAccess.objects.filter(
|
||||
template=self.kwargs["template_id"],
|
||||
template__accesses__user=self.request.user,
|
||||
).values("role")[:1]
|
||||
queryset = (
|
||||
queryset.filter(
|
||||
template__accesses__user=self.request.user,
|
||||
Q(template__accesses__user=user)
|
||||
| Q(template__accesses__team__in=teams),
|
||||
template=self.kwargs["template_id"],
|
||||
)
|
||||
.annotate(user_role=Subquery(user_role_query))
|
||||
.annotate(user_roles=Subquery(user_roles_query))
|
||||
.distinct()
|
||||
)
|
||||
return queryset
|
||||
|
||||
Reference in New Issue
Block a user