From 24f4a321665a18f7d29c28bddfc54557d7b10aac Mon Sep 17 00:00:00 2001 From: Dawid Wolski Date: Wed, 9 Apr 2025 14:01:50 +0200 Subject: [PATCH 1/8] Fix: Handle AttributeError in IntrospectTokenView --- oauth2_provider/views/introspect.py | 2 +- tests/test_introspection_view.py | 22 ++++++++++++++++++++++ tox.ini | 11 ++++++----- 3 files changed, 29 insertions(+), 6 deletions(-) diff --git a/oauth2_provider/views/introspect.py b/oauth2_provider/views/introspect.py index 5474c3a7e..194f7bbcd 100644 --- a/oauth2_provider/views/introspect.py +++ b/oauth2_provider/views/introspect.py @@ -33,7 +33,7 @@ def get_token_response(token_value=None): .objects.select_related("user", "application") .get(token_checksum=token_checksum) ) - except ObjectDoesNotExist: + except (AttributeError, ObjectDoesNotExist): return JsonResponse({"active": False}, status=200) else: if token.is_valid(): diff --git a/tests/test_introspection_view.py b/tests/test_introspection_view.py index 3db23bbcd..ed612e80e 100644 --- a/tests/test_introspection_view.py +++ b/tests/test_introspection_view.py @@ -278,6 +278,28 @@ def test_view_post_notexisting_token(self): "active": False, }, ) + + def test_view_post_no_token(self): + """ + Test that when you pass an empty token as form parameter, + a json with an inactive token state is provided + """ + auth_headers = { + "HTTP_AUTHORIZATION": "Bearer " + self.resource_server_token.token, + } + response = self.client.post( + reverse("oauth2_provider:introspect"), **auth_headers + ) + + self.assertEqual(response.status_code, 200) + content = response.json() + self.assertIsInstance(content, dict) + self.assertDictEqual( + content, + { + "active": False, + }, + ) def test_view_post_valid_client_creds_basic_auth(self): """Test HTTP basic auth working""" diff --git a/tox.ini b/tox.ini index 303b0d51d..d5cf8d2dc 100644 --- a/tox.ini +++ b/tox.ini @@ -5,10 +5,10 @@ envlist = docs, lint, sphinxlint, - py{38,39,310,311,312}-dj42, - py{310,311,312}-dj50, - py{310,311,312}-dj51, - py{310,311,312}-djmain, + py{38,39,310,311,312,313}-dj42, + py{310,311,312,313}-dj50, + py{310,311,312,313}-dj51, + py{310,311,312,313}-djmain, py39-multi-db-dj-42 [gh-actions] @@ -18,6 +18,7 @@ python = 3.10: py310 3.11: py311 3.12: py312 + 3.13: py313 [gh-actions:env] DJANGO = @@ -54,7 +55,7 @@ deps = passenv = PYTEST_ADDOPTS -[testenv:py{310,311,312}-djmain] +[testenv:py{310,311,312,313}-djmain] ignore_errors = true ignore_outcome = true From 5aca8524e17d3b6d4d302708880e92fefacc5d30 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 9 Apr 2025 12:34:49 +0000 Subject: [PATCH 2/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- tests/test_introspection_view.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/tests/test_introspection_view.py b/tests/test_introspection_view.py index ed612e80e..4fb1eea08 100644 --- a/tests/test_introspection_view.py +++ b/tests/test_introspection_view.py @@ -278,7 +278,7 @@ def test_view_post_notexisting_token(self): "active": False, }, ) - + def test_view_post_no_token(self): """ Test that when you pass an empty token as form parameter, @@ -287,9 +287,7 @@ def test_view_post_no_token(self): auth_headers = { "HTTP_AUTHORIZATION": "Bearer " + self.resource_server_token.token, } - response = self.client.post( - reverse("oauth2_provider:introspect"), **auth_headers - ) + response = self.client.post(reverse("oauth2_provider:introspect"), **auth_headers) self.assertEqual(response.status_code, 200) content = response.json() From 4a21ee360c2a4c71a3be2a5e29ca833cbab006e1 Mon Sep 17 00:00:00 2001 From: Dawid Wolski Date: Thu, 22 May 2025 16:29:29 +0200 Subject: [PATCH 3/8] Return HTTP 400 if no token is provided --- oauth2_provider/views/introspect.py | 8 ++++++-- tests/test_introspection_view.py | 12 +++--------- 2 files changed, 9 insertions(+), 11 deletions(-) diff --git a/oauth2_provider/views/introspect.py b/oauth2_provider/views/introspect.py index 194f7bbcd..a76b01ead 100644 --- a/oauth2_provider/views/introspect.py +++ b/oauth2_provider/views/introspect.py @@ -2,7 +2,7 @@ import hashlib from django.core.exceptions import ObjectDoesNotExist -from django.http import JsonResponse +from django.http import JsonResponse, HttpResponseBadRequest from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt @@ -33,8 +33,12 @@ def get_token_response(token_value=None): .objects.select_related("user", "application") .get(token_checksum=token_checksum) ) - except (AttributeError, ObjectDoesNotExist): + except ObjectDoesNotExist: return JsonResponse({"active": False}, status=200) + except AttributeError: + return HttpResponseBadRequest( + {"error": "invalid_request", "error_description": "Token parameter is missing."} + ) else: if token.is_valid(): data = { diff --git a/tests/test_introspection_view.py b/tests/test_introspection_view.py index 4fb1eea08..ad7d8983d 100644 --- a/tests/test_introspection_view.py +++ b/tests/test_introspection_view.py @@ -281,23 +281,17 @@ def test_view_post_notexisting_token(self): def test_view_post_no_token(self): """ - Test that when you pass an empty token as form parameter, - a json with an inactive token state is provided + Test that when you pass no token HTTP 400 is returned """ auth_headers = { "HTTP_AUTHORIZATION": "Bearer " + self.resource_server_token.token, } response = self.client.post(reverse("oauth2_provider:introspect"), **auth_headers) - self.assertEqual(response.status_code, 200) + self.assertEqual(response.status_code, 400) content = response.json() self.assertIsInstance(content, dict) - self.assertDictEqual( - content, - { - "active": False, - }, - ) + self.assertEqual(content["error"], "invalid_request") def test_view_post_valid_client_creds_basic_auth(self): """Test HTTP basic auth working""" From b791e539f103f8c1bdf734e54227cb018d2f13fd Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 22 May 2025 14:29:42 +0000 Subject: [PATCH 4/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- oauth2_provider/views/introspect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauth2_provider/views/introspect.py b/oauth2_provider/views/introspect.py index a76b01ead..efe995c66 100644 --- a/oauth2_provider/views/introspect.py +++ b/oauth2_provider/views/introspect.py @@ -2,7 +2,7 @@ import hashlib from django.core.exceptions import ObjectDoesNotExist -from django.http import JsonResponse, HttpResponseBadRequest +from django.http import HttpResponseBadRequest, JsonResponse from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt From 909064bb8686e09615e785cd998d65effb5b0219 Mon Sep 17 00:00:00 2001 From: Dawid Wolski Date: Thu, 22 May 2025 16:53:50 +0200 Subject: [PATCH 5/8] json response --- oauth2_provider/views/introspect.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/oauth2_provider/views/introspect.py b/oauth2_provider/views/introspect.py index efe995c66..1e6e68614 100644 --- a/oauth2_provider/views/introspect.py +++ b/oauth2_provider/views/introspect.py @@ -36,8 +36,9 @@ def get_token_response(token_value=None): except ObjectDoesNotExist: return JsonResponse({"active": False}, status=200) except AttributeError: - return HttpResponseBadRequest( - {"error": "invalid_request", "error_description": "Token parameter is missing."} + return JsonResponse( + {"error": "invalid_request", "error_description": "Token parameter is missing."}, + status=400, ) else: if token.is_valid(): From 7c20d918cef8212b21f65e1dcc341fd33f65d2a3 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Thu, 22 May 2025 14:54:12 +0000 Subject: [PATCH 6/8] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- oauth2_provider/views/introspect.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/oauth2_provider/views/introspect.py b/oauth2_provider/views/introspect.py index 1e6e68614..d3fb18b51 100644 --- a/oauth2_provider/views/introspect.py +++ b/oauth2_provider/views/introspect.py @@ -2,7 +2,7 @@ import hashlib from django.core.exceptions import ObjectDoesNotExist -from django.http import HttpResponseBadRequest, JsonResponse +from django.http import JsonResponse from django.utils.decorators import method_decorator from django.views.decorators.csrf import csrf_exempt From f3c619bb0176a15e8862e797545b47a70890c37f Mon Sep 17 00:00:00 2001 From: Dawid Wolski Date: Mon, 23 Jun 2025 18:36:22 +0200 Subject: [PATCH 7/8] check if token_value is None, then return --- oauth2_provider/views/introspect.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/oauth2_provider/views/introspect.py b/oauth2_provider/views/introspect.py index d3fb18b51..5b9810c82 100644 --- a/oauth2_provider/views/introspect.py +++ b/oauth2_provider/views/introspect.py @@ -26,6 +26,11 @@ class IntrospectTokenView(ClientProtectedScopedResourceView): @staticmethod def get_token_response(token_value=None): + if token_value is None: + return JsonResponse( + {"error": "invalid_request", "error_description": "Token parameter is missing."}, + status=400, + ) try: token_checksum = hashlib.sha256(token_value.encode("utf-8")).hexdigest() token = ( @@ -35,11 +40,6 @@ def get_token_response(token_value=None): ) except ObjectDoesNotExist: return JsonResponse({"active": False}, status=200) - except AttributeError: - return JsonResponse( - {"error": "invalid_request", "error_description": "Token parameter is missing."}, - status=400, - ) else: if token.is_valid(): data = { From 3ba8a3fa5550b40de760dcecc46305ca4d741310 Mon Sep 17 00:00:00 2001 From: Darrel O'Pry Date: Tue, 12 Aug 2025 09:02:59 -0400 Subject: [PATCH 8/8] chore: update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c4770459..514c45ec6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * #1512 client_secret not marked sensitive * #1521 Fix 0012 migration loading access token table into memory * #1584 Fix IDP container in docker compose environment could not find templates and static files. +* #1562 Fix: Handle AttributeError in IntrospectTokenView