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
13 changes: 11 additions & 2 deletions sentry_sdk/integrations/gql.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
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 @@ -99,9 +101,16 @@ def _request_info_from_transport(
}

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

with capture_internal_exceptions():
# Some transports accept a URL object as well as a string.
parsed_url = parse_url(str(url))
request_info["url"] = parsed_url.url
if parsed_url.query:
request_info["query_string"] = parsed_url.query

return request_info

Expand Down
65 changes: 64 additions & 1 deletion tests/integrations/gql/test_gql.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import re

import pytest
import responses
from gql import Client, __version__, gql
from gql.transport.exceptions import TransportQueryError
from gql.transport.requests import RequestsHTTPTransport

from sentry_sdk.integrations.gql import GQLIntegration
from sentry_sdk.integrations.gql import GQLIntegration, _request_info_from_transport
from sentry_sdk.utils import parse_version

GQL_VERSION = parse_version(__version__)
Expand Down Expand Up @@ -273,3 +275,64 @@ 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("send_default_pii", (False, True))
@responses.activate
def test_transport_url_is_sanitized(sentry_init, capture_events, send_default_pii):
sentry_init(integrations=[GQLIntegration()], send_default_pii=send_default_pii)
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#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"]["query_string"] == "access_token=[Filtered]&v=[Filtered]"
assert "password" not in str(event["request"])
assert "secret" not in str(event["request"])


def test_transport_url_without_query_has_no_query_string():
class FakeTransport:
url = "https://api.example.com/graphql"

assert _request_info_from_transport(FakeTransport()) == {
"method": "POST",
"url": "https://api.example.com/graphql",
}


def test_transport_url_object_is_sanitized():
class FakeUrl:
def __str__(self):
return "https://user:password@api.example.com/graphql?token=secret"

class FakeTransport:
url = FakeUrl()

assert _request_info_from_transport(FakeTransport()) == {
"method": "POST",
"url": "https://[Filtered]:[Filtered]@api.example.com/graphql",
"query_string": "token=[Filtered]",
}


def test_transport_without_url():
class FakeTransport:
pass

assert _request_info_from_transport(FakeTransport()) == {"method": "POST"}