Overriding Django setting doesn't reflect in test - python

I have a separate settings file for test environment. I'm using DRF for Authentication, but have set the default permission as rest_framework.permissions.AllowAny in the test_setting file. Now to test whether authentication is working or not, I need to override the key. But it doesn't seem to work.
I'm using the decorator #override_settings. But it doesn't seem to work. If I change the setttings file directly, then the test is passing.
#override_settings(REST_FRAMEWORK={
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
)
})
class AuthenticationTest(TestCase):
def test_api_should_not_be_hit_without_authorization(self):
response = client.get(reverse('some_api', kwargs={'key': value}))
self.assertEqual(response.status_code, status.HTTP_401_UNAUTHORIZED)
I'm expecting the status code to 401, but it seems to pass through and is giving me 200.
I pasted the below code in the test and it shows that the REST FRAMEWORK was actually overridden, but the test is still failing.
from django.conf import settings
dir(settings)
print(settings.REST_FRAMEWORK)
EDIT:
Here is the response that I am getting:
<Response status_code=200, "application/json">
Thanks

I've had this happen before when testing DRF endpoints as an authenticated user. If the user has access to the DRF browseable API views, an API request that caused a 401 can still be represented in a valid API view.
Try adding format='json' to your client.get() to ensure that you aren't hitting the DRF interface instead of the JSON API:
response = client.get(reverse('some_api', kwargs={'key': value}), format='json')
Instead of specifying the API format in client.get(), you could change the default API request format in a test settings module so it's used in all of your test requests:
REST_FRAMEWORK = {
...
'TEST_REQUEST_DEFAULT_FORMAT': 'json'
}

Related

DRF Response content-type set to None during tests

I'm using Django Rest Framework (version 3.6.2) to create REST API. I've defined my viewset that inherits from GenericViewSet and have overriden retrieve method to implement custom behaviour.
class FooViewSet(viewsets.GenericViewSet):
serializer_class = FooSerializer
def retrieve(self, request, *args, **kwargs):
...
serializer = self.get_serializer(data)
return Response(serializer.data)
I want to have BrowsableAPI while accessing this endpoint from the browser and receive json response when accessing this endpoint e.g. from the code. I've configured DRF with the following settings:
REST_FRAMEWORK = {
'DEFAULT_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
'rest_framework.renderers.BrowsableAPIRenderer',
),
'TEST_REQUEST_RENDERER_CLASSES': (
'rest_framework.renderers.JSONRenderer',
),
'TEST_REQUEST_DEFAULT_FORMAT':'json'
}
Everything works as expected, I can access the browsable API from the browser and when making request with the Postman tool I get json response. Unfortunately, I can't achieve the same result during tests.
class GetFooDetailViewTest(APITestCase):
def test_get_should_return_json(self):
response = self.client.get(self.VIEW_URL)
self.assertEqual(response.content_type, "application/json")
I expect the response to have content_type set to application/json (this is the header that I can see in the responses from browser and Postman). But this test fails - response.content_type is set to None. While debugging this test, I've discovered that response._headers dictionary looks like this
{
'vary': ('Vary', 'Cookie'),
'x-frame-options': ('X-Frame-Options', 'SAMEORIGIN'),
'content-type': ('Content-Type', 'application/json'),
'allow': ('Allow', 'GET, PUT, DELETE, OPTIONS')
}
So it seems like the correct header is being set, but it's not getting populated to the content_type attribute. Am I missing something?
This is how I test for the content type. In very few cases my code decides the content-type itself, so I to check that I personally did not do something wrong. DRF code is already tested.
self.assertEqual("application/json", resp['Content-Type'])
You just have to rely on DRF doing it right, its not something you can or need to test. For example, you don't test that DRF parsed your json body correctly. The test server isn't exactly like the real one, but its pretty close. For example, you will get real objects out of response.data, not the json encoded/decoded ones.
Check out the LiveServerTestCase if you need it, but it will be slower.
I ran into something similar and this worked for me:
response = self.client.get(self.VIEW_URL, HTTP_ACCEPT='application/json')

Django JWT auth: How to get user data?

I'm trying to desperately understand how to use JWT auth with Django.
This page explains how to get a token against username and password:
http://getblimp.github.io/django-rest-framework-jwt/
$ curl -X POST -H "Content-Type: application/json" -d '{"username":"admin","password":"password123"}' http://localhost:8000/api-token-auth/
Now in order to access protected api urls you must include the Authorization: JWT <your_token> header.
1) How can I get the user details (id, email..) of the "logged in" user from the server? If I used the session based auth I would just serialize and return request.user if it's logged in. I don't understand how the server would know who is who if nothing auth-related is persisted.
2) I don't even understand how the procedure described in that page is safe. Why can't the attacker just hijack the token and do what he wants? As I understood I just get a token and then send the same token back in every request. Is this even real JWT?
You use the typical Django auth mechanism with JWT.
You POST with the username and password and get the token back. Your auth view needs to have the following permission class:
from rest_framework.views import APIView
class Authenticate(APIView):
permission_classes = (AllowAny,)
The next time you sent the token it goes through here:
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
),
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
),
The authentication classes set request.user and you can use it as you normally do
2) I don't even understand how the procedure described in that page is safe. Why can't the attacker just hijack the token and do what he wants? As I understood I just get a token and then send the same token back in every request. Is this even real JWT?
You absolutely have to investigate the JWT refresh token mechanism. Tokens are usually short lived, the default is 5 minutes I think.

Django Rest Framework invalid username/password

Trying to do a simple 'GET' wih admin credentials returns
"detail": "Invalid username/password."
I have a custom user model where I deleted the username, instead I use facebook_id :
USERNAME_FIELD = 'facebook_id'
I tried changing the DEFAULT_PERMISSION_CLASSES:
('rest_framework.permissions.IsAuthenticated',), -- doesn't work!
('rest_framework.permissions.IsAdminUser',), -- doesn't work!
The only one that works is:
('rest_framework.permissions.AllowAny',),
But I do not want that, since I'm building an API for a Mobile App
I also declared a CustomUserAdmin model and CustomUserCreationForm , apparently this was not the problem
Help me understand what needs to be done to fix this annoying problem, I'm guessing it might have something to do with Permissions/Authentication or the fact that I CustomUserModel..
Also, let me know if there is a better way for a mobile app client to authenticate to the api
Have just had the same problem. In my case the source of the problem was Apache's Basic Authentication, my browser was sending Authorization header and Django REST Framework thought that this header was to be handled by it. The solution is pretty simple: just remove
'rest_framework.authentication.BasicAuthentication' from your
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
# ... auth classes here ...
]
}
Or explicitly set the default DEFAULT_AUTHENTICATION_CLASSES to remove BasicAuth from DRF's defaults.
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": (
"rest_framework.authentication.SessionAuthentication",
),
}
You have the default, and then you have per view. You can set the default to IsAuthenticated, and then you override your view's particular permission_classes. e.g.
class ObtainJSONWebLogin(APIView):
permission_classes = ()
or
class Foo(viewsets.ModelViewSet):
permission_classes = ()

How do I create a login API using Django Rest Framework?

I want to create a login api (or use an existing one if it is already pre-bundled) using django rest framework. However, I'm completely at a loss. Whenever I send a post request to the django rest framework "login" url, it just sends back the browsable api template page...
MY CONFIGURATION
urls.py
url(r'^api/v1/', include('rest_framework.urls', namespace='rest_framework'))
settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
WHAT I WANT
Request:
POST /api/v1/login username='name' pass='pass'
Response:
200 OK "{username: 'name', 'userId': '54321'}" set-cookie: sessionid="blahblah"
Take a look at the api view from django-rest-framework-jwt. It's an implementation for creating auth tokens rather than cookie sessions, but your implementation will be similar. See views.py and serializers.py. You can probably use the serializers.py unchanged, and just adjust your views to return the right parameters and possibly set the session cookie (can't recall if that's already performed in authentication).
If you want something like this I do the same thing however I use Token authentication.
Check out their token page here
This may not be what you want but the way I do it is (since I'm using it as a rest api endpoints for mobile clients)
I can do my url localhost:8000/api/users/ -H Authorization : Token
A browser could then use the regular login page that you create at the provided rest framework url
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')
and to get tokens for 'login-less' navigation
url(r'^api-token-auth/', 'rest_framework.authtoken.views.obtain_auth_token')
Then if you make calls and such you can pass the authorization tokens. Of course this is just how I do it and it's probably not the most efficient way but my goal was to create a way that I can provide users with session authentication for browsers and mobile access via tokens.
Then in your views.py make sure you add the authentication requirements for that view. Almost the same as session authentication section
permission_classes = (permissions.IsAdminUser,)
but also include
authentication_classes = (authentication.TokenAuthentication,)
I hope this helps but if not, good luck on your search.
Of course token is a good way to authenticate, but questioner is asking about session authentication.
Request:
POST /api/v1/login username='username' password='password'
Put csrftoken value at X-CSRFToken in header
Even though someone using email as username filed, username name parameter is required for email input (e.g. username='sample#domain.com')
Response:
302 FOUND sessionid="blahblah"
If you not specified next value, it will automatically redirect into /accounts/profile/ which can yield 404 error
Adding our views:
from rest_framework_jwt.views import refresh_jwt_token
urlpatterns = [
...
url(r'^rest-auth/', include('rest_auth.urls')),
url(r'^rest-auth/registration/', include('rest_auth.registration.urls')),
...
url(r'^refresh-token/', refresh_jwt_token),
]

Django rest-framework does not store authenticated user between calls

For the project I'm currently working on, I decided to write a custom authentication model. I'm using both the rest-framework and all-access. The local authentication is not implemented yet (it is, but in a different version, so no issue there).
The User model registers properly and OAuth authentication works. Users get logged alright.
When working with views, authentication works properly if I use django's View as base class. Not so much if I use rest-framework's APIView.
In my ProfileView (LOGIN_REDIRECT_URL redirects here) I receice a rest_framework.request.Request instance, which is OK by me. The problem is:
request.user is an instance of AnonymousUser (even after login)
I would guess the problem lies with my implementation, but then:
request._request.user is an authenticated user
(Note that request._request is the original django request)
I tend to think this is a rest-framework bug, but maybe one of you knows of a workaround?
Edit
Just to note that the login is made in a non-APIView class, but instead in a class that inherits from View. That's probably the reason the User instance is not passed to the APIView instance.
You need to set the DEFAULT_AUTHENTICATION_CLASSES in your settings.py. Else DRF will default to None as request.auth and AnonymousUser as request.user
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.BasicAuthentication',
'rest_framework.authentication.SessionAuthentication',
)
}
Once you set that and the user is logged in, ensure that the session_id is available in the request headers. Browsers typically store the session information and pass it in the headers of all requests.
If you are using a non-browser client (Eg. a mobile app), you will need to set the session id manually in the header.
Also since you mentioned you use oauth to sign-in the user, for oauth2 you need to specify the oauth2 authorization header instead of the session id.
"Authorization: Bearer <your-access-token>"
Refer to Authenticating in Django Rest Framework

Categories