From 93e5ad5eaa30f2bd0c1e81015021928475617355 Mon Sep 17 00:00:00 2001 From: John Byrne Date: Wed, 9 Aug 2023 08:27:47 -0400 Subject: [PATCH 01/13] Issue 1185 add token to request --- oauth2_provider/middleware.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/oauth2_provider/middleware.py b/oauth2_provider/middleware.py index 17ba6c35f..58dae6bea 100644 --- a/oauth2_provider/middleware.py +++ b/oauth2_provider/middleware.py @@ -1,6 +1,6 @@ from django.contrib.auth import authenticate from django.utils.cache import patch_vary_headers - +from oauth2_provider.models import AccessToken class OAuth2TokenMiddleware: """ @@ -33,6 +33,9 @@ def __call__(self, request): if user: request.user = request._cached_user = user + tokenstring = request.META["HTTP_AUTHORIZATION"].split()[1] + token = AccessToken.objects.get(token=tokenstring) + request.access_token = token response = self.get_response(request) patch_vary_headers(response, ("Authorization",)) return response From 6413f7857e229c003c5ed2db81b8ab0ecd3aa60e Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Wed, 9 Aug 2023 12:32:15 +0000 Subject: [PATCH 02/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- oauth2_provider/middleware.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/oauth2_provider/middleware.py b/oauth2_provider/middleware.py index 58dae6bea..e04970464 100644 --- a/oauth2_provider/middleware.py +++ b/oauth2_provider/middleware.py @@ -1,7 +1,9 @@ from django.contrib.auth import authenticate from django.utils.cache import patch_vary_headers + from oauth2_provider.models import AccessToken + class OAuth2TokenMiddleware: """ Middleware for OAuth2 user authentication From d9bf9c8a0be7a8c814dd4f29ad7616fe62f19c8b Mon Sep 17 00:00:00 2001 From: John Byrne Date: Tue, 15 Aug 2023 08:17:14 -0400 Subject: [PATCH 03/13] Issue 1185 make it a separate middleware class --- oauth2_provider/middleware.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/oauth2_provider/middleware.py b/oauth2_provider/middleware.py index 58dae6bea..e0814828f 100644 --- a/oauth2_provider/middleware.py +++ b/oauth2_provider/middleware.py @@ -32,10 +32,21 @@ def __call__(self, request): user = authenticate(request=request) if user: request.user = request._cached_user = user + response = self.get_response(request) + patch_vary_headers(response, ("Authorization",)) + return response + +class OAuth2ExtraTokenMiddleware: + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + if "HTTP_AUTHORIZATION" in request.META: tokenstring = request.META["HTTP_AUTHORIZATION"].split()[1] token = AccessToken.objects.get(token=tokenstring) request.access_token = token + request.access_token = token response = self.get_response(request) - patch_vary_headers(response, ("Authorization",)) return response From fbec411c09efcaf168f17ec64c28cb60dd6d4245 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 15 Aug 2023 12:33:48 +0000 Subject: [PATCH 04/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- oauth2_provider/middleware.py | 1 - 1 file changed, 1 deletion(-) diff --git a/oauth2_provider/middleware.py b/oauth2_provider/middleware.py index ed206dd31..46e8e9ef8 100644 --- a/oauth2_provider/middleware.py +++ b/oauth2_provider/middleware.py @@ -40,7 +40,6 @@ def __call__(self, request): class OAuth2ExtraTokenMiddleware: - def __init__(self, get_response): self.get_response = get_response From 4a16cef214244c779b6ca7af2127aa3de9a87bca Mon Sep 17 00:00:00 2001 From: John Byrne Date: Tue, 15 Aug 2023 08:36:54 -0400 Subject: [PATCH 05/13] Issue 1185 cleanup --- oauth2_provider/middleware.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/oauth2_provider/middleware.py b/oauth2_provider/middleware.py index ed206dd31..0ab52f828 100644 --- a/oauth2_provider/middleware.py +++ b/oauth2_provider/middleware.py @@ -1,9 +1,7 @@ from django.contrib.auth import authenticate from django.utils.cache import patch_vary_headers - from oauth2_provider.models import AccessToken - class OAuth2TokenMiddleware: """ Middleware for OAuth2 user authentication @@ -34,6 +32,7 @@ def __call__(self, request): user = authenticate(request=request) if user: request.user = request._cached_user = user + response = self.get_response(request) patch_vary_headers(response, ("Authorization",)) return response @@ -49,6 +48,5 @@ def __call__(self, request): tokenstring = request.META["HTTP_AUTHORIZATION"].split()[1] token = AccessToken.objects.get(token=tokenstring) request.access_token = token - request.access_token = token response = self.get_response(request) return response From 2c64574fd1700e16c84ffc1dc7678856c332f46b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 15 Aug 2023 12:38:27 +0000 Subject: [PATCH 06/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- oauth2_provider/middleware.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/oauth2_provider/middleware.py b/oauth2_provider/middleware.py index ec3ec0e1c..c36cfb8c5 100644 --- a/oauth2_provider/middleware.py +++ b/oauth2_provider/middleware.py @@ -1,7 +1,9 @@ from django.contrib.auth import authenticate from django.utils.cache import patch_vary_headers + from oauth2_provider.models import AccessToken + class OAuth2TokenMiddleware: """ Middleware for OAuth2 user authentication From ddb4b0472f5d8cf8f81a4bfef52560497ef6488c Mon Sep 17 00:00:00 2001 From: John Byrne Date: Mon, 28 Aug 2023 08:59:31 -0400 Subject: [PATCH 07/13] Add unit tests; handle some error conditions --- oauth2_provider/middleware.py | 15 ++++++++--- tests/test_auth_backends.py | 51 ++++++++++++++++++++++++++++++++++- 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/oauth2_provider/middleware.py b/oauth2_provider/middleware.py index c36cfb8c5..e7719a3c0 100644 --- a/oauth2_provider/middleware.py +++ b/oauth2_provider/middleware.py @@ -1,9 +1,12 @@ +import logging from django.contrib.auth import authenticate from django.utils.cache import patch_vary_headers from oauth2_provider.models import AccessToken +log = logging.getLogger(__name__) + class OAuth2TokenMiddleware: """ Middleware for OAuth2 user authentication @@ -45,9 +48,13 @@ def __init__(self, get_response): self.get_response = get_response def __call__(self, request): - if "HTTP_AUTHORIZATION" in request.META: - tokenstring = request.META["HTTP_AUTHORIZATION"].split()[1] - token = AccessToken.objects.get(token=tokenstring) - request.access_token = token + authheader = request.META.get("HTTP_AUTHORIZATION", "") + if authheader.startswith("Bearer"): + tokenstring = authheader.split()[1] + try: + token = AccessToken.objects.get(token=tokenstring) + request.access_token = token + except AccessToken.DoesNotExist as e: + log.exception(e) response = self.get_response(request) return response diff --git a/tests/test_auth_backends.py b/tests/test_auth_backends.py index 8eeb8ef12..4b13217f6 100644 --- a/tests/test_auth_backends.py +++ b/tests/test_auth_backends.py @@ -10,7 +10,7 @@ from django.utils.timezone import now, timedelta from oauth2_provider.backends import OAuth2Backend -from oauth2_provider.middleware import OAuth2TokenMiddleware +from oauth2_provider.middleware import OAuth2TokenMiddleware, OAuth2ExtraTokenMiddleware from oauth2_provider.models import get_access_token_model, get_application_model @@ -162,3 +162,52 @@ def test_middleware_response_header(self): response = m(request) self.assertIn("Vary", response) self.assertIn("Authorization", response["Vary"]) + +@override_settings( + AUTHENTICATION_BACKENDS=( + "oauth2_provider.backends.OAuth2Backend", + "django.contrib.auth.backends.ModelBackend", + ), +) +@modify_settings( + MIDDLEWARE={ + "append": "oauth2_provider.middleware.OAuth2TokenMiddleware", + } +) +class TestOAuth2ExtraTokenMiddleware(BaseTest): + def setUp(self): + super().setUp() + self.anon_user = AnonymousUser() + + def dummy_get_response(self, request): + return HttpResponse() + + def test_middleware_wrong_headers(self): + m = OAuth2ExtraTokenMiddleware(self.dummy_get_response) + request = self.factory.get("/a-resource") + m(request) + self.assertFalse(hasattr(request, "user")) + auth_headers = { + "HTTP_AUTHORIZATION": "Beerer " + "badstring", # a Beer token for you! + } + request = self.factory.get("/a-resource", **auth_headers) + m(request) + self.assertFalse(hasattr(request, "user")) + + def test_middleware_success(self): + m = OAuth2ExtraTokenMiddleware(self.dummy_get_response) + auth_headers = { + "HTTP_AUTHORIZATION": "Bearer " + "tokstr", + } + request = self.factory.get("/a-resource", **auth_headers) + m(request) + self.assertEqual(request.access_token, self.token) + + def test_middleware_response(self): + m = OAuth2ExtraTokenMiddleware(self.dummy_get_response) + auth_headers = { + "HTTP_AUTHORIZATION": "Bearer " + "tokstr", + } + request = self.factory.get("/a-resource", **auth_headers) + response = m(request) + self.assertIsInstance(response, HttpResponse) From bd8cc77b83699ac79a9ab66ffb3ad0c6a2ab3dcf Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Mon, 28 Aug 2023 12:59:56 +0000 Subject: [PATCH 08/13] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- oauth2_provider/middleware.py | 2 ++ tests/test_auth_backends.py | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/oauth2_provider/middleware.py b/oauth2_provider/middleware.py index e7719a3c0..28bd968f8 100644 --- a/oauth2_provider/middleware.py +++ b/oauth2_provider/middleware.py @@ -1,4 +1,5 @@ import logging + from django.contrib.auth import authenticate from django.utils.cache import patch_vary_headers @@ -7,6 +8,7 @@ log = logging.getLogger(__name__) + class OAuth2TokenMiddleware: """ Middleware for OAuth2 user authentication diff --git a/tests/test_auth_backends.py b/tests/test_auth_backends.py index 4b13217f6..d60504355 100644 --- a/tests/test_auth_backends.py +++ b/tests/test_auth_backends.py @@ -10,7 +10,7 @@ from django.utils.timezone import now, timedelta from oauth2_provider.backends import OAuth2Backend -from oauth2_provider.middleware import OAuth2TokenMiddleware, OAuth2ExtraTokenMiddleware +from oauth2_provider.middleware import OAuth2ExtraTokenMiddleware, OAuth2TokenMiddleware from oauth2_provider.models import get_access_token_model, get_application_model @@ -163,6 +163,7 @@ def test_middleware_response_header(self): self.assertIn("Vary", response) self.assertIn("Authorization", response["Vary"]) + @override_settings( AUTHENTICATION_BACKENDS=( "oauth2_provider.backends.OAuth2Backend", From 8b438a43ace8b014f433d2899c3a3716e2e9fde9 Mon Sep 17 00:00:00 2001 From: John Byrne Date: Mon, 28 Aug 2023 09:02:31 -0400 Subject: [PATCH 09/13] Fix unit test copy/paste issue --- tests/test_auth_backends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_auth_backends.py b/tests/test_auth_backends.py index d60504355..3de3f4fb8 100644 --- a/tests/test_auth_backends.py +++ b/tests/test_auth_backends.py @@ -193,7 +193,7 @@ def test_middleware_wrong_headers(self): } request = self.factory.get("/a-resource", **auth_headers) m(request) - self.assertFalse(hasattr(request, "user")) + self.assertFalse(hasattr(request, "access_token")) def test_middleware_success(self): m = OAuth2ExtraTokenMiddleware(self.dummy_get_response) From 3f0fc75b50af6cf973820aa61fc5c0fa124a42dd Mon Sep 17 00:00:00 2001 From: John Byrne Date: Mon, 28 Aug 2023 09:03:53 -0400 Subject: [PATCH 10/13] Fix unit test copy/paste issue --- tests/test_auth_backends.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_auth_backends.py b/tests/test_auth_backends.py index 3de3f4fb8..5614c1484 100644 --- a/tests/test_auth_backends.py +++ b/tests/test_auth_backends.py @@ -187,7 +187,7 @@ def test_middleware_wrong_headers(self): m = OAuth2ExtraTokenMiddleware(self.dummy_get_response) request = self.factory.get("/a-resource") m(request) - self.assertFalse(hasattr(request, "user")) + self.assertFalse(hasattr(request, "access_token")) auth_headers = { "HTTP_AUTHORIZATION": "Beerer " + "badstring", # a Beer token for you! } From 40ac8085f20350d6e4cb5e3c8f92540a1ceac66f Mon Sep 17 00:00:00 2001 From: John Byrne Date: Tue, 29 Aug 2023 08:05:27 -0400 Subject: [PATCH 11/13] Add documentation --- docs/tutorial/tutorial_03.rst | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/tutorial/tutorial_03.rst b/docs/tutorial/tutorial_03.rst index 09486c3d6..ef5d57969 100644 --- a/docs/tutorial/tutorial_03.rst +++ b/docs/tutorial/tutorial_03.rst @@ -47,6 +47,8 @@ will not try to get user from the session. If you use AuthenticationMiddleware, be sure it appears before OAuth2TokenMiddleware. However AuthenticationMiddleware is NOT required for using django-oauth-toolkit. +Note, `OAuth2TokenMiddleware` adds the user to the request object. There is also an optional `OAuth2ExtraTokenMiddleware` that adds the `Token` to the request. This makes it convenient to access the `Application` object within your views. To use it just add `oauth2_provider.middleware.OAuth2ExtraTokenMiddleware` to the `MIDDLEWARE` setting. + Protect your view ----------------- The authentication backend will run smoothly with, for example, `login_required` decorators, so From aab652cc63f716f1642ac32d4351cc889ee70f0e Mon Sep 17 00:00:00 2001 From: John Byrne Date: Tue, 29 Aug 2023 08:07:52 -0400 Subject: [PATCH 12/13] Update authors list --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 16c2058b8..58ae037ee 100644 --- a/AUTHORS +++ b/AUTHORS @@ -56,6 +56,7 @@ Jens Timmerman Jerome Leclanche Jesse Gibbs Jim Graham +John Byrne Jonas Nygaard Pedersen Jonathan Steffan Jordi Sanchez From 55500c55736633d19c21d1505bf8e8e8b2ed5629 Mon Sep 17 00:00:00 2001 From: John Byrne Date: Wed, 30 Aug 2023 07:54:45 -0400 Subject: [PATCH 13/13] Add more test coverage; update changelog --- CHANGELOG.md | 1 + tests/test_auth_backends.py | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 292300ce2..93176fe4b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [unreleased] ### Added +* #1185 Add middleware for adding access token to request * #1273 Add caching of loading of OIDC private key. * #1285 Add post_logout_redirect_uris field in application views. diff --git a/tests/test_auth_backends.py b/tests/test_auth_backends.py index 5614c1484..6b958ecb0 100644 --- a/tests/test_auth_backends.py +++ b/tests/test_auth_backends.py @@ -195,6 +195,15 @@ def test_middleware_wrong_headers(self): m(request) self.assertFalse(hasattr(request, "access_token")) + def test_middleware_token_does_not_exist(self): + m = OAuth2ExtraTokenMiddleware(self.dummy_get_response) + auth_headers = { + "HTTP_AUTHORIZATION": "Bearer " + "badtokstr", + } + request = self.factory.get("/a-resource", **auth_headers) + m(request) + self.assertFalse(hasattr(request, "access_token")) + def test_middleware_success(self): m = OAuth2ExtraTokenMiddleware(self.dummy_get_response) auth_headers = {