From 3becb442a5e39b82440072098774ec60c9b35f57 Mon Sep 17 00:00:00 2001 From: Daniel Szoke Date: Fri, 27 Sep 2024 13:42:30 +0200 Subject: [PATCH] fix(deploys): Maintain backwards compatibility Changing the scope to `project:releases` breaks this endpoint for tokens that could previously specify specific projects for the deploy via this endpoint via `project:read` (in practice, `project:write` was also required, since the endpoint cannot be accessed only with `project:read`). --- src/sentry/api/endpoints/release_deploys.py | 2 +- .../api/serializers/rest_framework/project.py | 13 +++++++++++-- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/src/sentry/api/endpoints/release_deploys.py b/src/sentry/api/endpoints/release_deploys.py index dab2eeb1ec708a..688a253a3bee26 100644 --- a/src/sentry/api/endpoints/release_deploys.py +++ b/src/sentry/api/endpoints/release_deploys.py @@ -29,7 +29,7 @@ class DeploySerializer(serializers.Serializer): dateStarted = serializers.DateTimeField(required=False, allow_null=True) dateFinished = serializers.DateTimeField(required=False, allow_null=True) projects = serializers.ListField( - child=ProjectField(scope="project:releases", id_allowed=True), + child=ProjectField(scope=("project:read", "project:releases"), id_allowed=True), required=False, allow_empty=False, ) diff --git a/src/sentry/api/serializers/rest_framework/project.py b/src/sentry/api/serializers/rest_framework/project.py index 66f3f3d4dd8a75..c2e71334cabbdf 100644 --- a/src/sentry/api/serializers/rest_framework/project.py +++ b/src/sentry/api/serializers/rest_framework/project.py @@ -1,3 +1,5 @@ +from collections.abc import Collection + from drf_spectacular.types import OpenApiTypes from drf_spectacular.utils import extend_schema_field from rest_framework import serializers @@ -9,7 +11,12 @@ @extend_schema_field(field=OpenApiTypes.STR) class ProjectField(serializers.Field): - def __init__(self, scope="project:write", id_allowed=False): + def __init__(self, scope: str | Collection[str] = "project:write", id_allowed: bool = False): + """ + The scope parameter specifies which permissions are required to access the project field. + If multiple scopes are provided, the project can be accessed when the user is authenticated with + any of the scopes. + """ self.scope = scope self.id_allowed = id_allowed super().__init__() @@ -27,6 +34,8 @@ def to_internal_value(self, data): project = Project.objects.get(organization=self.context["organization"], slug=data) except Project.DoesNotExist: raise ValidationError("Invalid project") - if not self.context["access"].has_project_scope(project, self.scope): + + scopes = (self.scope,) if isinstance(self.scope, str) else self.scope + if not self.context["access"].has_any_project_scope(project, scopes): raise ValidationError("Insufficient access to project") return project