From 9e5ecfe87f4ebd35b155f729d0f9ebb3b671fa15 Mon Sep 17 00:00:00 2001 From: Matt Robenolt Date: Tue, 4 Feb 2014 01:06:27 -0800 Subject: [PATCH] '*' allowed domain now allows no Referer/Origin to be set --- src/sentry/testutils/cases.py | 18 +++++++++++++++++- src/sentry/utils/http.py | 6 +++--- src/sentry/web/api.py | 19 +++++++++++-------- tests/integration/tests.py | 14 ++++++++++++++ 4 files changed, 45 insertions(+), 12 deletions(-) diff --git a/src/sentry/testutils/cases.py b/src/sentry/testutils/cases.py index df238c1ed252e0..c0c7e40ceb38ca 100644 --- a/src/sentry/testutils/cases.py +++ b/src/sentry/testutils/cases.py @@ -116,6 +116,22 @@ def _postWithHeader(self, data, key=None, secret=None): ) return resp + def _getWithoutReferer(self, data, key=None): + if key is None: + key = self.projectkey.public_key + + message = self._makeMessage(data) + qs = { + 'sentry_version': '4', + 'sentry_client': 'raven-js/lol', + 'sentry_key': key, + 'sentry_data': message, + } + resp = self.client.get( + '%s?%s' % (reverse('sentry-api-store', args=(self.project.pk,)), urllib.urlencode(qs)) + ) + return resp + def _getWithReferer(self, data, key=None): if key is None: key = self.projectkey.public_key @@ -129,7 +145,7 @@ def _getWithReferer(self, data, key=None): } resp = self.client.get( '%s?%s' % (reverse('sentry-api-store', args=(self.project.pk,)), urllib.urlencode(qs)), - HTTP_REFERER='http://lol.com/' + HTTP_REFERER='http://getsentry.com/' ) return resp diff --git a/src/sentry/utils/http.py b/src/sentry/utils/http.py index af675132792428..7999aa6e672f7b 100644 --- a/src/sentry/utils/http.py +++ b/src/sentry/utils/http.py @@ -87,9 +87,6 @@ def is_valid_origin(origin, project=None): - *.domain.com: matches domain.com and all subdomains, on any port - domain.com: matches domain.com on any port """ - # we always run a case insensitive check - origin = origin.lower() - allowed = get_origins(project) if '*' in allowed: return True @@ -97,6 +94,9 @@ def is_valid_origin(origin, project=None): if not origin: return False + # we always run a case insensitive check + origin = origin.lower() + # Fast check if origin in allowed: return True diff --git a/src/sentry/web/api.py b/src/sentry/web/api.py index 4f0dc5bf8f9df6..8ea513e47558d1 100644 --- a/src/sentry/web/api.py +++ b/src/sentry/web/api.py @@ -175,15 +175,15 @@ def _dispatch(self, request, project_id=None, *args, **kwargs): Raven.tags_context({'project': project.id}) origin = self.get_request_origin(request) - if origin is not None: - if not project: - return HttpResponse('Your client must be upgraded for CORS support.') - elif not is_valid_origin(origin, project): - return HttpResponse('Invalid origin: %r' % origin, content_type='text/plain', status=400) + if origin is not None and not project: + return HttpResponse('Your client must be upgraded for CORS support.', content_type='text/plain', status=400) # XXX: It seems that the OPTIONS call does not always include custom headers if request.method == 'OPTIONS': - response = self.options(request, project) + if is_valid_origin(origin, project): + response = self.options(request, project) + else: + return HttpResponse('Invalid origin: %r' % origin, content_type='text/plain', status=400) else: try: auth_vars = self._parse_header(request, project) @@ -213,8 +213,11 @@ def _dispatch(self, request, project_id=None, *args, **kwargs): if auth.version >= 3: if request.method == 'GET': # GET only requires an Origin/Referer check - if origin is None: - return HttpResponse('Missing required Origin or Referer header', status=400) + if not is_valid_origin(origin, project): + if origin is None: + return HttpResponse('Missing required Origin or Referer header', content_type='text/plain', status=400) + else: + return HttpResponse('Invalid origin: %r' % origin, content_type='text/plain', status=400) else: # Version 3 enforces secret key for server side requests if not auth.secret_key: diff --git a/tests/integration/tests.py b/tests/integration/tests.py index ca96450e90e523..0092f01c590ecb 100644 --- a/tests/integration/tests.py +++ b/tests/integration/tests.py @@ -7,6 +7,7 @@ from django.conf import settings as django_settings from django.core.urlresolvers import reverse +from django.test.utils import override_settings from django.utils import timezone from raven import Client @@ -161,6 +162,7 @@ def test_ungzipped_data(self): self.assertEquals(instance.site, 'not_a_real_site') self.assertEquals(instance.level, 40) + @override_settings(SENTRY_ALLOW_ORIGIN='getsentry.com') def test_correct_data_with_get(self): kwargs = {'message': 'hello', 'server_name': 'not_dcramer.local', 'level': 40, 'site': 'not_a_real_site'} resp = self._getWithReferer(kwargs) @@ -171,6 +173,18 @@ def test_correct_data_with_get(self): self.assertEquals(instance.level, 40) self.assertEquals(instance.site, 'not_a_real_site') + @override_settings(SENTRY_ALLOW_ORIGIN='getsentry.com') + def test_get_without_referer(self): + kwargs = {'message': 'hello', 'server_name': 'not_dcramer.local', 'level': 40, 'site': 'not_a_real_site'} + resp = self._getWithoutReferer(kwargs) + self.assertEquals(resp.status_code, 400, resp.content) + + @override_settings(SENTRY_ALLOW_ORIGIN='*') + def test_get_without_referer_allowed(self): + kwargs = {'message': 'hello', 'server_name': 'not_dcramer.local', 'level': 40, 'site': 'not_a_real_site'} + resp = self._getWithoutReferer(kwargs) + self.assertEquals(resp.status_code, 200, resp.content) + # def test_byte_sequence(self): # """ # invalid byte sequence for encoding "UTF8": 0xedb7af