Skip to content

Commit

Permalink
Proxito: redirect http->https for public domains (#10142)
Browse files Browse the repository at this point in the history
  • Loading branch information
stsewd authored Mar 14, 2023
1 parent 3380716 commit 830b899
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 24 deletions.
24 changes: 18 additions & 6 deletions readthedocs/proxito/tests/test_headers.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
class ProxitoHeaderTests(BaseDocServing):

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

def test_serve_headers(self):
r = self.client.get('/en/latest/', HTTP_HOST='project.dev.readthedocs.io')
r = self.client.get(
"/en/latest/", secure=True, HTTP_HOST="project.dev.readthedocs.io"
)
self.assertEqual(r.status_code, 200)
self.assertEqual(r["Cache-Tag"], "project,project:latest")
self.assertEqual(r["X-RTD-Domain"], "project.dev.readthedocs.io")
Expand All @@ -43,7 +45,11 @@ def test_serve_headers(self):
)

def test_subproject_serve_headers(self):
r = self.client.get('/projects/subproject/en/latest/', HTTP_HOST='project.dev.readthedocs.io')
r = self.client.get(
"/projects/subproject/en/latest/",
secure=True,
HTTP_HOST="project.dev.readthedocs.io",
)
self.assertEqual(r.status_code, 200)
self.assertEqual(r['Cache-Tag'], 'subproject,subproject:latest')
self.assertEqual(r['X-RTD-Domain'], 'project.dev.readthedocs.io')
Expand All @@ -59,7 +65,9 @@ def test_subproject_serve_headers(self):
self.assertEqual(r['X-RTD-Path'], '/proxito/media/html/subproject/latest/index.html')

def test_404_headers(self):
r = self.client.get('/foo/bar.html', HTTP_HOST='project.dev.readthedocs.io')
r = self.client.get(
"/foo/bar.html", secure=True, HTTP_HOST="project.dev.readthedocs.io"
)
self.assertEqual(r.status_code, 404)
self.assertEqual(r["Cache-Tag"], "project")
self.assertEqual(r["X-RTD-Domain"], "project.dev.readthedocs.io")
Expand Down Expand Up @@ -135,13 +143,17 @@ def test_user_domain_headers(self):

@override_settings(ALLOW_PRIVATE_REPOS=False)
def test_cache_headers_public_version_with_private_projects_not_allowed(self):
r = self.client.get('/en/latest/', HTTP_HOST='project.dev.readthedocs.io')
r = self.client.get(
"/en/latest/", secure=True, HTTP_HOST="project.dev.readthedocs.io"
)
self.assertEqual(r.status_code, 200)
self.assertEqual(r["CDN-Cache-Control"], "public")

@override_settings(ALLOW_PRIVATE_REPOS=True)
def test_cache_headers_public_version_with_private_projects_allowed(self):
r = self.client.get('/en/latest/', HTTP_HOST='project.dev.readthedocs.io')
r = self.client.get(
"/en/latest/", secure=True, HTTP_HOST="project.dev.readthedocs.io"
)
self.assertEqual(r.status_code, 200)
self.assertEqual(r["CDN-Cache-Control"], "public")

Expand Down
83 changes: 65 additions & 18 deletions readthedocs/proxito/tests/test_redirects.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,14 @@
class RedirectTests(BaseDocServing):

def test_root_url_no_slash(self):
r = self.client.get('', HTTP_HOST='project.dev.readthedocs.io')
r = self.client.get("", secure=True, HTTP_HOST="project.dev.readthedocs.io")
self.assertEqual(r.status_code, 302)
self.assertEqual(
r['Location'], 'https://project.dev.readthedocs.io/en/latest/',
)

def test_root_url(self):
r = self.client.get('/', HTTP_HOST='project.dev.readthedocs.io')
r = self.client.get("/", secure=True, HTTP_HOST="project.dev.readthedocs.io")
self.assertEqual(r.status_code, 302)
self.assertEqual(
r['Location'], 'https://project.dev.readthedocs.io/en/latest/',
Expand Down Expand Up @@ -53,18 +53,22 @@ def test_custom_domain_root_url_no_slash(self):
def test_single_version_root_url_doesnt_redirect(self):
self.project.single_version = True
self.project.save()
r = self.client.get('/', HTTP_HOST='project.dev.readthedocs.io')
r = self.client.get("/", secure=True, HTTP_HOST="project.dev.readthedocs.io")
self.assertEqual(r.status_code, 200)

def test_subproject_root_url(self):
r = self.client.get('/projects/subproject/', HTTP_HOST='project.dev.readthedocs.io')
r = self.client.get(
"/projects/subproject/", secure=True, HTTP_HOST="project.dev.readthedocs.io"
)
self.assertEqual(r.status_code, 302)
self.assertEqual(
r['Location'], 'https://project.dev.readthedocs.io/projects/subproject/en/latest/',
)

def test_subproject_root_url_no_slash(self):
r = self.client.get('/projects/subproject', HTTP_HOST='project.dev.readthedocs.io')
r = self.client.get(
"/projects/subproject", secure=True, HTTP_HOST="project.dev.readthedocs.io"
)
self.assertEqual(r.status_code, 302)
self.assertEqual(
r['Location'], 'https://project.dev.readthedocs.io/projects/subproject/en/latest/',
Expand All @@ -73,44 +77,52 @@ def test_subproject_root_url_no_slash(self):
def test_single_version_subproject_root_url_no_slash(self):
self.subproject.single_version = True
self.subproject.save()
r = self.client.get('/projects/subproject', HTTP_HOST='project.dev.readthedocs.io')
r = self.client.get(
"/projects/subproject", secure=True, HTTP_HOST="project.dev.readthedocs.io"
)
self.assertEqual(r.status_code, 302)
self.assertEqual(
r['Location'], 'https://project.dev.readthedocs.io/projects/subproject/',
)

def test_subproject_redirect(self):
r = self.client.get('/', HTTP_HOST='subproject.dev.readthedocs.io')
r = self.client.get("/", secure=True, HTTP_HOST="subproject.dev.readthedocs.io")
self.assertEqual(r.status_code, 302)
self.assertEqual(
r["Location"],
"https://project.dev.readthedocs.io/projects/subproject/",
)

r = self.client.get(
"/projects/subproject/", HTTP_HOST="project.dev.readthedocs.io"
"/projects/subproject/", secure=True, HTTP_HOST="project.dev.readthedocs.io"
)
self.assertEqual(r.status_code, 302)
self.assertEqual(
r["Location"],
"https://project.dev.readthedocs.io/projects/subproject/en/latest/",
)

r = self.client.get('/en/latest/', HTTP_HOST='subproject.dev.readthedocs.io')
r = self.client.get(
"/en/latest/", secure=True, HTTP_HOST="subproject.dev.readthedocs.io"
)
self.assertEqual(r.status_code, 302)
self.assertEqual(
r['Location'], 'https://project.dev.readthedocs.io/projects/subproject/en/latest/',
)

r = self.client.get('/en/latest/foo/bar', HTTP_HOST='subproject.dev.readthedocs.io')
r = self.client.get(
"/en/latest/foo/bar", secure=True, HTTP_HOST="subproject.dev.readthedocs.io"
)
self.assertEqual(r.status_code, 302)
self.assertEqual(
r['Location'], 'https://project.dev.readthedocs.io/projects/subproject/en/latest/foo/bar',
)

self.domain.canonical = True
self.domain.save()
r = self.client.get('/en/latest/foo/bar', HTTP_HOST='subproject.dev.readthedocs.io')
r = self.client.get(
"/en/latest/foo/bar", secure=True, HTTP_HOST="subproject.dev.readthedocs.io"
)
self.assertEqual(r.status_code, 302)
self.assertEqual(
r['Location'], 'https://docs1.example.com/projects/subproject/en/latest/foo/bar',
Expand All @@ -120,28 +132,34 @@ def test_single_version_subproject_redirect(self):
self.subproject.single_version = True
self.subproject.save()

r = self.client.get('/', HTTP_HOST='subproject.dev.readthedocs.io')
r = self.client.get("/", secure=True, HTTP_HOST="subproject.dev.readthedocs.io")
self.assertEqual(r.status_code, 302)
self.assertEqual(
r['Location'], 'https://project.dev.readthedocs.io/projects/subproject/',
)

r = self.client.get('/foo/bar/', HTTP_HOST='subproject.dev.readthedocs.io')
r = self.client.get(
"/foo/bar/", secure=True, HTTP_HOST="subproject.dev.readthedocs.io"
)
self.assertEqual(r.status_code, 302)
self.assertEqual(
r['Location'], 'https://project.dev.readthedocs.io/projects/subproject/foo/bar/',
)

self.domain.canonical = True
self.domain.save()
r = self.client.get('/foo/bar', HTTP_HOST='subproject.dev.readthedocs.io')
r = self.client.get(
"/foo/bar", secure=True, HTTP_HOST="subproject.dev.readthedocs.io"
)
self.assertEqual(r.status_code, 302)
self.assertEqual(
r['Location'], 'https://docs1.example.com/projects/subproject/foo/bar',
)

def test_root_redirect_with_query_params(self):
r = self.client.get('/?foo=bar', HTTP_HOST='project.dev.readthedocs.io')
r = self.client.get(
"/?foo=bar", secure=True, HTTP_HOST="project.dev.readthedocs.io"
)
self.assertEqual(r.status_code, 302)
self.assertEqual(
r['Location'],
Expand Down Expand Up @@ -172,23 +190,29 @@ def test_canonicalize_public_domain_to_cname_redirect(self):
self.domain.canonical = True
self.domain.save()

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

# We should redirect before 404ing
r = self.client.get('/en/latest/404after302', HTTP_HOST='project.dev.readthedocs.io')
r = self.client.get(
"/en/latest/404after302",
secure=True,
HTTP_HOST="project.dev.readthedocs.io",
)
self.assertEqual(r.status_code, 302)
self.assertEqual(
r['Location'], f'https://{self.domain.domain}/en/latest/404after302',
)
self.assertEqual(r["X-RTD-Redirect"], RedirectType.to_canonical_domain.name)

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

def test_https_public_domain_https_redirect(self):
paths = ["/", "/en/latest/", "/not-found"]
for path in paths:
r = self.client.get(
path, secure=False, HTTP_HOST="project.dev.readthedocs.io"
)
self.assertEqual(r.status_code, 302)
self.assertEqual(
r["Location"],
f"https://project.dev.readthedocs.io{path}",
)
self.assertEqual(r["X-RTD-Redirect"], RedirectType.http_to_https.name)

@override_settings(PUBLIC_DOMAIN_USES_HTTPS=False)
def test_http_public_domain_https_redirect(self):
r = self.client.get("", secure=False, HTTP_HOST="project.dev.readthedocs.io")
self.assertEqual(r.status_code, 302)
self.assertEqual(
r["Location"],
"http://project.dev.readthedocs.io/en/latest/",
)
self.assertEqual(r["X-RTD-Redirect"], RedirectType.system.name)


class ProxitoV2RedirectTests(RedirectTests):
# TODO: remove this class once the new implementation is the default.
Expand Down
11 changes: 11 additions & 0 deletions readthedocs/proxito/views/serve.py
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,17 @@ def _get_canonical_redirect_type(self, request):
log.debug("Proxito CNAME HTTPS Redirect.", domain=domain.domain)
return RedirectType.http_to_https

# Redirect HTTP -> HTTPS (302) for public domains.
if (
(
unresolved_domain.is_from_public_domain
or unresolved_domain.is_from_external_domain
)
and settings.PUBLIC_DOMAIN_USES_HTTPS
and not request.is_secure()
):
return RedirectType.http_to_https

# Check for subprojects before checking for canonical domains,
# so we can redirect to the main domain first.
# Custom domains on subprojects are not supported.
Expand Down

0 comments on commit 830b899

Please sign in to comment.