Add FakeSSLMiddleware
to the top of your MIDDLEWARE_CLASSES
stack when running tests or developing locally to allow https:// links to operate correctly. Can be used in conjunction with other SSL middleware to allow critical tests to be performed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | import re
SECURE_GET_VARIABLE = 'FAKE_HTTPS'
HTTPS_PATTERN = re.compile(r'https://([\w./\-:\?=%_]+)', re.IGNORECASE)
FAKE_HTTPS_PATTERN = re.compile(
r'http://([\w./\-:\?=%%]+)[\?&]%s=1' % SECURE_GET_VARIABLE, re.IGNORECASE)
ATTRIBUTE_NAMES = ('href', 'action')
ATTRIBUTE_PATTERN = re.compile(
r"""(?P<attribute_name>%s)\s*="""
r"""\s*(?P<quote_delimiter>["'])(?P<attribute_value>.*?[^\\])\2""" %
'|'.join(ATTRIBUTE_NAMES), re.IGNORECASE)
ATTRIBUTE_FORMAT = (
"%(attribute_name)s="
"%(quote_delimiter)c%(attribute_value)s%(quote_delimiter)c")
# note that SECURE_GET_VARIABLE is currently assume to be at the end of
# the query string. I have not been bitten by this assumption yet...
def secure_url(url):
""" Adds SECURE_GET_VARIABLE to query string within url if absent.
>>> secure_url('http://localhost:8000')
'http://localhost:8000?FAKE_HTTPS=1'
>>> secure_url('http://localhost:8000?test=true')
'http://localhost:8000?test=true&FAKE_HTTPS=1'
>>> secure_url('http://localhost:8000?test=true&FAKE_HTTPS=1')
'http://localhost:8000?test=true&FAKE_HTTPS=1'
>>> secure_url('http://localhost:8000?FAKE_HTTPS=1')
'http://localhost:8000?FAKE_HTTPS=1'
>>> url = 'http://localhost:8000/login/?next=/profile/%3FFAKE_HTTPS%3D1'
>>> secure_url(url)
'http://localhost:8000/login/?next=/profile/%3FFAKE_HTTPS%3D1&FAKE_HTTPS=1'
"""
if FAKE_HTTPS_PATTERN.match(url):
return url # unnecessary
if '?' in url:
joiner = '&'
else:
joiner = '?'
return '%s%c%s=1' % (url, joiner, SECURE_GET_VARIABLE)
def https_to_fake(string):
""" Convert https:// URL to http:// with SECURE_GET_VARIABLE.
>>> https_to_fake('https://localhost:8000')
'http://localhost:8000?FAKE_HTTPS=1'
>>> https_to_fake('https://localhost:8000?existing_variable=1')
'http://localhost:8000?existing_variable=1&FAKE_HTTPS=1'
>>> url = 'https://localhost:8000/login/?next=/profile/%3FFAKE_HTTPS%3D1'
>>> https_to_fake(url)
'http://localhost:8000/login/?next=/profile/%3FFAKE_HTTPS%3D1&FAKE_HTTPS=1'
"""
def _secure_url(match):
return secure_url('http://%s' % match.groups()[0])
return HTTPS_PATTERN.sub(_secure_url, string)
def fake_to_https(string):
""" Convert http:// URL to https:// if contains SECURE_GET_VARIABLE.
>>> fake_to_https('http://localhost:8000?FAKE_HTTPS=1')
'https://localhost:8000'
>>> fake_to_https('http://localhost:8000?existing_variable=1&FAKE_HTTPS=1')
'https://localhost:8000?existing_variable=1'
>>> url = 'http://localhost:8000/go/?next=/%3FFAKE_HTTPS%3D1&FAKE_HTTPS=1'
>>> fake_to_https(url)
'https://localhost:8000/go/?next=/%3FFAKE_HTTPS%3D1'
"""
return FAKE_HTTPS_PATTERN.sub(r'https://\1', string)
def secure_relative_urls(string):
""" Convert add SECURE_GET_VARIABLE to relative URLs in href attributes.
>>> secure_relative_urls('... href="/test" ...')
'... href="/test?FAKE_HTTPS=1" ...'
>>> secure_relative_urls('... href="http://example.com" ...')
'... href="http://example.com" ...'
>>> secure_relative_urls('... href="/test" ...')
'... href="/test?FAKE_HTTPS=1" ...'
"""
def _secure_relative_url(match):
params = match.groupdict()
if params['attribute_value'].find('://') >= 0:
pass # ignore absolute URLs
else:
params['attribute_value'] = secure_url(params['attribute_value'])
return ATTRIBUTE_FORMAT % params
return ATTRIBUTE_PATTERN.sub(_secure_relative_url, string)
class FakeSSLMiddleware(object):
""" Fake SSL For local/testing environments. """
def __init__(self, *args, **kwargs):
if not settings.DEBUG and not getattr(settings, 'TESTING', False):
raise RuntimeError, "Disable on production site!"
def process_request(self, request):
""" Hack request object to fake security.
- replace is_secure method with test for SECURE_GET_VARIABLE in GET
- remove SECURE_GET_VARIABLE from query string
- undo build_absolute_uri method's work of adding https:// to
relative URIs where request.is_secure...
(see django.http.utils.fix_location_header)
- make HTTP_REFERER look secure if contained SECURE_GET_VARIABLE,
so that CSRF doesn't catch us out.
"""
GET = request.GET.copy() # immutable QueryDict
secure = bool(GET.pop(SECURE_GET_VARIABLE, False))
request.GET = GET
request.is_secure = lambda: secure
request.environ['QUERY_STRING'] = \
re.sub(r'&?%s=1' % SECURE_GET_VARIABLE,'',
request.environ['QUERY_STRING'])
setattr(request,'_build_absolute_uri', request.build_absolute_uri)
def build_http_only_uri(location=None):
uri = request._build_absolute_uri(location)
return https_to_fake(uri)
request.build_absolute_uri = build_http_only_uri
if 'HTTP_REFERER' in request.META:
request.META['HTTP_REFERER'] = \
fake_to_https(request.META['HTTP_REFERER'])
def process_response(self, request, response):
""" Replace https:// with http:// within HTML responses + redirects """
if response.status_code in (301, 302):
headers = response._headers.copy() # immutable QueryDict
headers['location'] = (
'Location', https_to_fake(response['Location']))
response._headers = headers
elif response.status_code == 200:
if response['Content-Type'].find('html') >= 0:
try:
decoded_content = response.content.decode('utf-8')
except UnicodeDecodeError:
decoded_content = response.content
response.content = \
https_to_fake(decoded_content).encode('utf-8')
if request.is_secure():
response.content = secure_relative_urls(response.content)
return response
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 4 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months ago
- Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
- Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
- Help text hyperlinks by sa2812 1 year, 7 months ago
Comments
You forgot to add
from django.conf import settings
at the top of the snippet.Otherwise, it's working great, thanks!
#
Please login first before commenting.