diff --git a/src/sentry/api/serializers/models/apiapplication.py b/src/sentry/api/serializers/models/apiapplication.py index aaffdee856eeba..ec544f3c46c915 100644 --- a/src/sentry/api/serializers/models/apiapplication.py +++ b/src/sentry/api/serializers/models/apiapplication.py @@ -9,7 +9,15 @@ @register(ApiApplication) class ApiApplicationSerializer(Serializer): def serialize(self, obj, attrs, user, **kwargs): - is_secret_visible = obj.date_added > timezone.now() - timedelta(minutes=5) + is_owner = user and not user.is_anonymous and user.id == obj.owner_id + has_user_context = user is not None and not getattr(user, "is_anonymous", True) + # NOTE: When no user is passed, the secret remains visible to + # preserve behavior for superuser-gated getsentry admin endpoints. + # TODO: Remove this fallback once getsentry instance-level OAuth + # endpoints pass request.user to serialize(). + is_secret_visible = obj.date_added > timezone.now() - timedelta(minutes=5) and ( + is_owner or not has_user_context + ) return { "id": obj.client_id, "clientID": obj.client_id, diff --git a/tests/sentry/api/endpoints/test_api_authorizations.py b/tests/sentry/api/endpoints/test_api_authorizations.py index 52339730a58dc8..03dd1b3b2b673c 100644 --- a/tests/sentry/api/endpoints/test_api_authorizations.py +++ b/tests/sentry/api/endpoints/test_api_authorizations.py @@ -32,6 +32,15 @@ def test_simple(self) -> None: assert response.data[0]["id"] == str(auth.id) assert response.data[0]["organization"] is None + def test_fresh_app_hides_secret_from_non_owner(self) -> None: + other_user = self.create_user("other@example.com") + app = ApiApplication.objects.create(name="test", owner=other_user) + ApiAuthorization.objects.create(application=app, user=self.user) + + response = self.get_success_response() + assert len(response.data) == 1 + assert response.data[0]["application"]["clientSecret"] is None + def test_org_level_auth(self) -> None: org = self.create_organization(owner=self.user, slug="test-org-slug") app = ApiApplication.objects.create(