Browse Source

feat(hybrid-cloud): Implicitly set active organization based on subdomain (#37848)

Co-authored-by: Mark Story <mark@mark-story.com>
Alberto Leal 2 years ago
parent
commit
f0308966fd
2 changed files with 148 additions and 2 deletions
  1. 11 2
      src/sentry/web/frontend/base.py
  2. 137 0
      tests/sentry/web/frontend/test_auth_login.py

+ 11 - 2
src/sentry/web/frontend/base.py

@@ -14,6 +14,7 @@ from django.views.generic import View
 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 is_member_disabled_from_limit
 from sentry.auth import access
@@ -52,7 +53,6 @@ class OrganizationMixin:
         # TODO(dcramer): this is a huge hack, and we should refactor this
         # it is currently needed to handle the is_auth_required check on
         # OrganizationBase
-        organizations = None
         _active_org = getattr(self, "_active_org", None)
         if _active_org:
             (active_organization, requesting_user) = _active_org
@@ -70,6 +70,9 @@ class OrganizationMixin:
 
         if is_implicit:
             organization_slug = request.session.get("activeorg")
+            if request.subdomain is not None and request.subdomain != organization_slug:
+                # Customer domain is being used, set the subdomain as the requesting org slug.
+                organization_slug = request.subdomain
 
         if organization_slug is not None:
             if is_active_superuser(request):
@@ -82,6 +85,7 @@ class OrganizationMixin:
                 except Organization.DoesNotExist:
                     logger.info("Active organization [%s] not found", organization_slug)
 
+        organizations = None
         if active_organization is None:
             organizations = Organization.objects.get_for_user(user=request.user)
 
@@ -91,7 +95,9 @@ class OrganizationMixin:
             except StopIteration:
                 logger.info("Active organization [%s] not found in scope", organization_slug)
                 if is_implicit:
-                    del request.session["activeorg"]
+                    session = request.session
+                    if session and "activeorg" in session:
+                        del session["activeorg"]
                 active_organization = None
 
         if active_organization is None and organizations:
@@ -161,6 +167,9 @@ class OrganizationMixin:
             return self.respond("sentry/no-organization-access.html", status=403)
         else:
             url = "/organizations/new/"
+            if request.subdomain:
+                base = options.get("system.url-prefix")
+                url = f"{base}{url}"
         return HttpResponseRedirect(url)
 
 

+ 137 - 0
tests/sentry/web/frontend/test_auth_login.py

@@ -381,3 +381,140 @@ class AuthLoginNewsletterTest(TestCase):
         assert results[0].list_id == newsletter.get_default_list_id()
         assert results[0].subscribed
         assert not results[0].verified
+
+
+def provision_middleware():
+    # TODO: to be removed once CustomerDomainMiddleware is activated.
+    middleware = list(settings.MIDDLEWARE)
+    if "sentry.middleware.customer_domain.CustomerDomainMiddleware" not in middleware:
+        index = middleware.index("sentry.middleware.auth.AuthenticationMiddleware")
+        middleware.insert(index + 1, "sentry.middleware.customer_domain.CustomerDomainMiddleware")
+    return middleware
+
+
+class AuthLoginCustomerDomainTest(TestCase):
+    @fixture
+    def path(self):
+        return reverse("sentry-login")
+
+    def test_login_valid_credentials(self):
+        # load it once for test cookie
+        self.client.get(self.path)
+
+        resp = self.client.post(
+            self.path,
+            {"username": self.user.username, "password": "admin", "op": "login"},
+            HTTP_HOST="albertos-apples.testserver",
+            follow=True,
+        )
+        assert resp.status_code == 200
+        assert resp.redirect_chain == [
+            (reverse("sentry-login"), 302),
+            ("http://testserver/organizations/new/", 302),
+        ]
+
+    def test_login_valid_credentials_with_org(self):
+        self.create_organization(name="albertos-apples", owner=self.user)
+        # load it once for test cookie
+        self.client.get(self.path)
+
+        resp = self.client.post(
+            self.path,
+            {"username": self.user.username, "password": "admin", "op": "login"},
+            HTTP_HOST="albertos-apples.testserver",
+            follow=True,
+        )
+        assert resp.status_code == 200
+        assert resp.redirect_chain == [
+            (reverse("sentry-login"), 302),
+            ("/organizations/albertos-apples/issues/", 302),
+        ]
+
+    def test_login_valid_credentials_invalid_customer_domain(self):
+        self.create_organization(name="albertos-apples", owner=self.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": self.user.username, "password": "admin", "op": "login"},
+                # This should preferably be HTTP_HOST.
+                # Using SERVER_NAME until https://code.djangoproject.com/ticket/32106 is fixed.
+                SERVER_NAME="invalid.testserver",
+                follow=True,
+            )
+
+            assert resp.status_code == 200
+            assert resp.redirect_chain == [
+                (reverse("sentry-login"), 302),
+                ("http://albertos-apples.testserver/auth/login/", 302),
+                ("/organizations/albertos-apples/issues/", 302),
+            ]
+
+    def test_login_valid_credentials_non_staff(self):
+        org = self.create_organization(name="albertos-apples")
+        non_staff_user = self.create_user(is_staff=False)
+        self.create_member(organization=org, user=non_staff_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": non_staff_user.username, "password": "admin", "op": "login"},
+                # This should preferably be HTTP_HOST.
+                # Using SERVER_NAME until https://code.djangoproject.com/ticket/32106 is fixed.
+                SERVER_NAME="albertos-apples.testserver",
+                follow=True,
+            )
+            assert resp.status_code == 200
+            assert resp.redirect_chain == [
+                (reverse("sentry-login"), 302),
+                # Non-sentry staff should be kicked out of using customer domain
+                ("http://testserver/auth/login/", 302),
+                ("/organizations/albertos-apples/issues/", 302),
+            ]
+
+    def test_login_valid_credentials_not_a_member(self):
+        user = self.create_user()
+        self.create_organization(name="albertos-apples")
+        self.create_member(organization=self.organization, user=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"},
+                HTTP_HOST="albertos-apples.testserver",
+                follow=True,
+            )
+
+            assert resp.status_code == 200
+            assert resp.redirect_chain == [
+                (reverse("sentry-login"), 302),
+                (f"/organizations/{self.organization.slug}/issues/", 302),
+                ("/organizations/albertos-apples/issues/", 302),
+                ("/auth/login/albertos-apples/", 302),
+            ]
+
+    def test_login_valid_credentials_orgless(self):
+        user = self.create_user()
+        self.create_organization(name="albertos-apples")
+        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"},
+                HTTP_HOST="albertos-apples.testserver",
+                follow=True,
+            )
+
+            assert resp.status_code == 200
+            assert resp.redirect_chain == [
+                (reverse("sentry-login"), 302),
+                ("http://testserver/organizations/new/", 302),
+            ]