fix(api): Add org membership check to onboarding continuation email endpoint#108474
fix(api): Add org membership check to onboarding continuation email endpoint#108474
Conversation
…ndpoint The endpoint used SentryIsAuthenticated which only checks if the user is logged in, allowing any authenticated user to trigger onboarding emails for any organization. Replace with OrganizationPermission-based check that verifies org membership while still allowing any org member (even the member role with only org:read) to use the endpoint. Co-Authored-By: Claude <[email protected]>
| "POST": ApiPublishStatus.PRIVATE, | ||
| } | ||
| owner = ApiOwner.TELEMETRY_EXPERIENCE | ||
| # let anyone in the org use this endpoint |
There was a problem hiding this comment.
so is the comment off here? SentryIsAuthenticated means that anyone outside the org can access the endpoint? i assume there are a bunch of other org scoped endpoints that use this permission which we'll have to change as well?
There was a problem hiding this comment.
Yes, the comment was wrong — SentryIsAuthenticated only checks that the user is logged in. On an OrganizationEndpoint, the org membership check happens in OrganizationPermission.has_object_permission() (via determine_access()), so overriding permission_classes with just SentryIsAuthenticated bypasses it entirely. Any authenticated user could hit this endpoint for any org.
I audited all endpoints using SentryIsAuthenticated and this was the only org-scoped endpoint using it as the sole permission class. The one other org endpoint that includes it (DiscoverHomepageQueryEndpoint) also has DiscoverSavedQueryPermission alongside it, so it's still protected by DRF's AND logic — though the SentryIsAuthenticated there is redundant and could be cleaned up separately.
I also removed the misleading comment in 2950c40.
The comment said "let anyone in the org use this endpoint" but the previous permission class (SentryIsAuthenticated) didn't actually enforce org membership. Now that the permission class is correct, the comment is redundant and could mislead future readers.
…ndpoint (#108474) The `OrganizationOnboardingContinuationEmail` endpoint used `SentryIsAuthenticated` as its permission class, which only verifies that the user is logged in. This allowed any authenticated user to trigger onboarding continuation emails for any organization, leaking the org name in the email and polluting analytics with bogus `OnboardingContinuationSent` events. The code comment states the intent was to "let anyone in the org use this endpoint," but `SentryIsAuthenticated` doesn't perform any org membership check — it delegates to `BasePermission.has_object_permission` which unconditionally returns `True`. This replaces `SentryIsAuthenticated` with an `OrganizationPermission`-based permission class that requires `org:read` scope for POST, matching the pattern used by the sibling `OrganizationOnboardingTaskEndpoint`. This ensures org membership is verified via `determine_access()` while still allowing any org member (including the lowest `member` role) to use the endpoint. --------- Co-authored-by: Claude <[email protected]>
…ndpoint (#108474) The `OrganizationOnboardingContinuationEmail` endpoint used `SentryIsAuthenticated` as its permission class, which only verifies that the user is logged in. This allowed any authenticated user to trigger onboarding continuation emails for any organization, leaking the org name in the email and polluting analytics with bogus `OnboardingContinuationSent` events. The code comment states the intent was to "let anyone in the org use this endpoint," but `SentryIsAuthenticated` doesn't perform any org membership check — it delegates to `BasePermission.has_object_permission` which unconditionally returns `True`. This replaces `SentryIsAuthenticated` with an `OrganizationPermission`-based permission class that requires `org:read` scope for POST, matching the pattern used by the sibling `OrganizationOnboardingTaskEndpoint`. This ensures org membership is verified via `determine_access()` while still allowing any org member (including the lowest `member` role) to use the endpoint. --------- Co-authored-by: Claude <[email protected]>
The
OrganizationOnboardingContinuationEmailendpoint usedSentryIsAuthenticatedas itspermission class, which only verifies that the user is logged in. This allowed any authenticated
user to trigger onboarding continuation emails for any organization, leaking the org name in
the email and polluting analytics with bogus
OnboardingContinuationSentevents.The code comment states the intent was to "let anyone in the org use this endpoint," but
SentryIsAuthenticateddoesn't perform any org membership check — it delegates toBasePermission.has_object_permissionwhich unconditionally returnsTrue.This replaces
SentryIsAuthenticatedwith anOrganizationPermission-based permission classthat requires
org:readscope for POST, matching the pattern used by the siblingOrganizationOnboardingTaskEndpoint. This ensures org membership is verified viadetermine_access()while still allowing any org member (including the lowestmemberrole)to use the endpoint.