Skip to content

Commit 754785e

Browse files
authored
ref(cells): Implement org member invite redirect from old path to new (#112513)
We have to support the old invite urls for up to INVITE_DAYS_VALID (30 days) however putting this web redirect in place now allows us to clean up the rest of the frontend and api logic and other settings (such as `is_historical_monolith_region` handling) that was all connected to the legacy route.
1 parent d20aa17 commit 754785e

File tree

3 files changed

+97
-1
lines changed

3 files changed

+97
-1
lines changed
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from __future__ import annotations
2+
3+
from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
4+
from django.urls import reverse
5+
6+
from sentry.api.endpoints.accept_organization_invite import get_invite_state
7+
from sentry.api.invite_helper import ApiInviteHelper
8+
from sentry.demo_mode.utils import is_demo_user
9+
from sentry.utils.http import query_string
10+
from sentry.web.frontend.react_page import GenericReactPageView
11+
12+
13+
# TODO(cells): Temporary redirect to support previous invitations. Remove after May 8th
14+
class AcceptOrganizationInviteRedirectView(GenericReactPageView):
15+
auth_required = False
16+
17+
def handle(self, request: HttpRequest, **kwargs) -> HttpResponse:
18+
member_id: str = kwargs["member_id"]
19+
token: str = kwargs["token"]
20+
if request.user.is_authenticated and not is_demo_user(request.user):
21+
user_id: int | None = request.user.id
22+
else:
23+
user_id = None
24+
25+
invite_context = get_invite_state(
26+
member_id=int(member_id),
27+
organization_id_or_slug=None,
28+
user_id=user_id,
29+
request=request,
30+
)
31+
if invite_context is None:
32+
return self.handle_react(request, **kwargs)
33+
34+
helper = ApiInviteHelper(request=request, token=token, invite_context=invite_context)
35+
if not helper.valid_token:
36+
return self.handle_react(request, **kwargs)
37+
38+
redirect_url = reverse(
39+
"sentry-organization-accept-invite",
40+
kwargs={
41+
"organization_slug": invite_context.organization.slug,
42+
"member_id": member_id,
43+
"token": token,
44+
},
45+
)
46+
return HttpResponseRedirect(f"{redirect_url}{query_string(request)}")

src/sentry/web/urls.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@
2323
from sentry.users.web.user_avatar import UserAvatarPhotoView
2424
from sentry.web import api
2525
from sentry.web.frontend import csrf_failure, generic
26+
from sentry.web.frontend.accept_organization_invite_redirect import (
27+
AcceptOrganizationInviteRedirectView,
28+
)
2629
from sentry.web.frontend.auth_channel_login import AuthChannelLoginView
2730
from sentry.web.frontend.auth_close import AuthCloseView
2831
from sentry.web.frontend.auth_login import AuthLoginView
@@ -554,7 +557,7 @@
554557
),
555558
re_path(
556559
r"^accept/(?P<member_id>\d+)/(?P<token>\w+)/$",
557-
GenericReactPageView.as_view(auth_required=False),
560+
AcceptOrganizationInviteRedirectView.as_view(),
558561
name="sentry-accept-invite",
559562
),
560563
re_path(
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
from django.urls import reverse
2+
3+
from sentry.testutils.cases import TestCase
4+
from sentry.testutils.silo import control_silo_test
5+
6+
7+
@control_silo_test
8+
class AcceptOrganizationInviteRedirectViewTest(TestCase):
9+
def test_redirects_legacy_invite_to_org_scoped_route(self) -> None:
10+
organization = self.create_organization()
11+
member = self.create_member(
12+
organization=organization, email="newuser@example.com", token="abc"
13+
)
14+
15+
response = self.client.get(
16+
reverse("sentry-accept-invite", args=[member.id, member.token]) + "?referrer=email"
17+
)
18+
19+
assert response.status_code == 302
20+
assert response["Location"] == (
21+
reverse(
22+
"sentry-organization-accept-invite",
23+
kwargs={
24+
"organization_slug": organization.slug,
25+
"member_id": member.id,
26+
"token": member.token,
27+
},
28+
)
29+
+ "?referrer=email"
30+
)
31+
32+
def test_invalid_token_does_not_leak_org_slug(self) -> None:
33+
organization = self.create_organization()
34+
member = self.create_member(organization=organization, email="newuser@example.com")
35+
36+
response = self.client.get(
37+
reverse("sentry-accept-invite", args=[member.id, "invalidtoken"])
38+
)
39+
40+
assert response.status_code == 200
41+
self.assertTemplateUsed(response, "sentry/base-react.html")
42+
43+
def test_unresolved_legacy_invite_falls_back_to_react_page(self) -> None:
44+
response = self.client.get(reverse("sentry-accept-invite", args=[123456, "invalidtoken"]))
45+
46+
assert response.status_code == 200
47+
self.assertTemplateUsed(response, "sentry/base-react.html")

0 commit comments

Comments
 (0)