I need to restrict access to the APIs I have defined in my view. Here is my views.py:
rom rest_framework import generics
from rest_framework import permissions
from .serializers import LocationSerializer, PartSerializer, PartLocationSerializer, SiteSerializer
from .models import Location, Part, PartLocation, Site, SPIUser
class SPIPermission(permissions.BasePermission):
"""
blah blah blah ...
"""
def has_permission(self, request, view):
try:
username = request.user.username
SPIUser.objects.get(username=username)
except SPIUser.DoesNotExist:
return False
if not request.user.is_authenticated:
return False
return True
class LocationList(generics.ListCreateAPIView):
# using get_queryset().order_by('id') prevents UnorderedObjectListWarning
queryset = Location.objects.get_queryset().order_by('id')
serializer_class = LocationSerializer
permission_classes = (SPIPermission,)
I want to demonstrate in my unit tests that your have to be an SPIUser to be able to access these api endpoints so I write a simple unit test like so:
from .models import Location, Part, PartLocation, Site, SPIUser
from .urls import urlpatterns
from my.APITestCase import RemoteAuthenticatedTest
from django.db.models import ProtectedError
from django.test import TransactionTestCase
from django.urls import reverse
from rest_framework import status
import django.db.utils
import os
class ViewTestCases(RemoteAuthenticatedTest):
def test_spi_permission(self):
url = reverse('spi:locationlist')
response = self.client.get(url)
self.assertEqual(response.status_code, status.HTTP_403_FORBIDDEN)
SPIUser.objects.create(username=self.username)
response = self.client.get(url)
self.assertNotEquals(response.status_code, status.HTTP_403_FORBIDDEN)
This test fails with the this error message:
Failure
Traceback (most recent call last):
File "/apps/man/apman/spi/tests.py", line 21, in test_spi_permission
self.assertNotEquals(response.status_code, status.HTTP_403_FORBIDDEN)
AssertionError: 403 == 403
I noticed that the line in has_permission ...
username = request.user.username
... always sets the username to ''. So has_permission will always return False.
My unit test ViewTestCases inherits class RemoteAuthenticatedTest which is defined like so:
from rest_framework.test import APIClient,APITestCase
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
class RemoteAuthenticatedTest(APITestCase):
client_class = APIClient
def setUp(self):
self.username = 'mister_neutron'
self.password = 'XXXXXXXXXXX'
self.user = User.objects.create_user(username= self.username,
email='mister_neutron#example.com',
password=self.password)
#authentication user
self.client.login(username=self.username, password=self.password)
Token.objects.create(user=self.user)
super(RemoteAuthenticatedTest, self).setUp()
So I thought that request.user.username would be mister_neutron.
What am I doing wrong here?
Ah heck. I forgot that I am using RemoteUser authentication so when I make my I need to set REMOTE_USER like so:
response = self.client.get(url, REMOTE_USER=self.username)
Related
How can I verify the incoming webhook from Shopify? Shopify provides a python implementation (of Flask), but how can I do it in Django/DRF?
Set these two variables in the settings.py file
# settings.py
SHOPIFY_HMAC_HEADER = "HTTP_X_SHOPIFY_HMAC_SHA256"
SHOPIFY_API_SECRET = "5f6b6_my_secret"
Then, create a verify webhook function that accepts the Django request as it's parameter
# utils.py
import base64
import hashlib
import hmac
from django.conf import settings
from django.core.handlers.wsgi import WSGIRequest
def verify_shopify_webhook(request: WSGIRequest):
shopify_hmac_header = request.META.get(settings.SHOPIFY_HMAC_HEADER)
encoded_secret = settings.SHOPIFY_API_SECRET.encode("utf-8")
digest = hmac.new(
encoded_secret,
request.body,
digestmod=hashlib.sha256,
).digest()
computed_hmac = base64.b64encode(digest)
return hmac.compare_digest(computed_hmac, shopify_hmac_header.encode("utf-8"))
Then, create a view that accepts the incoming webhook and use the verify_shopify_webhook(...) function to verify the request.
# views.py
from django.http import HttpResponse
from django.utils.decorators import method_decorator
from django.views import View
from django.views.decorators.csrf import csrf_exempt
from .utils import verify_shopify_webhook
#method_decorator(csrf_exempt, name="dispatch")
class ShopifyWebhookView(View):
def post(self, request, *args, **kwargs):
verified = verify_shopify_webhook(request=request)
return HttpResponse(status=200 if verified else 403)
If you're using Django REST Framework, you can also use APIView as
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
from .utils import verify_shopify_webhook
class ShopifyWebhookView(APIView):
def post(self, request, *args, **kwargs):
verified = verify_shopify_webhook(request=request)
return Response(status=200 if verified else 403)
I'm struggling to figure how to get the id of the user I created using the create_user() function in my unit test.
Unit test below:
from django.test import TestCase
from django.contrib.auth import get_user_model
from django.urls import reverse
from rest_framework.test import APIClient
from rest_framework import status
DELETE_URL = reverse('user:delete_user')
class PrivateUserApiTests(TestCase):
"""Test the users API (private)"""
def setUp(self):
self.user = create_user(
email='tes111t#test.com',
password='test123',
name='name'
)
self.client = APIClient()
self.client.force_authenticate(user=self.user)
def test_user_successful_delete(self):
"""Test that user was succesfully deleted"""
payload = {'email': 'test#test123.com', 'password': 'test123'}
create_user(**payload) # Here is where I create the user I want to delete
user = get_user_model().objects.get()
res = self.client.delete(DELETE_URL) # I'm not sure how to pass the id of the user I just created.
The way my url works is: (the id passed in is the user that is deleted)
0.0.0.0:8000/api/user/delete-user/id
urls.py
urlpatterns = [
path('create/', views.CreateUserView.as_view(), name='create'),
path('token/', views.CreateTokenView.as_view(), name='token'),
path('me/', views.ManageUserView.as_view(), name='me'),
path('all_users/', views.RetrieveUsersView.as_view(), name='all_users'),
path('delete-user/<int:pk>', views.DeleteUserAPI.as_view(), name='delete_user')
]
When using the self.client.delete() How do i pass in the id?
Try using reverse like this:
response = self.client.delete(reverse('user', kwargs={'pk': user.pk}), follow=True)
Although that would require an API endpoint containing information about your user including their pk
I am using the token based Authentication in Django and need to add User object in addition to token being returned.
How do I override this class view ? Where do I need add this class and make the changes ? Currently this is found in the rest_framework package and I don't want to modify the library .
from rest_framework import parsers, renderers
from rest_framework.authtoken.models import Token
from rest_framework.authtoken.serializers import AuthTokenSerializer
from rest_framework.response import Response
from rest_framework.views import APIView
class ObtainAuthToken(APIView):
throttle_classes = ()
permission_classes = ()
parser_classes = (parsers.FormParser, parsers.MultiPartParser, parsers.JSONParser,)
renderer_classes = (renderers.JSONRenderer,)
serializer_class = AuthTokenSerializer
print "dasdsa"
def post(self, request):
serializer = self.serializer_class(data=request.data)
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response({'token': token.key})
obtain_auth_token = ObtainAuthToken.as_view()
From docs.
First that you need is to extend the ObtainAuthToken class.
# views.py
from rest_framework.authtoken.views import ObtainAuthToken
from rest_framework.authtoken.models import Token
from rest_framework.response import Response
class CustomAuthToken(ObtainAuthToken):
def post(self, request, *args, **kwargs):
serializer = self.serializer_class(data=request.data,
context={'request': request})
serializer.is_valid(raise_exception=True)
user = serializer.validated_data['user']
token, created = Token.objects.get_or_create(user=user)
return Response({
'token': token.key,
'user_id': user.pk,
'email': user.email
})
And after this add the CustomAuthToken class to your urls.py like view
# urls.py
from django.urls import path
from . import views
urlpatterns += [
path(r'api-token-auth/', views.CustomAuthToken.as_view())
]
You should extend your CustomClass from AuthToken, the route default url to your CustomClass:
from rest_framework_jwt.views import ObtainJSONWebToken
class JSONWebTokenAPIOverride(ObtainJSONWebToken):
"""
Override JWT
"""
def post(self, request):
# Do whatever you want
Then in your urls.py:
url(
r'^api-auth$',
cache_page(0)(views.JSONWebTokenAPIOverride.as_view())
)
I hope it helps
I wanted to override some default CRSF functionality and used the following approach:
from rest_framework.authentication import SessionAuthentication
class SessionCsrfExemptAuthentication(SessionAuthentication):
def enforce_csrf(self, request):
# Do not perform a csrf check
return False
Then in my settings file I referenced it in the following way:
'DEFAULT_AUTHENTICATION_CLASSES': (
'myapp.utils.authenticate.SessionCsrfExemptAuthentication',
'rest_framework.authentication.BasicAuthentication',
'rest_framework_jwt.authentication.JSONWebTokenAuthentication',
'oauth2_provider.ext.rest_framework.OAuth2Authentication',
'rest_framework_social_oauth2.authentication.SocialAuthentication',
),
This allowed me to import the existing functionality, override it and reference it in the settings file. I think you can use a similar approach here.
I use the option JWT_RESPONSE_PAYLOAD_HANDLER.
In the response I include the token, expiration timestamp and the user.
In settings.py add:
JWT_AUTH = {
...
'JWT_RESPONSE_PAYLOAD_HANDLER':'<app_name>.functions.custom_jwt_response',
}
Then in functions.py add the following
def custom_jwt_response(token, user=None, request=None):
import jwt
jwt = jwt.decode(token, verify=False)
return {
'token': token,
'token_exp': jwt['exp'],
'user': UserSerializer(user, context={'request': request}).data
}
The answers here are good but in my opinion they don't make full use of inheritance. When we inherit a class, we shouldn't just try to reinvent the wheel and instead make use of the super() keyword. Here is my code example, where I want to turn the username argument into lowercase before performing the authentication request:
class GetAuthToken(ObtainAuthToken):
"""
Override Django's ObtainAuthToken to provide custom way of authenticating user for token
"""
def post(self, request, *args, **kwargs):
#-- turn username to lowercase
if ('username' in request.data):
request.data['username'] = request.data['username'].lower()
#-- perform normal function
return super().post(request, *args, **kwargs)
I'm learning Django atm and I am stuck with creating a simple test case.
How I can test the following view? If the user is already logged in it redirects him to list view.
from django.shortcuts import render
from django.contrib.auth import authenticate, login, logout
from django.contrib.auth.forms import AuthenticationForm
from django.views.generic import TemplateView
from django.core.urlresolvers import reverse
from django.shortcuts import redirect
class LoginView(TemplateView):
"""
Base class for client area login.
"""
def get(self, request, *args, **kwargs):
"""
Display login form, only if user is not yet authenticated.
Otherwise redirect to tickets index.
:param request:
:param args:
:param kwargs:
:return:
"""
if request.user.is_authenticated():
return redirect(reverse('tickets:index'))
form = AuthenticationForm()
return render(request, 'login.html', {'form': form})
So far the test I have created looks like this:
from django.test import TestCase, RequestFactory
from django.core.urlresolvers import reverse
from django.contrib.auth.models import User, AnonymousUser
from app.account.views import LoginView
class TestLoginForm(TestCase):
def setUp(self):
"""
Set up environment
:return:
"""
self.user = User.objects.create_user(username="test", password="test123", email="test#gmail.com")
self.factory = RequestFactory()
def tearDown(self):
"""
Clean created user object
:return:
"""
self.user.delete()
self.factory = None
def test_login_form_redirect(self):
"""
Test login redirect
:return:
"""
request = self.factory.get(reverse('account:login'))
request.user = AnonymousUser()
view = LoginView()
response = view.get(request=request)
self.assertEqual(response.status_code, 200)
request.user = self.user
response = view.get(request=request)
self.assertRedirects(response, expected_url=reverse('tickets:index'), status_code=302, target_status_code=200)
The first assertation passed, but the second where the user should be redirected if already has logged in throws this error
AttributeError: 'HttpResponseRedirect' object has no attribute 'client'
What am I doing wrong?
Ok I have managed to make successful test case, with following code
def test_login_form_redirect(self):
"""
Test login redirect
:return:
"""
self.client.logout()
response = self.client.get(reverse('account:login'))
self.assertEqual(response.status_code, 200)
self.assertTrue(self.client.login(username='test', password='test123'))
response = self.client.get(reverse('account:login'))
self.assertRedirects(response, expected_url=reverse('tickets:index'), status_code=302, target_status_code=200)
After struggling mightily with this issue, I've come asking for a bit of help. I'm writing a test for a Django Rest Framework view, testing whether or not I can access the data whilst authenticated, and not. However, even when I'm authenticated, I still get 401 UNAUTHORIZED every time. Here's my tests:
from django.test import TestCase
from django.contrib.auth.models import User
from rest_framework.authtoken.models import Token
from rest_framework.test import APIRequestFactory, APIClient
from apps.core import models, admin
from apps.rest import views
class TestAPIViews(TestCase):
def setUp(self):
self.factory = APIRequestFactory()
self.client = APIClient()
self.user = User.objects.create_user('testuser', email='testuser#test.com', password='testing')
self.user.save()
token = Token.objects.create(user=self.user)
token.save()
def _require_login(self):
self.client.login(username='testuser', password='testing')
def test_ListAccounts_not_authenticated(self):
request = self.factory.get('/accounts/')
view = views.ListAccounts.as_view()
response = view(request)
self.assertEqual(response.status_code, 401,
'Expected Response Code 401, received {0} instead.'.format(response.status_code))
def test_ListAccounts_authenticated(self):
self.client._require_login()
print(self.user.is_authenticated()) # returns True
request = self.factory.get('/accounts/')
view = views.ListAccounts.as_view()
response = view(request)
self.assertEqual(response.status_code, 200,
'Expected Response Code 200, received {0} instead.'.format(response.status_code))
And here is the code for my DRF View:
from django.shortcuts import render
from django.contrib.auth import authenticate, login, logout
from django.db.models import Q
from apps.core import serializers, models
from apps.rest.permissions import IsAccountOwner
from rest_framework.views import APIView
from rest_framework import status, authentication, generics
from rest_framework.response import Response
from rest_framework.authtoken.models import Token
from rest_framework.authentication import SessionAuthentication, TokenAuthentication
from rest_framework.permissions import IsAuthenticated
from rest_framework.response import Response
from apps.core import serializers, models
'''
Auth Mixin for Account Admin Access
'''
class AuthMixin:
authentication_classes = (authentication.TokenAuthentication,
authentication.SessionAuthentication)
permission_classes = (IsAuthenticated,)
class GenericList(AuthMixin, generics.ListAPIView):
def get_queryset(self):
# Stubbed out - doing nothing special right now
qs = self.model.objects.filter()
return qs
class ListAccounts(GenericList):
model = models.Account
serializer_class = serializers.AccountSerializer
As one can see, I'm calling login in the test_ListAccounts_authenticated, and then printing out whether or not I'm authenticated (Which returns True), but I get a 401 UNAUTHORIZED Error no matter what. Anything I'm missing? Thanks in advance.
Instead of calling self.factory.get(), call self.client.get().
I'm guessing self.client.login has no effect on self.factory.