Browse Source

feat(data-secrecy): Render Data Secrecy Enabled Page for Superusers (#76939)

currently when data secrecy is enabled, and we try to do a superuser
into an org, we see
<img width="971" alt="image"
src="https://github.com/user-attachments/assets/b2475aec-956a-49d1-aa7f-6924f73dc8ce">

when you check the network tab, we see that we are getting some 500s as
well as some 401s. it is because the exception we raise in
`get_superuser_scopes` is not caught anywhere and keeps falling through.

For context: 
* 401: All API requests will return a 401 b/c the data secrecy error is
raised (it is captured and returned for the response)
* 500: For Django FE Views, the error is NOT handled, and it falls all
the way through, which ends up rendering the Interval Server Error. this
above also causes
[SENTRY-3EA4](https://sentry.sentry.io/issues/5792418520/). the stack
trace in that issue reflects the error falling all the way through.

With this PR, for FE Views, we will instead see
<img width="833" alt="image"
src="https://github.com/user-attachments/assets/b890d03a-7686-4b14-a3d3-264aa27bf858">
instead of the internal error, which makes it apparent why the superuser
can't access.

---------

Co-authored-by: Danny Lee <dlee@sentry.io>
Raj Joshi 6 months ago
parent
commit
db938681cf

+ 19 - 0
src/sentry/templates/sentry/data-secrecy.html

@@ -0,0 +1,19 @@
+{% extends "sentry/bases/modal.html" %}
+
+{% load i18n %}
+
+{% block title %}{% trans "Data Secrecy Enabled" %} | {{ block.super }}{% endblock %}
+
+{% block main %}
+  <section class="body">
+    <div class="alert alert-block alert-error">
+      <div style="font-size:24px;margin-bottom:10px">
+        <span class="icon-exclamation" style="font-size:20px;margin-right:10px;"></span>
+        <span>Data Secrecy Enabled</span>
+      </div>
+      <p>{% trans "This organization has Data Secrecy Enabled." %}</p>
+      <p>{% trans "An organization that has data secrecy enabled cannot be accessed via superuser." %}</p>
+      <p>{% trans "If you need to access the organization to help debug an issue, please contact the organization to grant a waiver under Organization Settings -> Security & Privacy -> Support Access" %}</p>
+    </div>
+  </section>
+{% endblock %}

+ 5 - 1
src/sentry/web/frontend/base.py

@@ -25,6 +25,7 @@ from django.views.generic import View
 from rest_framework.request import Request
 
 from sentry import options
+from sentry.api.exceptions import DataSecrecyError
 from sentry.api.utils import is_member_disabled_from_limit
 from sentry.auth import access
 from sentry.auth.superuser import is_active_superuser
@@ -380,7 +381,10 @@ class BaseView(View, OrganizationMixin):
 
         args, kwargs = self.convert_args(request, *args, **kwargs)
 
-        request.access = self.get_access(request, *args, **kwargs)
+        try:
+            request.access = self.get_access(request, *args, **kwargs)
+        except DataSecrecyError:
+            return render_to_response("sentry/data-secrecy.html", status=403, request=request)
 
         if not self.has_permission(request, *args, **kwargs):
             return self.handle_permission_required(request, *args, **kwargs)

+ 54 - 0
tests/sentry/web/frontend/test_data_secrecy_error.py

@@ -0,0 +1,54 @@
+from django.urls import reverse
+
+from sentry.silo.base import SiloMode
+from sentry.testutils.cases import TestCase
+from sentry.testutils.helpers.options import override_options
+from sentry.testutils.silo import assume_test_silo_mode, control_silo_test
+
+
+@control_silo_test
+class DataSecrecyErrorTest(TestCase):
+    def setUp(self):
+        super().setUp()
+        self.owner = self.create_user()
+        self.organization = self.create_organization(name="foo", owner=self.owner)
+        with assume_test_silo_mode(SiloMode.REGION):
+            self.organization.flags.prevent_superuser_access = True
+            self.organization.save()
+
+    def test_data_secrecy_renders_for_superuser_access(self):
+        user = self.create_user(is_superuser=True, is_staff=True)
+        self.create_identity_provider(type="dummy", external_id="1234")
+
+        self.login_as(user, organization_id=self.organization.id, superuser=True)
+
+        path = reverse("sentry-organization-issue-list", args=[self.organization.slug])
+        resp = self.client.get(path)
+        assert resp.status_code == 200
+        self.assertTemplateUsed("sentry/data-secrecy.html")
+
+    @override_options({"staff.ga-rollout": True})
+    def test_data_secrecy_does_not_render_for_staff_access(self):
+        user = self.create_user(is_superuser=True, is_staff=True)
+        self.create_identity_provider(type="dummy", external_id="1234")
+
+        self.login_as(user, organization_id=self.organization.id, staff=True)
+
+        path = reverse("sentry-organization-issue-list", args=[self.organization.slug])
+        resp = self.client.get(path)
+
+        assert resp.status_code == 200
+        self.assertTemplateNotUsed("sentry/data-secrecy.html")
+
+    def test_data_secrecy_does_not_render_for_regular_user(self):
+        user = self.create_user(is_superuser=False, is_staff=False)
+        self.create_member(user=user, organization=self.organization)
+        self.create_identity_provider(type="dummy", external_id="1234")
+
+        self.login_as(user, organization_id=self.organization.id)
+
+        path = reverse("sentry-organization-issue-list", args=[self.organization.slug])
+        resp = self.client.get(path)
+
+        assert resp.status_code == 200
+        self.assertTemplateNotUsed("sentry/data-secrecy.html")