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
31 changes: 28 additions & 3 deletions sentry_sdk/integrations/gql.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import sentry_sdk
from sentry_sdk.data_collection import (
_apply_data_collection_filtering_to_query_string,
)
from sentry_sdk.integrations import DidNotEnable, Integration, _check_minimum_version
from sentry_sdk.scope import should_send_default_pii
from sentry_sdk.utils import (
capture_internal_exceptions,
ensure_integration_enabled,
event_from_exception,
has_data_collection_enabled,
parse_url,
parse_version,
)

Expand Down Expand Up @@ -36,7 +41,7 @@
from typing import TYPE_CHECKING

if TYPE_CHECKING:
from typing import Any, Dict, Tuple, Union
from typing import Any, Dict, Optional, Tuple, Union

from sentry_sdk._types import Event, EventProcessor

Expand Down Expand Up @@ -99,9 +104,29 @@ def _request_info_from_transport(
}

try:
request_info["url"] = transport.url
url = transport.url
except AttributeError:
pass
return request_info

with capture_internal_exceptions():
client_options = sentry_sdk.get_client().options
use_data_collection = has_data_collection_enabled(client_options)

# Some transports accept a URL object as well as a string.
# Credentials are always removed from the URL. Query values are
# filtered here only when data_collection is not configured, since
# its url_query_params setting does its own filtering.
parsed_url = parse_url(str(url), sanitize=not use_data_collection)
request_info["url"] = parsed_url.url

query_string: "Optional[str]" = parsed_url.query
if use_data_collection and query_string:
query_string = _apply_data_collection_filtering_to_query_string(
query_string=query_string,
behaviour=client_options["data_collection"]["url_query_params"],
)
if query_string:
request_info["query_string"] = query_string

return request_info

Expand Down
83 changes: 83 additions & 0 deletions tests/integrations/gql/test_gql.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import re

import pytest
import responses
from gql import Client, __version__, gql
Expand Down Expand Up @@ -273,3 +275,84 @@ def test_real_gql_request_with_error_data_collection_variables(
assert event["request"]["data"].get("variables")
else:
assert not event["request"]["data"].get("variables")


@pytest.mark.parametrize(
"init_kwargs,expected_query_string",
[
pytest.param(
{},
"access_token=[Filtered]&v=[Filtered]&page=[Filtered]",
id="no_pii_no_data_collection",
),
pytest.param(
{"send_default_pii": True},
"access_token=[Filtered]&v=[Filtered]&page=[Filtered]",
id="legacy_pii_on",
),
pytest.param(
{"data_collection": {}},
"access_token=%5BFiltered%5D&v=1&page=2",
id="data_collection_defaults",
),
pytest.param(
{"data_collection": {"url_query_params": {"mode": "off"}}},
None,
id="data_collection_query_params_off",
),
pytest.param(
{
"data_collection": {
"url_query_params": {"mode": "denylist", "terms": ["page"]}
}
},
"access_token=%5BFiltered%5D&v=1&page=%5BFiltered%5D",
id="data_collection_denylist",
),
pytest.param(
{
"data_collection": {
"url_query_params": {"mode": "allowlist", "terms": ["page"]}
}
},
"access_token=%5BFiltered%5D&v=%5BFiltered%5D&page=2",
id="data_collection_allowlist",
),
pytest.param(
{
"send_default_pii": True,
"data_collection": {"url_query_params": {"mode": "off"}},
},
None,
id="data_collection_takes_precedence_over_send_default_pii",
),
],
)
@responses.activate
def test_transport_url_is_sanitized(
sentry_init, capture_events, init_kwargs, expected_query_string
):
sentry_init(integrations=[GQLIntegration()], **init_kwargs)
events = capture_events()

responses.add(
method=responses.POST,
url=re.compile(r"https://.*api\.example\.com/graphql.*"),
json={"errors": [{"message": "some error"}]},
status=200,
)
transport = RequestsHTTPTransport(
url="https://user:password@api.example.com/graphql?access_token=secret&v=1&page=2#frag"
)
client = Client(transport=transport)

with pytest.raises(TransportQueryError):
client.execute(gql("query Example { example }"))

(event,) = events
assert event["request"]["url"] == (
"https://[Filtered]:[Filtered]@api.example.com/graphql"
)
assert event["request"].get("query_string") == expected_query_string
assert "password" not in str(event["request"])
assert "secret" not in str(event["request"])