2024-04-25 09:22:28 +02:00
|
|
|
from typing import Any, cast
|
2022-02-08 17:22:09 +01:00
|
|
|
|
|
|
|
from django.db import IntegrityError
|
2022-02-14 13:55:16 +01:00
|
|
|
from rest_framework import exceptions, mixins, serializers, viewsets
|
2024-02-12 14:55:21 +01:00
|
|
|
from rest_framework.permissions import SAFE_METHODS, BasePermission
|
2022-02-14 13:55:16 +01:00
|
|
|
from rest_framework.request import Request
|
2022-02-08 17:22:09 +01:00
|
|
|
|
|
|
|
from ee.models.dashboard_privilege import DashboardPrivilege
|
2024-02-12 14:55:21 +01:00
|
|
|
from posthog.api.routing import TeamAndOrgViewSetMixin
|
2022-02-08 17:22:09 +01:00
|
|
|
from posthog.api.shared import UserBasicSerializer
|
2023-01-30 10:44:16 +01:00
|
|
|
from posthog.models import Dashboard, User
|
|
|
|
from posthog.user_permissions import UserPermissions, UserPermissionsSerializerMixin
|
2022-02-08 17:22:09 +01:00
|
|
|
|
|
|
|
|
2022-02-14 13:55:16 +01:00
|
|
|
class CanEditDashboardCollaborator(BasePermission):
|
|
|
|
message = "You don't have edit permissions for this dashboard."
|
2022-02-08 17:22:09 +01:00
|
|
|
|
2022-02-14 13:55:16 +01:00
|
|
|
def has_permission(self, request: Request, view) -> bool:
|
2022-02-08 17:22:09 +01:00
|
|
|
if request.method in SAFE_METHODS:
|
|
|
|
return True
|
|
|
|
try:
|
|
|
|
dashboard: Dashboard = Dashboard.objects.get(id=view.parents_query_dict["dashboard_id"])
|
|
|
|
except Dashboard.DoesNotExist:
|
|
|
|
raise exceptions.NotFound("Dashboard not found.")
|
|
|
|
|
2023-01-30 10:44:16 +01:00
|
|
|
return view.user_permissions.dashboard(dashboard).can_edit
|
2022-02-08 17:22:09 +01:00
|
|
|
|
2023-01-30 10:44:16 +01:00
|
|
|
|
|
|
|
class DashboardCollaboratorSerializer(serializers.ModelSerializer, UserPermissionsSerializerMixin):
|
2022-02-08 17:22:09 +01:00
|
|
|
user = UserBasicSerializer(read_only=True)
|
|
|
|
dashboard_id = serializers.IntegerField(read_only=True)
|
|
|
|
|
|
|
|
user_uuid = serializers.UUIDField(required=True, write_only=True)
|
|
|
|
|
|
|
|
class Meta:
|
|
|
|
model = DashboardPrivilege
|
|
|
|
fields = [
|
|
|
|
"id",
|
|
|
|
"dashboard_id",
|
|
|
|
"user",
|
|
|
|
"level",
|
|
|
|
"added_at",
|
|
|
|
"updated_at",
|
|
|
|
"user_uuid", # write_only (see above)
|
|
|
|
]
|
2024-05-20 12:48:33 +02:00
|
|
|
read_only_fields = ["id", "dashboard_id", "user"]
|
2022-02-08 17:22:09 +01:00
|
|
|
|
2024-04-25 09:22:28 +02:00
|
|
|
def validate(self, attrs: dict[str, Any]) -> dict[str, Any]:
|
2022-02-08 17:22:09 +01:00
|
|
|
dashboard: Dashboard = self.context["dashboard"]
|
2023-01-30 10:44:16 +01:00
|
|
|
dashboard_permissions = self.user_permissions.dashboard(dashboard)
|
|
|
|
if dashboard_permissions.effective_restriction_level <= Dashboard.RestrictionLevel.EVERYONE_IN_PROJECT_CAN_EDIT:
|
2022-02-08 17:22:09 +01:00
|
|
|
raise exceptions.ValidationError("Cannot add collaborators to a dashboard on the lowest restriction level.")
|
|
|
|
attrs = super().validate(attrs)
|
|
|
|
level = attrs.get("level")
|
|
|
|
if level is not None and level != Dashboard.PrivilegeLevel.CAN_EDIT:
|
|
|
|
raise serializers.ValidationError("Only edit access can be explicitly specified currently.")
|
|
|
|
return attrs
|
|
|
|
|
|
|
|
def create(self, validated_data):
|
|
|
|
dashboard: Dashboard = self.context["dashboard"]
|
|
|
|
user_uuid = validated_data.pop("user_uuid")
|
|
|
|
try:
|
2022-03-02 15:27:08 +01:00
|
|
|
validated_data["user"] = User.objects.filter(is_active=True).get(uuid=user_uuid)
|
2022-02-08 17:22:09 +01:00
|
|
|
except User.DoesNotExist:
|
|
|
|
raise serializers.ValidationError("User does not exist.")
|
2023-01-30 10:44:16 +01:00
|
|
|
|
|
|
|
modified_user_permissions = UserPermissions(
|
|
|
|
user=validated_data["user"],
|
|
|
|
team=self.context["view"].team,
|
|
|
|
)
|
|
|
|
if modified_user_permissions.current_team.effective_membership_level is None:
|
2022-02-08 17:22:09 +01:00
|
|
|
raise exceptions.ValidationError("Cannot add collaborators that have no access to the project.")
|
2023-01-30 10:44:16 +01:00
|
|
|
if modified_user_permissions.dashboard(dashboard).can_restrict:
|
2022-02-08 17:22:09 +01:00
|
|
|
raise exceptions.ValidationError(
|
|
|
|
"Cannot add collaborators that already have inherent access (the dashboard owner or a project admins)."
|
|
|
|
)
|
|
|
|
validated_data["dashboard_id"] = self.context["dashboard_id"]
|
|
|
|
try:
|
|
|
|
return super().create(validated_data)
|
|
|
|
except IntegrityError:
|
|
|
|
raise serializers.ValidationError("User already is a collaborator.")
|
|
|
|
|
|
|
|
|
|
|
|
class DashboardCollaboratorViewSet(
|
2024-02-12 14:55:21 +01:00
|
|
|
TeamAndOrgViewSetMixin,
|
2022-02-08 17:22:09 +01:00
|
|
|
mixins.ListModelMixin,
|
|
|
|
mixins.CreateModelMixin,
|
|
|
|
mixins.DestroyModelMixin,
|
|
|
|
viewsets.GenericViewSet,
|
|
|
|
):
|
2024-03-15 18:51:21 +01:00
|
|
|
scope_object = "dashboard"
|
2024-02-12 14:55:21 +01:00
|
|
|
permission_classes = [CanEditDashboardCollaborator]
|
2022-02-08 17:22:09 +01:00
|
|
|
pagination_class = None
|
2022-03-02 15:27:08 +01:00
|
|
|
queryset = DashboardPrivilege.objects.select_related("dashboard").filter(user__is_active=True)
|
2022-02-08 17:22:09 +01:00
|
|
|
lookup_field = "user__uuid"
|
|
|
|
serializer_class = DashboardCollaboratorSerializer
|
2024-08-19 17:56:47 +02:00
|
|
|
filter_rewrite_rules = {"project_id": "dashboard__team__project_id"}
|
2022-02-08 17:22:09 +01:00
|
|
|
|
2024-04-25 09:22:28 +02:00
|
|
|
def get_serializer_context(self) -> dict[str, Any]:
|
2022-02-08 17:22:09 +01:00
|
|
|
context = super().get_serializer_context()
|
|
|
|
try:
|
|
|
|
context["dashboard"] = Dashboard.objects.get(id=context["dashboard_id"])
|
|
|
|
except Dashboard.DoesNotExist:
|
|
|
|
raise exceptions.NotFound("Dashboard not found.")
|
|
|
|
return context
|
|
|
|
|
|
|
|
def perform_destroy(self, instance) -> None:
|
|
|
|
dashboard = cast(Dashboard, instance.dashboard)
|
2023-01-30 10:44:16 +01:00
|
|
|
if (
|
|
|
|
self.user_permissions.dashboard(dashboard).effective_restriction_level
|
|
|
|
<= Dashboard.RestrictionLevel.EVERYONE_IN_PROJECT_CAN_EDIT
|
|
|
|
):
|
2022-02-08 17:22:09 +01:00
|
|
|
raise exceptions.ValidationError(
|
|
|
|
"Cannot remove collaborators from a dashboard on the lowest restriction level."
|
|
|
|
)
|
|
|
|
return super().perform_destroy(instance)
|