Login

SSL Middleware

Author:
sjzabel
Posted:
March 6, 2007
Language:
Python
Version:
Pre .96
Tags:
middleware ssl
Score:
17 (after 17 ratings)

SSL Middleware

This middleware answers the problem of redirecting to (and from) a SSL secured path by stating what paths should be secured in urls.py file. To secure a path, add the additional view_kwarg 'SSL':True to the view_kwargs.

For example

urlpatterns = patterns('some_site.some_app.views', (r'^test/secure/$','test_secure',{'SSL':True}), )

All paths where 'SSL':False or where the kwarg of 'SSL' is not specified are routed to an unsecure path.

For example

urlpatterns = patterns('some_site.some_app.views', (r'^test/unsecure1/$','test_unsecure',{'SSL':False}), (r'^test/unsecure2/$','test_unsecure'), )

Gotcha's Redirects should only occur during GETs; this is due to the fact that POST data will get lost in the redirect.

Benefits/Reasoning

A major benefit of this approach is that it allows you to secure django.contrib views and generic views without having to modify the base code or wrapping the view.

This method is also better than the two alternative approaches of adding to the settings file or using a decorator.

It is better than the tactic of creating a list of paths to secure in the settings file, because you DRY. You are also not forced to consider all paths in a single location. Instead you can address the security of a path in the urls file that it is resolved in.

It is better than the tactic of using a @secure or @unsecure decorator, because it prevents decorator build up on your view methods. Having a bunch of decorators makes views cumbersome to read and looks pretty redundant. Also because the all views pass through the middleware you can specify the only secure paths and the remaining paths can be assumed to be unsecure and handled by the middleware.

This package is inspired by Antonio Cavedoni's SSL Middleware

Notes: Updated per Jay Parlar at http://www.djangosnippets.org/snippets/240/ - Added a test for the way webfaction handles forwarded SSL requests.

 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
__license__ = "Python"
__copyright__ = "Copyright (C) 2007, Stephen Zabel"
__author__ = "Stephen Zabel - sjzabel@gmail.com"
__contributors__ = "Jay Parlar - parlar@gmail.com"

from django.conf import settings
from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect, get_host

SSL = 'SSL'

class SSLRedirect:
    
    def process_view(self, request, view_func, view_args, view_kwargs):
        if SSL in view_kwargs:
            secure = view_kwargs[SSL]
            del view_kwargs[SSL]
        else:
            secure = False

        if not secure == self._is_secure(request):
            return self._redirect(request, secure)

    def _is_secure(self, request):
        if request.is_secure():
	    return True

        #Handle the Webfaction case until this gets resolved in the request.is_secure()
        if 'HTTP_X_FORWARDED_SSL' in request.META:
            return request.META['HTTP_X_FORWARDED_SSL'] == 'on'

        return False

    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 SSL redirect while maintaining POST data.
           Please structure your views so that redirects only occur during GETs."""

        return HttpResponsePermanentRedirect(newurl)

More like this

  1. @url decorator - getting rid of urlpatterns by southern_sun 7 years, 8 months ago
  2. TLS(SSL) middleware, per URL pattern or whole site by robmadole 4 years, 4 months ago
  3. Smart append slash middleware by akaihola 7 years, 2 months ago
  4. SSL Redirect Middleware by zbyte64 6 years, 9 months ago
  5. AppendSlashMiddleware by sjzabel 8 years, 1 month ago

Comments

sjzabel (on July 3, 2007):

Cool, I updated my code with your check for the webfaction header. I like the idea of checking both better than having two branches of the code.

Cheers

#

sleytr (on April 23, 2008):

To accept both http and https requests with same view I added "secure!=1 and " to the line 20;

 if secure!=1 and not secure == request.is_secure():

Then marked that view with {'SSL':1} in urls file.

#

sleytr (on April 23, 2008):

Sorry, in my previous comment I forgotten the fact that 1==True in Python. So, the value "1" must be changed with some another value. I changed them with -1.

#

davenaff (on June 11, 2008):

Nice work. I wanted to be able to run the middleware in dev (no ssl cert) and production (ssl cert) so, I made this change to lines 20&21:

    if settings.SSL_ENABLED:
        if not secure == self._is_secure(request):
            return self._redirect(request, secure)
    else:
        return

I then added this line to my production settings.py and made it false in my dev settings.py:

SSL_ENABLED = True

#

tim.savage (on July 31, 2008):

I've found that I only want to explicitly choose a protocol in certain views, I have made a change to process_view that will only redirect when it is explicitly asked for ie:

def process_view(self, request, view_func, view_args, view_kwargs):
    # If SSL requirement is specified redirect else leave as is
    if SSL in view_kwargs:
        secure = view_kwargs[SSL]
        del view_kwargs[SSL]

        if not secure == request.is_secure():
            return self._redirect(request, secure)

Now any view with the SSL option included will be forced to the requested protocol but the others will be ignored.

#

tim.savage (on July 31, 2008):

Note!

If you are running under IIS with PyISAPIe the example PyISAPIe handler does not include an implementation for the is_secure method and you will end up with a redirect loop. This can be solved by modifying the PyISAPIeRequest class and adding the method:

def is_secure(self):
    # This is an assumption, very little meta information is provided by the PyISAPIe module
    return self.META.get('SERVER_PORT', '80') == '443'

This solution is not perfect as it is assumed that SSL is configured and working on port 443 but as mentioned in the function there is no other information that allows for a more "informed" decision.

#

pihentagy (on September 26, 2008):

Yes, like tim.savage, I find situations, where you should allow both http and https access, and leave them, as is.

That is a matter of taste, which is the default...

  • to force http
  • to force https
  • to leave page as is

#

ryanshow (on April 26, 2010):

Lines 14 through 18 can be simplified into:

secure = view_kwargs.get(SSL, False) 
if SSL in view_kwargs:
    del view_kwargs[SSL]

It's not much, but I think it creates a more readable section of code.

If you still want be able to develop with runserver, replace the secure assignment line with:

secure = view_kwargs.get(SSL, False) if not settings.DEBUG else False

and make sure you're in debug mode whenever you use runserver.

Great snippet, though. Thanks!

#

propanbutan (on January 7, 2011):

As spookeylukey wrote, what you really want is:

secure = view_kwargs.pop(SSL, False)

#

sparky (on April 26, 2013):

In Django 1.5 django.http.get_host() is replaced with request.get_host()

#

Please login first before commenting.