Skip to content

Commit a243cbe

Browse files
Proxito: add CORS headers only on public versions (#10737)
* Proxito: add CORS headers only on public versions Add `Access-Control-Allow-Origin: *` and `Access-Control-Allow-Methods: OPTIONS, GET` HTTP headers when serving a PUBLIC version. This is required for DocDiff addon to be able to download the original version of the page and compare the DOMs to show the visual differences. Closes readthedocs/addons#93 * Merge conflict fixed * Update readthedocs/proxito/middleware.py * Update readthedocs/proxito/tests/test_headers.py --------- Co-authored-by: Eric Holscher <[email protected]>
1 parent ab5b75c commit a243cbe

File tree

3 files changed

+55
-0
lines changed

3 files changed

+55
-0
lines changed

dockerfiles/nginx/proxito.conf.template

+2
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,8 @@ server {
8080
add_header Access-Control-Allow-Origin $access_control_allow_origin always;
8181
set $access_control_allow_headers $upstream_http_access_control_allow_headers;
8282
add_header Access-Control-Allow-Headers $access_control_allow_headers always;
83+
set $access_control_allow_methods $upstream_http_access_control_allow_methods;
84+
add_header Access-Control-Allow-Methods $access_control_allow_methods always;
8385
set $x_frame_options $upstream_http_x_frame_options;
8486
add_header X-Frame-Options $x_frame_options always;
8587
set $x_content_type_options $upstream_http_x_content_type_options;

readthedocs/proxito/middleware.py

+28
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
unresolver,
2626
)
2727
from readthedocs.core.utils import get_cache_tag
28+
from readthedocs.projects.constants import PUBLIC
2829
from readthedocs.projects.models import Project
2930
from readthedocs.proxito.cache import add_cache_tags, cache_response, private_response
3031
from readthedocs.proxito.redirects import redirect_to_https
@@ -305,6 +306,32 @@ def add_hosting_integrations_headers(self, request, response):
305306
if addons:
306307
response["X-RTD-Hosting-Integrations"] = "true"
307308

309+
def add_cors_headers(self, request, response):
310+
"""
311+
Add CORS headers only on PUBLIC versions.
312+
313+
DocDiff addons requires making a request from
314+
``RTD_EXTERNAL_VERSION_DOMAIN`` to ``PUBLIC_DOMAIN`` to be able to
315+
compare both DOMs and show the visual differences.
316+
317+
This request needs ``Access-Control-Allow-Origin`` HTTP headers to be
318+
accepted by browsers. However, we cannot expose these headers for
319+
documentation that's not PUBLIC.
320+
"""
321+
project_slug = getattr(request, "path_project_slug", "")
322+
version_slug = getattr(request, "path_version_slug", "")
323+
324+
if project_slug and version_slug:
325+
allow_cors = Version.objects.filter(
326+
project__slug=project_slug,
327+
slug=version_slug,
328+
privacy_level=PUBLIC,
329+
).exists()
330+
if allow_cors:
331+
response.headers["Access-Control-Allow-Origin"] = "*.readthedocs.build"
332+
response.headers["Access-Control-Allow-Methods"] = "OPTIONS, GET"
333+
return response
334+
308335
def _get_https_redirect(self, request):
309336
"""
310337
Get a redirect response if the request should be redirected to HTTPS.
@@ -342,4 +369,5 @@ def process_response(self, request, response): # noqa
342369
self.add_hsts_headers(request, response)
343370
self.add_user_headers(request, response)
344371
self.add_hosting_integrations_headers(request, response)
372+
self.add_cors_headers(request, response)
345373
return response

readthedocs/proxito/tests/test_headers.py

+25
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from django.urls import reverse
44

55
from readthedocs.builds.constants import LATEST
6+
from readthedocs.projects.constants import PRIVATE, PUBLIC
67
from readthedocs.projects.models import Domain, HTTPHeader
78

89
from .base import BaseDocServing
@@ -158,6 +159,30 @@ def test_hosting_integrations_header(self):
158159
self.assertIsNotNone(r.get("X-RTD-Hosting-Integrations"))
159160
self.assertEqual(r["X-RTD-Hosting-Integrations"], "true")
160161

162+
def test_cors_headers_private_version(self):
163+
version = self.project.versions.get(slug=LATEST)
164+
version.privacy_level = PRIVATE
165+
version.save()
166+
167+
r = self.client.get(
168+
"/en/latest/", secure=True, headers={"host": "project.dev.readthedocs.io"}
169+
)
170+
self.assertEqual(r.status_code, 200)
171+
self.assertIsNone(r.get("Access-Control-Allow-Origin"))
172+
self.assertIsNone(r.get("Access-Control-Allow-Methods"))
173+
174+
def test_cors_headers_public_version(self):
175+
version = self.project.versions.get(slug=LATEST)
176+
version.privacy_level = PUBLIC
177+
version.save()
178+
179+
r = self.client.get(
180+
"/en/latest/", secure=True, headers={"host": "project.dev.readthedocs.io"}
181+
)
182+
self.assertEqual(r.status_code, 200)
183+
self.assertEqual(r["Access-Control-Allow-Origin"], "*.readthedocs.build")
184+
self.assertEqual(r["Access-Control-Allow-Methods"], "OPTIONS, GET")
185+
161186
@override_settings(ALLOW_PRIVATE_REPOS=False)
162187
def test_cache_headers_public_version_with_private_projects_not_allowed(self):
163188
r = self.client.get(

0 commit comments

Comments
 (0)