Django SSLMiddleware issue on Webfaction - django

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.

Related

Django multitenant: how to customize django setting "ACCOUNT_EMAIL_VERIFICATION" per tenant?

Django==1.11.7
django-tenant-schemas==1.8.0
django-allauth==0.34.0
Multi tenant site using django-tenant-schemas (postgres).
On different tenants, different settings are required.
More specifically, different setting is required for ACCOUNT_EMAIL_VERIFICATION
1 tenant needs ACCOUNT_EMAIL_VERIFICATION = "optional" while another one needs ACCOUNT_EMAIL_VERIFICATION ="mandatory"
Looking in the source code, the setting looks not customisable, it is fixed for the whole django site.
-> How can this be done?
You can compute the settings at runtime, since it's simply a python code.
Set that specific code programmatically, using your preferred way. One example:
# predefine the settings per tenant
ACCOUNT_EMAIL_VERIFICATION_PER_TENANT = {
"tenant_x": "mandatory",
"tenant_y": "optional",
}
# implement get_tenant
def get_tenant():
# here be tenant logic
pass
this_tenant = get_tenant()
ACCOUNT_EMAIL_VERIFICATION = ACCOUNT_EMAIL_VERIFICATION_PER_TENANT[get_tenant()]
Or you can have multiple settings files and join them as you wish. Here's how django does.
Oh and if you want to separate the logic from the settings file and have it run before evaluating the settings perhaps, you can inspect what is the trail of execution when you launch your server (e.g. starting from manage.py and insert your get_tenant logic somewhere in between). Most probably it will be somewhere starting from the wsgi.py file - where the application instance gets created and all the django fun begins.
When it comes to programming, you are always in control.
Solved in following way:
In settings.py:
try:
ACCOUNT_EMAIL_VERIFICATION = os.environ['ACCOUNT_EMAIL_VERIFICATION_OVERRIDE']
except KeyError:
ACCOUNT_EMAIL_VERIFICATION = 'mandatory'
In wsgi.py file of the tenant where e-mail verification is optional:
os.environ['ACCOUNT_EMAIL_VERIFICATION_OVERRIDE'] = 'optional'
wsgi files for the other tenants remain unchanged.
Gave the bounty to Adelin as he suggested to look into the wsgi file.
I stumbled upon this situation, and my dynamic solution is a middleware as follows without any hardcoding tenant's names
from django.conf import settings
from django.db import connection
from django_tenants.utils import get_public_schema_name, get_tenant_model
class TenantSettingsMiddleWare:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
self.request = request
self.overload_settings()
response = self.get_response(request)
return response
def overload_settings(self):
current_schema_obj = get_tenant_model().objects.get(schema_name=connection.schema_name)
settings.DEFAULT_FROM_EMAIL = 'admin#{}'.format(current_schema_obj.domains.last())
Cheers 🍻🍻🍻

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.

Why is my SSL middleware misbehaving?

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.

Django URL Detail

I have to assign to work on one Django project. I need to know about the URL say, http://....
Since with ‘urls.py’ we indeed have ‘raw’ information. How I come to know about the complete URL name; mean with
http+domain+parameters
Amit.
Look at this snippet :
http://djangosnippets.org/snippets/1197/
I modified it like this :
from django.contrib.sites.models import RequestSite
from django.contrib.sites.models import Site
def site_info(request):
site_info = {'protocol': request.is_secure() and 'https' or 'http'}
if Site._meta.installed:
site_info['domain'] = Site.objects.get_current().domain
site_info['name'] = Site.objects.get_current().name
else:
site_info['domain'] = RequestSite(request).domain
site_info['name'] = RequestSite(request).name
site_info['root'] = site_info['protocol'] + '://' + site_info['domain']
return {'site_info':site_info}
The if/else is because of different versions of Django Site API
This snippet is actually a context processor, so you have to paste it in a file called context_processors.py in your application, then add to your settings :
TEMPLATE_CONTEXT_PROCESSORS = DEFAULT_SETTINGS.TEMPLATE_CONTEXT_PROCESSORS + (
'name-of-your-app.context_processors.site_info',
)
The + is here to take care that we d'ont override the possible default context processor set up by django, now or in the future, we just add this one to the tuple.
Finally, make sure that you use RequestContext in your views when returning the response, and not just Context. This explained here in the docs.
It's just a matter of using :
def some_view(request):
# ...
return render_to_response('my_template.html',
my_data_dictionary,
context_instance=RequestContext(request))
HTTPS status would be handled differently by different web servers.
For my Nginx reverse proxy to Apache+WSGI setup, I explicitly set a header that apache (django) can check to see if the connection is secure.
This info would not be available in the URL but in your view request object.
django uses request.is_secure() to determine if the connection is secure. How it does so depends on the backend.
http://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.is_secure
For example, for mod_python, it's the following code:
def is_secure(self):
try:
return self._req.is_https()
except AttributeError:
# mod_python < 3.2.10 doesn't have req.is_https().
return self._req.subprocess_env.get('HTTPS', '').lower() in ('on', '1')
If you are using a proxy, you will probably find it useful that HTTP Headers are available in HttpRequest.META
http://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.META
Update: if you want to log every secure request, use the above example with a middleware
class LogHttpsMiddleware(object):
def process_request(self, request):
if request.is_secure():
protocol = 'https'
else:
protocol = 'http'
print "%s://www.mydomain.com%s" % (protocol, request.path)
Add LogHttpsMiddleware to your settings.py MIDDLEWARE_CLASSES

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.