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.
Related
I am having a problem with HttpResponseRedirect in Django. It seems that, whatever parameters I try, it either throws an error, or it redirects without changing the URL. I am using it on a custom login_user view, and I want the URL in the address bar to change after they are redirected. If I use redirect instead of HttpResponseRedirect, it does not change. Either way, I can get it to serve the correct template, but the URL stays the same. Being new to Django, it would be helpful if someone could explain to me how to do this and why my current code is not working.
I have seen a couple of similar questions to mine on Stack Exchange, but the answers have not helped.
Here are the relevant parts of my views.py (please note the indenting has gone weird due to copying and pasting in here, and is not the cause of the error).
from django.http import *
from django.contrib.auth import authenticate, login, logout
def login_user(request):
logout(request)
username = password = ''
if request.POST:
username = request.POST.get('username')
password = request.POST.get('password')
user = authenticate(username=username, password=password)
if user is not None:
if user.is_active:
login(request, user)
return HttpResponseRedirect('dashboard')
else:
state = "Your account is not active, please contact the app administrator."
else:
state = "Your username and/or password were incorrect."
state = "Please log in below..."
context = RequestContext(request, {
'state': state,
'username': username,
})
return render_to_response('bank/auth.html', {}, context)
dashboard is the name of another view, and it works fine in a redirect from my index view. I've also tried hard-coding the url in, but that doesn't work either. Any suggestions?? Thanks.
If you use HttpResponseRedirect, you must provide the url, not the name of the url.
You can either get the url by using reverse:
from django.core.urlresolvers import reverse
def my_view(request):
...
return HttpResponseRedirect(reverse('dashboard'))
or by using the redirect shortcut.
from django.shortcuts import redirect
def my_view(request):
...
return redirect('dashboard')
If using the either of the above approaches does not work, then there's probably a mistake somewhere else in the view. It's difficult to tell where, since the indentation is incorrect. Try adding some logging or print statements to see if you are really returning the redirect where you think you are.
In this particular case, the problem wasn't anything to do with my view code, it was actually a problem caused through using JQuery mobile. I found the answer here: Error with Redirects in JQuery Mobile which was to set the data-url attribute on the page div.
However, I have up-voted Alasdair's answer, as his way is the correct one of the ways I had tried.
I personally prefer the simple way as follows:
In urls.py:
url(r'^dashboard/$', 'appname.views.dashboard_view', name='dashboard_view'),
In views.py:
from django.http import HttpResponseRedirect
def dashboard_view(request):
...
return HttpResponseRedirect('/dashboard/')
Thanks In Advance.
I am facing an Issue in one of my Django Website. Here an authenticated user can access the Registration Page. But the client raised it as an issue. So I have tried to rectify that Issue and ended up with the following Solution.
Is it a good solution? Or how can I make it good?
The Process should be like this, when a loginned user try to access the Registration Page, he should be automatically Logged Out from the Site and then redirected to the Registration Page.
My code is
def user_signup(request, template_name='profiles/profile_register_form.html'):
if request.user.is_authenticated():
return custom_logout(request, next_page = "/accounts/register/")
def custom_logout(request, next_page='/'):
try:
language = request.session['django_language']
except:
language = False
response = logout(request, next_page=next_page)
if language:
request.session['django_language'] = language
return response
if i understood your question then
why custom_logout
you can call directly django logout like
if request.user.is_authenticated():
logout(request)
return HttpResponseRedirect('/login/') # whatever you register page
Your method was correct. It will save the current language session and will do the exact process you need
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 have a model which needs to store URLs which will be part of the Django environment. If I was storing normal URLs, I'd use models.URLField, and use verify_exists to make sure the URL actually exists.
However, this doesn't work that great in development, since the dev server is single-threaded, it hangs indefinitely, since it can't process two requests at once.
I was hoping to do something using resolve(), but am having difficulty adapting the function myview at the end of that documentation page to a version which does not take a request, since I want to check that a given local URL can be resolved, and called without a 404 being raised, from a blank session.
I was hoping to do this with a validator, something like this:
def validate_local_url(value):
try:
view, args, kwargs = resolve(value)
view(*args, **kwargs)
except Resolver404:
raise ValidationError(u'%s is not a local URL (not a valid URL)' % value)
except Http404:
raise ValidationError(u'%s is not a local URL (does not exist)' % value)
However, this fails without a valid request object being passed into kwargs. How do I generate a dummy (blank) request object? I've tried just using django.http.HttpRequest.
Just a wild idea, not sure if it will be helpful. Have you considered naming the urls and using reverse()? Reverse will work if the URL is valid and will fail when it is not.
Are you cool with using the django test Client?
If so, this should do it:
from django.test.client import Client
def validate_local_url(path):
c = Client()
try:
resp = c.get(path)
if resp.status_code == 404:
raise ValidationError(u'%s is not a local URL (does not exist)' % value)
except:
raise ValidationError(u'%s is not a local URL (not a valid URL)' % value)
Just, you know, make sure under penalty of death that validate_local_url can never be called by a local GET request, otherwise someone can trivially set your server on an infinite loop:
# urls.py
url('^infinite_loop/$', 'myapp.infinite_loop', 'infinite_loop')
#views.py
def infinite_loop_view(request, template_name="blah.html", form_class=MyForm):
my_form = form_class(request.REQUEST or None) # yes, admittedly this is dumb
if my_form.is_valid():
return HttpResponse("Congratulations! Your path was totally valid.")
return render_to_response(template_name, locals(), RequestContext(request))
And then:
http://example.com/infinite_loop/?path_field=infinite_loop
I'm trying to set up a custom backend that queries another database, for which I have created a model in the system. It uses its own rules (email instead of username, and a differently salted/hashed password) so I can't use built in authentication. I've set up a custom authentication backend like so:
class BlahBlahBackend:
def check_password():
# check password code here
return true
def authenticate(self, email=None, password=None):
import myapp.models.loginmodel
try:
person = myapp.models.loginmodel.People.objects.get(email=email)
if check_password(password, person.password):
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
username=person.first_name + person.last_name
name_count = User.objects.filter(username__startswith = username).count()
if name_count:
username = '%s%s'%(username, name_count + 1)
user = User.objects.create_user(username,email)
else:
user = User.objects.create_user(username,email)
except People.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
I've added BlahBlahBackend as an authentication backend:
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',
'socialauth.auth_backends.OpenIdBackend',
'socialauth.auth_backends.TwitterBackend',
'socialauth.auth_backends.FacebookBackend',
'socialauth.auth_backends.BlahBlahBackend',
)
As you can see, I'm also using some pre-existing auth backends that are also in socialauth.
I have a submission form that points to the following view:
def blahblah_login_complete(request):
email = request.POST.get('email')
password = request.POST.get('password')
user = authenticate(email,password)
# if user is authenticated then login user
if user:
login(request, user)
else:
return HttpResponseRedirect(reverse('socialauth_login_page'))
However, when I try to login in this way, it seems like one or more of the other backends are acting as if I'm trying to log in using their method.
I read that backends are cached and so ran
Session.objects.all().delete()
to clear out the backends cache.
My main questions are:
Does the order in which items are listed in AUTHENTICATION_BACKENDS
How does the system decide/know which Backend to use? This was never made clear by any of the documentation, and I find it a bit confusing.
Is there any way to force the use of a specific authorization based on the request. In other words, if someone submits a form, is there a way to force them to use the form-login-based authentication as opposed to the login via openid or Twitter?
Update:
It works! This is very cool, thanks. I guess it just seemed like the django doc was saying "You don't have to do anything else, it just sort of works like magic" and it turns out this is absolutely the case. So long as the backend is there and the credentials are set up correctly, the authentication will work. As it turns out the real problem was a misconfiguration in the urls.py file that wasn't sending the post from the login form to the correct handler, which is why it kept trying to use another authentication method.
You're supposed to use keyword arguments to django.contrib.auth.authenticate() The names should match the names of the arguments in your backend's authenticate method. The default backend handles the names 'username' & 'password'.
Your backend can use a different name for the keyword arguments e.g.: blahblah_email and blahblah_password, and then call authenticate(blahblah_email=..., blahblah_password=...).
It's clearly described here -
django tries each backend in order
defined, if first fails to
authenticate it goes to second etc.
I believe you can load backend class dynamically and authenticate
directly through it. Look at django authenticate() function sources on how to do that.
I guess django-cas will be a good reference for you :)
And yes, the order of AUTHENTICATION_BACKENDS matters.
Django loops over the backends list and stop at the first backend that has a authenticate method accepting the credential parameters you passed to it.