Skip to content

Commit 1dcef1b

Browse files
compat with LoginRequiredMiddleware middleware (#1454)
* compat with LoginRequiredMiddleware and login_not_required --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
1 parent 146e8bf commit 1dcef1b

File tree

9 files changed

+41
-3
lines changed

9 files changed

+41
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1717
## [unreleased]
1818
### Added
1919
* Add migration to include `token_checksum` field in AbstractAccessToken model.
20+
* Added compatibility with `LoginRequiredMiddleware` introduced in Django 5.1
2021
* #1404 Add a new setting `REFRESH_TOKEN_REUSE_PROTECTION`
2122
### Changed
2223
* Update token to TextField from CharField with 255 character limit and SHA-256 checksum in AbstractAccessToken model. Removing the 255 character limit enables supporting JWT tokens with additional claims

oauth2_provider/compat.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,14 @@
22
The `compat` module provides support for backwards compatibility with older
33
versions of Django and Python.
44
"""
5+
6+
try:
7+
# Django 5.1 introduced LoginRequiredMiddleware, and login_not_required decorator
8+
from django.contrib.auth.decorators import login_not_required
9+
except ImportError:
10+
11+
def login_not_required(view_func):
12+
return view_func
13+
14+
15+
__all__ = ["login_not_required"]

oauth2_provider/views/base.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
from django.views.decorators.debug import sensitive_post_parameters
1414
from django.views.generic import FormView, View
1515

16+
from ..compat import login_not_required
1617
from ..exceptions import OAuthToolkitError
1718
from ..forms import AllowForm
1819
from ..http import OAuth2ResponseRedirect
@@ -26,6 +27,8 @@
2627
log = logging.getLogger("oauth2_provider")
2728

2829

30+
# login_not_required decorator to bypass LoginRequiredMiddleware
31+
@method_decorator(login_not_required, name="dispatch")
2932
class BaseAuthorizationView(LoginRequiredMixin, OAuthLibMixin, View):
3033
"""
3134
Implements a generic endpoint to handle *Authorization Requests* as in :rfc:`4.1.1`. The view
@@ -274,6 +277,7 @@ def handle_no_permission(self):
274277

275278

276279
@method_decorator(csrf_exempt, name="dispatch")
280+
@method_decorator(login_not_required, name="dispatch")
277281
class TokenView(OAuthLibMixin, View):
278282
"""
279283
Implements an endpoint to provide access tokens
@@ -301,6 +305,7 @@ def post(self, request, *args, **kwargs):
301305

302306

303307
@method_decorator(csrf_exempt, name="dispatch")
308+
@method_decorator(login_not_required, name="dispatch")
304309
class RevokeTokenView(OAuthLibMixin, View):
305310
"""
306311
Implements an endpoint to revoke access or refresh tokens

oauth2_provider/views/introspect.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
from django.utils.decorators import method_decorator
77
from django.views.decorators.csrf import csrf_exempt
88

9-
from oauth2_provider.models import get_access_token_model
10-
from oauth2_provider.views.generic import ClientProtectedScopedResourceView
9+
from ..compat import login_not_required
10+
from ..models import get_access_token_model
11+
from ..views.generic import ClientProtectedScopedResourceView
1112

1213

1314
@method_decorator(csrf_exempt, name="dispatch")
15+
@method_decorator(login_not_required, name="dispatch")
1416
class IntrospectTokenView(ClientProtectedScopedResourceView):
1517
"""
1618
Implements an endpoint for token introspection based

oauth2_provider/views/oidc.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from jwcrypto.jwt import JWTExpired
1515
from oauthlib.common import add_params_to_uri
1616

17+
from ..compat import login_not_required
1718
from ..exceptions import (
1819
ClientIdMissmatch,
1920
InvalidIDTokenError,
@@ -39,6 +40,7 @@
3940
Application = get_application_model()
4041

4142

43+
@method_decorator(login_not_required, name="dispatch")
4244
class ConnectDiscoveryInfoView(OIDCOnlyMixin, View):
4345
"""
4446
View used to show oidc provider configuration information per
@@ -106,6 +108,7 @@ def get(self, request, *args, **kwargs):
106108
return response
107109

108110

111+
@method_decorator(login_not_required, name="dispatch")
109112
class JwksInfoView(OIDCOnlyMixin, View):
110113
"""
111114
View used to show oidc json web key set document
@@ -134,6 +137,7 @@ def get(self, request, *args, **kwargs):
134137

135138

136139
@method_decorator(csrf_exempt, name="dispatch")
140+
@method_decorator(login_not_required, name="dispatch")
137141
class UserInfoView(OIDCOnlyMixin, OAuthLibMixin, View):
138142
"""
139143
View used to show Claims about the authenticated End-User
@@ -211,6 +215,7 @@ def _validate_claims(request, claims):
211215
return True
212216

213217

218+
@method_decorator(login_not_required, name="dispatch")
214219
class RPInitiatedLogoutView(OIDCLogoutOnlyMixin, FormView):
215220
template_name = "oauth2_provider/logout_confirm.html"
216221
form_class = ConfirmLogoutForm

tests/conftest.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from urllib.parse import parse_qs, urlparse
55

66
import pytest
7+
from django import VERSION
78
from django.conf import settings as test_settings
89
from django.contrib.auth import get_user_model
910
from django.urls import reverse
@@ -294,3 +295,13 @@ def oidc_non_confidential_tokens(oauth2_settings, public_application, test_user,
294295
"openid",
295296
"http://other.org",
296297
)
298+
299+
300+
@pytest.fixture(autouse=True)
301+
def django_login_required_middleware(settings, request):
302+
if "nologinrequiredmiddleware" in request.keywords:
303+
return
304+
305+
# Django 5.1 introduced LoginRequiredMiddleware
306+
if VERSION[0] >= 5 and VERSION[1] >= 1:
307+
settings.MIDDLEWARE = [*settings.MIDDLEWARE, "django.contrib.auth.middleware.LoginRequiredMiddleware"]

tests/test_introspection_auth.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from django.utils import timezone
1212
from oauthlib.common import Request
1313

14+
from oauth2_provider.compat import login_not_required
1415
from oauth2_provider.models import get_access_token_model, get_application_model
1516
from oauth2_provider.oauth2_validators import OAuth2Validator
1617
from oauth2_provider.settings import oauth2_settings
@@ -93,7 +94,7 @@ def mocked_introspect_request_short_living_token(url, data, *args, **kwargs):
9394

9495
urlpatterns = [
9596
path("oauth2/", include("oauth2_provider.urls")),
96-
path("oauth2-test-resource/", ScopeResourceView.as_view()),
97+
path("oauth2-test-resource/", login_not_required(ScopeResourceView.as_view())),
9798
]
9899

99100

tests/test_rest_framework.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@ class AuthenticationNoneOAuth2View(MockView):
127127

128128

129129
@override_settings(ROOT_URLCONF=__name__)
130+
@pytest.mark.nologinrequiredmiddleware
130131
@pytest.mark.usefixtures("oauth2_settings")
131132
@pytest.mark.oauth2_settings(presets.REST_FRAMEWORK_SCOPES)
132133
class TestOAuth2Authentication(TestCase):

tox.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ addopts =
3434
-s
3535
markers =
3636
oauth2_settings: Custom OAuth2 settings to use - use with oauth2_settings fixture
37+
nologinrequiredmiddleware
3738

3839
[testenv]
3940
commands =

0 commit comments

Comments
 (0)