Skip to content
This repository was archived by the owner on Oct 23, 2023. It is now read-only.

Commit e3aad24

Browse files
mattrobenoltashwoods
authored andcommitted
Always supply a user.ip_address value
This is explicitly choosing to also parse the X-Forwarded-For header to yank out this value. Otherwise, the Sentry server relies only on the REMOTE_ADDR value which will always be wrong when when someone is behind a reverse proxy. This logic already exists in some other clients, and this has been brought up a number of times with users via tickets and support. Worth noting that it's potentially possible for this value to now be forged from a user, but the ramification of doing so is so low, it's not worth putting this behavior behind a feature flag IMO. The worst someone could do is make some data inside Sentry inaccurate and possibly confusing. No worse than the current state of the world where the data is completely inaccurate.
1 parent 2c30f0f commit e3aad24

File tree

6 files changed

+91
-23
lines changed

6 files changed

+91
-23
lines changed

raven/contrib/django/client.py

Lines changed: 14 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@
3030
from raven.contrib.django.middleware import SentryMiddleware
3131
from raven.utils.compat import string_types, binary_type, iterlists
3232
from raven.contrib.django.resolver import RouteResolver
33-
from raven.utils.wsgi import get_headers, get_environ
33+
from raven.utils.wsgi import get_headers, get_environ, get_client_ip
3434
from raven.utils import once
3535
from raven import breadcrumbs
3636

@@ -142,7 +142,14 @@ def __init__(self, *args, **kwargs):
142142
def install_sql_hook(self):
143143
install_sql_hook()
144144

145-
def get_user_info(self, user):
145+
def get_user_info(self, request):
146+
user_info = {
147+
'ip_address': get_client_ip(request.META),
148+
}
149+
user = getattr(request, 'user', None)
150+
if user is None:
151+
return user_info
152+
146153
try:
147154
if hasattr(user, 'is_authenticated'):
148155
# is_authenticated was a method in Django < 1.10
@@ -151,7 +158,7 @@ def get_user_info(self, user):
151158
else:
152159
authenticated = user.is_authenticated
153160
if not authenticated:
154-
return None
161+
return user_info
155162

156163
user_info = {}
157164

@@ -164,22 +171,18 @@ def get_user_info(self, user):
164171
user_info['username'] = user.get_username()
165172
elif hasattr(user, 'username'):
166173
user_info['username'] = user.username
167-
168-
return user_info
169174
except Exception:
170175
# We expect that user objects can be somewhat broken at times
171176
# and try to just handle as much as possible and ignore errors
172177
# as good as possible here.
173-
return None
178+
pass
179+
180+
return user_info
174181

175182
def get_data_from_request(self, request):
176183
result = {}
177184

178-
user = getattr(request, 'user', None)
179-
if user is not None:
180-
user_info = self.get_user_info(user)
181-
if user_info:
182-
result['user'] = user_info
185+
result['user'] = self.get_user_info(request)
183186

184187
try:
185188
uri = request.build_absolute_uri()

raven/contrib/flask.py

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -143,29 +143,34 @@ def get_user_info(self, request):
143143
Requires Flask-Login (https://pypi.python.org/pypi/Flask-Login/)
144144
to be installed and setup.
145145
"""
146+
try:
147+
user_info = {
148+
'ip_address': request.access_route[0],
149+
}
150+
except IndexError:
151+
user_info = {}
152+
146153
if not has_flask_login:
147-
return
154+
return user_info
148155

149156
if not hasattr(current_app, 'login_manager'):
150-
return
157+
return user_info
151158

152159
try:
153160
is_authenticated = current_user.is_authenticated
154161
except AttributeError:
155162
# HACK: catch the attribute error thrown by flask-login is not attached
156163
# > current_user = LocalProxy(lambda: _request_ctx_stack.top.user)
157164
# E AttributeError: 'RequestContext' object has no attribute 'user'
158-
return {}
165+
return user_info
159166

160167
if callable(is_authenticated):
161168
is_authenticated = is_authenticated()
162169

163170
if not is_authenticated:
164-
return {}
171+
return user_info
165172

166-
user_info = {
167-
'id': current_user.get_id(),
168-
}
173+
user_info['id'] = current_user.get_id()
169174

170175
if 'SENTRY_USER_ATTRS' in current_app.config:
171176
for attr in current_app.config['SENTRY_USER_ATTRS']:

raven/utils/wsgi.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,17 @@ def get_current_url(environ, root_only=False, strip_querystring=False,
9292
if qs:
9393
cat('?' + qs)
9494
return ''.join(tmp)
95+
96+
97+
def get_client_ip(environ):
98+
"""
99+
Naively yank the first IP address in an X-Forwarded-For header
100+
and assume this is correct.
101+
102+
Note: Don't use this in security sensitive situations since this
103+
value may be forged from a client.
104+
"""
105+
try:
106+
return environ['HTTP_X_FORWARDED_FOR'].split(',')[0].strip()
107+
except (KeyError, IndexError):
108+
return environ.get('REMOTE_ADDR')

tests/contrib/django/tests.py

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ def test_user_info(self):
250250
@pytest.mark.skipif(not DJANGO_15, reason='< Django 1.5')
251251
def test_get_user_info_abstract_user(self):
252252
from django.db import models
253+
from django.http import HttpRequest
253254
from django.contrib.auth.models import AbstractBaseUser
254255

255256
class MyUser(AbstractBaseUser):
@@ -263,8 +264,25 @@ class MyUser(AbstractBaseUser):
263264
264265
id=1,
265266
)
266-
user_info = self.raven.get_user_info(user)
267+
268+
request = HttpRequest()
269+
request.META['REMOTE_ADDR'] = '127.0.0.1'
270+
request.user = user
271+
user_info = self.raven.get_user_info(request)
272+
assert user_info == {
273+
'ip_address': '127.0.0.1',
274+
'username': user.username,
275+
'id': user.id,
276+
'email': user.email,
277+
}
278+
279+
request = HttpRequest()
280+
request.META['REMOTE_ADDR'] = '127.0.0.1'
281+
request.META['HTTP_X_FORWARDED_FOR'] = '1.1.1.1, 2.2.2.2'
282+
request.user = user
283+
user_info = self.raven.get_user_info(request)
267284
assert user_info == {
285+
'ip_address': '1.1.1.1',
268286
'username': user.username,
269287
'id': user.id,
270288
'email': user.email,
@@ -273,6 +291,7 @@ class MyUser(AbstractBaseUser):
273291
@pytest.mark.skipif(not DJANGO_110, reason='< Django 1.10')
274292
def test_get_user_info_is_authenticated_property(self):
275293
from django.db import models
294+
from django.http import HttpRequest
276295
from django.contrib.auth.models import AbstractBaseUser
277296

278297
class MyUser(AbstractBaseUser):
@@ -289,8 +308,25 @@ def is_authenticated(self):
289308
290309
id=1,
291310
)
292-
user_info = self.raven.get_user_info(user)
311+
312+
request = HttpRequest()
313+
request.META['REMOTE_ADDR'] = '127.0.0.1'
314+
request.user = user
315+
user_info = self.raven.get_user_info(request)
316+
assert user_info == {
317+
'ip_address': '127.0.0.1',
318+
'username': user.username,
319+
'id': user.id,
320+
'email': user.email,
321+
}
322+
323+
request = HttpRequest()
324+
request.META['REMOTE_ADDR'] = '127.0.0.1'
325+
request.META['HTTP_X_FORWARDED_FOR'] = '1.1.1.1, 2.2.2.2'
326+
request.user = user
327+
user_info = self.raven.get_user_info(request)
293328
assert user_info == {
329+
'ip_address': '1.1.1.1',
294330
'username': user.username,
295331
'id': user.id,
296332
'email': user.email,

tests/contrib/flask/tests.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ def log_a_generic_error():
5959
def capture_exception():
6060
try:
6161
raise ValueError('Boom')
62-
except:
62+
except Exception:
6363
current_app.extensions['sentry'].captureException()
6464
return 'Hello'
6565

@@ -318,4 +318,4 @@ def test_user(self):
318318
assert event['message'] == 'ValueError: hello world'
319319
assert 'request' in event
320320
assert 'user' in event
321-
self.assertDictEqual(event['user'], User().to_dict())
321+
self.assertDictEqual(event['user'], dict({'ip_address': '127.0.0.1'}, **User().to_dict()))

tests/utils/wsgi/tests.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
from raven.utils.testutils import TestCase
2-
from raven.utils.wsgi import get_headers, get_host, get_environ
2+
from raven.utils.wsgi import get_headers, get_host, get_environ, get_client_ip
33

44

55
class GetHeadersTest(TestCase):
@@ -84,3 +84,13 @@ def test_http_nonstandard_port(self):
8484
'SERVER_PORT': '81',
8585
})
8686
self.assertEquals(result, 'example.com:81')
87+
88+
89+
class GetClientIpTest(TestCase):
90+
def test_has_remote_addr(self):
91+
result = get_client_ip({'REMOTE_ADDR': '127.0.0.1'})
92+
self.assertEquals(result, '127.0.0.1')
93+
94+
def test_xff(self):
95+
result = get_client_ip({'HTTP_X_FORWARDED_FOR': '1.1.1.1, 127.0.0.1'})
96+
self.assertEquals(result, '1.1.1.1')

0 commit comments

Comments
 (0)