django-webtest, user authentication and view decorators - django

I'm using django-webtest (v1.5.6) to test a decorator is limiting access to a view to authenticated users.
My view is simply:
#active_account_required
def homepage(request):
return render(request, 'accounts/account_homepage.html', {
'user': request.user,
})
The active_account_required decorator is:
def active_account_required(function = None):
"""
Check the user is both logged in and has a valid, activated user account.
If a user tries to access a view protected by this decorator, they will be
redirected accordingly.
See http://passingcuriosity.com/2009/writing-view-decorators-for-django/
"""
def _dec(view_func):
def _view(request, *args, **kwargs):
if request.user.is_anonymous():
return HttpResponseRedirect(reverse_lazy('auth_login'))
if not request.user.get_profile().is_activated():
return HttpResponseRedirect(reverse_lazy('registration_activation_incomplete'))
return view_func(request, *args, **kwargs)
_view.__name__ = view_func.__name__
_view.__dict__ = view_func.__dict__
_view.__doc__ = view_func.__doc__
return _view
if function is None:
return _dec
else:
return _dec(function)
My test method is
class AccountViewsTests(WebTest):
def test_activated_user_can_access_account_homepage(self):
"""
Test an activated user can access the account homepage
"""
user = G(User)
user.get_profile().datetime_activated = timezone.now()
res = self.app.get(reverse('account_homepage'), user = user)
pdb.set_trace()
self.assertTemplateUsed(res, 'accounts/account_homepage.html',
'Activated account did not access account homepage correctly')
(The user object is created using the G function from django-dynamic-fixture)
When running the test, the decorator is preventing access to the homepage view.
You can see I'm using pdb to inspect the objects. User is a valid user object that should pass all the tests in the active_account_required decorator:
(Pdb) user
<User: 2>
(Pdb) user.is_anonymous()
False
(Pdb) user.get_profile().is_activated()
True
Despite the user being correct, the response from self.app.get(reverse('account_homepage'), user = user) is a 302 redirect to the registration_activation_incomplete URL as per the decorator code:
(Pdb) res
<302 FOUND text/html location: http://localhost:80/accounts/registration/activate/incomplete/ no body>
It appears the user object is not being sent correctly in the WebTest request, but this matches the django-webtest documentation. I've also tried passing the user in by username as user='2' but get the same result.
Any ideas?

Oops - the problem is that I simply forgot to save my user profile after setting the activation timestamp!
Changing the test code to:
user = G(User)
user.get_profile().datetime_activated = timezone.now()
user.get_profile().save()
res = self.app.get(reverse('account_homepage'), user = user)
i.e. adding user.get_profile().save() got it working :)
Sorry for the noise.

Related

Django Testing - Accessing Site with authenticated user

I am trying to do a unit test for authenticated user url access, currently I am trying to authenticate a user but I am unable to do so...
Test.py
def setUp(self):
self.user = User.objects.create_user(email='test#testmail.com', password='test_password', is_staff=True)
self.user.save()
self.client = Client()
def test_portfolio_url(self):
self.client.login(username='test#testmail.com', password='test_password')
url = reverse('portfolio')
response = self.client.get(url)
self.assertEqual(response.status_code, 200)
self.assertEqual(resolve(url).func, portfolio)
self.assertTemplateUsed(response, 'portfolio.html')
views.py
#login_required(login_url='login')
def portfolio(request, user_id=None):
if user_id and request.user.is_staff:
user = User.objects.get(id=user_id)
else:
...
return render(request, 'portfolio.html', {'portfolio': portfolio_data})
This is the response I suppose to get when logged in
in setUp() you create a user with no username
in test_portfolio_url() you try to login with a username, but use the email.
in setUp(), you don't need to call user.save() - the create_user() call already saves the user
A bit late ,but I found solution for this maybe would be helpful for someone.. So I made function in the class for every test/view where the user needs to be authenticated and here it is.
def login(self):
self.username = 'test1'
self.password = '12345qwe'
user = ModelUser.objects.create_user(username=self.username)
user.set_password(self.password)
user.save()
client = Client()
client.login(username=self.username, password=self.password)
return user, client
In the test you just need to call the function like this:
user, client = self.login()
So here you have already set up the user and the client and you can continue for example to take the response
response = client.get...
Of course you can improve the code as you like ,good luck!

Django Cookie with Login function

I'm trying to set my first cookie with Django when users are logged on my application.
When user is logged, the template is well-displayed but none cookie in my application which is named : Cookie
My function looks like :
def Login(request):
error = False
if request.method == "POST":
form = ConnexionForm(request.POST)
if form.is_valid():
username = form.cleaned_data["username"]
password = form.cleaned_data["password"]
user = authenticate(username=username, password=password)
if user:
login(request, user)
toto = GEDCookie(request)
return render(request, 'Home_Homepage.html', {'toto':toto})
else:
error = True
else:
form = ConnexionForm()
return render(request, 'Authentication_Homepage.html', locals())
#csrf_exempt
def GEDCookie(request):
SID = Logger.login("test", "10test")
response = HttpResponse("Cookie")
response.set_cookie('Cookie', SID, max_age=None)
return response
I missed something in my script ?
This isn't how you use cookies at all.
Inside your Login view, you're calling a separate view - GEDCookie that returns an HTTP response. But instead of returning that response directly to the user, which would set the cookie, you're for some reason trying to insert it in a template. That doesn't make sense.
If you want to set a cookie in your login view, you need to do so on the response that you return to the user.
Note also that after a successful login (or other post), you should always redirect, not display a template directly. So:
if user:
login(request, user)
response = redirect('home')
response.set_cookie('whatever')
return response
Finally, you almost certainly don't need a cookie here in any case. If you want to store data related to the current user, use the session.
As you can clearly see that you are not attaching your cookie to your real response, you are passing it as the context in render function which is an issue.
def Login(request):
error = False
if request.method == "POST":
form = ConnexionForm(request.POST)
if form.is_valid():
username = form.cleaned_data["username"]
password = form.cleaned_data["password"]
user = authenticate(username=username, password=password)
if user:
login(request, user)
SID = Logger.login("test", "10test")
response = render(request, 'Home_Homepage.html', {})
response.set_cookie('Cookie', SID, max_age=None)
return response
else:
error = True
else:
form = ConnexionForm()
return render(request, 'Authentication_Homepage.html', locals())
https://docs.djangoproject.com/en/3.0/ref/request-response/#django.http.HttpResponse.set_cookie Please refer this link for individual arguments of inbuilt function.
Create signal.py in app. where your user model is present or add in main project directory and Add below snippet in signal.py
from django.db.models.signals import pre_save, pre_delete, post_save, post_delete
from django.dispatch import receiver
from django.dispatch import Signal
from allauth.account.signals import user_logged_in # it signal for post login
from django.shortcuts import render
#receiver(user_logged_in) # Decorator of receiving signal while user going to logged in
def post_login(sender, user, request, response, **kwargs):
response.set_cookie('team', 'india') # This will set cookie
return response
In given snippet, default response will come in argument, so direct redirect to that response, if you want to change then render other template using render/redirect django.shortcuts methods like below,
response = render(request, 'base.html', {})

Custom django authentication backend doesn't log user in first time, but works second time

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.

Should I remove user id from URL if I want to keep data available just to exactly one user?

Lets just suppose that we have following url:
example.com/users/1
if user with ID=1 opens it, user receive info about his account, but if user switch 1 with 2, then he can view other user details as well, and we of course do not want that, so I have following solutions:
1) just pass currently logged in user id threw request.user.id inside a template
2) add permission, but I did not find type of permissions that would allow me to do that. Of course I could create dozens of permissions each for each user, but of course that is very nasty way.
Any other ideas how to cope with that in Django?
You can either fill context with the request which makes sure the user will never see another user's data, e.g. (using CBVs):
class AccountView(TemplateView):
"""
Generic account view
"""
template_name = "users/account.html"
def get_context_data(self, **kwargs):
context = super(AccountView, self).get_context_data(**kwargs)
context['user'] = User.objects.get(id=self.request.user.id)
return context
#method_decorator(login_required(login_url=reverse('login')))
def dispatch(self, *args, **kwargs):
return super(AccountView, self).dispatch(*args, **kwargs)
Another approach, to make sure 'fake' urls render 404's is to write an owner_required decorator, e.g.:
def owner_required(function):
#wraps(function)
def decorator(*args, **kwargs):
request = args[1]
user = get_object_or_404(User, username=request.user.username)
if user.is_authenticated() and user.username == kwargs.get('slug'):
return function(*args, **kwargs)
raise Http404
return decorator
You don't need permission to do this.
In your views.py
from django.http import Http404
def myview(request, user_id):
user = request.user
if user_id != request.user.id:
raise Http404
#all your logic here
But if you want user profile to be private, you don't need to use user_id in your url pattern. just use the user object stored in the request variable.

Django, request.user is always Anonymous User

I am using a custom authentication backend for Django (which runs off couchdb). I have a custom user model.
As part of the login, I am doing a request.user = user and saving the user id in session.
However, on subsequent requests, I am not able to retrieve the request.user. It is always an AnonymousUser. I can, however, retrieve the user id from the session and can confirm that the session cookie is being set correctly.
What am I missing?
I do not want to use a relational db as I want to maintain all my user data in couchdb.
Edit: I have written a class which does not inherit from Django's auth User. It, however, has the username and email attributes. For this reason, my backend does not return a class which derives from auth User.
The request.user is set by the django.contrib.auth.middleware.AuthenticationMiddleware.
Check django/contrib/auth/middleware.py:
class LazyUser(object):
def __get__(self, request, obj_type=None):
if not hasattr(request, '_cached_user'):
from django.contrib.auth import get_user
request._cached_user = get_user(request)
return request._cached_user
class AuthenticationMiddleware(object):
def process_request(self, request):
request.__class__.user = LazyUser()
return None
Then look at the get_user function in django/contrib/auth/__init__.py:
def get_user(request):
from django.contrib.auth.models import AnonymousUser
try:
user_id = request.session[SESSION_KEY]
backend_path = request.session[BACKEND_SESSION_KEY]
backend = load_backend(backend_path)
user = backend.get_user(user_id) or AnonymousUser()
except KeyError:
user = AnonymousUser()
return user
Your backend will need to implement the get_user function.
I too have custom authentication backend and always got AnonymousUser after successful authentication and login. I had the get_user method in my backend. What I was missing was that get_user must get the user by pk only, not by email or whatever your credentials in authenticate are:
class AccountAuthBackend(object):
#staticmethod
def authenticate(email=None, password=None):
try:
user = User.objects.get(email=email)
if user.check_password(password):
return user
except User.DoesNotExist:
return None
#staticmethod
def get_user(id_):
try:
return User.objects.get(pk=id_) # <-- tried to get by email here
except User.DoesNotExist:
return None
Its easy to miss this line in the docs:
The get_user method takes a user_id – which could be a username,
database ID or whatever, but has to be the primary key of your User
object – and returns a User object.
It so happened that email is not primary key in my schema. Hope this saves somebody some time.
You say you've written a custom authentication backend, but in fact what you seem to have written is a complete custom authentication app, which doesn't interface with Django's contrib.auth.
If you want to use a non-relational database for your authentication data, all you need to do is create a class that provides two methods: get_user(user_id) and authenticate(**credentials). See the documentation. Once you have authenticated a user, you simply call Django's normal login methods. There should be no reason to manually set request.user or put anything into the session.
Update after edit That has nothing to do with it. There's no requirement that the user class derives from auth.models.User. You still just need to define a get_user method that will return an instance of your user class.
Please elaborate. If you are using a custom user model (which is different from a custom user PROFILE model), then you are basically on your own and the django.contrib.auth framework can not help you with authentication. If you are writing your own authentication system and are not using django.contrib.auth, then you need to turn that off because it seem to be interfering with your system.
In case you are using an API (Django-rest-framework) and accessing a view using a get, post, etc. methods.
You can get a user by sending the Bearer/JWT token corresponding to that user.
Wrong
# prints Anonymous User
def printUser(request):
print(request.user)
Correct
# using decorators
# prints username of the user
#api_view(['GET']) # or ['POST'] .... according to the requirement
def printUser()
print(request.user)
I had similar problem when I used custom authentication backend. I used field different than the primary key in the method get_user.
It directly solved after using primary key which must be number (not str)
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id) # <-- must be primary key and number
except User.DoesNotExist:
return None
After sending Token using Authorization header, the token will be gotten in dispatch function as bellow:
'''
def dispatch(self, request, *args, **kwargs):
self.args = args
self.kwargs = kwargs
request = self.initialize_request(request, *args, **kwargs)
self.request = request
self.headers = self.default_response_headers # deprecate?
try:
self.initial(request, *args, **kwargs)
# Get the appropriate handler method
if request.method.lower() in self.http_method_names:
handler = getattr(self, request.method.lower(),
self.http_method_not_allowed)
else:
handler = self.http_method_not_allowed
response = handler(request, *args, **kwargs)
except Exception as exc:
response = self.handle_exception(exc)
self.response = self.finalize_response(request, response, *args, **kwargs)
return self.response
So you are using django_role_permission's HasRoleMixin, the dispatch method of this mixin will hide dispatch of the view.
I think that the solution is to redefine the mixin of roles-permissions
user = authenticate(username=username, password=password)
if user is not None:
return render(request, 'home.html',{'user_id':user.id})
Added these in my view
from rest_framework.permissions import IsAuthenticated
from rest_framework.authentication import TokenAuthentication
authentication_classes = (TokenAuthentication,)
permission_classes = (IsAuthenticated,)
and started getting original user