How can I test that a user is logged in after submitting the registration form?
I tried the following but it returns True even before I added the login logic to my registration view.
def test_that_user_gets_logged_in(self):
response = self.client.post(reverse('auth-registration'),
{ 'username':'foo',
'password1':'bar',
'password2':'bar' } )
user = User.objects.get(username='foo')
assert user.is_authenticated()
The code that's being tested:
class RegistrationView(CreateView):
template_name = 'auth/registration.html'
form_class = UserCreationForm
success_url = '/'
def auth_login(self, request, username, password):
'''
Authenticate always needs to be called before login because it
adds which backend did the authentication which is required by login.
'''
user = authenticate(username=username, password=password)
login(request, user)
def form_valid(self, form):
'''
Overwrite form_valid to login.
'''
#save the user
response = super(RegistrationView, self).form_valid(form)
#Get the user creditials
username = form.cleaned_data['username']
password = form.cleaned_data['password1']
#authenticate and login
self.auth_login(self.request, username, password)
return response
You can use the get_user method of the auth module. It says it wants a request as parameter, but it only ever uses the session attribute of the request. And it just so happens that our Client has that attribute.
from django.contrib import auth
user = auth.get_user(self.client)
assert user.is_authenticated
This is not the best answer. See https://stackoverflow.com/a/35871564/307511
Chronial has given
an excellent example on how to make this assertion below. His answer
better than mine for nowadays code.
The most straightforward method to test if a user is logged in is by testing the Client object:
self.assertIn('_auth_user_id', self.client.session)
You could also check if a specific user is logged in:
self.assertEqual(int(self.client.session['_auth_user_id']), user.pk)
As an additional info, the response.request object is not a HttpRequest object; instead, it's an ordinary dict with some info about the actual request, so it won't have the user attribute anyway.
Also, testing the response.context object is not safe because you don't aways have a context.
Django's TestClient has a login method which returns True if the user was successfully logged in.
The method is_authenticated() on the User model always returns True. False is returned for request.user.is_authenticated() in the case that request.user is an instance of AnonymousUser, which is_authenticated() method always returns False.
While testing you can have a look at response.context['request'].user.is_authenticated().
You can also try to access another page in test which requires to be logged in, and see if response.status returns 200 or 302 (redirect from login_required).
Where are you initialising your self.client? What else is in your setUp method? I have a similar test and your code should work fine. Here's how I do it:
from django.contrib.auth.models import User
from django.test import TestCase
from django.test.client import Client
class UserTestCase(TestCase):
def setUp(self):
self.client = Client()
def testLogin(self):
print User.objects.all() # returns []
response = self.client.post(reverse('auth-registration'),
{ 'username':'foo',
'password1':'bar',
'password2':'bar' } )
print User.objects.all() # returns one user
print User.objects.all()[0].is_authenticated() # returns True
EDIT
If I comment out my login logic, I don't get any User after self.client.post(. If you really want to check if the user has been authenticated, use the self.client to access another url which requires user authentication. Continuing from the above, access another page:
response = self.client.get(reverse('another-page-which-requires-authentication'))
print response.status_code
The above should return 200 to confirm that the user has authenticated. Anything else, it will redirect to the login page with a 302 code.
There is another succinct way, using wsgi_request in response:
response = self.client.post('/signup', data)
assert response.wsgi_request.user.is_authenticated()
and #Chronial 's manner is also available with wsgi_request:
from django.contrib import auth
user = auth.get_user(response.wsgi_request)
assert user.is_authenticated()
Because response.wsgi_request object has a session attribute.
However, I think using response.wsgi_request.user is more simple.
Related
I want to write a decorator like the login_required decorator of Django to check the Azure AD authentication and the Django authentication at the same time. If one of the two is not true, it redirects to the login page.
For the authentication, I used the tutorial (https://learn.microsoft.com/en-us/graph/tutorials/python). I do not how to deal with groups and permissions since I use Azure AD authentication. So I take the username and surname from the token that comes from the Azure Authentication and with this two infos, I create an user in the User Django models. I know it is not the best idea, but I can start to play with groups and permissions.
The django authentication is automatic without that the user create it. It is done in the callback function.
def callback(request):
# Make the token request
result = get_token_from_code(request)
#Get the user's profile
user = get_user(result['access_token'])
# Store user
store_user(request, user)
# Get user info
# user attribute like displayName,surname,mail etc. are defined by the
# institute incase you are using single-tenant. You can get these
# attribute by exploring Microsoft graph-explorer.
username = user['displayName']
password = user['surname']
email = user['mail']
try:
# if use already exist
user = User.objects.get(username=username)
except User.DoesNotExist:
# if user does not exist then create a new user
user = User.objects.create_user(username,email,password)
user.save()
user = authenticate(username=username,password=password)
if user is not None:
login(request,user)
messages.success(request,"Success: You were successfully logged in.")
return redirect('home')
return redirect('home')
If I want to check if the user is authenticated by Azure AD. From the tutorial, I should do something like that :
if request.session.get('user').get('is_authenticated') :
But I do not know how to combine with the django authentication to check both. Anyone can help me
Thanks
simplest way would be to use the user_passes_test decorator to make your own function and apply that as a decorator to your views as per the docs
from django.contrib.auth.decorators import user_passes_test
def check_azure(user):
# so something here to check the azure login which should result in True/False
return #theResult of your check
#user_passes_test(check_azure)
def my_view(request):
...
Here is my solution :
from django.shortcuts import redirect
def authenticated_user(view_func) :
def wrapper_func(request, *args, **kwargs):
if request.user.is_authenticated and request.session.get('user').get('is_authenticated') :
return view_func(request, *args, **kwargs)
else :
return redirect('login')
return wrapper_func
In Django tests, I want to log in and make a request.
Here the code
def test_something(self):
self.client.login(email=EMAIL, password=PASSWORD)
response = self.client.get(reverse_lazy("my_releases"))
self.assertEqual(response, 200)
But the code doesn't work and returns
AttributeError: 'AnonymousUser' object has no attribute 'profile'
How to make request in test as logged in user?
As the documentation on .login(…) says:
(…)
login() returns True if it the credentials were accepted and login
was successful.
Finally, you’ll need to remember to create user accounts before
you can use this method.
The tests run normally on a separate database, and thus the items created while debugging, or in production, do not have effect on that.
You thus first create a user and then test it with:
from django.contrib.auth import get_user_model
# …
def test_something(self):
User = get_user_model()
User.objects.create_user('my-user-name', email=EMAIL, password=PASSWORD)
self.assertTrue(self.client.login(email=EMAIL, password=PASSWORD))
response = self.client.get(reverse_lazy("my_releases"))
self.assertEqual(response, 200)
The standard authentication engine works with a username and password, not with an email and password, so if you used the builtin authentication manager, you login with:
from django.contrib.auth import get_user_model
# …
def test_something(self):
User = get_user_model()
User.objects.create_user('my-user-name', email=EMAIL, password=PASSWORD)
self.assertTrue(self.client.login(username='my-user-name', password=PASSWORD))
response = self.client.get(reverse_lazy("my_releases"))
self.assertEqual(response, 200)
So I'm using Rdio to login and create users, and wrote a backend to handle its oauth. The first time you try to sign in using Rdio, it creates a user and an attached Rdio user, but it doesn't create a session and return the session cookie.
The flow is like any oauth2 flow: you press a button on my app, it redirects w/ get params to Rdio, and Rdio calls a callback view on my app (along with a code in the GET params). In that callback view, I call authenticate:
class RdioCallbackView(View):
def get(self, request):
""" here, you need to create and auth a django user and create and tie the rdio user's stuff to it """
if request.user.is_authenticated() == False:
try:
rdio_code = request.GET['code']
except KeyError:
return redirect(reverse('login'))
# authenticate
user = auth.authenticate(rdio_code=rdio_code)
if user is not None and user.is_active:
auth.login(request, user)
else:
return render(request, 'home/login.html', {'rdio_url': create_rdio_auth_url(), 'message': "That code didn't seem to work"})
else:
# user exists!
user = request.user
return HttpResponseRedirect(reverse('the-next-view'))
The custom auth backend looks like this:
class RdioBackend(object):
def authenticate(self, rdio_code=None):
token_info = exchange_rdio_code(rdio_code)
try:
access_token = token_info['access_token']
refresh_token = token_info['refresh_token']
except KeyError:
return None
except TypeError:
# the code was probably already used.
return None
rdio_user_dict = get_rdio_user_for_access_token(access_token)
rdio_key = rdio_user_dict['key']
try:
rdio_user = RdioUser.objects.get(rdio_id=rdio_key)
rdio_user.access_token = access_token
rdio_user.refresh_token = refresh_token
rdio_user.save()
user = rdio_user.user
except RdioUser.DoesNotExist:
user = User.objects.create(username=rdio_key)
user.set_unusable_password()
rdio_user = RdioUser.objects.create(
rdio_id = rdio_key,
access_token = access_token,
refresh_token = token_info['refresh_token'],
user = user,
)
return user
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
And that's where things get weird. It doesn't seem to make a new Session object, and definitely doesn't return a session cookie. However, when I go back and do the Rdio login again for a second time, it returns a session cookie, makes the session on the backend, and login and auth work perfectly.
And I think my AUTHENTICATION_BACKENDS settings is right:
AUTHENTICATION_BACKENDS = (
'appname.backend.RdioBackend',
'django.contrib.auth.backends.ModelBackend',
)
Edit: More possibly relevant info:
The views that it's redirecting to have a LoginRequiredMixin:
class LoginRequiredMixin(object):
#classmethod
def as_view(cls, **initkwargs):
view = super(LoginRequiredMixin, cls).as_view(**initkwargs)
return login_required(view)
And in RdioCallbackView, when I change the final line from return HttpResponseRedirect(reverse('the-next-view')) to instead just serve the template directly with return render(request, 'path/to.html', param_dict), it does serve the cookie and make a sessionid, but then it deletes it from the DB and from the browser the moment I navigate away from that screen.
This might be the dumbest bug ever. It turns out that if you create a user without a password, you don't need to call user.set_unusable_password(). And if you do call user.set_unusable_password(), it somehow messes with any auth you do (even AFTER you call that).
So to fix this, I just got rid of the call to user.set_unusable_password() in my custom django auth backend.
Let's say i have a form that does something in database and requires user authentication that has been sent by POST, is it possible inside request someone evil to change the user in order to exploit the system?
The following example creates an item in database but requires a logged in user. Can someone send other user's data in request.user?
from django.shortcuts import render, redirect
from django.contrib.auth.decorators import login_required
from items_core.models import Item
from items.forms import CreateItemForm
from django.views.decorators.csrf import csrf_exempt
#csrf_exempt
#login_required
def create(request):
errors = None
if request.method == 'POST':
form = CreateItemForm(request.POST)
if form.is_valid():
try:
Item.objects.get(
name = form.cleaned_data['name'],
user = request.user
)
errors = 'Item already exist. Please provide other name.'
except Item.DoesNotExist:
Item.objects.create(
name = form.cleaned_data['name'],
user = request.user
)
return redirect('items:list')
form = CreateItemForm()
else:
form = CreateItemForm()
template = {
'form':form,
'items':Item.objects.filter(user=request.user),
'request':request,
'errors':errors
}
return render(request, 'items/item_create.html', template)
Thanks!
The request.user object is of type SimpleLazyObject which is added by the auth middleware to the requestobject.
SimpleLazyObject(LazyObject): is used to delay the instantiation of the wrapped class
At the time of requesting the actual logged in user, get_user method gets called.
def get_user(request):
if not hasattr(request, '_cached_user'):
request._cached_user = auth.get_user(request)
return request._cached_user
Here, auth.get_user() would inturn validate this way:
backend_path = request.session[BACKEND_SESSION_KEY]
backend = load_backend(backend_path)
user = backend.get_user(user_id) or AnonymousUser()
Hence if the request.user object is tampered with, this validation would fail as the session data validation would fail
user attribute on request i.e request.useris set by AuthenticationMiddleware. process_request for this middleware internally uses get_user() provided by django auth system which is defined in django.contrib.auth.__init__.py.
This get_user() uses django session and django session internally use cookies. Django session use a cookie with key as sessionid.
So, say a malicious user gets hold of the cookie of a legitimate user and sends this cookie to the server, server will think that the request has come from a legitimate user and will login as the legitimate user. But since the request was sent by the malicious user, he now has access to the resources of the legitimate user.
This is the code that i've come up with inspired from django RemoteUserBackend its not yet complete, I am not sure where in normal backends or in remoteuserbackend for instance where exactly the authenticate method called from ? Sorry I am new to django and the userlog in process seems to work like magic
from django.contrib import ModelBackend
from django.contrib.auth.models import User, Permission
def facebook_login_required(orig_view):
def wrapper(request):
if not request.user.is_authenticated():
redirect_url = 'https://www.facebook.com/dialog/oauth?client_id=%s&redirect_uri=%s&scope=email,read_stream'%(SETTINGS.FB_APPID,request.getlocation)
HttpResponseRedirect(redirect_url)
else:
# user is logged in, its safe to process the view
return orig_view
return wrapper
class FacebookAuthBackend(ModelBackend):
def authenticate(self,userid):
"""
The ``userid`` passed here is considered trusted.This method
simply returns the ``User`` objects with the given id, else
it creates a new user with the this ``userid`` if the it does
not existz
"""
if not userid:
user = User(userid=userid)
user.save()
user = None
try:
user = User.objects.get(userid=userid)
except User.DoesNotExist:
pass
return user
def get_user(self,userid):
try:
User.objects.get(userid=userid)
except User.DoesNotExist:
return None
Well, you can always have a look at the source code and figure out how things work and with yours don't
https://github.com/omab/django-social-auth/blob/master/social_auth/backends/facebook.py