Skip to content

Commit 830b899

Browse files
authored
Proxito: redirect http->https for public domains (#10142)
1 parent 3380716 commit 830b899

File tree

3 files changed

+94
-24
lines changed

3 files changed

+94
-24
lines changed

readthedocs/proxito/tests/test_headers.py

+18-6
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
class ProxitoHeaderTests(BaseDocServing):
1717

1818
def test_redirect_headers(self):
19-
r = self.client.get('', HTTP_HOST='project.dev.readthedocs.io')
19+
r = self.client.get("", secure=True, HTTP_HOST="project.dev.readthedocs.io")
2020
self.assertEqual(r.status_code, 302)
2121
self.assertEqual(r['X-RTD-Redirect'], 'system')
2222
self.assertEqual(
@@ -30,7 +30,9 @@ def test_redirect_headers(self):
3030
self.assertIsNone(r.get("X-RTD-Path"))
3131

3232
def test_serve_headers(self):
33-
r = self.client.get('/en/latest/', HTTP_HOST='project.dev.readthedocs.io')
33+
r = self.client.get(
34+
"/en/latest/", secure=True, HTTP_HOST="project.dev.readthedocs.io"
35+
)
3436
self.assertEqual(r.status_code, 200)
3537
self.assertEqual(r["Cache-Tag"], "project,project:latest")
3638
self.assertEqual(r["X-RTD-Domain"], "project.dev.readthedocs.io")
@@ -43,7 +45,11 @@ def test_serve_headers(self):
4345
)
4446

4547
def test_subproject_serve_headers(self):
46-
r = self.client.get('/projects/subproject/en/latest/', HTTP_HOST='project.dev.readthedocs.io')
48+
r = self.client.get(
49+
"/projects/subproject/en/latest/",
50+
secure=True,
51+
HTTP_HOST="project.dev.readthedocs.io",
52+
)
4753
self.assertEqual(r.status_code, 200)
4854
self.assertEqual(r['Cache-Tag'], 'subproject,subproject:latest')
4955
self.assertEqual(r['X-RTD-Domain'], 'project.dev.readthedocs.io')
@@ -59,7 +65,9 @@ def test_subproject_serve_headers(self):
5965
self.assertEqual(r['X-RTD-Path'], '/proxito/media/html/subproject/latest/index.html')
6066

6167
def test_404_headers(self):
62-
r = self.client.get('/foo/bar.html', HTTP_HOST='project.dev.readthedocs.io')
68+
r = self.client.get(
69+
"/foo/bar.html", secure=True, HTTP_HOST="project.dev.readthedocs.io"
70+
)
6371
self.assertEqual(r.status_code, 404)
6472
self.assertEqual(r["Cache-Tag"], "project")
6573
self.assertEqual(r["X-RTD-Domain"], "project.dev.readthedocs.io")
@@ -135,13 +143,17 @@ def test_user_domain_headers(self):
135143

136144
@override_settings(ALLOW_PRIVATE_REPOS=False)
137145
def test_cache_headers_public_version_with_private_projects_not_allowed(self):
138-
r = self.client.get('/en/latest/', HTTP_HOST='project.dev.readthedocs.io')
146+
r = self.client.get(
147+
"/en/latest/", secure=True, HTTP_HOST="project.dev.readthedocs.io"
148+
)
139149
self.assertEqual(r.status_code, 200)
140150
self.assertEqual(r["CDN-Cache-Control"], "public")
141151

142152
@override_settings(ALLOW_PRIVATE_REPOS=True)
143153
def test_cache_headers_public_version_with_private_projects_allowed(self):
144-
r = self.client.get('/en/latest/', HTTP_HOST='project.dev.readthedocs.io')
154+
r = self.client.get(
155+
"/en/latest/", secure=True, HTTP_HOST="project.dev.readthedocs.io"
156+
)
145157
self.assertEqual(r.status_code, 200)
146158
self.assertEqual(r["CDN-Cache-Control"], "public")
147159

readthedocs/proxito/tests/test_redirects.py

+65-18
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@
1515
class RedirectTests(BaseDocServing):
1616

1717
def test_root_url_no_slash(self):
18-
r = self.client.get('', HTTP_HOST='project.dev.readthedocs.io')
18+
r = self.client.get("", secure=True, HTTP_HOST="project.dev.readthedocs.io")
1919
self.assertEqual(r.status_code, 302)
2020
self.assertEqual(
2121
r['Location'], 'https://project.dev.readthedocs.io/en/latest/',
2222
)
2323

2424
def test_root_url(self):
25-
r = self.client.get('/', HTTP_HOST='project.dev.readthedocs.io')
25+
r = self.client.get("/", secure=True, HTTP_HOST="project.dev.readthedocs.io")
2626
self.assertEqual(r.status_code, 302)
2727
self.assertEqual(
2828
r['Location'], 'https://project.dev.readthedocs.io/en/latest/',
@@ -53,18 +53,22 @@ def test_custom_domain_root_url_no_slash(self):
5353
def test_single_version_root_url_doesnt_redirect(self):
5454
self.project.single_version = True
5555
self.project.save()
56-
r = self.client.get('/', HTTP_HOST='project.dev.readthedocs.io')
56+
r = self.client.get("/", secure=True, HTTP_HOST="project.dev.readthedocs.io")
5757
self.assertEqual(r.status_code, 200)
5858

5959
def test_subproject_root_url(self):
60-
r = self.client.get('/projects/subproject/', HTTP_HOST='project.dev.readthedocs.io')
60+
r = self.client.get(
61+
"/projects/subproject/", secure=True, HTTP_HOST="project.dev.readthedocs.io"
62+
)
6163
self.assertEqual(r.status_code, 302)
6264
self.assertEqual(
6365
r['Location'], 'https://project.dev.readthedocs.io/projects/subproject/en/latest/',
6466
)
6567

6668
def test_subproject_root_url_no_slash(self):
67-
r = self.client.get('/projects/subproject', HTTP_HOST='project.dev.readthedocs.io')
69+
r = self.client.get(
70+
"/projects/subproject", secure=True, HTTP_HOST="project.dev.readthedocs.io"
71+
)
6872
self.assertEqual(r.status_code, 302)
6973
self.assertEqual(
7074
r['Location'], 'https://project.dev.readthedocs.io/projects/subproject/en/latest/',
@@ -73,44 +77,52 @@ def test_subproject_root_url_no_slash(self):
7377
def test_single_version_subproject_root_url_no_slash(self):
7478
self.subproject.single_version = True
7579
self.subproject.save()
76-
r = self.client.get('/projects/subproject', HTTP_HOST='project.dev.readthedocs.io')
80+
r = self.client.get(
81+
"/projects/subproject", secure=True, HTTP_HOST="project.dev.readthedocs.io"
82+
)
7783
self.assertEqual(r.status_code, 302)
7884
self.assertEqual(
7985
r['Location'], 'https://project.dev.readthedocs.io/projects/subproject/',
8086
)
8187

8288
def test_subproject_redirect(self):
83-
r = self.client.get('/', HTTP_HOST='subproject.dev.readthedocs.io')
89+
r = self.client.get("/", secure=True, HTTP_HOST="subproject.dev.readthedocs.io")
8490
self.assertEqual(r.status_code, 302)
8591
self.assertEqual(
8692
r["Location"],
8793
"https://project.dev.readthedocs.io/projects/subproject/",
8894
)
8995

9096
r = self.client.get(
91-
"/projects/subproject/", HTTP_HOST="project.dev.readthedocs.io"
97+
"/projects/subproject/", secure=True, HTTP_HOST="project.dev.readthedocs.io"
9298
)
9399
self.assertEqual(r.status_code, 302)
94100
self.assertEqual(
95101
r["Location"],
96102
"https://project.dev.readthedocs.io/projects/subproject/en/latest/",
97103
)
98104

99-
r = self.client.get('/en/latest/', HTTP_HOST='subproject.dev.readthedocs.io')
105+
r = self.client.get(
106+
"/en/latest/", secure=True, HTTP_HOST="subproject.dev.readthedocs.io"
107+
)
100108
self.assertEqual(r.status_code, 302)
101109
self.assertEqual(
102110
r['Location'], 'https://project.dev.readthedocs.io/projects/subproject/en/latest/',
103111
)
104112

105-
r = self.client.get('/en/latest/foo/bar', HTTP_HOST='subproject.dev.readthedocs.io')
113+
r = self.client.get(
114+
"/en/latest/foo/bar", secure=True, HTTP_HOST="subproject.dev.readthedocs.io"
115+
)
106116
self.assertEqual(r.status_code, 302)
107117
self.assertEqual(
108118
r['Location'], 'https://project.dev.readthedocs.io/projects/subproject/en/latest/foo/bar',
109119
)
110120

111121
self.domain.canonical = True
112122
self.domain.save()
113-
r = self.client.get('/en/latest/foo/bar', HTTP_HOST='subproject.dev.readthedocs.io')
123+
r = self.client.get(
124+
"/en/latest/foo/bar", secure=True, HTTP_HOST="subproject.dev.readthedocs.io"
125+
)
114126
self.assertEqual(r.status_code, 302)
115127
self.assertEqual(
116128
r['Location'], 'https://docs1.example.com/projects/subproject/en/latest/foo/bar',
@@ -120,28 +132,34 @@ def test_single_version_subproject_redirect(self):
120132
self.subproject.single_version = True
121133
self.subproject.save()
122134

123-
r = self.client.get('/', HTTP_HOST='subproject.dev.readthedocs.io')
135+
r = self.client.get("/", secure=True, HTTP_HOST="subproject.dev.readthedocs.io")
124136
self.assertEqual(r.status_code, 302)
125137
self.assertEqual(
126138
r['Location'], 'https://project.dev.readthedocs.io/projects/subproject/',
127139
)
128140

129-
r = self.client.get('/foo/bar/', HTTP_HOST='subproject.dev.readthedocs.io')
141+
r = self.client.get(
142+
"/foo/bar/", secure=True, HTTP_HOST="subproject.dev.readthedocs.io"
143+
)
130144
self.assertEqual(r.status_code, 302)
131145
self.assertEqual(
132146
r['Location'], 'https://project.dev.readthedocs.io/projects/subproject/foo/bar/',
133147
)
134148

135149
self.domain.canonical = True
136150
self.domain.save()
137-
r = self.client.get('/foo/bar', HTTP_HOST='subproject.dev.readthedocs.io')
151+
r = self.client.get(
152+
"/foo/bar", secure=True, HTTP_HOST="subproject.dev.readthedocs.io"
153+
)
138154
self.assertEqual(r.status_code, 302)
139155
self.assertEqual(
140156
r['Location'], 'https://docs1.example.com/projects/subproject/foo/bar',
141157
)
142158

143159
def test_root_redirect_with_query_params(self):
144-
r = self.client.get('/?foo=bar', HTTP_HOST='project.dev.readthedocs.io')
160+
r = self.client.get(
161+
"/?foo=bar", secure=True, HTTP_HOST="project.dev.readthedocs.io"
162+
)
145163
self.assertEqual(r.status_code, 302)
146164
self.assertEqual(
147165
r['Location'],
@@ -172,23 +190,29 @@ def test_canonicalize_public_domain_to_cname_redirect(self):
172190
self.domain.canonical = True
173191
self.domain.save()
174192

175-
r = self.client.get('/', HTTP_HOST='project.dev.readthedocs.io')
193+
r = self.client.get("/", secure=True, HTTP_HOST="project.dev.readthedocs.io")
176194
self.assertEqual(r.status_code, 302)
177195
self.assertEqual(
178196
r['Location'], f'https://{self.domain.domain}/',
179197
)
180198
self.assertEqual(r["X-RTD-Redirect"], RedirectType.to_canonical_domain.name)
181199

182200
# We should redirect before 404ing
183-
r = self.client.get('/en/latest/404after302', HTTP_HOST='project.dev.readthedocs.io')
201+
r = self.client.get(
202+
"/en/latest/404after302",
203+
secure=True,
204+
HTTP_HOST="project.dev.readthedocs.io",
205+
)
184206
self.assertEqual(r.status_code, 302)
185207
self.assertEqual(
186208
r['Location'], f'https://{self.domain.domain}/en/latest/404after302',
187209
)
188210
self.assertEqual(r["X-RTD-Redirect"], RedirectType.to_canonical_domain.name)
189211

190212
def test_translation_redirect(self):
191-
r = self.client.get('/', HTTP_HOST='translation.dev.readthedocs.io')
213+
r = self.client.get(
214+
"/", secure=True, HTTP_HOST="translation.dev.readthedocs.io"
215+
)
192216
self.assertEqual(r.status_code, 302)
193217
self.assertEqual(
194218
r['Location'], f'https://project.dev.readthedocs.io/es/latest/',
@@ -262,6 +286,29 @@ def test_slash_redirect(self):
262286
# The test client strips multiple slashes at the front of the URL
263287
# Additional tests for this are in ``test_middleware:test_front_slash``
264288

289+
def test_https_public_domain_https_redirect(self):
290+
paths = ["/", "/en/latest/", "/not-found"]
291+
for path in paths:
292+
r = self.client.get(
293+
path, secure=False, HTTP_HOST="project.dev.readthedocs.io"
294+
)
295+
self.assertEqual(r.status_code, 302)
296+
self.assertEqual(
297+
r["Location"],
298+
f"https://project.dev.readthedocs.io{path}",
299+
)
300+
self.assertEqual(r["X-RTD-Redirect"], RedirectType.http_to_https.name)
301+
302+
@override_settings(PUBLIC_DOMAIN_USES_HTTPS=False)
303+
def test_http_public_domain_https_redirect(self):
304+
r = self.client.get("", secure=False, HTTP_HOST="project.dev.readthedocs.io")
305+
self.assertEqual(r.status_code, 302)
306+
self.assertEqual(
307+
r["Location"],
308+
"http://project.dev.readthedocs.io/en/latest/",
309+
)
310+
self.assertEqual(r["X-RTD-Redirect"], RedirectType.system.name)
311+
265312

266313
class ProxitoV2RedirectTests(RedirectTests):
267314
# TODO: remove this class once the new implementation is the default.

readthedocs/proxito/views/serve.py

+11
Original file line numberDiff line numberDiff line change
@@ -286,6 +286,17 @@ def _get_canonical_redirect_type(self, request):
286286
log.debug("Proxito CNAME HTTPS Redirect.", domain=domain.domain)
287287
return RedirectType.http_to_https
288288

289+
# Redirect HTTP -> HTTPS (302) for public domains.
290+
if (
291+
(
292+
unresolved_domain.is_from_public_domain
293+
or unresolved_domain.is_from_external_domain
294+
)
295+
and settings.PUBLIC_DOMAIN_USES_HTTPS
296+
and not request.is_secure()
297+
):
298+
return RedirectType.http_to_https
299+
289300
# Check for subprojects before checking for canonical domains,
290301
# so we can redirect to the main domain first.
291302
# Custom domains on subprojects are not supported.

0 commit comments

Comments
 (0)