From 9448b0a4487e0231ba247d35bc34a0f0410a3b5e Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Thu, 8 Nov 2018 12:59:16 +0100 Subject: [PATCH 1/2] Allow all /api/v2/ CORS if the Domain is known --- readthedocs/core/signals.py | 8 ++++- .../rtd_tests/tests/test_middleware.py | 31 ++++++++++++------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/readthedocs/core/signals.py b/readthedocs/core/signals.py index eb0ff4f22dd..9880e064cd1 100644 --- a/readthedocs/core/signals.py +++ b/readthedocs/core/signals.py @@ -49,6 +49,12 @@ def decide_if_cors(sender, request, **kwargs): # pylint: disable=unused-argumen if request.path_info.startswith('/api/v2/sustainability'): return True + # Don't do domain checking for APIv2 when the Domain is known + if request.path_info.startswith('/api/v2/'): + domain = Domain.objects.filter(domain__icontains=host) + if domain.exists(): + return True + valid_url = False for url in WHITELIST_URLS: if request.path_info.startswith(url): @@ -69,7 +75,7 @@ def decide_if_cors(sender, request, **kwargs): # pylint: disable=unused-argumen domain = Domain.objects.filter( Q(domain__icontains=host), - Q(project=project) | Q(project__subprojects__child=project) + Q(project=project) | Q(project__subprojects__child=project), ) if domain.exists(): return True diff --git a/readthedocs/rtd_tests/tests/test_middleware.py b/readthedocs/rtd_tests/tests/test_middleware.py index 57470855242..15d3a1d32f7 100644 --- a/readthedocs/rtd_tests/tests/test_middleware.py +++ b/readthedocs/rtd_tests/tests/test_middleware.py @@ -185,21 +185,12 @@ def test_invalid_domain(self): resp = self.middleware.process_response(request, {}) self.assertNotIn('Access-Control-Allow-Origin', resp) - def test_invalid_project(self): - request = self.factory.get( - self.url, - {'project': 'foo'}, - HTTP_ORIGIN='http://my.valid.domain', - ) - resp = self.middleware.process_response(request, {}) - self.assertNotIn('Access-Control-Allow-Origin', resp) - def test_valid_subproject(self): self.assertTrue( Project.objects.filter( pk=self.project.pk, - subprojects__child=self.subproject - ).exists() + subprojects__child=self.subproject, + ).exists(), ) request = self.factory.get( self.url, @@ -208,3 +199,21 @@ def test_valid_subproject(self): ) resp = self.middleware.process_response(request, {}) self.assertIn('Access-Control-Allow-Origin', resp) + + def test_apiv2_endpoint_allowed(self): + request = self.factory.get( + '/api/v2/version/', + {'project__slug': self.project.slug, 'active': True}, + HTTP_ORIGIN='http://my.valid.domain', + ) + resp = self.middleware.process_response(request, {}) + self.assertIn('Access-Control-Allow-Origin', resp) + + def test_apiv2_endpoint_not_allowed(self): + request = self.factory.get( + '/api/v2/version/', + {'project__slug': self.project.slug, 'active': True}, + HTTP_ORIGIN='http://invalid.domain', + ) + resp = self.middleware.process_response(request, {}) + self.assertNotIn('Access-Control-Allow-Origin', resp) From 4e5f747a5d71695890b5331357af0828e6ce9299 Mon Sep 17 00:00:00 2001 From: Manuel Kaufmann Date: Mon, 19 Nov 2018 10:44:49 +0100 Subject: [PATCH 2/2] Allow CORS only in SAFE_METHODS --- readthedocs/core/signals.py | 3 ++- readthedocs/rtd_tests/tests/test_middleware.py | 9 +++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/readthedocs/core/signals.py b/readthedocs/core/signals.py index 9880e064cd1..d8b90b57d82 100644 --- a/readthedocs/core/signals.py +++ b/readthedocs/core/signals.py @@ -13,6 +13,7 @@ from django.db.models import Q, Count from django.dispatch import receiver from future.backports.urllib.parse import urlparse +from rest_framework.permissions import SAFE_METHODS from readthedocs.oauth.models import RemoteOrganization from readthedocs.projects.models import Project, Domain @@ -50,7 +51,7 @@ def decide_if_cors(sender, request, **kwargs): # pylint: disable=unused-argumen return True # Don't do domain checking for APIv2 when the Domain is known - if request.path_info.startswith('/api/v2/'): + if request.path_info.startswith('/api/v2/') and request.method in SAFE_METHODS: domain = Domain.objects.filter(domain__icontains=host) if domain.exists(): return True diff --git a/readthedocs/rtd_tests/tests/test_middleware.py b/readthedocs/rtd_tests/tests/test_middleware.py index 15d3a1d32f7..11089b39ce4 100644 --- a/readthedocs/rtd_tests/tests/test_middleware.py +++ b/readthedocs/rtd_tests/tests/test_middleware.py @@ -217,3 +217,12 @@ def test_apiv2_endpoint_not_allowed(self): ) resp = self.middleware.process_response(request, {}) self.assertNotIn('Access-Control-Allow-Origin', resp) + + # POST is not allowed + request = self.factory.post( + '/api/v2/version/', + {'project__slug': self.project.slug, 'active': True}, + HTTP_ORIGIN='http://my.valid.domain', + ) + resp = self.middleware.process_response(request, {}) + self.assertNotIn('Access-Control-Allow-Origin', resp)