diff --git a/src/sentry/conf/server.py b/src/sentry/conf/server.py index bc8ccde3a6f0..b33afaf69dfe 100644 --- a/src/sentry/conf/server.py +++ b/src/sentry/conf/server.py @@ -2174,6 +2174,7 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]: "project:releases", "project:read", "org:read", + "service_account:read", "member:invite", "member:read", "team:read", @@ -2201,6 +2202,7 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]: "event:write", "event:admin", "org:read", + "service_account:read", "member:read", "member:invite", "project:read", @@ -2354,6 +2356,7 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]: "project:releases", "project:read", "org:read", + "service_account:read", "member:invite", "member:read", "team:read", @@ -2385,6 +2388,7 @@ def custom_parameter_sort(parameter: dict) -> tuple[str, int]: "event:write", "event:admin", "org:read", + "service_account:read", "member:read", "member:invite", "project:read", diff --git a/src/sentry/service_accounts/endpoints/organization_service_accounts.py b/src/sentry/service_accounts/endpoints/organization_service_accounts.py index 04f1dababb5a..1906c54323a2 100644 --- a/src/sentry/service_accounts/endpoints/organization_service_accounts.py +++ b/src/sentry/service_accounts/endpoints/organization_service_accounts.py @@ -231,7 +231,7 @@ class ServiceAccountUpdateSerializer(ServiceAccountCreateSerializer): class ServiceAccountPermission(OrganizationPermission): scope_map = { - "GET": ["org:read", "org:write", "org:admin"], + "GET": ["service_account:read"], "POST": ["service_account:write"], "PUT": ["service_account:write"], "DELETE": ["service_account:write"], diff --git a/tests/sentry/conf/test_scopes.py b/tests/sentry/conf/test_scopes.py index ac3d115e07e8..1606a5c7044b 100644 --- a/tests/sentry/conf/test_scopes.py +++ b/tests/sentry/conf/test_scopes.py @@ -65,6 +65,11 @@ def test_service_account_scopes_granted_to_manager_and_owner(self) -> None: granted = {role["id"] for role in roles if SERVICE_ACCOUNT_SCOPES <= role["scopes"]} assert granted == {"manager", "owner"} + def test_service_account_read_granted_to_org_readers(self) -> None: + for roles in (settings.SENTRY_ROLES, settings.SENTRY_GRANULAR_ROLES): + granted = {role["id"] for role in roles if "service_account:read" in role["scopes"]} + assert granted == {"member", "admin", "manager", "owner"} + def test_service_account_scopes_not_grantable_to_non_humans(self) -> None: assert SERVICE_ACCOUNT_SCOPES <= SENTRY_SCOPES assert SERVICE_ACCOUNT_SCOPES.isdisjoint(GRANTABLE_SCOPES) diff --git a/tests/sentry/service_accounts/endpoints/test_organization_service_accounts.py b/tests/sentry/service_accounts/endpoints/test_organization_service_accounts.py index fa81023cc34c..454910cb4d5e 100644 --- a/tests/sentry/service_accounts/endpoints/test_organization_service_accounts.py +++ b/tests/sentry/service_accounts/endpoints/test_organization_service_accounts.py @@ -110,6 +110,20 @@ def test_member_can_list(self) -> None: assert len(response.data) == 1 + def test_list_with_token_scopes(self) -> None: + self.create_service_account(organization=self.organization) + url = reverse(self.endpoint, args=[self.organization.slug]) + + def list_with(scope_list: list[str]) -> Any: + token = self.create_user_auth_token(user=self.user, scope_list=scope_list) + return self.client.get(url, HTTP_AUTHORIZATION=f"Bearer {token.token}") + + with self.feature(FEATURE_FLAG): + assert list_with(["service_account:read"]).status_code == 200 + # Neither org:read nor the explicit write scope grants reads. + assert list_with(["org:read"]).status_code == 403 + assert list_with(["service_account:write"]).status_code == 403 + def test_create(self) -> None: with assume_test_silo_mode(SiloMode.CELL): project = self.create_project(organization=self.organization)