Why is my SSL middleware misbehaving? - django

I'm using middleware to force certain pages to be served over HTTPS:
class SSLRedirect:
def __init__(self):
self.enabled = getattr(settings, 'SSL_ENABLED')
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 self.enabled:
logger.debug('SSL Disabled')
return
...
The problem is that my switch in settings.py does not seem to have an effect. If I load a url for which I haven't set SSL, I get the SSL Disabled message in my log as expected. However if I load a url for which SSL is set, but SSL_ENABLED is False in settings.py, the page still tries to load over HTTPS (and fails, because I'm doing this on ./mange.py runserver), and I get no log message. Why isn't this approach working?

This turned out not to be a bug with the code.
In the case that I did want to redirect, I was returning:
return HttpResponsePermanentRedirect(newurl)
My browser had cached this, so the redirect was happening even with the switch turned off. Clearing my browser cache fixed this.

Related

Why is Selenium causing a CSRF 403?

I'm trying to create a simple login test using Django and Selenium, but getting a 403 due to a CSRF failure. I'm expecting the middleware to add the cookie on the GET request and then parse it back out on the POST.
Here's what I've checked so far:
1. Is the cookie being set on the GET request to /accounts/login/?
Yes, the cookie is being set in the process_response method
2. Is the cookie available on the Selenium driver?
Yes
ipdb> self.selenium.get_cookies()
[{u'domain': u'localhost', u'name': u'csrftoken', u'value': u'DzNbEn9kZw0WZQ4OsRLouriFN5MOIQos', u'expiry': 1470691410, u'path': u'/', u'httpOnly': False, u'secure': True}]
3. Is the cookie found during the POST request?
No, this try/except from django.middleware.CsrfViewMiddleware.process_view fails:
source
try:
csrf_token = _sanitize_token(
request.COOKIES[settings.CSRF_COOKIE_NAME])
# Use same token next time
request.META['CSRF_COOKIE'] = csrf_token
except KeyError:
csrf_token = None
# Generate token and store it in the request, so it's
# available to the view.
request.META["CSRF_COOKIE"] = _get_new_csrf_key()
Code
class TestLogin(StaticLiveServerTestCase):
#classmethod
def setUpClass(cls):
cls.selenium = getattr(webdriver, settings.SELENIUM_WEBDRIVER)()
cls.selenium.maximize_window()
cls.selenium.implicitly_wait(5)
super(TestLogin, cls).setUpClass()
#classmethod
def tearDownClass(cls):
cls.selenium.quit()
super(TestLogin, cls).tearDownClass()
def test_login(self):
self.selenium.get('{}{}'.format(self.live_server_url, '/accounts/login/?next=/'))
assert "Django" in self.selenium.title
un_el = self.selenium.find_element_by_id('id_username').send_keys('the_un')
pw_el = self.selenium.find_element_by_id('id_password')
pw_el.send_keys('the_pw')
pw_el.send_keys(Keys.RETURN)
try:
WebDriverWait(self.selenium, 5).until(EC.title_contains("New Title"))
except TimeoutException as e:
msg = "Could not find 'New Title' in title. Current title: {}".format(self.selenium.title)
raise TimeoutException(msg)
finally:
self.selenium.quit()
Question
What can I try next to debug this?
Oldish question, but after getting stuck with this for a few hours the answer was simple.
From the docs:
If a browser connects initially via HTTP, which is the default for
most browsers, it is possible for existing cookies to be leaked. For
this reason, you should set your SESSION_COOKIE_SECURE and
CSRF_COOKIE_SECURE settings to True. This instructs the browser to
only send these cookies over HTTPS connections. Note that this will
mean that sessions will not work over HTTP, and the CSRF protection
will prevent any POST data being accepted over HTTP (which will be
fine if you are redirecting all HTTP traffic to HTTPS).
Like me, you are probably using django_extensions + Werkzeug for the majority of your work, and are by default running all of your local work over SSL.
If you're using unittest or Djangos version of it, I'd recommend that you modify these settings at test runtime, like so:
...
from django.conf import settings
class ProfilePagetest(LiveServerTestCase):
def setUp(self):
settings.CSRF_COOKIE_SECURE = False
settings.SESSION_COOKIE_SECURE = False
self.url = reverse('clientpage:profile')
self.username = 'name#names.com'
self.password = 'strange decisions...'
get_user_model().objects.create_user(self.username, self.username, self.password)
self.browser = webdriver.Firefox()
This should stop the CSRF validation issues.

Amazon ELB not working and throwing 500 Server Error

Created Amazon Load Balancer with 2 EC2 micro instance behind.
2 EC2 micro instances are having python services.
Services are running fine and responding while directly calling them
Services NOT running when we call them via public DNS of Load Balancer. ELB throws 500 error.
Example of Direct calling EC2 instances services:
ec2-54-200-1-2.us-west-2.compute.amazonaws.com/myservice ==> returns data
Example of calling Load Balancer:
test-12345678.us-west-2.elb.amazonaws.com/myservice ==> returns 500 error
Further points:
DJANGO property ALLOWED_HOSTS is set to ['*'] but did not work.
Using HTTP protocol i.e. Mapping Load Balancer Protocol = HTTP with port 80 to Instance Protocol = HTTP with port 80
I acknowledge this is a very old question but I used a solution that I believe is better and doesn't jeopardize the security of the system by setting the ALLOWED_HOSTS = ['*'].
Here's a piece of Middleware class I wrote that I feel can be reused at will.
It inherits from CommonMiddleware and it is the one who should be used instead of CommonMiddleware in the MIDDLEWARE_CLASSES setting in settings.py.
from django.middleware.common import CommonMiddleware
from django.conf import settings
from django.http.response import HttpResponse
from django.utils import importlib
class CommonWithAllowedHeartbeatMiddleWare(CommonMiddleware):
"""
A middleware class to take care of LoadBalancers whose HOST headers might change dynamically
but are supposed to only access a specific path that returns a 200-OK status.
This middleware allows these requests to the "heartbeat" path to bypass the ALLOWED_HOSTS mechanism check
without jeopardizing the rest of the framework.
(for more details on ALLOWED_HOSTS setting, see: https://docs.djangoproject.com/en/1.5/ref/settings/#allowed-hosts )
settings.HEARTBEAT_PATH (default: "/heartbeat/"):
This will be the path that is cleared and bypasses the common middleware's ALLOWED_HOSTS check.
settings.HEARTBEAT_METHOD (default: None):
The full path to the method that should be called that checks the status.
This setting allows you to hook a method that will be called that can perform any kind of health checks and return the result.
If no method is specified a simple HttpResponse("OK", status_code=200) will be returned.
The method should expect the HttpRequest object of this request and return either True, False or a HttpResponse object.
- True: middleware will return HttpResponse("OK")
- False: middleware will return HttpResponse("Unhealthy", status_code=503) [status 503 - "Service Unavailable"]
- HttpResponse: middleware will just return the HttpResponse object it got back.
IMPORTANT NOTE:
The method being called and any method it calls, cannot call HttpRequest.get_host().
If they do, the ALLOWED_HOSTS mechanism will be called and SuspeciousOperation exception will be thrown.
"""
def process_request(self, request):
# Checking if the request is for the heartbeat path
if request.path == getattr(settings, "HEARTBEAT_PATH", "/heartbeat/"):
method_path = getattr(settings, "HEARTBEAT_METHOD", None)
# Checking if there is a method that should be called instead of the default HttpResponse
if method_path:
# Getting the module to import and method to call
last_sep = method_path.rfind(".")
module_name = method_path[:last_sep]
method = method_path[(last_sep+1):]
module = importlib.import_module(module_name)
# Calling the desired method
result = getattr(module, method)(request)
# If the result is alreay a HttpResponse object just return it
if isinstance(result, HttpResponse):
return result
# Otherwise, if the result is False, return a 503-response
elif not result:
return HttpResponse("Unhealthy", status_code=503)
# Just return OK
return HttpResponse("OK")
# If this is not a request to the specific heartbeat path, just let the common middleware do its magic
return CommonMiddleware.process_request(self, request)
Of course I realize that this mechanism bypasses the CommonMiddleware altogether, but it does that only when the heartbeat path is requested, so I feel this is a little price to pay.
Hope somebody else finds it useful.

Django Login/Session Not Sticking Over HTTPS

I'm working on a Django site hosted on an Apache server with mod_wsgi.
The site is only on https as we have Apache redirect any http requests to https.
The project I'm working on is called Skittle.
I have a custom user model called SkittleUser which inherits from AbstractBaseUser and is set as the AUTH_USER_MODEL in our settings.py file.
os.environ['HTTPS'] = "on" is set in the wsgi.py file.
SESSION_COOKIE_SECURE = True and CSRF_COOKIE_SECURE = True are both set in settings.py
The issue that we are having right now is that logging in as a user is unreliable.
When you go to the login page, some times it works while other times it doesn't.
Then while browsing the site, you will suddenly lose your session and be kicked down to an anonymous user.
We are currently running our test site here if anybody wants to take a look:
https://skittle.newlinetechnicalinnovations.com/discover/
Our production site is at www.dnaskittle.com but does not yet incorporate user logins as the feature doesn't work.
A test user:
email: test#dnaskittle.com
password: asdf
If the login does not work, you will see in the top right "Welcome, Login" in which case, just try clicking on Login again and use the same credentials.
It may take 5-6 times of doing that process before you will actually get logged in.
You will know it works when you see "Welcome Tester, Logout, My Genomes"
After you are logged in, it may stick for a while, but browsing around to other pages will eventually kick you back off.
There is no consistent amount of pages that you can go through before this happens, and it doesn't happen on any specific page.
Any insights on this would be greatly appreciated.
Also of note, going to the Django admin page (which is not our code, but base django code) has the same issue.
I've gotten this issue sorted out now.
Users can not login while on HTTPS using while using the listed setup.
What I did:
In settings.py add:
SESSION_SAVE_EVERY_REQUEST = True
SESSION_COOKIE_NAME = 'DNASkittle'
I also wiped the current django_sessions database in case that was causing issues with old lingering data.
I did not setup extra middleware or SSLRedirect, and everything is working all ship shape.
It's little longer and complex the SSL system. to handle the session/login properly with https you can set a configuration with the session_cookies.
settings.py:
ENABLE_SSL=False #in the debug mode in the production passed it to True
MIDDLEWARE_CLASSES = (
'commerce.SSLMiddleware.SSLRedirect', #)
# the session here is to use in your views when a user is connected
SESSION_COKIE_NAME='sessionid'
#the module to store sessions data
SESSION_ENGINE='django.contrib.sessions.backends.db'
#age of cookie in seconds (default: 2 weeks)
SESSION_COOKIE_AGE=7776000 # the number of seconds in 90 days
#whether a user's session cookie expires when the web browser is closed
SESSION_EXPIRE_AT_BROWSER_CLOSE=False
#whether the session cookie should be secure (https:// only)
SESSION_COOKIE_SECURE=False
SSLMiddleware is a file you going to define in your project like.
SSLMiddleware.py:
from django.conf import settings
from django.http import HttpResponseRedirect, HttpResponsePermanentRedirect
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
if 'HTTP_X_FORWARD_SSL' in request.META:
return request.META['HTTP_X_FORWARD_SSL'] == 'on'
return False
def _redirect(self,request,secure):
protocol = secure and "https" or "http"
newurl ="%s://%s%s" % (protocol, request, request.get_full_path())
if settings.DEBUG and request.method=="POST":
raise RuntimeError, \
return HttpResponsePermanentRedirect(newurl)
Now in your urls which should handle your logins or connection add the line
urls.py:
from project import settings
urlpatterns += patterns('django.contrib.auth.views',
(r'^login/$','login',{'template_name':'registration/login.html','SSL':settings.ENABLE_SSL},'login'),
Try to adapt this to your code. and don't forget to turn ENABLE_SSL to True
For users login with HTTPS, you have to enable SSL and use code that matches your case to use it. For the user session you can use this:
First check if you have in settings.py:
INSTALLED_APPS= ('django.contrib.sessions',)
and use the request.sessions in your file.py:
request.session['id']='the_id_to_store_in_browser' or 'other_thing'
Here you use request.session like a special SessionStore class which is similar to python dictionary.
In your views.py for example before rendering a template or redirecting test the cookie with the code:
if :
# whatever you want
if request.session.test_cookie_worked():
request.session.delete_test_cookie()
return HttpResponseRedirect(up-to-you)
else:
# whatever you want
request.session.set_test_cookie()
return what-you-want
with this code, the user session will stick a wide, depending on your SESSION_COOKIE_AGE period.

Django SSLMiddleware issue on Webfaction

I've been using the following SSLMiddleware on Linode for a while, and my SSL worked perfectly on that, now I've changed my server to Webfaction, and all of sudden, my HTTPS pages are not working in a way as it's redirected to https page correctly, but all my css files, images within the css files(no absolute url), javascript have all become non secure sources(referring to http:// instead of https://), I'm really puzzled right now as I don't know if it's got to do with SSLMiddleware or something else, I haven't changed anything in settings.py either apart from database parameter value.. Please help. Thanks in advance.
__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 settings.ENABLE_SSL:
if not secure == self._is_secure(request):
return self._redirect(request, secure)
else:
return
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)
I recently implemented SSL on WebFaction without any custom Middleware tinkering and it was a very straightforward process.
Have a look here: http://community.webfaction.com/questions/512/how-do-i-set-up-a-https-ssl-django-site
If that doesn't help, open up a ticket with them. They're usually very good about resolving issues very quickly.

Django: HTTPS for just login page?

I just added this SSL middleware to my site http://www.djangosnippets.org/snippets/85/ which I used to secure only my login page so that passwords aren't sent in clear-text. Of course, when the user navigates away from that page he's suddenly logged out. I understand why this happens, but is there a way to pass the cookie over to HTTP so that users can stay logged in?
If not, is there an easy way I can use HTTPS for the login page (and maybe the registration page), and then have it stay on HTTPS if the user is logged in, but switch back to HTTP if the user doesn't log in?
There are a lot of pages that are visible to both logged in users and not, so I can't just designate certain pages as HTTP or HTTPS.
Actually, modifying the middleware like so seems to work pretty well:
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 request.user.is_authenticated():
secure = True
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://secure" or "http://www"
newurl = "%s.%s%s" % (protocol,settings.DOMAIN,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)
Better is to secure everything. Half secure seems secure, but is totally not. To put it blank: by doing so you are deceiving your end users by giving them a false sense of security.
So either don't use ssl or better: use it all the way. The overhead for both server and end user is negligible.