I'm testing whether an API User can POST to a view if the User can pass the IsAuthenticated permission. Upon running the test I get the assertion error: AssertionError: 401 != 201.
When permission_classes = (permissions.IsAuthenticated,) is commented out and the test is ran again, I'm finding that request.user is turning out to be an AnonymousUser. Yet, I have a User created and attached to the request as shown below.
I'm not sure what is causing this, and looking to understand how I can pass a User instance to the request.
Note - I'm trying to do this in the Django testing API rather than use Django REST Framework.
tests.py
class TestUserPreferencesResource(TestCase):
'''Verify that a User is capable of setting preferences for their profile'''
#classmethod
def setUpTestData(cls):
cls.user = User.objects.create_user('Mock', password="secret")
cls.user_prefs = json.dumps({
"age": ['Baby', 'Adult'],
"gender": ['Male'],
"size": ["Medium", "Large"]
})
cls.factory = RequestFactory()
cls.credentials = b64encode(b"Mock:secret").decode("ascii")
def test_user_preferences_settings(self):
request = self.factory.post(
reverse("pref-settings"),
data=self.user_prefs,
content_type="application/json",
headers = {
"Authorization": f"Basic {self.credentials}"
},
)
request.user = self.user
print(request.user)
response = UserPrefView.as_view()(request)
self.assertEqual(response.status_code, 201)
views.py
class UserPrefView(
CreateModelMixin,
UpdateModelMixin,
GenericAPIView):
queryset = UserPref.objects.all()
permission_classes = (permissions.IsAuthenticated,)
serilaizer_class = serializers.UserPreferenceSerializer
def post(self, request, format=None):
import pdb; pdb.set_trace()
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
serializer.save(user=request.user)
return Response(serializer.data)
Related
I am new to DRF and for learning I am writing test for my DRF Rest API. I am trying to test Login view, however, the test is failing even though I am providing correct credentials. Unusual thing is that it works perfectly fine when I am making a login request from Postman
I tried analyzing the data I provide, however I don't find any issues. In the test case I create new User and then try to log in.
My Test case:
def test_login_user(self):
"""
Ensure user can log in
"""
username = 'TestUserLogin'
password = 'test978453442'
url = reverse('login-list')
user = User.objects.create(
email='testUserLogin#test.com',
first_name='Matheus',
last_name='Smith',
password=password,
title='professor',
username=username,
groups=Group.objects.get(name=GROUPS[0])
)
response = self.client.post(url, {'username': username, 'password': password}, format='json')
self.assertEqual(response.status_code, status.HTTP_200_OK)
My Token Generator class
class CustomExpiringObtainAuthToken(ObtainAuthToken):
def post(self, request, *args, **kwargs):
"""
Override!
Create token everytime this endpoint is called
"""
serializer = self.get_serializer(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
if hasattr(user, 'auth_token'):
user.auth_token.delete()
token = Token.objects.create(user=user)
return Response({'token': token.key})
My Login View:
class LoginView(ViewSet):
serializer_class = AuthTokenSerializer
def create(self, request):
return CustomExpiringObtainAuthToken().as_view()(request=request._request)
I tried to add a Patch method to my API in Django and I'm always ending with a "Method not allowed".
I added mixins.UpdateModelMixin as mention in the Django Rest Framework documentation, however, it still returns the same error. I look and don't find where I need to put authorization for Patch to be allowed.
this is the code related to that view and path declaration in urls.py and views.py.
urls.py
schema_view = get_schema_view(
openapi.Info(
title="WAF Management Portal API",
default_version="v1",
description="REST api for interaction between Frontend and Backend.",
contact=openapi.Contact(email="soc-dev-automation#bell.ca"),
),
public=True,
permission_classes=(permissions.AllowAny,),
)
path(
'action/dothis/', ActionApiView.as_view(), name="action_api_view"
),
views.py
class ActionApiView(mixins.UpdateModelMixin, ActionAPIView):
"""
post:
add one or more settings to selected policy
patch:
modify or more settings to selected policy
"""
def get_queryset(self):
return Policy.objects.allowed_to_user(self.request.user)
def get_serializer(self, *args, **kwargs):
return SettingsSerializer(*args, **kwargs)
#swagger_auto_schema()
def post(self, request):
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
selected_policies = serializer.get_selected_policies(queryset)
.....do some data manipulation (included action_id variable)...
response = {
....prepare response
}
return redirect("another_view", action_id=action_id)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
#swagger_auto_schema()
def patch(self, request):
queryset = self.filter_queryset(self.get_queryset())
serializer = self.get_serializer(data=request.data)
if serializer.is_valid():
selected_policies = serializer.get_selected_policies(queryset)
.....do some data manipulation (included action_id variable)...
response = {
....prepare response
}
return redirect("another_view", action_id=action_id)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
If ActionAPIView is inheriting from ModelViewSet , you may need to rename the patch function to update and change your urls to
path(
'action/dothis/', ActionApiView.as_view({'patch':'update'}), name="action_api_view"
),
Im using get_queryset, in ListAPIView
I want to check the user's access token, before providing the list, I done the below but the issue is that get_query set does not return a Response, is there a way to return a response, or I should use an alternative :
this my class in the views.py :
class ListProductsOfCategory(generics.ListAPIView):
serializer_class = ProductSerializer
lookup_url_kwarg = 'category_id'
def get_queryset(self):
# I get the token here from the headers
token = self.request.META.get("HTTP_TOKEN", "")
if not token:
return Response(
data={
"message": "no token!"
},
status=status.HTTP_400_BAD_REQUEST
)
if not UserAccess.objects.filter(accessToken=token).exists():
return Response(
data={
"message": "invalid token!"
},
status=status.HTTP_400_BAD_REQUEST
)
category_id = self.kwargs.get(self.lookup_url_kwarg)
return Product.objects.filter(category_id=category_id)
note that everything is working perfect If I removed the token related part.
thanks in advance.
after last update this is the repsonse :
I'd suggest you to move check token logic into dispatch() method. It's a better place than get_queryset. Or even better to write your own authentication class in order to share it between views.
With some fixes (see updated get_queryset()) it can be:
UPDATE
I think you can go with built-in restframework.exceptions.AuthenticationFailed.
If you are not satisfied with default DRF exceptions you can create your own custom exceptions. For example somewhere in exceptions.py:
from rest_framework.exceptions import APIException
class MissingTokenException(APIException):
status_code = 400
default_detail = 'Your request does not contain token'
default_code = 'no_token'
class InvalidTokenException(APIException):
status_code = 400
default_detail = 'Your request contain invalid token'
default_code = 'invalid_token'
Then you can use them in views.py:
from rest_framework import serializers
from .exceptions import MissingTokenException, InvalidTokenException
class ListProductsOfCategory(generics.ListAPIView):
serializer_class = ProductSerializer
lookup_url_kwarg = 'category_id'
def dispatch(self, *args, **kwargs):
token = self.request.META.get("HTTP_TOKEN", "")
if not token:
raise MissingTokenException
if not UserAccess.objects.filter(accessToken=token).exists():
raise InvalidTokenException
return super().dispatch(*args, **kwargs)
def get_queryset(self):
qs = super().get_queryset()
category_id = self.kwargs.get(self.lookup_url_kwarg)
return qs.filter(category_id=category_id)
I'm not 100% if I'm getting this right, but I believe you can just use the regular authentication mechanisms that DRF provides. In this particular example, I think this section of the DRF docs should show you how to do it the "DRF" way: Setting Authentication Scheme
If you add the TokenAuthentication scheme to your application, you don't need to verify the token in your get_queryset method, but you can just use decorators to restrict access for function-based views or permission_classes for class-based views:
View-based
I guess this is what you'd be most interested in.
class ListProductsOfCategory(generics.ListAPIView):
serializer_class = ProductSerializer
lookup_url_kwarg = 'category_id'
authentication_classes = (TokenAuthentication, ) # Add others if desired
permission_classes = (IsAuthenticated,)
Route-based
If you only want to restrict access for some of your routes (e.g. only post or detail views), then you can write your own permission class. For example, see this question here: How to add django rest framework permissions on specific method only ?
When I attempt to test my Create/POST route for my Django Rest Framework API I receive a response status code of 401 with the error detail telling me ErrorDetail(string=u'Authentication credentials were not provided.', code=u'not_authenticated'). The weird thing is I Django tells me I'm authenticated when I check is is_authenticated.
Does anyone have an idea what might be causing this? All relevant code provided below.
# test_api.py
def authorise_user_and_test_is_authenticated(self, user_id):
"""
Log in user and test this is successful
"""
user = User.objects.get(pk=user_id)
self.client.login(username=user.username, password=user.password)
authorised_user = auth.get_user(self.client)
return self.assertTrue(user.is_authenticated())
def test_create_project(self):
'''
When given valid parameters a project is created.
'''
user = User.objects.get(username="user_001")
self.authorise_user_and_test_is_authenticated(user.id) # pass of authenication and auth testing to method, when tested with is_authenicated() it returns true.
response = self.client.post('/api/user/{0}/project/create/'.format(user.id),
json.dumps({"model_name": "POSTed Project",
"description": "Project tested by posting",
"shared_users[]": [2]
}),
content_type='application/json')
self.assertEqual(response.status_code, 201)
# views.py
class MyCreateView(generics.GenericAPIView):
pass
serializer_class = FerronPageCreateAndUpdateSerializer
def get_queryset(self):
return User.objects.filter(pk=self.kwargs.get('user'))
def post(self, request, format=None, **kwargs):
# This dictionary is used to ensure that the last_modified_by field is always updated on post to be the current user
print request.data
request_data = {
'user': request.user.id,
'model_name': request.data['model_name'],
'description': request.data['description'],
'last_modified_by': request.user.id,
'shared_users': request.data.getlist('shared_users[]', [])
}
serializer = FerronPageCreateAndUpdateSerializer(data=request_data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
# settings.py
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
],
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.TokenAuthentication',
'rest_framework.authentication.SessionAuthentication'
),
}
# url matcher
url(r'^user/(?P<user>\d+)/project/create/$', MyCreateView.as_view(), name='create-project')
class FerronPageCreateAndUpdateSerializer(serializers.ModelSerializer):
shared_users = serializers.PrimaryKeyRelatedField(many=True, queryset=User.objects.all(), read_only=False)
description = serializers.CharField(max_length=300, trim_whitespace=True, required=False, allow_blank=True)
class Meta:
model = Project
fields = [
'pk',
'user',
'data',
'model_name',
'description',
'created_at',
'date_modified',
'shared_users',
'last_modified_by'
]
Turn's out the issue was here self.client.login(username=user.username, password=user.password) inside the authorise_user_and_test_is_authenticated(self, user_id) method.
The problem was that I was using the password an instance of a user I had already created. This meant when I gave the argument password=user.password, I was trying to log in using a password that had already been hashed. What I needed to do instead was log in with the original unhashed version of the password e.g. password='openseasame'.
I have developed a simple webservice, but failed to use post with Django Rest Framework as it complains about CSRF:
"detail": "CSRF Failed: CSRF cookie not set."
Removing the api_view decorator does stop the message from appearing but then I won't be able to access the request.data. I think that the api_view does check CSRF although I added the csrf_exempt decorator.
This is my view:
#permission_classes((IsAuthenticated, ))
#csrf_exempt
#api_view(['POST'])
def get_stats(request):
"""
Returns the stats available.
"""
user = request.user
if request.method == 'POST':
serializer = StatsRequestSerializer(data=request.data)
stats_request = serializer.data
return JSONResponse(stats_request)
#serializer = QuizSerializer(user.quizes.all(), many=True)
#return JSONResponse(serializer.data)
response = ActionResponse(status='error', error='Invalid request')
serializer = ActionResponseSerializer(response)
return JSONResponse(serializer.data, status=400)
This is my model:
class StatsRequest(models.Model):
"""
A model which describes a request for some stats for specific users.
"""
start_date = models.DateField()
end_date = models.DateField()
and this is my request POST:
{"start_date" : "1992-01-15", "end_date" : "1992-01-15" }
Any ideas?
More info:
AUTHENTICATION_BACKENDS = (
'social.backends.facebook.FacebookOAuth2',
'social.backends.google.GoogleOAuth2',
'django.contrib.auth.backends.ModelBackend'
)
So, after trying to figure this out for a couple of hours I finally did it.
Tracing the source code of DRF and Django lead me to believe that I need to find a workaround for this as the CSRF verification is made explicitly even if turned off, probably the CSRF check is being made at the api_view decorator. So I simply created my own decorator:
from functools import wraps
from django.utils.decorators import available_attrs, decorator_from_middleware
def csrf_clear(view_func):
"""
Skips the CSRF checks by setting the 'csrf_processing_done' to true.
"""
def wrapped_view(*args, **kwargs):
request = args[0]
request.csrf_processing_done = True
return view_func(*args, **kwargs)
return wraps(view_func, assigned=available_attrs(view_func))(wrapped_view)
and my view with the new decorator:
#csrf_clear
#api_view(['POST'])
#permission_classes((IsAuthenticated, ))
def get_stats(request):
"""
Returns the stats available.
"""
user = request.user
if request.method == 'POST':
serializer = StatsRequestSerializer(data=request.data)
if serializer.is_valid():
stats_request = serializer.data
return JSONResponse(stats_request)
#serializer = QuizSerializer(user.quizes.all(), many=True)
#return JSONResponse(serializer.data)
response = ActionResponse(status='error', error='Invalid request')
serializer = ActionResponseSerializer(response)
return JSONResponse(serializer.data, status=400)
urls.py
from django.views.decorators.csrf import csrf_exempt
urlpatterns = [
url(r'^snippets/$', views.SnippetList.as_view()),
url(r'^snippets/(?P<pk>[0-9]+)/$', csrf_exempt(views.SnippetDetail.as_view())),
]
views.py
from django.views.decorators.csrf import csrf_exempt
from rest_framework.views import APIView
class SnippetList(APIView):
#csrf_exempt
#need_post_parameters([PARAM_MESSAGE_OBJ])
def post(self, request, *args, **kwargs):
data = request.POST.get(PARAM_MESSAGE_OBJ)
try:
message_obj = json.loads(data)
except Exception as e:
return HttpResponseBadRequest(error_json("Could not parse JSON"))
http://www.chenxm.cc/post/509.html