Skip to content

Commit f645e76

Browse files
committed
fix(django): handle disallowed hosts (#1235)
1 parent f8ff030 commit f645e76

File tree

3 files changed

+47
-17
lines changed

3 files changed

+47
-17
lines changed

ddtrace/contrib/django/patch.py

Lines changed: 1 addition & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,6 @@
1313

1414
from ddtrace import config, Pin
1515
from ddtrace.vendor import debtcollector, wrapt
16-
from ddtrace.compat import parse
1716
from ddtrace.constants import ANALYTICS_SAMPLE_RATE_KEY
1817
from ddtrace.contrib import func_name, dbapi
1918
from ddtrace.ext import http, sql as sqlx, SpanTypes
@@ -394,21 +393,7 @@ def traced_get_response(django, pin, func, instance, args, kwargs):
394393
span.set_tag("http.route", route)
395394

396395
# Set HTTP Request tags
397-
# Build `http.url` tag value from request info
398-
# DEV: We are explicitly omitting query strings since they may contain sensitive information
399-
span.set_tag(
400-
http.URL,
401-
parse.urlunparse(
402-
parse.ParseResult(
403-
scheme=request.scheme,
404-
netloc=request.get_host(), # this will include `host:port`
405-
path=request.path,
406-
params="",
407-
query="",
408-
fragment="",
409-
)
410-
),
411-
)
396+
span.set_tag(http.URL, utils.get_request_uri(request))
412397

413398
response = func(*args, **kwargs)
414399

ddtrace/contrib/django/utils.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
from ...compat import parse
12
from ...internal.logger import get_logger
23

34

@@ -57,3 +58,36 @@ def set_tag_array(span, prefix, value):
5758
else:
5859
for i, v in enumerate(value, start=0):
5960
span.set_tag("{0}.{1}".format(prefix, i), v)
61+
62+
63+
def get_request_uri(request):
64+
"""
65+
Helper to rebuild the original request url
66+
67+
query string or fragments are not included.
68+
"""
69+
# DEV: Use django.http.request.HttpRequest._get_raw_host() when available
70+
# otherwise back-off to PEP 333 as done in django 1.8.x
71+
if hasattr(request, "_get_raw_host"):
72+
host = request._get_raw_host()
73+
else:
74+
try:
75+
# Try to build host how Django would have
76+
# https://github.com/django/django/blob/e8d0d2a5efc8012dcc8bf1809dec065ebde64c81/django/http/request.py#L85-L102
77+
if "HTTP_HOST" in request.META:
78+
host = request.META["HTTP_HOST"]
79+
else:
80+
host = request.META["SERVER_NAME"]
81+
port = str(request.META["SERVER_PORT"])
82+
if port != ("443" if request.is_secure() else "80"):
83+
host = "{0}:{1}".format(host, port)
84+
except Exception:
85+
# This really shouldn't ever happen, but lets guard here just in case
86+
log.debug("Failed to build Django request host", exc_info=True)
87+
host = "unknown"
88+
89+
# Build request url from the information available
90+
# DEV: We are explicitly omitting query strings since they may contain sensitive information
91+
return parse.urlunparse(
92+
parse.ParseResult(scheme=request.scheme, netloc=host, path=request.path, params="", query="", fragment="",)
93+
)

tests/contrib/django/test_django.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import django
2-
from django.test import modify_settings
2+
from django.test import modify_settings, override_settings
33
import os
44
import pytest
55

@@ -118,6 +118,17 @@ def test_v1XX_middleware(client, test_spans):
118118
assert span_resources == expected_resources
119119

120120

121+
def test_disallowed_host(client, test_spans):
122+
with override_settings(ALLOWED_HOSTS="not-testserver"):
123+
resp = client.get("/")
124+
assert resp.status_code == 400
125+
assert b"Bad Request (400)" in resp.content
126+
127+
root_span = test_spans.get_root_span()
128+
assert root_span.get_tag("http.status_code") == "400"
129+
assert root_span.get_tag("http.url") == "http://testserver/"
130+
131+
121132
"""
122133
Middleware tests
123134
"""

0 commit comments

Comments
 (0)