Skip to content

Commit 290fd0d

Browse files
committed
fix(hybrid-cloud): Redirect to org restoration page for customer domains
1 parent 8e1ef19 commit 290fd0d

File tree

4 files changed

+159
-18
lines changed

4 files changed

+159
-18
lines changed

src/sentry/web/frontend/base.py

Lines changed: 34 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,12 @@
1818
from rest_framework.request import Request
1919
from rest_framework.response import Response
2020

21+
from sentry import options
2122
from sentry.api.serializers import serialize
2223
from sentry.api.utils import generate_organization_url, is_member_disabled_from_limit
2324
from sentry.auth import access
2425
from sentry.auth.superuser import is_active_superuser
25-
from sentry.models import Organization, Project, ProjectStatus, Team, TeamStatus
26+
from sentry.models import Organization, OrganizationStatus, Project, ProjectStatus, Team, TeamStatus
2627
from sentry.models.avatars.base import AvatarBase
2728
from sentry.models.user import User
2829
from sentry.services.hybrid_cloud.organization import (
@@ -215,28 +216,28 @@ def redirect_to_org(self: _HasRespond, request: Request) -> HttpResponse:
215216
elif not features.has("organizations:create"):
216217
return self.respond("sentry/no-organization-access.html", status=403)
217218
else:
218-
org_exists = False
219-
url = "/organizations/new/"
219+
url = reverse("sentry-organization-create")
220220
if using_customer_domain:
221221
url = absolute_uri(url)
222222

223223
if using_customer_domain and request.user and request.user.is_authenticated:
224-
organizations = organization_service.get_organizations(
225-
user_id=request.user.id, scope=None, only_visible=True
226-
)
227224
requesting_org_slug = request.subdomain
228-
org_exists = (
229-
organization_service.check_organization_by_slug(
230-
slug=requesting_org_slug, only_visible=True
231-
)
232-
is not None
225+
org_context = organization_service.get_organization_by_slug(
226+
slug=requesting_org_slug, only_visible=False, user_id=request.user.id
233227
)
234-
if org_exists and organizations:
235-
# If the user is a superuser, redirect them to the org's landing page (e.g. issues page)
236-
if request.user.is_superuser:
237-
url = Organization.get_url(requesting_org_slug)
228+
if org_context and org_context.organization:
229+
if org_context.organization.status == OrganizationStatus.PENDING_DELETION:
230+
url = reverse("sentry-customer-domain-restore-organization")
231+
elif org_context.organization.status == OrganizationStatus.DELETION_IN_PROGRESS:
232+
url_prefix = options.get("system.url-prefix")
233+
url = reverse("sentry-organization-create")
234+
return HttpResponseRedirect(absolute_uri(url, url_prefix=url_prefix))
238235
else:
239-
url = reverse("sentry-auth-organization", args=[requesting_org_slug])
236+
# If the user is a superuser, redirect them to the org's landing page (e.g. issues page)
237+
if request.user.is_superuser:
238+
url = Organization.get_url(requesting_org_slug)
239+
else:
240+
url = reverse("sentry-auth-organization", args=[requesting_org_slug])
240241
url_prefix = generate_organization_url(requesting_org_slug)
241242
url = absolute_uri(url, url_prefix=url_prefix)
242243

@@ -487,8 +488,8 @@ def is_auth_required(
487488

488489
return False
489490

490-
def handle_permission_required(self, request: Request, organization: Organization | RpcOrganization, *args: Any, **kwargs: Any) -> HttpResponse: # type: ignore[override]
491-
if self.needs_sso(request, organization):
491+
def handle_permission_required(self, request: Request, organization: Organization | RpcOrganization | None, *args: Any, **kwargs: Any) -> HttpResponse: # type: ignore[override]
492+
if organization and self.needs_sso(request, organization):
492493
logger.info(
493494
"access.must-sso",
494495
extra={"organization_id": organization.id, "user_id": request.user.id},
@@ -506,6 +507,21 @@ def handle_permission_required(self, request: Request, organization: Organizatio
506507
redirect_uri = make_login_link_with_redirect(path, after_login_redirect)
507508

508509
else:
510+
if is_using_customer_domain(request):
511+
# In the customer domain world, if an organziation is pending deletion, we redirect the user to the
512+
# organization restoration page.
513+
org_context = organization_service.get_organization_by_slug(
514+
slug=request.subdomain, only_visible=False, user_id=request.user.id
515+
)
516+
if org_context and org_context.member:
517+
if org_context.organization.status == OrganizationStatus.PENDING_DELETION:
518+
url_base = generate_organization_url(org_context.organization.slug)
519+
restore_org_path = reverse("sentry-customer-domain-restore-organization")
520+
return self.redirect(f"{url_base}{restore_org_path}")
521+
elif org_context.organization.status == OrganizationStatus.DELETION_IN_PROGRESS:
522+
url_base = options.get("system.url-prefix")
523+
create_org_path = reverse("sentry-organization-create")
524+
return self.redirect(f"{url_base}{create_org_path}")
509525
redirect_uri = self.get_no_permission_url(request, *args, **kwargs)
510526
return self.redirect(redirect_uri)
511527

tests/sentry/web/frontend/test_auth_login.py

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -610,6 +610,25 @@ def test_login_valid_credentials_orgless(self):
610610
follow=True,
611611
)
612612

613+
assert resp.status_code == 200
614+
assert resp.redirect_chain == [
615+
("http://albertos-apples.testserver/auth/login/", 302),
616+
("http://albertos-apples.testserver/auth/login/albertos-apples/", 302),
617+
]
618+
619+
def test_login_valid_credentials_org_does_not_exist(self):
620+
user = self.create_user()
621+
with override_settings(MIDDLEWARE=tuple(provision_middleware())):
622+
# load it once for test cookie
623+
self.client.get(self.path)
624+
625+
resp = self.client.post(
626+
self.path,
627+
{"username": user.username, "password": "admin", "op": "login"},
628+
SERVER_NAME="albertos-apples.testserver",
629+
follow=True,
630+
)
631+
613632
assert resp.status_code == 200
614633
assert resp.redirect_chain == [
615634
("http://albertos-apples.testserver/auth/login/", 302),

tests/sentry/web/frontend/test_home.py

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from django.urls import reverse
44

5+
from sentry.models import OrganizationStatus
56
from sentry.testutils import TestCase
67
from sentry.utils import json
78
from sentry.utils.client_state import get_client_state_key, get_redis_client
@@ -51,3 +52,56 @@ def test_redirect_to_onboarding(self):
5152
get_redis_client().set(key, json.dumps({"state": "started", "url": "select-platform/"}))
5253
resp = self.client.get(self.path)
5354
self.assertRedirects(resp, f"/onboarding/{org.slug}/select-platform/")
55+
56+
def test_customer_domain(self):
57+
org = self.create_organization(owner=self.user)
58+
59+
self.login_as(self.user)
60+
61+
with self.feature({"organizations:customer-domains": [org.slug]}):
62+
response = self.client.get(
63+
"/",
64+
SERVER_NAME=f"{org.slug}.testserver",
65+
follow=True,
66+
)
67+
assert response.status_code == 200
68+
assert response.redirect_chain == [
69+
(f"http://{org.slug}.testserver/issues/", 302),
70+
]
71+
assert self.client.session["activeorg"] == org.slug
72+
73+
def test_customer_domain_org_pending_deletion(self):
74+
org = self.create_organization(owner=self.user, status=OrganizationStatus.PENDING_DELETION)
75+
76+
self.login_as(self.user)
77+
78+
with self.feature({"organizations:customer-domains": [org.slug]}):
79+
response = self.client.get(
80+
"/",
81+
SERVER_NAME=f"{org.slug}.testserver",
82+
follow=True,
83+
)
84+
assert response.status_code == 200
85+
assert response.redirect_chain == [
86+
(f"http://{org.slug}.testserver/restore/", 302),
87+
]
88+
assert "activeorg" not in self.client.session
89+
90+
def test_customer_domain_org_deletion_in_progress(self):
91+
org = self.create_organization(
92+
owner=self.user, status=OrganizationStatus.DELETION_IN_PROGRESS
93+
)
94+
95+
self.login_as(self.user)
96+
97+
with self.feature({"organizations:customer-domains": [org.slug]}):
98+
response = self.client.get(
99+
"/",
100+
SERVER_NAME=f"{org.slug}.testserver",
101+
follow=True,
102+
)
103+
assert response.status_code == 200
104+
assert response.redirect_chain == [
105+
("http://testserver/organizations/new/", 302),
106+
]
107+
assert "activeorg" not in self.client.session

tests/sentry/web/frontend/test_react_page.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from django.urls import URLResolver, get_resolver, reverse
44

5+
from sentry.models import OrganizationStatus
56
from sentry.testutils import TestCase
67
from sentry.web.frontend.react_page import NON_CUSTOMER_DOMAIN_URL_NAMES, ReactMixin
78

@@ -288,3 +289,54 @@ def test_customer_domain_superuser(self):
288289
assert response.redirect_chain == [
289290
(f"http://{other_org.slug}.testserver/issues/", 302),
290291
]
292+
293+
def test_customer_domain_loads(self):
294+
org = self.create_organization(owner=self.user, status=OrganizationStatus.ACTIVE)
295+
296+
self.login_as(self.user)
297+
298+
with self.feature({"organizations:customer-domains": [org.slug]}):
299+
response = self.client.get(
300+
"/issues/",
301+
SERVER_NAME=f"{org.slug}.testserver",
302+
)
303+
assert response.status_code == 200
304+
self.assertTemplateUsed(response, "sentry/base-react.html")
305+
assert response.context["request"]
306+
assert self.client.session["activeorg"] == org.slug
307+
308+
def test_customer_domain_org_pending_deletion(self):
309+
org = self.create_organization(owner=self.user, status=OrganizationStatus.PENDING_DELETION)
310+
311+
self.login_as(self.user)
312+
313+
with self.feature({"organizations:customer-domains": [org.slug]}):
314+
response = self.client.get(
315+
"/issues/",
316+
SERVER_NAME=f"{org.slug}.testserver",
317+
follow=True,
318+
)
319+
assert response.status_code == 200
320+
assert response.redirect_chain == [
321+
(f"http://{org.slug}.testserver/restore/", 302),
322+
]
323+
assert "activeorg" not in self.client.session
324+
325+
def test_customer_domain_org_deletion_in_progress(self):
326+
org = self.create_organization(
327+
owner=self.user, status=OrganizationStatus.DELETION_IN_PROGRESS
328+
)
329+
330+
self.login_as(self.user)
331+
332+
with self.feature({"organizations:customer-domains": [org.slug]}):
333+
response = self.client.get(
334+
"/issues/",
335+
SERVER_NAME=f"{org.slug}.testserver",
336+
follow=True,
337+
)
338+
assert response.status_code == 200
339+
assert response.redirect_chain == [
340+
("http://testserver/organizations/new/", 302),
341+
]
342+
assert "activeorg" not in self.client.session

0 commit comments

Comments
 (0)