I have an application where we have sub-classed the Django 'User' object into, say, 'AppAccount' object which has additional attributes. Now I have a view where I do the following:
appAccountObject.backend = 'django.contrib.auth.backends.ModelBackend'
login(request, appAccountObject)
redirect(someOtherView)
Now according to pdb, request.user is an instance of AppAccount right after the login() call, but request.user is a Django User instance in the first line of someOtherView.
Why is the redirect call changing the User object back to the normal Django User? How can I avoid this?
Also, is the above code correct? Should adding the backend attribute be okay to bypass a call to authenticate? If not, what should the correct way of doing this be: I want to login a user automatically, without their credentials and then redirect to another view which is wrapped by a #login_required decorator.
Thanks.
A redirect causes a whole new request from the user's browser, hence the user object has to be fetched from the database again based on the session cookie and assigned to request.user. This happens in the authentication middleware. Unless you've written your own version of this, it's always going to use the default user class.
This is just one of the reasons why it's a bad idea to subclass User. Instead, extend it with a UserProfile class with a OneToOne relation to User.
Related
I am working on an app where the user will be able to login to his profile. I am using Django-registration-redux. I am using the below code to inculde in my project.
LOGIN_REDIRECT_URL = '/profile/view/(?P<pk>[0-9]+)/'
I want to redirect the user to his profile after logging in. I know that is not the way you can actually call an url in settings file. any solution for the problem?
I believe you can use https://docs.djangoproject.com/en/dev/ref/urlresolvers/#reverse-lazy to add URL resolution to a setting.
You don't need to pass the user pk to the view. You can get this value and every other data field of the user from request.user object.
As stated in Django docs, you could do something like this:
def profile(request):
if request.user.is_authenticated:
# Do something for logged-in users.
request.user.do_something()
[...]
else:
# Do something for anonymous users like redirect to registration
pass
I would like to create a Mixin which will:
First - Check if a user is authenticated, if not, redirect to login url. If yes...
Second - Check if user has a defined Profile (a user), if not, redirect to Profile creation, else, allow user to access the View.
I was planning to do sometinhg like:
class ProfileRequiredMixin(LoginRequiredMixin,PermissionRequiredMixin):
#TODO check how multiple inheritance works treating conflicting methods
'''This Mixin should first check if user is autheticated, if not,
redirect to login. If it is, check if it has a profile.
If it does not, redirect to profile creation url. If it has, allow
access to view.'''
pass
But I am confused as how to overwrite the handle_no_permission() and dispatch() methods.
I solved the problem in the following way:
class TestIfHasProfileMixin(UserPassesTestMixin):
'''This Mixin should first check if user has a profile.
If it does not, redirect to profile creation url. If it has, allow
access to view.'''
def test_func(self):
try:
Profile.objects.get(user=self.request.user)
return True
except Profile.DoesNotExist:
return False
def handle_no_permission(self):
'''to:[login,Profile] will signup or create profiles'''
if self.raise_exception:
raise PermissionDenied(self.get_permission_denied_message())
return redirect('users:create-profile')
class ProfileRequiredMixin(LoginRequiredMixin,TestIfHasProfileMixin):
'''This Mixin should first check if user is autheticated, if not,
redirect to login. If it is, check if it has a profile.
If it does not, redirect to profile creation url. If it has, allow
access to view.'''
pass
Now every view that requires a Profile inherits from ProfileRequiredMixin, which will first test for login (redirect to login creation if there is not) and after that check for Profile and redirect to Profile creation if none exists.
i've recently implemented a simple change password view in my django project. The thing is that the old session should be destroyed for security reasons. What's the best way of doing this without asking the user to log in again.
I think i could just logout/login him/her, something like this:
from django.contrib.auth import login as auth_login
from django.contrib.auth import logout as auth_logout
#login_required
def change_password(request):
# My stuff
request.user.set_password(new_password)
request.user.save()
# I need this:
logout(request)
login(request,request.user)
But i think this is not the best idea. What do you think?
Is there another way to do this?
Am I missing something? (I mean, is this secure)
Take a look at this app https://github.com/atugushev/django-password-session.
This package makes invalidated all sessions (except a current session) after change a password.
Also this feature finally was implemented in Django 1.7. See: https://docs.djangoproject.com/en/dev/topics/auth/default/#session-invalidation-on-password-change
I just found out that this is now a built-in feature of Django, and has been since 1.7:
https://docs.djangoproject.com/en/1.7/topics/auth/default/#session-invalidation-on-password-change
Essentially, all sessions now include a hash of the users' password, so if the user ever changes their password, all their existing sessions are automatically invalidated.
So, the short answer to your question is: upgrade django.
One possibly undesirable side effect of this change is that, by default, a user ends up having to log in again as soon as they change their password. So you probably actually want the current user session to stay logged in. See the docs already linked, Django's built-in views for password change do that for you default, or you can manually call a function called update_session_auth_hash
django clears the session on logout so you will be fine:
https://docs.djangoproject.com/en/dev/topics/auth/#django.contrib.auth.logout
When you call logout(), the session data for the current request is completely cleaned out. All existing data is removed. This is to prevent another person from using the same Web browser to log in and have access to the previous user's session data.
I don't understand whats are these security reasons that forces to reset session. But, the way is:
#login_required
def change_password(request):
request.user.set_password(new_password)
request.user.save()
username = request.user.username
logout(request)
user = authenticate(username=username, password=new_password) #<-- here!!
if user is not None:
login(request,user)
else:
#raise your exception
you should authenticate before login. Quoting doc:
Calling authenticate() first When you're manually logging a user in,
you must call authenticate() before you call login(). authenticate()
sets an attribute on the User noting which authentication backend
successfully authenticated that user (see the backends documentation
for details), and this information is needed later during the login
process.
For my website pretty much every page has a header bar displaying "Welcome, ABC" where "ABC" is the username. That means request.user will be called for every single request resulting in database hits over and over again.
But once a user is logged in, I should be able to store his user instance in his cookie and encrypt it. That way I can avoid hitting the database repeatedly and just retrieve request.user from the cookie instead.
How would you modify Django to do this? Is there any Django plugins that does what I need?
Thanks
You want to use the session middleware, and you'll want to read the documentation. The session middleware supports multiple session engines. Ideally you'd use memcached or redis, but you could write your own session engine to store all the data in the user's cookie. Once you enable the middleware, it's available as part of the request object. You interact with request.session, which acts like a dict, making it easy to use. Here are a couple of examples from the docs:
This simplistic view sets a has_commented variable to True after a user posts a comment. It doesn’t let a user post a comment more than once:
def post_comment(request, new_comment):
if request.session.get('has_commented', False):
return HttpResponse("You've already commented.")
c = comments.Comment(comment=new_comment)
c.save()
request.session['has_commented'] = True
return HttpResponse('Thanks for your comment!')
This simplistic view logs in a "member" of the site:
def login(request):
m = Member.objects.get(username=request.POST['username'])
if m.password == request.POST['password']:
request.session['member_id'] = m.id
return HttpResponse("You're logged in.")
else:
return HttpResponse("Your username and password didn't match.")
This smells of over-optimisation. Getting a user from the db is a single hit per request, or possibly two if you use a Profile model as well. If your site is such that an extra two queries makes a big difference to performance, you may have bigger problems.
The user is attached to the request object using the Authentication Middleware provided by django (django.contrib.auth.middleware). It users a function the get_user function in django.contrib.auth.init to get the user from the backend you are using. You can easily change this function to look for the user in another location (e.g. cookie).
When a user is logged in, django puts the userid in the session (request.session[SESSION_KEY]=user.id). When a user logs off, it erases the user's id from the session. You can override these login and logoff functions to also store a user object in the browsers cookie / erase user object from cookie in the browser. Both of these functions are also in django.contrib.auth.init
See here for settting cookies: Django Cookies, how can I set them?
Once you have proper caching the number of database hits should be reduced significantly - then again I'm not really and expert on caching. I think it would be a bad idea to modify request.user to solve your problem. I think a better solution would be to create some utility, method or custom template tag that attempts to load your require user data from the cookie, and return the result. If the user data is not found in the cookie, then a call to request.user should be made, save the data to the cookie, and then return the result. You could possibly use a post_save signal to check for changes to the user data, so that you can make update to the cookie as required.
I'm using a user profile model with a ForeignKey to the User, authenticated by django.contrib.auth.
The same auth module is of course used for the admin interface, so if a superuser/staff member is logged in to the admin interface, and enters the main site, the site will accept session cookie and authenticate him. This creates a problem because a superuser/admin doesn't need to have a UserProfile and shouldn't be recognized by the main site.
What's the easiest way to solve this, so that the sessions from admin don't carry on to the site?
I dont think there is a way to solve exactly this,
"What's the easiest way to solve this, so that the sessions from admin don't carry on to the site?"
But depending on what you wnat to do, you may try,
don't create UserProfile for superuser
if request.user.is_superuser():
UserProf.objects.create(...)
I always have the problem where I want to keep a logged in Admin user and a logged in normal user, simultaneously, when I am developing. To do this, I have two entries in /etc/hosts
127.0.0.1 uswaretech.tld
127.0.0.1 admin.uswaretech.tld
Now, normal user always logs in via uswaretech.tld and admin always via admin.uswaretech.tld so thy can both be logge din simultaneously.
From a design standpoint your idea seems like a bit of a hack, but if you really want to do this you may use a middleware.
class MyMiddleware(object):
def process_request(self, request):
if request.user.is_authenticated:
try:
UserProfile.objects.get(user=request.user)
except UserProfile.DoesNotExist:
from django.contrib.auth.models import AnonymousUser
request.user = request._cached_user = AnonymousUser()
return None
This should be at the top of the middleware list to prevent possible side-effects.
if request.session.get ('has_account', False):
return HttpResponse ("You have no account, sorry.")
Then make sure, every user of your front-end gets, if his session is initiated, the value has_account set properly.