Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

compat with LoginRequiredMiddleware middleware #1454

Merged
merged 16 commits into from
Aug 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## [unreleased]
### Added
* Add migration to include `token_checksum` field in AbstractAccessToken model.
* Added compatibility with `LoginRequiredMiddleware` introduced in Django 5.1
* #1404 Add a new setting `REFRESH_TOKEN_REUSE_PROTECTION`
### Changed
* 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
Expand Down
11 changes: 11 additions & 0 deletions oauth2_provider/compat.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,14 @@
The `compat` module provides support for backwards compatibility with older
versions of Django and Python.
"""

try:
# Django 5.1 introduced LoginRequiredMiddleware, and login_not_required decorator
from django.contrib.auth.decorators import login_not_required
except ImportError:

def login_not_required(view_func):
return view_func


__all__ = ["login_not_required"]
5 changes: 5 additions & 0 deletions oauth2_provider/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
from django.views.decorators.debug import sensitive_post_parameters
from django.views.generic import FormView, View

from ..compat import login_not_required
from ..exceptions import OAuthToolkitError
from ..forms import AllowForm
from ..http import OAuth2ResponseRedirect
Expand All @@ -26,6 +27,8 @@
log = logging.getLogger("oauth2_provider")


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


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


@method_decorator(csrf_exempt, name="dispatch")
@method_decorator(login_not_required, name="dispatch")
class RevokeTokenView(OAuthLibMixin, View):
"""
Implements an endpoint to revoke access or refresh tokens
Expand Down
6 changes: 4 additions & 2 deletions oauth2_provider/views/introspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt

from oauth2_provider.models import get_access_token_model
from oauth2_provider.views.generic import ClientProtectedScopedResourceView
from ..compat import login_not_required
from ..models import get_access_token_model
from ..views.generic import ClientProtectedScopedResourceView


@method_decorator(csrf_exempt, name="dispatch")
@method_decorator(login_not_required, name="dispatch")
class IntrospectTokenView(ClientProtectedScopedResourceView):
"""
Implements an endpoint for token introspection based
Expand Down
5 changes: 5 additions & 0 deletions oauth2_provider/views/oidc.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from jwcrypto.jwt import JWTExpired
from oauthlib.common import add_params_to_uri

from ..compat import login_not_required
from ..exceptions import (
ClientIdMissmatch,
InvalidIDTokenError,
Expand All @@ -39,6 +40,7 @@
Application = get_application_model()


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


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


@method_decorator(csrf_exempt, name="dispatch")
@method_decorator(login_not_required, name="dispatch")
class UserInfoView(OIDCOnlyMixin, OAuthLibMixin, View):
"""
View used to show Claims about the authenticated End-User
Expand Down Expand Up @@ -211,6 +215,7 @@ def _validate_claims(request, claims):
return True


@method_decorator(login_not_required, name="dispatch")
class RPInitiatedLogoutView(OIDCLogoutOnlyMixin, FormView):
template_name = "oauth2_provider/logout_confirm.html"
form_class = ConfirmLogoutForm
Expand Down
11 changes: 11 additions & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from urllib.parse import parse_qs, urlparse

import pytest
from django import VERSION
from django.conf import settings as test_settings
from django.contrib.auth import get_user_model
from django.urls import reverse
Expand Down Expand Up @@ -294,3 +295,13 @@ def oidc_non_confidential_tokens(oauth2_settings, public_application, test_user,
"openid",
"http://other.org",
)


@pytest.fixture(autouse=True)
def django_login_required_middleware(settings, request):
if "nologinrequiredmiddleware" in request.keywords:
return

# Django 5.1 introduced LoginRequiredMiddleware
if VERSION[0] >= 5 and VERSION[1] >= 1:
settings.MIDDLEWARE = [*settings.MIDDLEWARE, "django.contrib.auth.middleware.LoginRequiredMiddleware"]
3 changes: 2 additions & 1 deletion tests/test_introspection_auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
from django.utils import timezone
from oauthlib.common import Request

from oauth2_provider.compat import login_not_required
from oauth2_provider.models import get_access_token_model, get_application_model
from oauth2_provider.oauth2_validators import OAuth2Validator
from oauth2_provider.settings import oauth2_settings
Expand Down Expand Up @@ -93,7 +94,7 @@ def mocked_introspect_request_short_living_token(url, data, *args, **kwargs):

urlpatterns = [
path("oauth2/", include("oauth2_provider.urls")),
path("oauth2-test-resource/", ScopeResourceView.as_view()),
path("oauth2-test-resource/", login_not_required(ScopeResourceView.as_view())),
]


Expand Down
1 change: 1 addition & 0 deletions tests/test_rest_framework.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ class AuthenticationNoneOAuth2View(MockView):


@override_settings(ROOT_URLCONF=__name__)
@pytest.mark.nologinrequiredmiddleware
@pytest.mark.usefixtures("oauth2_settings")
@pytest.mark.oauth2_settings(presets.REST_FRAMEWORK_SCOPES)
class TestOAuth2Authentication(TestCase):
Expand Down
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ addopts =
-s
markers =
oauth2_settings: Custom OAuth2 settings to use - use with oauth2_settings fixture
nologinrequiredmiddleware

[testenv]
commands =
Expand Down
Loading