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.
Related
I'm having very weird issue with session authentication.
I'm using session authentication over DRF APIs for some legacy django views. And login process implemented with DRF and react app.
The server code for sign-in looks like this:
class AuthViewSet(viewsets.ViewSet):
def create(self, request, *args, **kwargs):
is_session_auth = request.data.get('session_auth', False)
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
token = serializer.save()
if is_session_auth is True:
login(request, token.user)
return Response(dict(token=token.key))
So, as far as I expected, the response should has set-cookie header which makes browser to set sessionid and csrftoken cookies. And it works well most of times.
But rarely, some users experiencing login failures. I'm faild to reproduce it, but this is what they describe:
When user submit sign in form, the request sent to the server and got response successfully.
Then the javascript app push url to login/complete, as expected.
But in login complete view, the request failed to authenticate. And the view redirect request to the original login view.
User retry login, but got same result.
I have no idea how this happened so rarely, to the so small amount of users. Am I miss something?
Thanks for help.
==============
Add some more information.
I dig into this problem, and find out that users' sessions are not decodable.
session.get_decoded() for session in Session.objects.all() returns Session data corrupted error. Is it relevant to login failure?
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.
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.
Dear omnoscient beings at Stackoverflow,
In Django 1.3 I am making a process_request middleware that gets a token from an url, logs the user in (if it's correct) and removes the token from the url. However:
I) Django recommends against accessing POST/GET data in middleware, I'm not really sure why so... Does the same apply to request.path ? https://docs.djangoproject.com/en/dev/topics/http/middleware/#process-view
II) I want to remove the token from the URL, so /album3/pic42/~53Cr3t70K3n/like/ -> /album3/pic42/like/. Changing request.path however does not work. The page will not be found, while
The middleware does process correctly (verified by print)
Directly entering /album3/pic42/like/ does work
The error (with token) shows Request URL: http://www.site.com/album3/pic42/like/
Is there a fix for this, or am I approaching this from the wrong angle entirely?
Thanks in advance!
I just realized that to change it client side, obviously I need a redirect (why didn't I think of that...). However, it would still be useful to be able to rewrite it just server-side without a new request, for example for accessing a personalized image.
P.s.: more details if needed, feel free to skip
I am working on a site that (will) sends personalized emails to users. I would like users to be able to click links in the email and be logged in automatically, by means of a token in the email link. This is in addition to normal login. (I know it's a less secure because people might forward an e-mail, but it's good enough for my site). The url would look something like this:
/album3/pic42/~53Cr3t70K3n/like/ (with http://www.site.com stripped, Django does that)
I am writing a middleware to match this and log the user in when appropriate, a authentication backend for accepting a token as valid credentials and a token model.
Middleware process_request function:
def process_request(self, request):
if '/~' in request.path:
result = re.search('(.*)/~(.+?)/(.*)', request.path)
(uidb36, token) = result.group(2).split('-', 2)
user = authenticate(uidb36 = uidb36, token = token)
if user: login(request, user)
return HttpResponseRedirect('%s/%s' % (result.group(1), result.group(3)) + ('?' + '&'.join('='.join(item) for item in request.GET.items()) if request.GET else ''))
return None
Right now it works with redirects, I'd like to also be able to do it internally.
If you don't want to mess up with upload handlers, I have a better solution for you:
Create a rule in your urls.py to specifically catch visits with tokens
Put it at the start of the urlpatterns to make sure that it will be evaluated first. Something like this will do:
(r'/~', 'my_app_name.my_redirect_view'),
Create a view:
def my_redirect_view(request):
#Compiled regular expressions work much faster
beloved_tokens = re.compile(r'(.*)/~(.+?)/(.*)')
result = beloved_tokens.search(request.path)
try:
(uidb36, token) = result.group(2).split('-', 2)
path_end = result.group(3)
# We use "try" to be sure that no one had
# messed with our beloved tokens:
except AttributeError:
raise Http404
else:
user = authenticate(uidb36 = uidb36, token = token)
if user:
login(request, user)
return HttpResponseRedirect('%s/%s' % (result.group(1), result.group(3)) + ('?' + '&'.join('='.join(item) for item in request.GET.items()) if request.GET else ''))
else:
raise Http404
IMO changing request.path is more bad(if it can be called bad) compared to accessing GET/POST parameters, so just pass token as GET parameter and login based on token and do not redirect or do request.path modifications
I see token as a added attribute to a valid url, hence a middleware acknowledges that and does something with that token, but url still is handled by the correct view, so middleware to me seems a very logical fit here.
I've written a small bit of middleware that catches if a user is using a temporary password and, if so, redirects them to a page that forces them to create a new password. My problem is that the page works fine when the user is logged in and NOT using a temp password (i.e. they go to the change password URL manually), but when they ARE using a temp password the redirect from the middleware yields a 403 Forbidden page.
The middleware does one other thing in process_view after the temp password check, but this is the relevant code:
class MyMiddleware( object ):
def process_view( self, request, view_func, view_args, view_kwargs ):
if request.user.is_authenticated( ):
try:
if request.user.get_profile( ).using_temp:
return HttpResponseRedirect( reverse( 'change_password' ) )
except Object.DoesNotExist:
pass
# Not using temp password, let the request process
return None
Note that rendering the template directly could be used, with something like render_to_response, to fix the problem but that will cause the browser's URL to not follow as well as it not being able to really exit the page it renders.
First, I think your indenting is off in the example, but how about the following as a solution to detect when the current path is the change_password URL? This should get rid of that infinite redirect you have going on.
class MyMiddleware( object ):
def process_view( self, request, view_func, view_args, view_kwargs ):
if request.user.is_authenticated( ):
try:
if request.user.get_profile( ).using_temp and request.path != reverse('change_password'):
return HttpResponseRedirect( reverse( 'change_password' ) )
except Object.DoesNotExist:
pass
# Not using temp password, let the request process
return None
Which version of django are you using ?
If your are using the latest beta, setting the logging may be helpful
http://docs.djangoproject.com/en/dev/topics/logging/
Django Debug Toolbar might be helpful here. It can trap redirects and show you where it's redirecting before actually going there. This helps run down broken redirects.
That said, I'd recommend using a different "change password" page for users with temporary passwords, so it can handle permissions checking differently. The page you have might have a #login_required decorator, and a temporary password might not be considered "really" logged in.