request.user not being populated by my django auth backend - python

I have written a authorization backend class that implements a authenticate method and a get_user method as per the django docs. I've added
AUTHENTICATION_BACKENDS = ('src.lib.auth_backend.MyBackend',)
to my settings.py file. Through print statements I can see that my code is being run and that it is returning a user object of the class I defined with AUTH_USER_MODEL in my settings.py.
By the time I get the request object within my django-rest-framework has_object_permsion function, request.user is always set to AnonymousUser.
Am I missing a step?
I've tried this with and without django.contrib.auth.middleware.AuthenticationMiddleware installed and get the same outcome.
This failure is happening in the following unit test
def test_get_user(self):
client = APIClient() # rest_framework.test.APIClient
client.login(username='user1',password='user1Password')
res = client.get('/websvc/users/' + str(user.user_id) + '/') # request.user will be AnonymousUser
self.assertEqual(res.status_code, 200) # it will be 403, which is appropriate for AnonymousUser

A couple of things may be wrong.
Have you created the user, before runnign the test?
What returns client.login?
Show us the whole test, to tell you what's wrong.

Related

Can I safely use `self.request.user.is_authenticated` in views?

We are using Django 2.1 for Speedy Net. There is a view which I want to be different for admin (staff) than for regular users. I added the following (3 first) lines to the code:
def get_user_queryset(self):
if (self.request.user.is_authenticated):
if ((self.request.user.is_staff) and (self.request.user.is_superuser)):
return User.objects.get_queryset()
return User.objects.active()
But the problem is, one of the tests fails with an error message:
AttributeError: 'WSGIRequest' object has no attribute 'user'
(link) And I want to know - Is it safe to use self.request.user.is_authenticated in views? Should I fix the test to work or can my code fail in production? The test uses RequestFactory(), is there a problem with RequestFactory() not containing attribute 'user'? Or is there a problem with my code which may also fail in production?
If I should fix the test, how do I do it? Here is the code of the test which fails:
class UserMixinTextCaseMixin(object):
def set_up(self):
super().set_up()
self.factory = RequestFactory()
self.user = ActiveUserFactory(slug='look-at-me', username='lookatme')
self.other_user = ActiveUserFactory()
def test_find_user_by_exact_slug(self):
view = UserMixinTestView.as_view()(self.factory.get('/look-at-me/some-page/'), slug='look-at-me')
self.assertEqual(first=view.get_user().id, second=self.user.id)
By the way, I'm not sure what is the purpose of slug='look-at-me' in the view = ... line of the test?
Yeah... It's safe to use request.user or self.request.user in Django and it is the Django way of checking whether the User is authenticated or not.
AttributeError: 'WSGIRequest' object has no attribute 'user'
Django assigning the authenticated user to the request object within the Django Middleware execution, specifically within the AuthenticationMiddleware class (source-code of AuthenticationMiddleware class).
So, adding the AuthenticationMiddleware class to the Middleware settings or creating your own middleware class is will solve the AttributeError: 'WSGIRequest' object has no attribute 'user' error.
I found out this answer on Stack Overflow.
From the docs:
# Recall that middleware are not supported. You can simulate a
# logged-in user by setting request.user manually.
request.user = self.user
# Or you can simulate an anonymous user by setting request.user to
# an AnonymousUser instance.
request.user = AnonymousUser()
Adding request.user = AnonymousUser() in the tests solves the problem:
class UserMixinTextCaseMixin(object):
def set_up(self):
super().set_up()
self.factory = RequestFactory()
self.user = ActiveUserFactory(slug='look-at-me', username='lookatme')
self.other_user = ActiveUserFactory()
def test_find_user_by_exact_slug(self):
request = self.factory.get('/look-at-me/some-page/')
request.user = AnonymousUser()
view = UserMixinTestView.as_view()(request=request, slug='look-at-me')
self.assertEqual(first=view.get_user().id, second=self.user.id)
Replacing MIDDLEWARE with MIDDLEWARE_CLASSES doesn't solve the problem and we are already using 'django.contrib.auth.middleware.AuthenticationMiddleware', but this is not used by RequestFactory().

Django Rest Framework gives 302 in Unit tests when force_login() on detail view?

I'm using Django Rest Framework to serve an API. I've got a couple tests which work great. To do a post the user needs to be logged in and I also do some checks for the detail view for a logged in user. I do this as follows:
class DeviceTestCase(APITestCase):
USERNAME = "username"
EMAIL = 'a#b.com'
PASSWORD = "password"
def setUp(self):
self.sa_group, _ = Group.objects.get_or_create(name=settings.KEYCLOAK_SA_WRITE_PERMISSION_NAME)
self.authorized_user = User.objects.create_user(self.USERNAME, self.EMAIL, self.PASSWORD)
self.sa_group.user_set.add(self.authorized_user)
def test_post(self):
device = DeviceFactory.build()
url = reverse('device-list')
self.client.force_login(self.authorized_user)
response = self.client.post(url, data={'some': 'test', 'data': 'here'}, format='json')
self.client.logout()
self.assertEqual(status.HTTP_201_CREATED, response.status_code)
# And some more tests here
def test_detail_logged_in(self):
device = DeviceFactory.create()
url = reverse('device-detail', kwargs={'pk': device.pk})
self.client.force_login(self.authorized_user)
response = self.client.get(url)
self.client.logout()
self.assertEqual(status.HTTP_200_OK, response.status_code, 'Wrong response code for {}'.format(url))
# And some more tests here
The first test works great. It posts the new record and all checks pass. The second test fails though. It gives an error saying
AssertionError: 200 != 302 : Wrong response code for /sa/devices/1/
It turns out the list view redirects the user to the login screen. Why does the first test log the user in perfectly, but does the second test redirect the user to the login screen? Am I missing something?
Here is the view:
class APIAuthGroup(InAuthGroup):
"""
A permission to allow all GETS, but only allow a POST if a user is logged in,
and is a member of the slimme apparaten role inside keycloak.
"""
allowed_group_names = [settings.KEYCLOAK_SA_WRITE_PERMISSION_NAME]
def has_permission(self, request, view):
return request.method in SAFE_METHODS \
or super(APIAuthGroup, self).has_permission(request, view)
class DevicesViewSet(DatapuntViewSetWritable):
"""
A view that will return the devices and makes it possible to post new ones
"""
queryset = Device.objects.all().order_by('id')
serializer_class = DeviceSerializer
serializer_detail_class = DeviceSerializer
http_method_names = ['post', 'list', 'get']
permission_classes = [APIAuthGroup]
Here is why you are getting this error.
Dependent Libraries
I did some searching by Class Names to find which libraries you were using so that I can re-create the problem on my machine. The library causing the problem is the one called keycloak_idc. This library installs another library mozilla_django_oidc which would turn out to be the reason you are getting this.
Why This Library Is Causing The Problem
Inside the README file of this library, it gives you instructions on how to set it up. These are found in this file. Inside these instructions, it instructed you to add the AUTHENTICATION_BACKENDS
AUTHENTICATION_BACKENDS = [
'keycloak_oidc.auth.OIDCAuthenticationBackend',
...
]
When you add this authentication backend, all your requests pass through a Middleware defined inside the SessionRefresh class defined inside mozilla_django_oidc/middleware.py. Inside this class, the method process_request() is always called.
The first thing this method does is call the is_refreshable_url() method which always returns False if the request method was POST. Otherwise (when the request method is GET), it will return True.
Now the body of this if condition was as follows.
if not self.is_refreshable_url(request):
LOGGER.debug('request is not refreshable')
return
# lots of stuff in here
return HttpResponseRedirect(redirect_url)
Since this is a middleware, if the request was POST and the return was None, Django would just proceed with actually doing your request. However when the request is GET and the line return HttpResponseRedirect(redirect_url) is triggered instead, Django will not even proceed with calling your view and will return the 302 response immediately.
The Solution
After a couple of hours debugging this, I do not the exact logic behind this middleware or what exactly are you trying to do to provide a concrete solution since this all started based off guess-work but a naive fix can be that you remove the AUTHENTICATION_BACKENDS from your settings file. While I feel that this is not acceptable, maybe you can try using another library that accomplishes what you're trying to do or find an alternative way to do it. Also, maybe you can contact the author and see what they think.
So i guess you have tested this and you get still the same result:
class APIAuthGroup(InAuthGroup):
def has_permission(self, request, view):
return True
Why do you use DeviceFactory.build() in the first test and DeviceFactory.create() in the second?
Maybe a merge of the two can help you:
def test_get(self):
device = DeviceFactory.build()
url = reverse('device-list')
response = self.client.get(url)
self.assertEqual(status.HTTP_200_OK, response.status_code)
Is this a problem with the setUp() method? From what I see, you may be setting self.authorize_user to a user that was already created on the first test.
Instead, I would create the user on each test, making sure that the user doesn't exist already, like so:
user_exists = User.objects.filter(username=self.USERNAME, email=self.EMAIL).exists()
if not user_exists:
self.authorize_user = User.objects.create_user....
That would explain why your first test did pass, why your second didn't, and why #anupam-chaplot's answer didn't reproduce the error.
Your reasoning and code looks ok.
However you are not giving the full code, there must be error you are not seeing.
Suspicious fact
It isn't be default 302 when you are not logged in.
(#login_required, etc redirects but your code doesn't have it)
Your APIAuthGroup permission does allow GET requests for non-logged-in user ( return request.method in SAFE_METHODS), and you are using GET requests (self.client.get(url))
So it means you are not hitting the endpoint that you think you are hitting (your get request is not hitting the DevicesViewSet method)
Or it could be the case you have some global permission / redirect related setting in your settings.py which could be DRF related..
eg :
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.IsAuthenticated',
]
}
Guess
url = reverse('device-detail', kwargs={'pk': device.pk})
might not point to the url you are thinking..
maybe there's another url (/sa/devices/1/) that overrides the viewset's url. (You might have a django view based url)
And I didn't address why you are getting redirected after force_login.
If it's indeed login related redirect, all I can think of is self.authorized_user.refresh_from_db() or refreshing the request ..
I guess some loggin related property (such as session, or request.user) might point to old instance .. (I have no evidence or fact this can happen, but just a hunch) and you better off not logging out/in for every test case)
You should make a seperate settings file for testing and add to the test command --settings=project_name.test_settings, that's how I was told to do.

Authenticate in Django without a database

I have a Django app that gets it's data completely from apis. so I don't have to use database. Session data is stored on signed cookies. I tried to code a custom User model and a custom auth backend like on the docs, but I get the following error:
django.core.exceptions.ImproperlyConfigured: AUTH_USER_MODEL refers to model 'my_app.MyUser' that has not been installed
My settings.py:
AUTH_USER_MODEL = 'my_app.MyUser'
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',
'my_app.backends.LoginAuthBackend',)
models.py:
class MyUser(object):
def save(self):
pass
objects = None
username = ""
Here, If a try use the AbstractUser from django instead of Object I got the following error: AttributeError: 'NoneType' object has no attribute '_meta' or the db table doesn't exit.
backends.py
class LoginAuthBackend(object):
def authenticate(self, username=None, password=None):
if username and password:
try:
response = my_auth_function(username, password)
if response.status_code == 200:
token = response.get('my_key')
user = MyUser()
return user
except MyCustomException:
return None
It's drives me crazy. Looks like Django that's not easy to use without a DB.
EDIT
After several of tries, a simple way to solve this is remove 'django.contrib.auth.backends.ModelBackend' from AUTHENTICATION_BACKENDS and AUTH_USER_MODEL from settings. The model continues basically the same way. works smoothly
The default set of authentication back-end processors is defined in the AUTHENTICATION_BACKENDS setting. See the Django documentation for Customizing authentication.
By default, AUTHENTICATION_BACKENDS is set to:
['django.contrib.auth.backends.ModelBackend']
That’s the basic authentication backend that checks the Django users database and queries the built-in permissions.
So, if you don't want the django.contrib.auth.backends.ModelBackend authentication method, remove that from the list. You'll probably want to find (or create) a different one and add that to the list.

Include authenticated user in dictionary for all views

I am working through the Pyramid authorization tutorial and I have noticed the pattern where
logged_in = request.authenticated_userid
is added to each view dictionary. Can it be avoided? I.e. is there a configuration which automatically ads user id to each view. Or is there a way to create a base, abstract view with the user id and inherit from it?
Part of the code from the tutorial:
#view_config(context='.models.Page', renderer='templates/view.pt', permission='view')
def view_page(context, request):
# not relevant code
return dict(page = context, content = content, edit_url = edit_url,
logged_in = request.authenticated_userid)
#view_config(name='add_page', context='.models.Wiki', renderer='templates/edit.pt',
permission='edit')
def add_page(context, request):
# not relevant code
return dict(page=page, save_url=save_url,
logged_in=request.authenticated_userid)
It's been awhile since I last looked, but I think logged_in in the samples is just an example to use to conditionally check if there is a logged on user or not. You could probably just as easily refer to request.authenticated_userid within any of your views or templates, too, and get the same behavior and not have to explicitly add a status to the response dict. The request object should be available to be referenced in your view templates, too.
Alternatively, I've used their cookbook to add a user object to the request to make a friendly request.user object that I can use to both check for logged in status where needed, plus get at my other user object details if I need to as well.

where django store request['user'] in what file

As mentioned in the documentation, authenticated user's object is stored within user variable in templates. i need where django stored user variable in apps file thanks:
user = request.user
request['user'] = user #where is?
thanks for help
It's in the AuthenticationMiddleware.
The official doc mentioned it:
link:
AuthenticationMiddleware associates users with requests using
sessions.
link:
class AuthenticationMiddleware
Adds the user attribute, representing
the currently-logged-in user, to every incoming HttpRequest object.
See Authentication in Web requests.
source code(django.contrib.auth.middleware.py):
class AuthenticationMiddleware(object):
def process_request(self, request):
assert hasattr(request, 'session'), "The Django authentication middleware requires session middleware to be installed. Edit your MIDDLEWARE_CLASSES setting to insert 'django.contrib.sessions.middleware.SessionMiddleware'."
request.user = SimpleLazyObject(lambda: get_user(request))
Make sure you're using RequestContext. Otherwise user is not available in the templates.

Categories

Resources