diff --git a/src/sentry/api/endpoints/prompts_activity.py b/src/sentry/api/endpoints/prompts_activity.py index 24659dbc7669..8824ee295f1c 100644 --- a/src/sentry/api/endpoints/prompts_activity.py +++ b/src/sentry/api/endpoints/prompts_activity.py @@ -23,7 +23,7 @@ from sentry.apidocs.parameters import GlobalParams from sentry.apidocs.response_types import DetailResponse from sentry.apidocs.utils import inline_sentry_response_serializer -from sentry.auth.principal import is_owned_by_acting_user +from sentry.auth.principal import is_owned_by_acting_user, require_user from sentry.models.organization import Organization from sentry.models.project import Project from sentry.models.promptsactivity import PromptsActivity @@ -223,10 +223,11 @@ def put(self, request: Request, organization: Organization, **kwargs: object) -> data["snoozed_ts"] = None data["dismissed_ts"] = None + user_id = require_user(request).id try: with transaction.atomic(router.db_for_write(PromptsActivity)): PromptsActivity.objects.update_or_create( - feature=feature, user_id=request.user.id, defaults={"data": data}, **fields + feature=feature, user_id=user_id, defaults={"data": data}, **fields ) except IntegrityError: pass diff --git a/src/sentry/dashboards/endpoints/organization_dashboards.py b/src/sentry/dashboards/endpoints/organization_dashboards.py index d1ca1555f184..4aedab249545 100644 --- a/src/sentry/dashboards/endpoints/organization_dashboards.py +++ b/src/sentry/dashboards/endpoints/organization_dashboards.py @@ -406,7 +406,7 @@ def has_object_permission( # check if user is restricted from editing dashboard if hasattr(obj, "permissions"): - return obj.permissions.has_edit_permissions(request.user.id) + return obj.permissions.has_edit_permissions(acting_user_id(request.user)) # if no permissions are assigned, it is considered accessible to all users return True diff --git a/tests/sentry/api/endpoints/test_prompts_activity.py b/tests/sentry/api/endpoints/test_prompts_activity.py index f0c2db75832d..8f7b644c0a85 100644 --- a/tests/sentry/api/endpoints/test_prompts_activity.py +++ b/tests/sentry/api/endpoints/test_prompts_activity.py @@ -2,6 +2,7 @@ from django.urls import reverse from rest_framework.response import Response +from sentry.models.apikey import ApiKey from sentry.models.promptsactivity import PromptsActivity from sentry.silo.base import SiloMode from sentry.testutils.cases import APITestCase @@ -90,6 +91,48 @@ def test_service_account_sees_no_prompt_state(self) -> None: class PutPromptsActivityTest(PromptsActivityTestBase): method = "put" + def _dismiss(self, **headers: str | bytes) -> Response: + return self.client.put( + self.path, + { + "organization_id": self.org.id, + "project_id": self.project.id, + "feature": "releases", + "status": "dismissed", + }, + **headers, + ) + + def test_api_key_cannot_dismiss(self) -> None: + with assume_test_silo_mode(SiloMode.CONTROL): + key = ApiKey.objects.create(organization_id=self.org.id, scope_list=["org:read"]) + self.client.logout() + + resp = self._dismiss(HTTP_AUTHORIZATION=self.create_basic_auth_header(key.key)) + + assert resp.status_code == 401, resp.content + assert not PromptsActivity.objects.filter(feature="releases").exists() + + @override_settings(SEER_API_SHARED_SECRET="service-account-secret") + @with_feature("organizations:service-accounts") + def test_service_account_cannot_dismiss(self) -> None: + service_account = self.create_service_account( + organization=self.org, scope_list=["org:read"] + ) + viewer_context = encode_viewer_context( + ViewerContext( + actor=Actor(type=ActorType.SERVICE_ACCOUNT, actor_id=service_account.id), + organization_id=self.org.id, + ), + key="service-account-secret", + ) + + resp = self._dismiss(HTTP_X_VIEWER_CONTEXT=viewer_context) + + assert resp.status_code == 403, resp.content + assert resp.data["detail"]["code"] == "user-required" + assert not PromptsActivity.objects.filter(feature="releases").exists() + def test_regular_org_member_can_dismiss(self) -> None: member_user = self.create_user() self.create_member(user=member_user, organization=self.org, role="member") diff --git a/tests/sentry/auth/test_principal.py b/tests/sentry/auth/test_principal.py index 814c6bfd2ccc..d64e8447508e 100644 --- a/tests/sentry/auth/test_principal.py +++ b/tests/sentry/auth/test_principal.py @@ -275,9 +275,9 @@ def _is_request_user_type_check(node: ast.AST) -> bool: # Lower these as callers move off. Exact, so the baseline can't drift back up. -REQUEST_USER_ID_BASELINE = 133 +REQUEST_USER_ID_BASELINE = 131 REQUEST_USER_TYPE_CHECK_BASELINE = 29 -REQUEST_USER_ALLOWLIST_BASELINE = 12 +REQUEST_USER_ALLOWLIST_BASELINE = 10 def test_request_user_id_uses_only_go_down() -> None: diff --git a/tools/mypy_helpers/request_user_allowlist.txt b/tools/mypy_helpers/request_user_allowlist.txt index 5adf87438271..c44b36578f2b 100644 --- a/tools/mypy_helpers/request_user_allowlist.txt +++ b/tools/mypy_helpers/request_user_allowlist.txt @@ -1,10 +1,8 @@ src/sentry/api/bases/organizationmember.py src/sentry/api/endpoints/organization_releases.py -src/sentry/api/endpoints/prompts_activity.py src/sentry/core/endpoints/organization_member_details.py src/sentry/core/endpoints/organization_member_index.py src/sentry/core/endpoints/organization_member_requests_invite_index.py -src/sentry/dashboards/endpoints/organization_dashboards.py src/sentry/data_export/endpoints/data_export.py src/sentry/issues/endpoints/group_integration_details.py src/sentry/notebooks/endpoints/base.py