Skip to content

Commit 12f6e6a

Browse files
authored
feat(cells): Remove legacy non-org-scoped accept invite API route (#112767)
The `/api/0/accept-invite/{member_id}/{token}/` required no organization context, relying on a cell mapping lookup to determine the org. The org-scoped `/api/0/accept-invite/{organization_id_or_slug}/{member_id}/{token}/` is now the only supported route. This PR removes the legacy API route and any references to it: - Remove the URL pattern from api/urls.py - Remove the legacy pattern from controlsiloUrlPatterns.ts (regenerated) - Remove the legacy type from knownSentryApiUrls.generated.ts (regenerated) - Remove entries from the apidocs ownership and publish status allowlists It also cleans up `AcceptOrganizationInvite` and `get_invite_state` now that `organization_id_or_slug` is always provided: - Fix member_id and organization_id_or_slug type annotations in convert_args and get() to the proper type, django passes kwargs as strings - Drop `int | str` from `organization_id_or_slug` since no caller ever passes an integer - Remove redundant str() wrapping on .isdecimal() call - Remove dead not request.user.is_authenticated branch in post() - Add TODO to further narrow type and remove None from get_invite_state once the temporary redirect is cleaned up Removing this route is harmless as the web UI has been previously updated to always use the new url in #112634. This is effectively dead code. This is not a public URL that requires a formal deprecation process.
1 parent fe2ec76 commit 12f6e6a

7 files changed

Lines changed: 9 additions & 45 deletions

File tree

src/sentry/api/endpoints/accept_organization_invite.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,16 @@ def handle_empty_organization_id_or_slug(
7171

7272
def get_invite_state(
7373
member_id: int,
74-
organization_id_or_slug: int | str | None,
74+
# TODO(cells): Remove None once AcceptOrganizationInviteRedirectView is deleted
75+
organization_id_or_slug: str | None,
7576
user_id: int | None,
7677
request: HttpRequest,
7778
) -> RpcUserInviteContext | None:
7879
if organization_id_or_slug is None:
7980
return handle_empty_organization_id_or_slug(member_id, user_id, request)
8081

8182
else:
82-
if str(organization_id_or_slug).isdecimal():
83+
if organization_id_or_slug.isdecimal():
8384
invite_context = organization_service.get_invite_by_id(
8485
organization_id=organization_id_or_slug,
8586
organization_member_id=member_id,
@@ -107,9 +108,9 @@ class AcceptOrganizationInvite(Endpoint):
107108
def convert_args(
108109
self,
109110
request: Request,
110-
member_id: int,
111+
member_id: str,
111112
token: str,
112-
organization_id_or_slug: int | str | None = None,
113+
organization_id_or_slug: str,
113114
*args,
114115
**kwargs,
115116
):
@@ -143,7 +144,7 @@ def get_helper(
143144
def get(
144145
self,
145146
request: Request,
146-
member_id: int,
147+
member_id: str,
147148
token: str,
148149
invite_context: RpcUserInviteContext,
149150
**kwargs,
@@ -267,7 +268,7 @@ def post(
267268
response = Response(
268269
status=status.HTTP_400_BAD_REQUEST, data={"details": "member already exists"}
269270
)
270-
elif not request.user.is_authenticated or not helper.valid_request:
271+
elif not helper.valid_request:
271272
return Response(
272273
status=status.HTTP_400_BAD_REQUEST,
273274
data={"details": "unable to accept organization invite"},

src/sentry/api/urls.py

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3788,11 +3788,6 @@ def create_group_urls(name_prefix: str) -> list[URLPattern | URLResolver]:
37883788
DataExportNotificationsEndpoint.as_view(),
37893789
name="sentry-api-0-data-export-notifications",
37903790
),
3791-
re_path(
3792-
r"^accept-invite/(?P<member_id>[^/]+)/(?P<token>[^/]+)/$",
3793-
AcceptOrganizationInvite.as_view(),
3794-
name="sentry-api-0-accept-organization-invite",
3795-
),
37963791
re_path(
37973792
r"^notification-defaults/$",
37983793
NotificationDefaultsEndpoints.as_view(),

src/sentry/apidocs/api_ownership_allowlist_dont_modify.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@
5656
"/api/0/projects/{organization_id_or_slug}/{project_id_or_slug}/keys/{key_id}/",
5757
"/api/0/organizations/{organization_id_or_slug}/recent-searches/",
5858
"/api/0/organizations/{organization_id_or_slug}/projects/",
59-
"/api/0/accept-invite/{member_id}/{token}/",
6059
"/api/0/projects/{organization_id_or_slug}/{project_id_or_slug}/environments/{environment}/",
6160
"/api/0/projects/{organization_id_or_slug}/{project_id_or_slug}/releases/token/",
6261
"/api/0/organizations/{organization_id_or_slug}/searches/{search_id}/",

src/sentry/apidocs/api_publish_status_allowlist_dont_modify.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -809,7 +809,6 @@
809809
"/api/0/authenticators/": {"GET"},
810810
"/api/0/accept-transfer/": {"GET", "POST"},
811811
"/api/0/accept-invite/{organization_id_or_slug}/{member_id}/{token}/": {"GET", "POST"},
812-
"/api/0/accept-invite/{member_id}/{token}/": {"GET", "POST"},
813812
"/api/0/profiling/projects/{project_id}/profile/{profile_id}/": {"GET"},
814813
"/api/0/organizations/{organization_id_or_slug}/{var}/{issue_id}/participants/": {"GET"},
815814
"/api/0/{var}/{issue_id}/participants/": {"GET"},

static/app/data/controlsiloUrlPatterns.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,6 @@ export const controlsiloUrlPatterns: RegExp[] = [
154154
new RegExp('^api/0/authenticators/$'),
155155
new RegExp('^api/0/accept-invite/[^/]+/[^/]+/[^/]+/$'),
156156
new RegExp('^api/0/data-export/notifications/google-cloud/$'),
157-
new RegExp('^api/0/accept-invite/[^/]+/[^/]+/$'),
158157
new RegExp('^api/0/notification-defaults/$'),
159158
new RegExp('^api/0/sentry-apps-stats/$'),
160159
new RegExp('^api/0/doc-integrations/$'),

static/app/utils/api/knownSentryApiUrls.generated.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@
99

1010
export type KnownSentryApiUrls =
1111
| '/'
12-
| '/accept-invite/$memberId/$token/'
1312
| '/accept-invite/$organizationIdOrSlug/$memberId/$token/'
1413
| '/accept-transfer/'
1514
| '/api-applications/'

tests/sentry/api/endpoints/test_accept_organization_invite.py

Lines changed: 2 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
from sentry.models.organizationmapping import OrganizationMapping
1616
from sentry.models.organizationmember import InviteStatus, OrganizationMember
1717
from sentry.models.organizationmembermapping import OrganizationMemberMapping
18-
from sentry.silo.base import SiloMode
1918
from sentry.silo.safety import unguarded_write
2019
from sentry.testutils.cases import TestCase
2120
from sentry.testutils.cell import override_cells
@@ -36,26 +35,20 @@ def setUp(self) -> None:
3635

3736
def _get_paths(self, args):
3837
return (
39-
reverse("sentry-api-0-accept-organization-invite", args=args),
4038
reverse(
4139
"sentry-api-0-organization-accept-organization-invite",
4240
args=[self.organization.slug] + args,
4341
),
4442
reverse(
4543
"sentry-api-0-organization-accept-organization-invite",
46-
args=[self.organization.id] + args,
44+
args=[str(self.organization.id)] + args,
4745
),
4846
)
4947

5048
def _get_urls(self):
51-
return (
52-
"sentry-api-0-accept-organization-invite",
53-
"sentry-api-0-organization-accept-organization-invite",
54-
)
49+
return ("sentry-api-0-organization-accept-organization-invite",)
5550

5651
def _get_path(self, url, args):
57-
if url == self._get_urls()[0]:
58-
return reverse(url, args=args)
5952
return reverse(url, args=[self.organization.slug] + args)
6053

6154
def _require_2fa_for_organization(self):
@@ -188,27 +181,6 @@ def test_multi_region_organizationmember_id(self) -> None:
188181
self._assert_pending_invite_details_in_session(om)
189182
assert self.client.session["invite_organization_id"] == self.organization.id
190183

191-
def test_multi_region_organizationmember_id__non_monolith(self) -> None:
192-
self._require_2fa_for_organization()
193-
assert not self.user.has_2fa()
194-
195-
self.login_as(self.user)
196-
197-
with assume_test_silo_mode_of(OrganizationMember), outbox_context(flush=False):
198-
om = OrganizationMember.objects.create(
199-
email="newuser@example.com", token="abc", organization_id=self.organization.id
200-
)
201-
with unguarded_write(using=router.db_for_write(OrganizationMemberMapping)):
202-
OrganizationMemberMapping.objects.create(
203-
organization_id=self.organization.id, organizationmember_id=om.id
204-
)
205-
206-
with override_settings(SILO_MODE=SiloMode.CONTROL, SENTRY_MONOLITH_REGION="something-else"):
207-
resp = self.client.get(
208-
reverse("sentry-api-0-accept-organization-invite", args=[om.id, om.token])
209-
)
210-
assert resp.status_code == 400
211-
212184
def test_user_has_2fa(self) -> None:
213185
self._require_2fa_for_organization()
214186
self._enroll_user_in_2fa(self.user)

0 commit comments

Comments
 (0)