Login

Fake SSL Middleware for Tests and Local Development

Author:
DrMeers
Posted:
May 5, 2010
Language:
Python
Version:
1.1
Score:
1 (after 1 ratings)

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

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 4 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 11 months ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months ago

Comments

dmalinovsky (on March 21, 2012):

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.