Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions src/sentry/web/frontend/accept_organization_invite_redirect.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
from __future__ import annotations

from django.http import HttpRequest, HttpResponse, HttpResponseRedirect
from django.urls import reverse

from sentry.api.endpoints.accept_organization_invite import get_invite_state
from sentry.api.invite_helper import ApiInviteHelper
from sentry.demo_mode.utils import is_demo_user
from sentry.utils.http import query_string
from sentry.web.frontend.react_page import GenericReactPageView


# TODO(cells): Temporary redirect to support previous invitations. Remove after May 8th
class AcceptOrganizationInviteRedirectView(GenericReactPageView):
auth_required = False

def handle(self, request: HttpRequest, **kwargs) -> HttpResponse:
member_id: str = kwargs["member_id"]
token: str = kwargs["token"]
if request.user.is_authenticated and not is_demo_user(request.user):
user_id: int | None = request.user.id
else:
user_id = None

invite_context = get_invite_state(
member_id=int(member_id),
organization_id_or_slug=None,
user_id=user_id,
request=request,
)
Comment thread
cursor[bot] marked this conversation as resolved.
if invite_context is None:
return self.handle_react(request, **kwargs)

helper = ApiInviteHelper(request=request, token=token, invite_context=invite_context)
if not helper.valid_token:
return self.handle_react(request, **kwargs)

redirect_url = reverse(
"sentry-organization-accept-invite",
kwargs={
"organization_slug": invite_context.organization.slug,
"member_id": member_id,
"token": token,
},
)
Comment thread
sentry-warden[bot] marked this conversation as resolved.
return HttpResponseRedirect(f"{redirect_url}{query_string(request)}")
5 changes: 4 additions & 1 deletion src/sentry/web/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@
from sentry.users.web.user_avatar import UserAvatarPhotoView
from sentry.web import api
from sentry.web.frontend import csrf_failure, generic
from sentry.web.frontend.accept_organization_invite_redirect import (
AcceptOrganizationInviteRedirectView,
)
from sentry.web.frontend.auth_channel_login import AuthChannelLoginView
from sentry.web.frontend.auth_close import AuthCloseView
from sentry.web.frontend.auth_login import AuthLoginView
Expand Down Expand Up @@ -554,7 +557,7 @@
),
re_path(
r"^accept/(?P<member_id>\d+)/(?P<token>\w+)/$",
GenericReactPageView.as_view(auth_required=False),
AcceptOrganizationInviteRedirectView.as_view(),
name="sentry-accept-invite",
),
re_path(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from django.urls import reverse

from sentry.testutils.cases import TestCase
from sentry.testutils.silo import control_silo_test


@control_silo_test
class AcceptOrganizationInviteRedirectViewTest(TestCase):
def test_redirects_legacy_invite_to_org_scoped_route(self) -> None:
organization = self.create_organization()
member = self.create_member(
organization=organization, email="newuser@example.com", token="abc"
)

response = self.client.get(
reverse("sentry-accept-invite", args=[member.id, member.token]) + "?referrer=email"
)

assert response.status_code == 302
assert response["Location"] == (
reverse(
"sentry-organization-accept-invite",
kwargs={
"organization_slug": organization.slug,
"member_id": member.id,
"token": member.token,
},
)
+ "?referrer=email"
)

def test_invalid_token_does_not_leak_org_slug(self) -> None:
organization = self.create_organization()
member = self.create_member(organization=organization, email="newuser@example.com")

response = self.client.get(
reverse("sentry-accept-invite", args=[member.id, "invalidtoken"])
)

assert response.status_code == 200
self.assertTemplateUsed(response, "sentry/base-react.html")

def test_unresolved_legacy_invite_falls_back_to_react_page(self) -> None:
response = self.client.get(reverse("sentry-accept-invite", args=[123456, "invalidtoken"]))

assert response.status_code == 200
self.assertTemplateUsed(response, "sentry/base-react.html")
Loading