SSL Middleware

 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 6 years, 7 months ago
  2. TLS(SSL) middleware, per URL pattern or whole site by robmadole 3 years, 4 months ago
  3. Smart append slash middleware by akaihola 6 years, 2 months ago
  4. SSL Redirect Middleware by zbyte64 5 years, 9 months ago
  5. AppendSlashMiddleware by sjzabel 7 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()

#

(Forgotten your password?)