Skip to content

ref(browser reporting): Use option to control Reporting-Endpoints header #93560

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions src/sentry/middleware/reporting_endpoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
from django.http.request import HttpRequest
from django.http.response import HttpResponseBase

from sentry import options


class ReportingEndpointMiddleware:
"""
Expand All @@ -19,10 +21,14 @@ 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
# 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
response["Reporting-Endpoints"] = (
"default=https://sentry.my.sentry.io/api/0/reporting-api-experiment/"
Expand Down
9 changes: 9 additions & 0 deletions src/sentry/options/defaults.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
89 changes: 33 additions & 56 deletions tests/sentry/middleware/test_reporting_endpoint.py
Original file line number Diff line number Diff line change
@@ -1,68 +1,45 @@
from unittest.mock import Mock
from unittest.mock import MagicMock, patch

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):
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

@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
Loading