Allows url patterns to include a boolean indicating whether a view requires TLS(SSL). The accompanying middleware handles the redirects needed to make sure that it upholds this requirement.
WARNING: this monkey-patches some Django internals and is difficult to test since Django's TestClient does not support TLS. If you use this make sure you test it thouroughly.
Add this to your Django settings
USE_TLS = True # The default for this setting is False.
URL pattern usage
url(r'^login$', 'myproject.login.index',
{'require_tls': True}, name='login-index'),
Use require_tls
True to force the middleware to perform redirects needed to
make sure your are serving this view using https.
Use require_tls
False to force the middleware to redirect to http. Be
careful with this setting, this may not behave as you expect. If you don't
care if the view is served via https or http then do not include
require_tls
in the pattern.
If you wish to have every view in the site served with TLS then specify the following Django setting
ALWAYS_USE_TLS = True # Django setting, use TLS for every view
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 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 | """
Allows url patterns to include a boolean indicating whether a view requires
TLS(SSL). The accompanying middleware handles the redirects needed to make
sure that it upholds this requirement.
WARNING: this monkey-patches some Django internals and is difficult to test
since Django's TestClient does not support TLS. If you use this make sure you
test it thouroughly.
Add this to your Django settings::
USE_TLS = True # The default for this setting is False.
URL pattern usage::
url(r'^login$', 'myproject.login.index',
{'require_tls': True}, name='login-index'),
Use ``require_tls`` True to force the middleware to perform redirects needed to
make sure your are serving this view using https.
Use ``require_tls`` False to force the middleware to redirect to http. Be
careful with this setting, this may not behave as you expect. If you don't
care if the view is served via https or http then do not include
``require_tls`` in the pattern.
If you wish to have every view in the site served with TLS then specify the
following Django setting::
ALWAYS_USE_TLS = True # Django setting, use TLS for every view
"""
from django.conf import settings
from django.core import urlresolvers
from django.http import HttpResponsePermanentRedirect
from django.http import get_host
from django.utils.encoding import smart_str
ALWAYS_USE_TLS = getattr(settings, 'ALWAYS_USE_TLS', False)
USE_TLS = getattr(settings, 'USE_TLS', ALWAYS_USE_TLS)
def resolve_pattern(path, urlconf=None):
"""
Works the same as Django's url resolver but instead of return a view
function that handles a specific path it gives you the pattern that matches
it
"""
return urlresolvers.get_resolver(urlconf).resolve_pattern(path)
def resolve_pattern_method(self, path):
"""
Monkey-patchable to the RegexURLResolver class
The difference is that this method will return the pattern instead of what
Django's RegexURLResolver.resolve returns
"""
tried = []
match = self.regex.search(path)
if match:
new_path = path[match.end():]
for pattern in self.url_patterns:
try:
if hasattr(pattern, 'resolve_pattern'):
# Use our resolve_pattern method
sub_match = pattern.resolve_pattern(new_path)
if isinstance(sub_match, urlresolvers.RegexURLPattern):
return sub_match
else:
sub_match = pattern.resolve(new_path)
except urlresolvers.Resolver404, e:
sub_tried = e.args[0].get('tried')
if sub_tried is not None:
tried.extend([(pattern.regex.pattern + ' ' + t) for t in sub_tried])
else:
tried.append(pattern.regex.pattern)
else:
if sub_match:
if hasattr(pattern, 'callback'):
# Found our pattern
return pattern
sub_match_dict = dict([(smart_str(k), v) for k, v in match.groupdict().items()])
sub_match_dict.update(self.default_kwargs)
for k, v in sub_match[2].iteritems():
sub_match_dict[smart_str(k)] = v
return sub_match[0], sub_match[1], sub_match_dict
tried.append(pattern.regex.pattern)
raise urlresolvers.Resolver404, {'tried': tried, 'path': new_path}
raise urlresolvers.Resolver404, {'path' : path}
# Monkey patch the additional methods
urlresolvers.RegexURLResolver.resolve_pattern = resolve_pattern_method
def reverse_decorator(func):
def inner(*args, **kwargs):
if 'request' in kwargs:
request = kwargs['request']
del kwargs['request']
else:
request = None
# Call the real reverse function
abs_path = func(*args, **kwargs)
if not request or not USE_TLS:
# Short-circuit, no need to continue becuase there is no request we
# can get smart with or TLS is turned off
return abs_path
# Let's figure out if we need this URL to be secure or not
pattern = resolve_pattern(abs_path)
if 'require_tls' in pattern.default_args:
secure = pattern.default_args['require_tls']
else:
secure = None
if not secure is None and request and \
not secure == is_secure(request):
# Secure indicates either http or https and the request is not
# currently being served that way
protocol = 'https' if secure else 'http'
abs_path = '%s://%s%s' % (protocol, get_host(request), abs_path,)
return abs_path
return inner
urlresolvers.reverse = reverse_decorator(urlresolvers.reverse)
def is_secure(request):
"""
Determines if the given request is over HTTPS or not
"""
if request.is_secure():
return True
return False
class TlsRedirect(object):
def process_view(self, request, view_func, view_args, view_kwargs):
if 'require_tls' in view_kwargs:
secure = view_kwargs['require_tls']
del view_kwargs['require_tls']
else:
secure = None
if not any([USE_TLS, ALWAYS_USE_TLS]):
return None
if ALWAYS_USE_TLS:
# This site is setup to always use TLS so we set the secure bit to
# always be true
secure = True
# If secure is None, the require_tls was not present in the view
# keyword args at all. In this case, we don't want to redirect.
# Leaving this keyword off probably indicates that the particular url
# doesn't require http or https, we go with whatever we got
if not secure is None and \
not secure == is_secure(request):
return self._redirect(request, secure)
def _redirect(self, request, secure):
protocol = secure and 'https' or 'http'
newurl = '%s://%s%s' % (
protocol, get_host(request), request.get_full_path(),)
if settings.DEBUG and request.method == 'POST':
raise RuntimeError('Django can\'t perform a TLS redirect while '
'maintaining POST data. Please structure your views so that '
'redirects only occur during GETs.')
return HttpResponsePermanentRedirect(newurl)
|
More like this
- Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
- JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks 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, 6 months ago
Comments
Hmm, I think that better (more generic) way to specify which method shoud be protected using SSL is decorator for view and/or routers:
eg,
...
#
Yep, a decorator is simpler but we needed a bit more control at the time and the option to secure the entire site. And with the monkey-patching this one is certainly not the most comfortable.
#
Please login first before commenting.