Skip to content
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
37 changes: 36 additions & 1 deletion sentry_sdk/integrations/quart.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
from typing import TYPE_CHECKING

import sentry_sdk
from sentry_sdk.data_collection import _apply_data_collection_filtering_to_query_string
from sentry_sdk.integrations import DidNotEnable, Integration
from sentry_sdk.integrations._wsgi_common import _filter_headers
from sentry_sdk.integrations.asgi import SentryAsgiMiddleware
Expand All @@ -17,6 +18,8 @@
capture_internal_exceptions,
ensure_integration_enabled,
event_from_exception,
has_data_collection_enabled,
parse_url,
)

if TYPE_CHECKING:
Expand Down Expand Up @@ -203,7 +206,39 @@ async def _request_websocket_started(app: "Quart", **kwargs: "Any") -> None:

segment.set_attributes(header_attributes)

if should_send_default_pii():
client_options = sentry_sdk.get_client().options
filtered_query_string = None
if has_data_collection_enabled(client_options):
query_string = request_websocket.query_string.decode(
"utf-8", errors="replace"
)
if query_string:
filtered_query_string = (
_apply_data_collection_filtering_to_query_string(
query_string=query_string,
behaviour=client_options["data_collection"][
"url_query_params"
],
)
)
if filtered_query_string:
segment.set_attribute(
"url.query",
filtered_query_string,
)

parsed_url = parse_url(request_websocket.url)
segment.set_attribute(
"url.full",
f"{parsed_url.url}?{filtered_query_string}"
if filtered_query_string
else parsed_url.url,
)

# TODO: Add the user properties that are seen in the branch below here once
# code is added to respect the `user_info` settings within the data collection
# configuration
elif should_send_default_pii():
segment.set_attribute("url.full", request_websocket.url)
segment.set_attribute(
"url.query",
Expand Down
152 changes: 152 additions & 0 deletions tests/integrations/quart/test_quart.py
Original file line number Diff line number Diff line change
Expand Up @@ -1131,3 +1131,155 @@ async def test_span_streaming_sensitive_header_passthrough_with_pii_and_no_data_
segment["attributes"]["http.request.header.authorization"]
== "Bearer secret-token"
)


NO_QUERY_STRING = object()
_QUERY_PARAM_DATA_COLLECTION_CASES = [
pytest.param(
{"send_default_pii": True},
"toy=tennisball&color=red&auth=secret",
id="send_default_pii_true",
),
pytest.param(
{"send_default_pii": False},
NO_QUERY_STRING,
id="send_default_pii_false",
),
pytest.param(
{},
NO_QUERY_STRING,
id="defaults",
),
pytest.param(
{"_experiments": {"data_collection": {}}},
"toy=tennisball&color=red&auth=[Filtered]",
id="data_collection_denylist_default",
),
pytest.param(
{
"_experiments": {
"data_collection": {
"url_query_params": {"mode": "denylist", "terms": ["toy"]}
}
}
},
"toy=[Filtered]&color=red&auth=[Filtered]",
id="data_collection_denylist_custom_terms",
),
pytest.param(
{
"_experiments": {
"data_collection": {
"url_query_params": {"mode": "allowlist", "terms": ["toy"]}
}
}
},
"toy=tennisball&color=[Filtered]&auth=[Filtered]",
id="data_collection_allowlist",
),
pytest.param(
{
"_experiments": {
"data_collection": {
"url_query_params": {"mode": "allowlist", "terms": ["auth"]}
}
}
},
"toy=[Filtered]&color=[Filtered]&auth=[Filtered]",
id="data_collection_allowlist_sensitive_term",
),
pytest.param(
{"_experiments": {"data_collection": {"url_query_params": {"mode": "off"}}}},
NO_QUERY_STRING,
id="data_collection_off",
),
pytest.param(
{
"send_default_pii": True,
"_experiments": {"data_collection": {"url_query_params": {"mode": "off"}}},
},
NO_QUERY_STRING,
id="data_collection_wins_over_send_default_pii",
),
]


@pytest.mark.asyncio
@pytest.mark.parametrize(
"init_kwargs, expected_query", _QUERY_PARAM_DATA_COLLECTION_CASES
)
async def test_span_streaming_url_query_data_collection(
sentry_init, capture_items, init_kwargs, expected_query
):
init_kwargs = dict(init_kwargs)
experiments = {"trace_lifecycle": "stream"}
experiments.update(init_kwargs.pop("_experiments", {}))
sentry_init(
integrations=[quart_sentry.QuartIntegration()],
traces_sample_rate=1.0,
_experiments=experiments,
**init_kwargs,
)
items = capture_items("span")

app = quart_app_factory()
client = app.test_client()
response = await client.get("/message?toy=tennisball&color=red&auth=secret")
assert response.status_code == 200

sentry_sdk.flush()

spans = [item.payload for item in items]
assert len(spans) == 1

segment = spans[0]

data_collection_enabled = "data_collection" in experiments
url_attrs_expected = data_collection_enabled or init_kwargs.get(
"send_default_pii", False
)

if expected_query is NO_QUERY_STRING:
assert "url.query" not in segment["attributes"]
if url_attrs_expected:
# When the filtered query string is empty, url.full carries the
# base URL only (no query).
assert segment["attributes"]["url.full"] == "http://localhost/message"
else:
assert "url.full" not in segment["attributes"]
else:
assert segment["attributes"]["url.query"] == expected_query
assert segment["attributes"]["url.full"] == (
f"http://localhost/message?{expected_query}"
)


@pytest.mark.asyncio
async def test_span_streaming_url_query_multi_and_blank_values(
sentry_init, capture_items
):
sentry_init(
integrations=[quart_sentry.QuartIntegration()],
traces_sample_rate=1.0,
_experiments={"trace_lifecycle": "stream", "data_collection": {}},
)
items = capture_items("span")

app = quart_app_factory()
client = app.test_client()
response = await client.get("/message?foo=1&foo=2&empty=")
assert response.status_code == 200

sentry_sdk.flush()

spans = [item.payload for item in items]
assert len(spans) == 1

segment = spans[0]
query = segment["attributes"]["url.query"]
# Repeated keys are preserved and blank values are kept.
assert "foo=1" in query
assert "foo=2" in query
assert "empty=" in query
# url.full carries the same filtered query string.
assert segment["attributes"]["url.full"] == f"http://localhost/message?{query}"
Loading