Skip to content

Commit dccc8bc

Browse files
committed
fix(replays): Don't match pending invites for replay access with no user
get_org_member looked the caller up by user_id=request.user.id. For an API key that's None, which matched pending invites: a pending invite on the replay allowlist granted the key access, and several pending invites raised MultipleObjectsReturned.
1 parent 9071031 commit dccc8bc

3 files changed

Lines changed: 42 additions & 2 deletions

File tree

‎src/sentry/replays/permissions.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
from django.http import HttpRequest
66

7+
from sentry.auth.principal import owned_by_acting_user
78
from sentry.auth.superuser import superuser_has_permission
89
from sentry.models.options.organization_option import OrganizationOption
910
from sentry.models.organizationmember import OrganizationMember
@@ -85,6 +86,8 @@ def granular_permissions_turned_off(organization: Organization) -> bool:
8586
def get_org_member(organization: Organization, request: HttpRequest) -> OrganizationMember | None:
8687
"""Get the OrganizationMember for the request user, or None if not a member."""
8788
try:
88-
return OrganizationMember.objects.get(organization=organization, user_id=request.user.id)
89+
return OrganizationMember.objects.get(
90+
owned_by_acting_user(request.user), organization=organization
91+
)
8992
except OrganizationMember.DoesNotExist:
9093
return None

‎tests/sentry/auth/test_principal.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,7 +275,7 @@ def _is_request_user_type_check(node: ast.AST) -> bool:
275275

276276

277277
# Lower these as callers move off. Exact, so the baseline can't drift back up.
278-
REQUEST_USER_ID_BASELINE = 134
278+
REQUEST_USER_ID_BASELINE = 133
279279
REQUEST_USER_TYPE_CHECK_BASELINE = 29
280280
REQUEST_USER_ALLOWLIST_BASELINE = 22
281281

‎tests/sentry/replays/endpoints/test_replay_granular_permissions.py‎

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
from rest_framework.response import Response
2+
3+
from sentry.models.apikey import ApiKey
14
from sentry.models.apitoken import ApiToken
25
from sentry.models.options.organization_option import OrganizationOption
36
from sentry.models.orgauthtoken import OrgAuthToken
@@ -442,3 +445,37 @@ def test_sentry_app_with_event_read_empty_allowlist_has_access(self) -> None:
442445
)
443446
# SentryApp with event:read bypasses member allowlist
444447
assert response.status_code == 200
448+
449+
def _api_key_get_replays(self) -> Response:
450+
with assume_test_silo_mode(SiloMode.CONTROL):
451+
key = ApiKey.objects.create(
452+
organization_id=self.organization.id, scope_list=["org:read", "event:read"]
453+
)
454+
url = f"/api/0/organizations/{self.organization.slug}/replays/"
455+
return self.client.get(url, HTTP_AUTHORIZATION=self.create_basic_auth_header(key.key))
456+
457+
def test_api_key_does_not_get_a_pending_invites_access(self) -> None:
458+
with self.feature("organizations:session-replay"):
459+
self._enable_granular_permissions()
460+
OrganizationMemberReplayAccess.objects.create(
461+
organizationmember=self.member_with_access
462+
)
463+
# An org requiring 2FA turns non-compliant members back into pending invites,
464+
# keeping their allowlist entry.
465+
self.member_with_access.remove_user()
466+
self.member_with_access.save()
467+
468+
response = self._api_key_get_replays()
469+
assert response.status_code == 403
470+
471+
def test_api_key_with_several_pending_invites_denied(self) -> None:
472+
with self.feature("organizations:session-replay"):
473+
self._enable_granular_permissions()
474+
OrganizationMemberReplayAccess.objects.create(
475+
organizationmember=self.member_with_access
476+
)
477+
self.create_member(organization=self.organization, email="a@example.com")
478+
self.create_member(organization=self.organization, email="b@example.com")
479+
480+
response = self._api_key_get_replays()
481+
assert response.status_code == 403

0 commit comments

Comments
 (0)