How to verify Shopify webhook in Django? - python

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)

Related

Django: create endpoint for a file

I have a mesh.obj file and I would like to create an endpoint for it. For example, the endpoint could be of the form 'path/to/mesh'. How would I do this?
In your urls.py:
url(r'^test-files/(?P<name>.+)/$', views.test_files, name='test_files'),
In your views.py:
from django.http.response import HttpResponse
from django.views.decorators.csrf import csrf_exempt
#csrf_exempt # (Allows file download with POST requests, can be omitted)
def test_files(request, name):
if name == "myxml":
fsock = open("/djangopath/data/static/files/my.xml", "rb")
return HttpResponse(fsock)

Even after dispatching, CSRF token won't work

So I followed the instructions from the answers on this thread:
#csrf_exempt does not work on generic view based class
However, when I send POST requests via Postman, it still keeps throwing 403 error and I wonder what I am missing here. I tried the last answer(using braces.views and CsrfExemptMixin) as well and it still wouldn't work. Below is my code so far
import json
import jwt
from psr.settings import SECRET_KEY
from django.http import HttpResponse, JsonResponse
from django.contrib.auth.forms import AuthenticationForm
from django.contrib import messages
from django.contrib.auth import login, logout, authenticate
from django.contrib.auth import views as auth_views
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from braces.views import CsrfExemptMixin
from .models import User
class LoginView(auth_views.LoginView):
#method_decorator(csrf_exempt)
def dispatch(self, request, *args, **kwargs):
return super(LoginView, self).dispatch(request, *args, **kwargs)
def post(self, request):
form = AuthenticationForm(data = request.POST)
if form.is_valid():
user = authenticate(email=request.POST['email'], password=request.POST['password'])
if user is not None:
messages.add_message(request, messages.SUCCESS, "Welcome back, {}".format(user))
login(request, user)
token = jwt.encode({'id': user.id}, SECRET_KEY, algorithm='HS256').decode('utf-8')
return JsonResponse({'token': token}, status=200)
Am I missing something here?
Thanks a lot in advance!

Django unit tests, custom permission and request.user.username

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)

How to test Django redirect from view class

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)

Testing authentication in Django Rest Framework Views -- Cannot authenticate when testing

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.

Categories

Resources