Added a new required ALLOWED_HOSTS setting for HTTP host header validation.
This is a security fix; disclosure and advisory coming shortly.
This commit is contained in:
parent
1add79bc40
commit
d51fb74360
|
@ -29,6 +29,10 @@ ADMINS = ()
|
||||||
# * Receive x-headers
|
# * Receive x-headers
|
||||||
INTERNAL_IPS = ()
|
INTERNAL_IPS = ()
|
||||||
|
|
||||||
|
# Hosts/domain names that are valid for this site.
|
||||||
|
# "*" matches anything, ".example.com" matches example.com and all subdomains
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
# Local time zone for this installation. All choices can be found here:
|
# Local time zone for this installation. All choices can be found here:
|
||||||
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name (although not all
|
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name (although not all
|
||||||
# systems may support all possibilities). When USE_TZ is True, this is
|
# systems may support all possibilities). When USE_TZ is True, this is
|
||||||
|
|
|
@ -25,6 +25,10 @@ DEBUG = True
|
||||||
|
|
||||||
TEMPLATE_DEBUG = True
|
TEMPLATE_DEBUG = True
|
||||||
|
|
||||||
|
# Hosts/domain names that are valid for this site; required if DEBUG is False
|
||||||
|
# See https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/#allowed-hosts
|
||||||
|
ALLOWED_HOSTS = []
|
||||||
|
|
||||||
|
|
||||||
# Application definition
|
# Application definition
|
||||||
|
|
||||||
|
|
|
@ -108,6 +108,7 @@ class PasswordResetTest(AuthViewsTestCase):
|
||||||
self.assertEqual(len(mail.outbox), 1)
|
self.assertEqual(len(mail.outbox), 1)
|
||||||
self.assertEqual("staffmember@example.com", mail.outbox[0].from_email)
|
self.assertEqual("staffmember@example.com", mail.outbox[0].from_email)
|
||||||
|
|
||||||
|
@override_settings(ALLOWED_HOSTS=['adminsite.com'])
|
||||||
def test_admin_reset(self):
|
def test_admin_reset(self):
|
||||||
"If the reset view is marked as being for admin, the HTTP_HOST header is used for a domain override."
|
"If the reset view is marked as being for admin, the HTTP_HOST header is used for a domain override."
|
||||||
response = self.client.post('/admin_password_reset/',
|
response = self.client.post('/admin_password_reset/',
|
||||||
|
|
|
@ -6,6 +6,7 @@ from django.contrib.contenttypes.views import shortcut
|
||||||
from django.contrib.sites.models import Site, get_current_site
|
from django.contrib.sites.models import Site, get_current_site
|
||||||
from django.http import HttpRequest, Http404
|
from django.http import HttpRequest, Http404
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.test.utils import override_settings
|
||||||
from django.utils.http import urlquote
|
from django.utils.http import urlquote
|
||||||
from django.utils import six
|
from django.utils import six
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
from django.utils.encoding import python_2_unicode_compatible
|
||||||
|
@ -203,6 +204,7 @@ class ContentTypesTests(TestCase):
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(ALLOWED_HOSTS=['example.com'])
|
||||||
def test_shortcut_view(self):
|
def test_shortcut_view(self):
|
||||||
"""
|
"""
|
||||||
Check that the shortcut view (used for the admin "view on site"
|
Check that the shortcut view (used for the admin "view on site"
|
||||||
|
|
|
@ -5,6 +5,7 @@ from django.contrib.sites.models import Site, RequestSite, get_current_site
|
||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from django.http import HttpRequest
|
from django.http import HttpRequest
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.test.utils import override_settings
|
||||||
|
|
||||||
|
|
||||||
class SitesFrameworkTests(TestCase):
|
class SitesFrameworkTests(TestCase):
|
||||||
|
@ -41,6 +42,7 @@ class SitesFrameworkTests(TestCase):
|
||||||
site = Site.objects.get_current()
|
site = Site.objects.get_current()
|
||||||
self.assertEqual("Example site", site.name)
|
self.assertEqual("Example site", site.name)
|
||||||
|
|
||||||
|
@override_settings(ALLOWED_HOSTS=['example.com'])
|
||||||
def test_get_current_site(self):
|
def test_get_current_site(self):
|
||||||
# Test that the correct Site object is returned
|
# Test that the correct Site object is returned
|
||||||
request = HttpRequest()
|
request = HttpRequest()
|
||||||
|
|
|
@ -64,11 +64,12 @@ class HttpRequest(object):
|
||||||
if server_port != ('443' if self.is_secure() else '80'):
|
if server_port != ('443' if self.is_secure() else '80'):
|
||||||
host = '%s:%s' % (host, server_port)
|
host = '%s:%s' % (host, server_port)
|
||||||
|
|
||||||
# Disallow potentially poisoned hostnames.
|
allowed_hosts = ['*'] if settings.DEBUG else settings.ALLOWED_HOSTS
|
||||||
if not host_validation_re.match(host.lower()):
|
if validate_host(host, allowed_hosts):
|
||||||
raise SuspiciousOperation('Invalid HTTP_HOST header: %s' % host)
|
return host
|
||||||
|
else:
|
||||||
return host
|
raise SuspiciousOperation(
|
||||||
|
"Invalid HTTP_HOST header (you may need to set ALLOWED_HOSTS): %s" % host)
|
||||||
|
|
||||||
def get_full_path(self):
|
def get_full_path(self):
|
||||||
# RFC 3986 requires query string arguments to be in the ASCII range.
|
# RFC 3986 requires query string arguments to be in the ASCII range.
|
||||||
|
@ -450,3 +451,45 @@ def bytes_to_text(s, encoding):
|
||||||
return six.text_type(s, encoding, 'replace')
|
return six.text_type(s, encoding, 'replace')
|
||||||
else:
|
else:
|
||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
def validate_host(host, allowed_hosts):
|
||||||
|
"""
|
||||||
|
Validate the given host header value for this site.
|
||||||
|
|
||||||
|
Check that the host looks valid and matches a host or host pattern in the
|
||||||
|
given list of ``allowed_hosts``. Any pattern beginning with a period
|
||||||
|
matches a domain and all its subdomains (e.g. ``.example.com`` matches
|
||||||
|
``example.com`` and any subdomain), ``*`` matches anything, and anything
|
||||||
|
else must match exactly.
|
||||||
|
|
||||||
|
Return ``True`` for a valid host, ``False`` otherwise.
|
||||||
|
|
||||||
|
"""
|
||||||
|
# All validation is case-insensitive
|
||||||
|
host = host.lower()
|
||||||
|
|
||||||
|
# Basic sanity check
|
||||||
|
if not host_validation_re.match(host):
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Validate only the domain part.
|
||||||
|
if host[-1] == ']':
|
||||||
|
# It's an IPv6 address without a port.
|
||||||
|
domain = host
|
||||||
|
else:
|
||||||
|
domain = host.rsplit(':', 1)[0]
|
||||||
|
|
||||||
|
for pattern in allowed_hosts:
|
||||||
|
pattern = pattern.lower()
|
||||||
|
match = (
|
||||||
|
pattern == '*' or
|
||||||
|
pattern.startswith('.') and (
|
||||||
|
domain.endswith(pattern) or domain == pattern[1:]
|
||||||
|
) or
|
||||||
|
pattern == domain
|
||||||
|
)
|
||||||
|
if match:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
|
@ -78,6 +78,9 @@ def setup_test_environment():
|
||||||
mail.original_email_backend = settings.EMAIL_BACKEND
|
mail.original_email_backend = settings.EMAIL_BACKEND
|
||||||
settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
|
settings.EMAIL_BACKEND = 'django.core.mail.backends.locmem.EmailBackend'
|
||||||
|
|
||||||
|
settings._original_allowed_hosts = settings.ALLOWED_HOSTS
|
||||||
|
settings.ALLOWED_HOSTS = ['*']
|
||||||
|
|
||||||
mail.outbox = []
|
mail.outbox = []
|
||||||
|
|
||||||
deactivate()
|
deactivate()
|
||||||
|
@ -96,6 +99,9 @@ def teardown_test_environment():
|
||||||
settings.EMAIL_BACKEND = mail.original_email_backend
|
settings.EMAIL_BACKEND = mail.original_email_backend
|
||||||
del mail.original_email_backend
|
del mail.original_email_backend
|
||||||
|
|
||||||
|
settings.ALLOWED_HOSTS = settings._original_allowed_hosts
|
||||||
|
del settings._original_allowed_hosts
|
||||||
|
|
||||||
del mail.outbox
|
del mail.outbox
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -56,6 +56,42 @@ of (Full name, email address). Example::
|
||||||
Note that Django will email *all* of these people whenever an error happens.
|
Note that Django will email *all* of these people whenever an error happens.
|
||||||
See :doc:`/howto/error-reporting` for more information.
|
See :doc:`/howto/error-reporting` for more information.
|
||||||
|
|
||||||
|
.. setting:: ALLOWED_HOSTS
|
||||||
|
|
||||||
|
ALLOWED_HOSTS
|
||||||
|
-------------
|
||||||
|
|
||||||
|
Default: ``[]`` (Empty list)
|
||||||
|
|
||||||
|
A list of strings representing the host/domain names that this Django site can
|
||||||
|
serve. This is a security measure to prevent an attacker from poisoning caches
|
||||||
|
and password reset emails with links to malicious hosts by submitting requests
|
||||||
|
with a fake HTTP ``Host`` header, which is possible even under many
|
||||||
|
seemingly-safe webserver configurations.
|
||||||
|
|
||||||
|
Values in this list can be fully qualified names (e.g. ``'www.example.com'``),
|
||||||
|
in which case they will be matched against the request's ``Host`` header
|
||||||
|
exactly (case-insensitive, not including port). A value beginning with a period
|
||||||
|
can be used as a subdomain wildcard: ``'.example.com'`` will match
|
||||||
|
``example.com``, ``www.example.com``, and any other subdomain of
|
||||||
|
``example.com``. A value of ``'*'`` will match anything; in this case you are
|
||||||
|
responsible to provide your own validation of the ``Host`` header (perhaps in a
|
||||||
|
middleware; if so this middleware must be listed first in
|
||||||
|
:setting:`MIDDLEWARE_CLASSES`).
|
||||||
|
|
||||||
|
If the ``Host`` header (or ``X-Forwarded-Host`` if
|
||||||
|
:setting:`USE_X_FORWARDED_HOST` is enabled) does not match any value in this
|
||||||
|
list, the :meth:`django.http.HttpRequest.get_host()` method will raise
|
||||||
|
:exc:`~django.core.exceptions.SuspiciousOperation`.
|
||||||
|
|
||||||
|
When :setting:`DEBUG` is ``True`` or when running tests, host validation is
|
||||||
|
disabled; any host will be accepted. Thus it's usually only necessary to set it
|
||||||
|
in production.
|
||||||
|
|
||||||
|
This validation only applies via :meth:`~django.http.HttpRequest.get_host()`;
|
||||||
|
if your code accesses the ``Host`` header directly from ``request.META`` you
|
||||||
|
are bypassing this security protection.
|
||||||
|
|
||||||
.. setting:: ALLOWED_INCLUDE_ROOTS
|
.. setting:: ALLOWED_INCLUDE_ROOTS
|
||||||
|
|
||||||
ALLOWED_INCLUDE_ROOTS
|
ALLOWED_INCLUDE_ROOTS
|
||||||
|
|
|
@ -354,6 +354,16 @@ Backwards incompatible changes in 1.5
|
||||||
deprecation timeline for a given feature, its removal may appear as a
|
deprecation timeline for a given feature, its removal may appear as a
|
||||||
backwards incompatible change.
|
backwards incompatible change.
|
||||||
|
|
||||||
|
``ALLOWED_HOSTS`` required in production
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
The new :setting:`ALLOWED_HOSTS` setting validates the request's ``Host``
|
||||||
|
header and protects against host-poisoning attacks. This setting is now
|
||||||
|
required whenever :setting:`DEBUG` is ``False``, or else
|
||||||
|
:meth:`django.http.HttpRequest.get_host()` will raise
|
||||||
|
:exc:`~django.core.exceptions.SuspiciousOperation`. For more details see the
|
||||||
|
:setting:`full documentation<ALLOWED_HOSTS>` for the new setting.
|
||||||
|
|
||||||
Managers on abstract models
|
Managers on abstract models
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -160,47 +160,40 @@ server, there are some additional steps you may need:
|
||||||
|
|
||||||
.. _host-headers-virtual-hosting:
|
.. _host-headers-virtual-hosting:
|
||||||
|
|
||||||
Host headers and virtual hosting
|
Host header validation
|
||||||
================================
|
======================
|
||||||
|
|
||||||
Django uses the ``Host`` header provided by the client to construct URLs
|
Django uses the ``Host`` header provided by the client to construct URLs in
|
||||||
in certain cases. While these values are sanitized to prevent Cross
|
certain cases. While these values are sanitized to prevent Cross Site Scripting
|
||||||
Site Scripting attacks, they can be used for Cross-Site Request
|
attacks, a fake ``Host`` value can be used for Cross-Site Request Forgery,
|
||||||
Forgery and cache poisoning attacks in some circumstances. We
|
cache poisoning attacks, and poisoning links in emails.
|
||||||
recommend you ensure your Web server is configured such that:
|
|
||||||
|
|
||||||
* It always validates incoming HTTP ``Host`` headers against the expected
|
Because even seemingly-secure webserver configurations are susceptible to fake
|
||||||
host name.
|
``Host`` headers, Django validates ``Host`` headers against the
|
||||||
* Disallows requests with no ``Host`` header.
|
:setting:`ALLOWED_HOSTS` setting in the
|
||||||
* Is *not* configured with a catch-all virtual host that forwards requests
|
:meth:`django.http.HttpRequest.get_host()` method.
|
||||||
to a Django application.
|
|
||||||
|
This validation only applies via :meth:`~django.http.HttpRequest.get_host()`;
|
||||||
|
if your code accesses the ``Host`` header directly from ``request.META`` you
|
||||||
|
are bypassing this security protection.
|
||||||
|
|
||||||
|
For more details see the full :setting:`ALLOWED_HOSTS` documentation.
|
||||||
|
|
||||||
|
.. warning::
|
||||||
|
|
||||||
|
Previous versions of this document recommended configuring your webserver to
|
||||||
|
ensure it validates incoming HTTP ``Host`` headers. While this is still
|
||||||
|
recommended, in many common webservers a configuration that seems to
|
||||||
|
validate the ``Host`` header may not in fact do so. For instance, even if
|
||||||
|
Apache is configured such that your Django site is served from a non-default
|
||||||
|
virtual host with the ``ServerName`` set, it is still possible for an HTTP
|
||||||
|
request to match this virtual host and supply a fake ``Host`` header. Thus,
|
||||||
|
Django now requires that you set :setting:`ALLOWED_HOSTS` explicitly rather
|
||||||
|
than relying on webserver configuration.
|
||||||
|
|
||||||
Additionally, as of 1.3.1, Django requires you to explicitly enable support for
|
Additionally, as of 1.3.1, Django requires you to explicitly enable support for
|
||||||
the ``X-Forwarded-Host`` header if your configuration requires it.
|
the ``X-Forwarded-Host`` header (via the :setting:`USE_X_FORWARDED_HOST`
|
||||||
|
setting) if your configuration requires it.
|
||||||
Configuration for Apache
|
|
||||||
------------------------
|
|
||||||
|
|
||||||
The easiest way to get the described behavior in Apache is as follows. Create
|
|
||||||
a `virtual host`_ using the ServerName_ and ServerAlias_ directives to restrict
|
|
||||||
the domains Apache reacts to. Please keep in mind that while the directives do
|
|
||||||
support ports the match is only performed against the hostname. This means that
|
|
||||||
the ``Host`` header could still contain a port pointing to another webserver on
|
|
||||||
the same machine. The next step is to make sure that your newly created virtual
|
|
||||||
host is not also the default virtual host. Apache uses the first virtual host
|
|
||||||
found in the configuration file as default virtual host. As such you have to
|
|
||||||
ensure that you have another virtual host which will act as catch-all virtual
|
|
||||||
host. Just add one if you do not have one already, there is nothing special
|
|
||||||
about it aside from ensuring it is the first virtual host in the configuration
|
|
||||||
file. Debian/Ubuntu users usually don't have to take any action, since Apache
|
|
||||||
ships with a default virtual host in ``sites-available`` which is linked into
|
|
||||||
``sites-enabled`` as ``000-default`` and included from ``apache2.conf``. Just
|
|
||||||
make sure not to name your site ``000-abc``, since files are included in
|
|
||||||
alphabetical order.
|
|
||||||
|
|
||||||
.. _virtual host: http://httpd.apache.org/docs/2.2/vhosts/
|
|
||||||
.. _ServerName: http://httpd.apache.org/docs/2.2/mod/core.html#servername
|
|
||||||
.. _ServerAlias: http://httpd.apache.org/docs/2.2/mod/core.html#serveralias
|
|
||||||
|
|
||||||
|
|
||||||
.. _additional-security-topics:
|
.. _additional-security-topics:
|
||||||
|
|
|
@ -7,6 +7,7 @@ from django.http import HttpRequest, HttpResponse
|
||||||
from django.middleware.csrf import CsrfViewMiddleware, CSRF_KEY_LENGTH
|
from django.middleware.csrf import CsrfViewMiddleware, CSRF_KEY_LENGTH
|
||||||
from django.template import RequestContext, Template
|
from django.template import RequestContext, Template
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.test.utils import override_settings
|
||||||
from django.views.decorators.csrf import csrf_exempt, requires_csrf_token, ensure_csrf_cookie
|
from django.views.decorators.csrf import csrf_exempt, requires_csrf_token, ensure_csrf_cookie
|
||||||
|
|
||||||
|
|
||||||
|
@ -269,6 +270,7 @@ class CsrfViewMiddlewareTest(TestCase):
|
||||||
csrf_cookie = resp2.cookies[settings.CSRF_COOKIE_NAME]
|
csrf_cookie = resp2.cookies[settings.CSRF_COOKIE_NAME]
|
||||||
self._check_token_present(resp, csrf_id=csrf_cookie.value)
|
self._check_token_present(resp, csrf_id=csrf_cookie.value)
|
||||||
|
|
||||||
|
@override_settings(ALLOWED_HOSTS=['www.example.com'])
|
||||||
def test_https_bad_referer(self):
|
def test_https_bad_referer(self):
|
||||||
"""
|
"""
|
||||||
Test that a POST HTTPS request with a bad referer is rejected
|
Test that a POST HTTPS request with a bad referer is rejected
|
||||||
|
@ -281,6 +283,7 @@ class CsrfViewMiddlewareTest(TestCase):
|
||||||
self.assertNotEqual(None, req2)
|
self.assertNotEqual(None, req2)
|
||||||
self.assertEqual(403, req2.status_code)
|
self.assertEqual(403, req2.status_code)
|
||||||
|
|
||||||
|
@override_settings(ALLOWED_HOSTS=['www.example.com'])
|
||||||
def test_https_good_referer(self):
|
def test_https_good_referer(self):
|
||||||
"""
|
"""
|
||||||
Test that a POST HTTPS request with a good referer is accepted
|
Test that a POST HTTPS request with a good referer is accepted
|
||||||
|
@ -292,6 +295,7 @@ class CsrfViewMiddlewareTest(TestCase):
|
||||||
req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
req2 = CsrfViewMiddleware().process_view(req, post_form_view, (), {})
|
||||||
self.assertEqual(None, req2)
|
self.assertEqual(None, req2)
|
||||||
|
|
||||||
|
@override_settings(ALLOWED_HOSTS=['www.example.com'])
|
||||||
def test_https_good_referer_2(self):
|
def test_https_good_referer_2(self):
|
||||||
"""
|
"""
|
||||||
Test that a POST HTTPS request with a good referer is accepted
|
Test that a POST HTTPS request with a good referer is accepted
|
||||||
|
|
|
@ -84,7 +84,13 @@ class RequestsTests(unittest.TestCase):
|
||||||
self.assertEqual(request.build_absolute_uri(location="/path/with:colons"),
|
self.assertEqual(request.build_absolute_uri(location="/path/with:colons"),
|
||||||
'http://www.example.com/path/with:colons')
|
'http://www.example.com/path/with:colons')
|
||||||
|
|
||||||
@override_settings(USE_X_FORWARDED_HOST=False)
|
@override_settings(
|
||||||
|
USE_X_FORWARDED_HOST=False,
|
||||||
|
ALLOWED_HOSTS=[
|
||||||
|
'forward.com', 'example.com', 'internal.com', '12.34.56.78',
|
||||||
|
'[2001:19f0:feee::dead:beef:cafe]', 'xn--4ca9at.com',
|
||||||
|
'.multitenant.com', 'INSENSITIVE.com',
|
||||||
|
])
|
||||||
def test_http_get_host(self):
|
def test_http_get_host(self):
|
||||||
# Check if X_FORWARDED_HOST is provided.
|
# Check if X_FORWARDED_HOST is provided.
|
||||||
request = HttpRequest()
|
request = HttpRequest()
|
||||||
|
@ -131,6 +137,9 @@ class RequestsTests(unittest.TestCase):
|
||||||
'[2001:19f0:feee::dead:beef:cafe]',
|
'[2001:19f0:feee::dead:beef:cafe]',
|
||||||
'[2001:19f0:feee::dead:beef:cafe]:8080',
|
'[2001:19f0:feee::dead:beef:cafe]:8080',
|
||||||
'xn--4ca9at.com', # Punnycode for öäü.com
|
'xn--4ca9at.com', # Punnycode for öäü.com
|
||||||
|
'anything.multitenant.com',
|
||||||
|
'multitenant.com',
|
||||||
|
'insensitive.com',
|
||||||
]
|
]
|
||||||
|
|
||||||
poisoned_hosts = [
|
poisoned_hosts = [
|
||||||
|
@ -139,6 +148,7 @@ class RequestsTests(unittest.TestCase):
|
||||||
'example.com:dr.frankenstein@evil.tld:80',
|
'example.com:dr.frankenstein@evil.tld:80',
|
||||||
'example.com:80/badpath',
|
'example.com:80/badpath',
|
||||||
'example.com: recovermypassword.com',
|
'example.com: recovermypassword.com',
|
||||||
|
'other.com', # not in ALLOWED_HOSTS
|
||||||
]
|
]
|
||||||
|
|
||||||
for host in legit_hosts:
|
for host in legit_hosts:
|
||||||
|
@ -156,7 +166,7 @@ class RequestsTests(unittest.TestCase):
|
||||||
}
|
}
|
||||||
request.get_host()
|
request.get_host()
|
||||||
|
|
||||||
@override_settings(USE_X_FORWARDED_HOST=True)
|
@override_settings(USE_X_FORWARDED_HOST=True, ALLOWED_HOSTS=['*'])
|
||||||
def test_http_get_host_with_x_forwarded_host(self):
|
def test_http_get_host_with_x_forwarded_host(self):
|
||||||
# Check if X_FORWARDED_HOST is provided.
|
# Check if X_FORWARDED_HOST is provided.
|
||||||
request = HttpRequest()
|
request = HttpRequest()
|
||||||
|
@ -229,6 +239,16 @@ class RequestsTests(unittest.TestCase):
|
||||||
request.get_host()
|
request.get_host()
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(DEBUG=True, ALLOWED_HOSTS=[])
|
||||||
|
def test_host_validation_disabled_in_debug_mode(self):
|
||||||
|
"""If ALLOWED_HOSTS is empty and DEBUG is True, all hosts pass."""
|
||||||
|
request = HttpRequest()
|
||||||
|
request.META = {
|
||||||
|
'HTTP_HOST': 'example.com',
|
||||||
|
}
|
||||||
|
self.assertEqual(request.get_host(), 'example.com')
|
||||||
|
|
||||||
|
|
||||||
def test_near_expiration(self):
|
def test_near_expiration(self):
|
||||||
"Cookie will expire when an near expiration time is provided"
|
"Cookie will expire when an near expiration time is provided"
|
||||||
response = HttpResponse()
|
response = HttpResponse()
|
||||||
|
|
Loading…
Reference in New Issue