diff --git a/readthedocs/core/middleware.py b/readthedocs/core/middleware.py
index 6fc5bbe55c8..92a3972bda5 100644
--- a/readthedocs/core/middleware.py
+++ b/readthedocs/core/middleware.py
@@ -1,19 +1,14 @@
-# -*- coding: utf-8 -*-
-
-"""Middleware for core app."""
-
import logging
from django.conf import settings
from django.contrib.sessions.middleware import SessionMiddleware
-from django.core.cache import cache
from django.core.exceptions import MultipleObjectsReturned, ObjectDoesNotExist
from django.http import Http404, HttpResponseBadRequest
from django.urls.base import set_urlconf
from django.utils.deprecation import MiddlewareMixin
from django.utils.translation import ugettext_lazy as _
+from django.shortcuts import render
-from readthedocs.core.utils import cname_to_slug
from readthedocs.projects.models import Domain, Project
@@ -112,46 +107,21 @@ def process_request(self, request):
)
# Try header first, then DNS
elif not hasattr(request, 'domain_object'):
- try:
- slug = cache.get(host)
- if not slug:
- slug = cname_to_slug(host)
- cache.set(host, slug, 60 * 60)
- # Cache the slug -> host mapping permanently.
- log.info(
- LOG_TEMPLATE.format(
- msg='CNAME cached: {}->{}'.format(slug, host),
- **log_kwargs
- ),
- )
- request.slug = slug
- request.urlconf = SUBDOMAIN_URLCONF
- log.warning(
- LOG_TEMPLATE.format(
- msg='CNAME detected: %s' % request.slug,
- **log_kwargs
- ),
- )
- except: # noqa
- # Some crazy person is CNAMEing to us. 404.
- log.warning(
- LOG_TEMPLATE.format(msg='CNAME 404', **log_kwargs),
- )
- raise Http404(_('Invalid hostname'))
+ # Some person is CNAMEing to us without configuring a domain - 404.
+ log.warning(LOG_TEMPLATE.format(msg='CNAME 404', **log_kwargs))
+ return render(request, 'core/dns-404.html', context={'host': host}, status=404)
# Google was finding crazy www.blah.readthedocs.org domains.
# Block these explicitly after trying CNAME logic.
if len(domain_parts) > 3 and not settings.DEBUG:
# Stop www.fooo.readthedocs.org
if domain_parts[0] == 'www':
- log.debug(
- LOG_TEMPLATE.format(msg='404ing long domain', **log_kwargs),
- )
+ log.debug(LOG_TEMPLATE.format(
+ msg='404ing long domain', **log_kwargs
+ ))
return HttpResponseBadRequest(_('Invalid hostname'))
- log.debug(
- LOG_TEMPLATE
- .format(msg='Allowing long domain name', **log_kwargs),
- )
- # raise Http404(_('Invalid hostname'))
+ log.debug(LOG_TEMPLATE.format(
+ msg='Allowing long domain name', **log_kwargs
+ ))
# Normal request.
return None
diff --git a/readthedocs/core/templates/core/dns-404.html b/readthedocs/core/templates/core/dns-404.html
new file mode 100644
index 00000000000..d611955b1b0
--- /dev/null
+++ b/readthedocs/core/templates/core/dns-404.html
@@ -0,0 +1,43 @@
+{% extends "base.html" %}
+{% load core_tags %}
+{% load i18n %}
+
+{% block title %}
+ {% trans "Maze Found - Invalid Host" %}
+{% endblock %}
+
+{% block header-wrapper %}
+ {% include "error_header.html" %}
+{% endblock %}
+
+{% block notify %}{% endblock %}
+
+{# Hide the language select form so we don't set a CSRF cookie #}
+{% block language-select-form %}{% endblock %}
+
+
+{% block content %}
+
{% trans '404 - Invalid Host' %}
+{% blocktrans %}The host "{{ host }}" is unknown to Read the Docs{% endblocktrans %}
+
+
+ {% with docsurl='https://docs.readthedocs.io/en/stable/custom_domains.html' %}
+ {% blocktrans trimmed %}
+ If you control this domain and believe this is in error,
+ please review our custom domain documentation.
+ In the past, we allowed custom domains to point to us without configuring the domain in the Read the Docs dashboard
+ and we attempted to intelligently guess the correct project based on DNS settings.
+ Now, we believe that explicit is better than implicit.
+ Below are some steps to help you get your domain working again:
+ {% endblocktrans %}
+ {% endwith %}
+
+
+
+
+ - {% trans 'Ensure you have a CNAME record pointing to
readthedocs.io
' %}
+ - {% trans 'Add your desired domain in the Read the Docs dashboard for your project (under Your Project >> Admin >> Domains)' %}
+
+
+
+{% endblock %}
diff --git a/readthedocs/core/utils/__init__.py b/readthedocs/core/utils/__init__.py
index 2123ce5a326..a8a5b5a0567 100644
--- a/readthedocs/core/utils/__init__.py
+++ b/readthedocs/core/utils/__init__.py
@@ -59,15 +59,6 @@ def broadcast(type, task, args, kwargs=None, callback=None): # pylint: disable=
return task_promise
-def cname_to_slug(host):
- # TODO: remove
- from dns import resolver
- answer = [ans for ans in resolver.query(host, 'CNAME')][0]
- domain = answer.target.to_unicode()
- slug = domain.split('.')[0]
- return slug
-
-
def prepare_build(
project,
version=None,
diff --git a/readthedocs/rtd_tests/tests/test_middleware.py b/readthedocs/rtd_tests/tests/test_middleware.py
index 4a93274da4a..3f3f761271e 100644
--- a/readthedocs/rtd_tests/tests/test_middleware.py
+++ b/readthedocs/rtd_tests/tests/test_middleware.py
@@ -1,15 +1,11 @@
-# -*- coding: utf-8 -*-
-
from corsheaders.middleware import CorsMiddleware
from django.conf import settings
-from django.core.cache import cache
from django.http import Http404
from django.test import TestCase
from django.test.client import RequestFactory
from django.test.utils import override_settings
from django.urls.base import get_urlconf, set_urlconf
from django_dynamic_fixture import get
-from mock import patch
from readthedocs.core.middleware import SubdomainMiddleware
from readthedocs.projects.models import Domain, Project, ProjectRelationship
@@ -26,12 +22,18 @@ def setUp(self):
self.middleware = SubdomainMiddleware()
self.url = '/'
self.owner = create_user(username='owner', password='test')
- self.pip = get(Project, slug='pip', users=[self.owner], privacy_level='public')
+ self.pip = get(
+ Project,
+ slug='pip',
+ users=[self.owner],
+ privacy_level='public'
+ )
def test_failey_cname(self):
+ self.assertFalse(Domain.objects.filter(domain='my.host.com').exists())
request = self.factory.get(self.url, HTTP_HOST='my.host.com')
- with self.assertRaises(Http404):
- self.middleware.process_request(request)
+ r = self.middleware.process_request(request)
+ self.assertEqual(r.status_code, 404)
self.assertEqual(request.cname, True)
@override_settings(PRODUCTION_DOMAIN='readthedocs.org')
@@ -74,7 +76,9 @@ def test_restore_urlconf_after_request(self):
@override_settings(PRODUCTION_DOMAIN='prod.readthedocs.org')
def test_subdomain_different_length(self):
- request = self.factory.get(self.url, HTTP_HOST='pip.prod.readthedocs.org')
+ request = self.factory.get(
+ self.url, HTTP_HOST='pip.prod.readthedocs.org'
+ )
self.middleware.process_request(request)
self.assertEqual(request.urlconf, self.urlconf_subdomain)
self.assertEqual(request.subdomain, True)
@@ -92,19 +96,13 @@ def test_domain_object(self):
def test_domain_object_missing(self):
self.domain = get(Domain, domain='docs.foobar2.com', project=self.pip)
request = self.factory.get(self.url, HTTP_HOST='docs.foobar.com')
- with self.assertRaises(Http404):
- self.middleware.process_request(request)
-
- def test_proper_cname(self):
- cache.get = lambda x: 'my_slug'
- request = self.factory.get(self.url, HTTP_HOST='my.valid.homename')
- self.middleware.process_request(request)
- self.assertEqual(request.urlconf, self.urlconf_subdomain)
- self.assertEqual(request.cname, True)
- self.assertEqual(request.slug, 'my_slug')
+ r = self.middleware.process_request(request)
+ self.assertEqual(r.status_code, 404)
def test_request_header(self):
- request = self.factory.get(self.url, HTTP_HOST='some.random.com', HTTP_X_RTD_SLUG='pip')
+ request = self.factory.get(
+ self.url, HTTP_HOST='some.random.com', HTTP_X_RTD_SLUG='pip'
+ )
self.middleware.process_request(request)
self.assertEqual(request.urlconf, self.urlconf_subdomain)
self.assertEqual(request.cname, True)
@@ -113,7 +111,7 @@ def test_request_header(self):
@override_settings(PRODUCTION_DOMAIN='readthedocs.org')
def test_proper_cname_uppercase(self):
- cache.get = lambda x: x.split('.')[0]
+ get(Domain, project=self.pip, domain='pip.random.com')
request = self.factory.get(self.url, HTTP_HOST='PIP.RANDOM.COM')
self.middleware.process_request(request)
self.assertEqual(request.urlconf, self.urlconf_subdomain)
@@ -121,20 +119,35 @@ def test_proper_cname_uppercase(self):
self.assertEqual(request.slug, 'pip')
def test_request_header_uppercase(self):
- request = self.factory.get(self.url, HTTP_HOST='some.random.com', HTTP_X_RTD_SLUG='PIP')
+ request = self.factory.get(
+ self.url, HTTP_HOST='some.random.com', HTTP_X_RTD_SLUG='PIP'
+ )
self.middleware.process_request(request)
self.assertEqual(request.urlconf, self.urlconf_subdomain)
self.assertEqual(request.cname, True)
self.assertEqual(request.rtdheader, True)
self.assertEqual(request.slug, 'pip')
- @override_settings(USE_SUBDOMAIN=True)
- # no need to do a real dns query so patch cname_to_slug
- @patch('readthedocs.core.middleware.cname_to_slug', new=lambda x: 'doesnt')
- def test_use_subdomain_on(self):
- request = self.factory.get(self.url, HTTP_HOST='doesnt.really.matter')
- ret_val = self.middleware.process_request(request)
- self.assertIsNone(ret_val, None)
+ def test_use_subdomain(self):
+ domain = 'doesnt.exists.org'
+ get(Domain, project=self.pip, domain=domain)
+ request = self.factory.get(self.url, HTTP_HOST=domain)
+ res = self.middleware.process_request(request)
+ self.assertIsNone(res)
+ self.assertEqual(request.slug, 'pip')
+ self.assertTrue(request.domain_object)
+
+ def test_long_bad_subdomain(self):
+ domain = 'www.pip.readthedocs.org'
+ request = self.factory.get(self.url, HTTP_HOST=domain)
+ res = self.middleware.process_request(request)
+ self.assertEqual(res.status_code, 400)
+
+ def test_long_subdomain(self):
+ domain = 'some.long.readthedocs.org'
+ request = self.factory.get(self.url, HTTP_HOST=domain)
+ res = self.middleware.process_request(request)
+ self.assertIsNone(res)
class TestCORSMiddleware(TestCase):
diff --git a/requirements/pip.txt b/requirements/pip.txt
index a7f9804855a..cc6adafb4bc 100644
--- a/requirements/pip.txt
+++ b/requirements/pip.txt
@@ -35,8 +35,6 @@ celery==4.1.1
django-allauth==0.38.0
-dnspython==1.16.0
-
# VCS
httplib2==0.12.0