Skip to content

Commit

Permalink
fix(hybrid-cloud): Redirect to org restoration page for customer doma…
Browse files Browse the repository at this point in the history
…ins (#45159)
  • Loading branch information
dashed authored Feb 28, 2023
1 parent a345807 commit e242496
Show file tree
Hide file tree
Showing 4 changed files with 159 additions and 18 deletions.
52 changes: 34 additions & 18 deletions src/sentry/web/frontend/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,11 +18,12 @@
from rest_framework.request import Request
from rest_framework.response import Response

from sentry import options
from sentry.api.serializers import serialize
from sentry.api.utils import generate_organization_url, is_member_disabled_from_limit
from sentry.auth import access
from sentry.auth.superuser import is_active_superuser
from sentry.models import Organization, Project, ProjectStatus, Team, TeamStatus
from sentry.models import Organization, OrganizationStatus, Project, ProjectStatus, Team, TeamStatus
from sentry.models.avatars.base import AvatarBase
from sentry.models.user import User
from sentry.services.hybrid_cloud.organization import (
Expand Down Expand Up @@ -215,28 +216,28 @@ def redirect_to_org(self: _HasRespond, request: Request) -> HttpResponse:
elif not features.has("organizations:create"):
return self.respond("sentry/no-organization-access.html", status=403)
else:
org_exists = False
url = "/organizations/new/"
url = reverse("sentry-organization-create")
if using_customer_domain:
url = absolute_uri(url)

if using_customer_domain and request.user and request.user.is_authenticated:
organizations = organization_service.get_organizations(
user_id=request.user.id, scope=None, only_visible=True
)
requesting_org_slug = request.subdomain
org_exists = (
organization_service.check_organization_by_slug(
slug=requesting_org_slug, only_visible=True
)
is not None
org_context = organization_service.get_organization_by_slug(
slug=requesting_org_slug, only_visible=False, user_id=request.user.id
)
if org_exists and organizations:
# If the user is a superuser, redirect them to the org's landing page (e.g. issues page)
if request.user.is_superuser:
url = Organization.get_url(requesting_org_slug)
if org_context and org_context.organization:
if org_context.organization.status == OrganizationStatus.PENDING_DELETION:
url = reverse("sentry-customer-domain-restore-organization")
elif org_context.organization.status == OrganizationStatus.DELETION_IN_PROGRESS:
url_prefix = options.get("system.url-prefix")
url = reverse("sentry-organization-create")
return HttpResponseRedirect(absolute_uri(url, url_prefix=url_prefix))
else:
url = reverse("sentry-auth-organization", args=[requesting_org_slug])
# If the user is a superuser, redirect them to the org's landing page (e.g. issues page)
if request.user.is_superuser:
url = Organization.get_url(requesting_org_slug)
else:
url = reverse("sentry-auth-organization", args=[requesting_org_slug])
url_prefix = generate_organization_url(requesting_org_slug)
url = absolute_uri(url, url_prefix=url_prefix)

Expand Down Expand Up @@ -487,8 +488,8 @@ def is_auth_required(

return False

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

else:
if is_using_customer_domain(request):
# In the customer domain world, if an organziation is pending deletion, we redirect the user to the
# organization restoration page.
org_context = organization_service.get_organization_by_slug(
slug=request.subdomain, only_visible=False, user_id=request.user.id
)
if org_context and org_context.member:
if org_context.organization.status == OrganizationStatus.PENDING_DELETION:
url_base = generate_organization_url(org_context.organization.slug)
restore_org_path = reverse("sentry-customer-domain-restore-organization")
return self.redirect(f"{url_base}{restore_org_path}")
elif org_context.organization.status == OrganizationStatus.DELETION_IN_PROGRESS:
url_base = options.get("system.url-prefix")
create_org_path = reverse("sentry-organization-create")
return self.redirect(f"{url_base}{create_org_path}")
redirect_uri = self.get_no_permission_url(request, *args, **kwargs)
return self.redirect(redirect_uri)

Expand Down
19 changes: 19 additions & 0 deletions tests/sentry/web/frontend/test_auth_login.py
Original file line number Diff line number Diff line change
Expand Up @@ -610,6 +610,25 @@ def test_login_valid_credentials_orgless(self):
follow=True,
)

assert resp.status_code == 200
assert resp.redirect_chain == [
("http://albertos-apples.testserver/auth/login/", 302),
("http://albertos-apples.testserver/auth/login/albertos-apples/", 302),
]

def test_login_valid_credentials_org_does_not_exist(self):
user = self.create_user()
with override_settings(MIDDLEWARE=tuple(provision_middleware())):
# load it once for test cookie
self.client.get(self.path)

resp = self.client.post(
self.path,
{"username": user.username, "password": "admin", "op": "login"},
SERVER_NAME="albertos-apples.testserver",
follow=True,
)

assert resp.status_code == 200
assert resp.redirect_chain == [
("http://albertos-apples.testserver/auth/login/", 302),
Expand Down
54 changes: 54 additions & 0 deletions tests/sentry/web/frontend/test_home.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from django.urls import reverse

from sentry.models import OrganizationStatus
from sentry.testutils import TestCase
from sentry.utils import json
from sentry.utils.client_state import get_client_state_key, get_redis_client
Expand Down Expand Up @@ -51,3 +52,56 @@ def test_redirect_to_onboarding(self):
get_redis_client().set(key, json.dumps({"state": "started", "url": "select-platform/"}))
resp = self.client.get(self.path)
self.assertRedirects(resp, f"/onboarding/{org.slug}/select-platform/")

def test_customer_domain(self):
org = self.create_organization(owner=self.user)

self.login_as(self.user)

with self.feature({"organizations:customer-domains": [org.slug]}):
response = self.client.get(
"/",
SERVER_NAME=f"{org.slug}.testserver",
follow=True,
)
assert response.status_code == 200
assert response.redirect_chain == [
(f"http://{org.slug}.testserver/issues/", 302),
]
assert self.client.session["activeorg"] == org.slug

def test_customer_domain_org_pending_deletion(self):
org = self.create_organization(owner=self.user, status=OrganizationStatus.PENDING_DELETION)

self.login_as(self.user)

with self.feature({"organizations:customer-domains": [org.slug]}):
response = self.client.get(
"/",
SERVER_NAME=f"{org.slug}.testserver",
follow=True,
)
assert response.status_code == 200
assert response.redirect_chain == [
(f"http://{org.slug}.testserver/restore/", 302),
]
assert "activeorg" not in self.client.session

def test_customer_domain_org_deletion_in_progress(self):
org = self.create_organization(
owner=self.user, status=OrganizationStatus.DELETION_IN_PROGRESS
)

self.login_as(self.user)

with self.feature({"organizations:customer-domains": [org.slug]}):
response = self.client.get(
"/",
SERVER_NAME=f"{org.slug}.testserver",
follow=True,
)
assert response.status_code == 200
assert response.redirect_chain == [
("http://testserver/organizations/new/", 302),
]
assert "activeorg" not in self.client.session
52 changes: 52 additions & 0 deletions tests/sentry/web/frontend/test_react_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from django.urls import URLResolver, get_resolver, reverse

from sentry.models import OrganizationStatus
from sentry.testutils import TestCase
from sentry.web.frontend.react_page import NON_CUSTOMER_DOMAIN_URL_NAMES, ReactMixin

Expand Down Expand Up @@ -288,3 +289,54 @@ def test_customer_domain_superuser(self):
assert response.redirect_chain == [
(f"http://{other_org.slug}.testserver/issues/", 302),
]

def test_customer_domain_loads(self):
org = self.create_organization(owner=self.user, status=OrganizationStatus.ACTIVE)

self.login_as(self.user)

with self.feature({"organizations:customer-domains": [org.slug]}):
response = self.client.get(
"/issues/",
SERVER_NAME=f"{org.slug}.testserver",
)
assert response.status_code == 200
self.assertTemplateUsed(response, "sentry/base-react.html")
assert response.context["request"]
assert self.client.session["activeorg"] == org.slug

def test_customer_domain_org_pending_deletion(self):
org = self.create_organization(owner=self.user, status=OrganizationStatus.PENDING_DELETION)

self.login_as(self.user)

with self.feature({"organizations:customer-domains": [org.slug]}):
response = self.client.get(
"/issues/",
SERVER_NAME=f"{org.slug}.testserver",
follow=True,
)
assert response.status_code == 200
assert response.redirect_chain == [
(f"http://{org.slug}.testserver/restore/", 302),
]
assert "activeorg" not in self.client.session

def test_customer_domain_org_deletion_in_progress(self):
org = self.create_organization(
owner=self.user, status=OrganizationStatus.DELETION_IN_PROGRESS
)

self.login_as(self.user)

with self.feature({"organizations:customer-domains": [org.slug]}):
response = self.client.get(
"/issues/",
SERVER_NAME=f"{org.slug}.testserver",
follow=True,
)
assert response.status_code == 200
assert response.redirect_chain == [
("http://testserver/organizations/new/", 302),
]
assert "activeorg" not in self.client.session

0 comments on commit e242496

Please sign in to comment.