From a11fe5acc67011a8f17350e6a280d4792a8890be Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Fri, 13 Jun 2025 15:36:41 -0700 Subject: [PATCH 1/5] add option --- src/sentry/options/defaults.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/sentry/options/defaults.py b/src/sentry/options/defaults.py index 8a788786ab669f..08bcca2c4aec20 100644 --- a/src/sentry/options/defaults.py +++ b/src/sentry/options/defaults.py @@ -3458,6 +3458,15 @@ flags=FLAG_ALLOW_EMPTY | FLAG_AUTOMATOR_MODIFIABLE, ) +# Enable adding the `Reporting-Endpoints` header, which will in turn enable the sending of Reporting +# API reports from the browser (as long as it's Chrome). +register( + "issues.browser_reporting.reporting_endpoints_header_enabled", + type=Bool, + default=False, + flags=FLAG_AUTOMATOR_MODIFIABLE, +) + # Enable the collection of Reporting API reports via the `/api/0/reporting-api-experiment/` # endpoint. When this is false, the endpoint will just 404. register( From 00359340d0dc01b18648e5c681834139f353318d Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Fri, 13 Jun 2025 15:36:44 -0700 Subject: [PATCH 2/5] switch check to use option --- src/sentry/middleware/reporting_endpoint.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sentry/middleware/reporting_endpoint.py b/src/sentry/middleware/reporting_endpoint.py index 7db0f6704e4e63..98f1c0534ac4ab 100644 --- a/src/sentry/middleware/reporting_endpoint.py +++ b/src/sentry/middleware/reporting_endpoint.py @@ -3,6 +3,8 @@ from django.http.request import HttpRequest from django.http.response import HttpResponseBase +from sentry import options + class ReportingEndpointMiddleware: """ @@ -19,10 +21,8 @@ def __call__(self, request: HttpRequest) -> HttpResponseBase: def process_response( self, request: HttpRequest, response: HttpResponseBase ) -> HttpResponseBase: - # Check if the request has staff attribute and if staff is active - staff = getattr(request, "staff", None) - if staff and staff.is_active: - # This will enable crashes, intervention and deprecation warnings + if options.get("issues.browser_reporting.reporting_endpoints_header_enabled"): + # This will enable crashes and intervention and deprecation reports # They always report to the default endpoint response["Reporting-Endpoints"] = ( "default=https://sentry.my.sentry.io/api/0/reporting-api-experiment/" From 0a8139494a718adf7e6151984ebaa6d5a673179a Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Fri, 13 Jun 2025 15:36:46 -0700 Subject: [PATCH 3/5] bail early for replay endpoints --- src/sentry/middleware/reporting_endpoint.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/sentry/middleware/reporting_endpoint.py b/src/sentry/middleware/reporting_endpoint.py index 98f1c0534ac4ab..38d7115877213d 100644 --- a/src/sentry/middleware/reporting_endpoint.py +++ b/src/sentry/middleware/reporting_endpoint.py @@ -21,6 +21,12 @@ def __call__(self, request: HttpRequest) -> HttpResponseBase: def process_response( self, request: HttpRequest, response: HttpResponseBase ) -> HttpResponseBase: + # There are some Relay endpoints which need to be able to run without touching the database + # (so the `options` check below is no good), and besides, Relay has no use for a + # browser-specific header, so we need to (but also can) bail early. + if "api/0/relays" in request.path: + return response + if options.get("issues.browser_reporting.reporting_endpoints_header_enabled"): # This will enable crashes and intervention and deprecation reports # They always report to the default endpoint From 591726f849e4cfd7118bf2adb6ca99d373cf528a Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Fri, 13 Jun 2025 15:36:48 -0700 Subject: [PATCH 4/5] fix tests --- .../middleware/test_reporting_endpoint.py | 78 +++++-------------- 1 file changed, 21 insertions(+), 57 deletions(-) diff --git a/tests/sentry/middleware/test_reporting_endpoint.py b/tests/sentry/middleware/test_reporting_endpoint.py index 7ff5a67e99da73..871ac74d721b8e 100644 --- a/tests/sentry/middleware/test_reporting_endpoint.py +++ b/tests/sentry/middleware/test_reporting_endpoint.py @@ -1,10 +1,9 @@ -from unittest.mock import Mock - -from django.http import HttpResponse, HttpResponseBase +from django.http import HttpResponse from django.test import RequestFactory from sentry.middleware.reporting_endpoint import ReportingEndpointMiddleware from sentry.testutils.cases import TestCase +from sentry.testutils.helpers.options import override_options class ReportingEndpointMiddlewareTestCase(TestCase): @@ -12,57 +11,22 @@ def setUp(self) -> None: self.middleware = ReportingEndpointMiddleware(lambda request: HttpResponse()) self.factory = RequestFactory() - def _no_header_set(self, result: HttpResponseBase) -> None: - assert "Reporting-Endpoints" not in result - - def test_adds_header_for_staff_user(self) -> None: - """Test that the ReportingEndpoint header is added when user is Sentry staff.""" - request = self.factory.get("/") - - # Mock staff object with is_active = True - staff_mock = Mock() - staff_mock.is_active = True - setattr(request, "staff", staff_mock) - - response = HttpResponse() - result = self.middleware.process_response(request, response) - - assert "Reporting-Endpoints" in result - assert ( - result["Reporting-Endpoints"] - == "default=https://sentry.my.sentry.io/api/0/reporting-api-experiment/" - ) - - def test_no_header_for_non_staff_user(self) -> None: - """Test that the ReportingEndpoint header is not added when user is not Sentry staff.""" - request = self.factory.get("/") - - # Mock staff object with is_active = False - staff_mock = Mock() - staff_mock.is_active = False - setattr(request, "staff", staff_mock) - - response = HttpResponse() - result = self.middleware.process_response(request, response) - - self._no_header_set(result) - - def test_no_header_when_no_staff_attribute(self) -> None: - """Test that the ReportingEndpoint header is not added when request has no staff attribute.""" - request = self.factory.get("/") - - # No staff attribute on request - response = HttpResponse() - result = self.middleware.process_response(request, response) - - self._no_header_set(result) - - def test_no_header_when_staff_is_none(self) -> None: - """Test that the ReportingEndpoint header is not added when staff is None.""" - request = self.factory.get("/") - setattr(request, "staff", None) - - response = HttpResponse() - result = self.middleware.process_response(request, response) - - self._no_header_set(result) + def test_obeys_option(self) -> None: + with override_options( + {"issues.browser_reporting.reporting_endpoints_header_enabled": True} + ): + request = self.factory.get("/") + response = self.middleware.process_response(request, HttpResponse()) + + assert ( + response.get("Reporting-Endpoints") + == "default=https://sentry.my.sentry.io/api/0/reporting-api-experiment/" + ) + + with override_options( + {"issues.browser_reporting.reporting_endpoints_header_enabled": False} + ): + request = self.factory.get("/") + response = self.middleware.process_response(request, HttpResponse()) + + assert response.get("Reporting-Endpoints") is None From c8b92a1f723d9bfea992db47de51b3655eebb14c Mon Sep 17 00:00:00 2001 From: Katie Byers Date: Fri, 13 Jun 2025 15:36:50 -0700 Subject: [PATCH 5/5] add test for relay endpoints --- tests/sentry/middleware/test_reporting_endpoint.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/tests/sentry/middleware/test_reporting_endpoint.py b/tests/sentry/middleware/test_reporting_endpoint.py index 871ac74d721b8e..b4d8e05e54a1ce 100644 --- a/tests/sentry/middleware/test_reporting_endpoint.py +++ b/tests/sentry/middleware/test_reporting_endpoint.py @@ -1,3 +1,5 @@ +from unittest.mock import MagicMock, patch + from django.http import HttpResponse from django.test import RequestFactory @@ -30,3 +32,14 @@ def test_obeys_option(self) -> None: response = self.middleware.process_response(request, HttpResponse()) assert response.get("Reporting-Endpoints") is None + + @patch("src.sentry.middleware.reporting_endpoint.options.get") + def test_no_options_check_in_relay_endpoints(self, mock_options_get: MagicMock) -> None: + with override_options( + {"issues.browser_reporting.reporting_endpoints_header_enabled": True} + ): + request = self.factory.get("/api/0/relays/register/challenge/") + response = self.middleware.process_response(request, HttpResponse()) + + mock_options_get.assert_not_called() + assert response.get("Reporting-Endpoints") is None