From 837a274fef128de6d2a072f4579b5f3fff08f7cf Mon Sep 17 00:00:00 2001 From: Hugo Rodger-Brown Date: Sun, 18 Apr 2021 10:21:54 +0100 Subject: [PATCH] Fix issue with JSON arrays --- .github/workflows/tox.yml | 2 +- .pre-commit-config.yaml | 8 ++++---- pyproject.toml | 3 ++- request_token/middleware.py | 15 +++++++++++++-- tests/test_middleware.py | 31 ++++++++++++++++++++++++------- tox.ini | 3 ++- 6 files changed, 46 insertions(+), 16 deletions(-) diff --git a/.github/workflows/tox.yml b/.github/workflows/tox.yml index 27bf9c1..7b1a1e3 100644 --- a/.github/workflows/tox.yml +++ b/.github/workflows/tox.yml @@ -38,7 +38,7 @@ jobs: strategy: matrix: python: [3.8,3.9] - django: [31,main] + django: [31,32,main] env: TOXENV: py${{ matrix.python }}-django${{ matrix.django }} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 9dba136..283d831 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,21 +1,21 @@ repos: # python import sorting - will amend files - repo: https://github.com/pre-commit/mirrors-isort - rev: v5.4.2 + rev: v5.8.0 hooks: - id: isort language_version: python3.8 # python code formatting - will amend files - repo: https://github.com/ambv/black - rev: 19.10b0 + rev: 20.8b1 hooks: - id: black language_version: python3.8 # Flake8 includes pyflakes, pycodestyle, mccabe, pydocstyle, bandit - repo: https://gitlab.com/pycqa/flake8 - rev: 3.8.3 + rev: 3.9.1 hooks: - id: flake8 language_version: python3.8 @@ -24,7 +24,7 @@ repos: # python static type checking - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.782 + rev: v0.812 hooks: - id: mypy language_version: python3.8 diff --git a/pyproject.toml b/pyproject.toml index 8750b09..57b3a0a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "django-request-token" -version = "0.14" +version = "0.14.1" description = "JWT-backed Django app for managing querystring tokens." license = "MIT" authors = ["YunoJuno "] @@ -14,6 +14,7 @@ classifiers = [ "Framework :: Django :: 2.2", "Framework :: Django :: 3.0", "Framework :: Django :: 3.1", + "Framework :: Django :: 3.2", "Operating System :: OS Independent", "Programming Language :: Python :: 3 :: Only", "Programming Language :: Python :: 3.7", diff --git a/request_token/middleware.py b/request_token/middleware.py index 151f225..7280306 100644 --- a/request_token/middleware.py +++ b/request_token/middleware.py @@ -2,7 +2,7 @@ import json import logging -from typing import Callable +from typing import Callable, Optional from django.core.exceptions import ImproperlyConfigured, PermissionDenied from django.http.request import HttpRequest @@ -28,6 +28,17 @@ class RequestTokenMiddleware: def __init__(self, get_response: Callable): self.get_response = get_response + def extract_ajax_token(self, request: HttpRequest) -> Optional[str]: + """Extract token from AJAX request.""" + try: + payload = json.loads(request.body) + except json.decoder.JSONDecodeError: + return None + try: + return payload.get(JWT_QUERYSTRING_ARG) + except AttributeError: + return None + def __call__(self, request: HttpRequest) -> HttpResponse: # noqa: C901 """ Verify JWT request querystring arg. @@ -62,7 +73,7 @@ def __call__(self, request: HttpRequest) -> HttpResponse: # noqa: C901 token = request.GET.get(JWT_QUERYSTRING_ARG) if not token and request.method == "POST": if request.META.get("CONTENT_TYPE") == "application/json": - token = json.loads(request.body).get(JWT_QUERYSTRING_ARG) + token = self.extract_ajax_token(request) if not token: token = request.POST.get(JWT_QUERYSTRING_ARG) else: diff --git a/tests/test_middleware.py b/tests/test_middleware.py index 53e611d..49095df 100644 --- a/tests/test_middleware.py +++ b/tests/test_middleware.py @@ -1,4 +1,5 @@ import json +from typing import Any from unittest import mock from django.contrib.auth import get_user_model @@ -14,7 +15,6 @@ class MockSession(object): - """Fake Session model used to support `session_key` property.""" @property @@ -23,7 +23,6 @@ def session_key(self): class MiddlewareTests(TestCase): - """RequestTokenMiddleware tests.""" def setUp(self): @@ -31,21 +30,22 @@ def setUp(self): self.factory = RequestFactory() self.middleware = RequestTokenMiddleware(get_response=lambda r: HttpResponse()) self.token = RequestToken.objects.create_token(scope="foo") + self.default_payload = {JWT_QUERYSTRING_ARG: self.token.jwt()} def get_request(self): - request = self.factory.get("/?%s=%s" % (JWT_QUERYSTRING_ARG, self.token.jwt())) + request = self.factory.get(f"/?{JWT_QUERYSTRING_ARG}={self.token.jwt()}") request.user = self.user request.session = MockSession() return request def post_request(self): - request = self.factory.post("/", {JWT_QUERYSTRING_ARG: self.token.jwt()}) + request = self.factory.post("/", self.default_payload) request.user = self.user request.session = MockSession() return request - def post_request_with_JSON(self): - data = json.dumps({JWT_QUERYSTRING_ARG: self.token.jwt()}) + def post_request_with_JSON(self, payload: Any): + data = json.dumps(payload) request = self.factory.post("/", data, "application/json") request.user = self.user request.session = MockSession() @@ -80,10 +80,16 @@ def test_process_POST_request_with_valid_token(self): self.assertEqual(request.token, self.token) def test_process_POST_request_with_valid_token_with_json(self): - request = self.post_request_with_JSON() + request = self.post_request_with_JSON(self.default_payload) self.middleware(request) self.assertEqual(request.token, self.token) + def test_process_AJAX_request_with_array(self): + """Test for issue #50.""" + request = self.post_request_with_JSON([1]) + self.middleware(request) + self.assertFalse(hasattr(request, "token")) + def test_process_request_not_allowed(self): # PUT requests won't decode the token request = self.factory.put("/?rt=foo") @@ -132,3 +138,14 @@ def test_process_exception(self, mock_log): # round it out with a non-token error response = self.middleware.process_exception(request, Exception("foo")) self.assertIsNone(response) + + def test_extract_json_token__array(self): + """Test for issue #51.""" + request = self.post_request_with_JSON(["foo"]) + middleware = RequestTokenMiddleware(lambda r: HttpResponse()) + self.assertIsNone(middleware.extract_ajax_token(request)) + + def test_extract_json_token(self): + request = self.post_request_with_JSON(self.default_payload) + middleware = RequestTokenMiddleware(lambda r: HttpResponse()) + self.assertEqual(middleware.extract_ajax_token(request), self.token.jwt()) diff --git a/tox.ini b/tox.ini index 73175da..b88d7c9 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,6 @@ [tox] isolated_build = True -envlist = fmt, lint, mypy, py{3.7,3.8,3.9}-django{22,30,31,main} +envlist = fmt, lint, mypy, py{3.7,3.8,3.9}-django{22,30,31,32,main} [testenv] deps = @@ -12,6 +12,7 @@ deps = django22: Django>=2.2,<2.3 django30: Django>=3.0,<3.1 django31: Django>=3.1,<3.2 + django32: Django>=3.2,<3.3 djangomain: https://github.com/django/django/archive/main.tar.gz commands =