mirror of https://github.com/django/django.git
Fixed #31405 -- Added LoginRequiredMiddleware.
Co-authored-by: Adam Johnson <me@adamj.eu> Co-authored-by: Mehmet İnce <mehmet@mehmetince.net> Co-authored-by: Sarah Boyce <42296566+sarahboyce@users.noreply.github.com>
This commit is contained in:
parent
7857507c7f
commit
c7fc9f20b4
|
@ -7,11 +7,12 @@ from django.contrib.admin import ModelAdmin, actions
|
||||||
from django.contrib.admin.exceptions import AlreadyRegistered, NotRegistered
|
from django.contrib.admin.exceptions import AlreadyRegistered, NotRegistered
|
||||||
from django.contrib.admin.views.autocomplete import AutocompleteJsonView
|
from django.contrib.admin.views.autocomplete import AutocompleteJsonView
|
||||||
from django.contrib.auth import REDIRECT_FIELD_NAME
|
from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||||
|
from django.contrib.auth.decorators import login_not_required
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.db.models.base import ModelBase
|
from django.db.models.base import ModelBase
|
||||||
from django.http import Http404, HttpResponsePermanentRedirect, HttpResponseRedirect
|
from django.http import Http404, HttpResponsePermanentRedirect, HttpResponseRedirect
|
||||||
from django.template.response import TemplateResponse
|
from django.template.response import TemplateResponse
|
||||||
from django.urls import NoReverseMatch, Resolver404, resolve, reverse
|
from django.urls import NoReverseMatch, Resolver404, resolve, reverse, reverse_lazy
|
||||||
from django.utils.decorators import method_decorator
|
from django.utils.decorators import method_decorator
|
||||||
from django.utils.functional import LazyObject
|
from django.utils.functional import LazyObject
|
||||||
from django.utils.module_loading import import_string
|
from django.utils.module_loading import import_string
|
||||||
|
@ -259,6 +260,8 @@ class AdminSite:
|
||||||
return self.admin_view(view, cacheable)(*args, **kwargs)
|
return self.admin_view(view, cacheable)(*args, **kwargs)
|
||||||
|
|
||||||
wrapper.admin_site = self
|
wrapper.admin_site = self
|
||||||
|
# Used by LoginRequiredMiddleware.
|
||||||
|
wrapper.login_url = reverse_lazy("admin:login", current_app=self.name)
|
||||||
return update_wrapper(wrapper, view)
|
return update_wrapper(wrapper, view)
|
||||||
|
|
||||||
# Admin-site-wide views.
|
# Admin-site-wide views.
|
||||||
|
@ -402,6 +405,7 @@ class AdminSite:
|
||||||
return LogoutView.as_view(**defaults)(request)
|
return LogoutView.as_view(**defaults)(request)
|
||||||
|
|
||||||
@method_decorator(never_cache)
|
@method_decorator(never_cache)
|
||||||
|
@login_not_required
|
||||||
def login(self, request, extra_context=None):
|
def login(self, request, extra_context=None):
|
||||||
"""
|
"""
|
||||||
Display the login form for the given HttpRequest.
|
Display the login form for the given HttpRequest.
|
||||||
|
|
|
@ -5,7 +5,7 @@ from django.db.models.signals import post_migrate
|
||||||
from django.utils.translation import gettext_lazy as _
|
from django.utils.translation import gettext_lazy as _
|
||||||
|
|
||||||
from . import get_user_model
|
from . import get_user_model
|
||||||
from .checks import check_models_permissions, check_user_model
|
from .checks import check_middleware, check_models_permissions, check_user_model
|
||||||
from .management import create_permissions
|
from .management import create_permissions
|
||||||
from .signals import user_logged_in
|
from .signals import user_logged_in
|
||||||
|
|
||||||
|
@ -28,3 +28,4 @@ class AuthConfig(AppConfig):
|
||||||
user_logged_in.connect(update_last_login, dispatch_uid="update_last_login")
|
user_logged_in.connect(update_last_login, dispatch_uid="update_last_login")
|
||||||
checks.register(check_user_model, checks.Tags.models)
|
checks.register(check_user_model, checks.Tags.models)
|
||||||
checks.register(check_models_permissions, checks.Tags.models)
|
checks.register(check_models_permissions, checks.Tags.models)
|
||||||
|
checks.register(check_middleware)
|
||||||
|
|
|
@ -4,10 +4,27 @@ from types import MethodType
|
||||||
from django.apps import apps
|
from django.apps import apps
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core import checks
|
from django.core import checks
|
||||||
|
from django.utils.module_loading import import_string
|
||||||
|
|
||||||
from .management import _get_builtin_permissions
|
from .management import _get_builtin_permissions
|
||||||
|
|
||||||
|
|
||||||
|
def _subclass_index(class_path, candidate_paths):
|
||||||
|
"""
|
||||||
|
Return the index of dotted class path (or a subclass of that class) in a
|
||||||
|
list of candidate paths. If it does not exist, return -1.
|
||||||
|
"""
|
||||||
|
cls = import_string(class_path)
|
||||||
|
for index, path in enumerate(candidate_paths):
|
||||||
|
try:
|
||||||
|
candidate_cls = import_string(path)
|
||||||
|
if issubclass(candidate_cls, cls):
|
||||||
|
return index
|
||||||
|
except (ImportError, TypeError):
|
||||||
|
continue
|
||||||
|
return -1
|
||||||
|
|
||||||
|
|
||||||
def check_user_model(app_configs=None, **kwargs):
|
def check_user_model(app_configs=None, **kwargs):
|
||||||
if app_configs is None:
|
if app_configs is None:
|
||||||
cls = apps.get_model(settings.AUTH_USER_MODEL)
|
cls = apps.get_model(settings.AUTH_USER_MODEL)
|
||||||
|
@ -218,3 +235,28 @@ def check_models_permissions(app_configs=None, **kwargs):
|
||||||
codenames.add(codename)
|
codenames.add(codename)
|
||||||
|
|
||||||
return errors
|
return errors
|
||||||
|
|
||||||
|
|
||||||
|
def check_middleware(app_configs, **kwargs):
|
||||||
|
errors = []
|
||||||
|
|
||||||
|
login_required_index = _subclass_index(
|
||||||
|
"django.contrib.auth.middleware.LoginRequiredMiddleware",
|
||||||
|
settings.MIDDLEWARE,
|
||||||
|
)
|
||||||
|
|
||||||
|
if login_required_index != -1:
|
||||||
|
auth_index = _subclass_index(
|
||||||
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
settings.MIDDLEWARE,
|
||||||
|
)
|
||||||
|
if auth_index == -1 or auth_index > login_required_index:
|
||||||
|
errors.append(
|
||||||
|
checks.Error(
|
||||||
|
"In order to use django.contrib.auth.middleware."
|
||||||
|
"LoginRequiredMiddleware, django.contrib.auth.middleware."
|
||||||
|
"AuthenticationMiddleware must be defined before it in MIDDLEWARE.",
|
||||||
|
id="auth.E013",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return errors
|
||||||
|
|
|
@ -60,6 +60,10 @@ def user_passes_test(
|
||||||
return view_func(request, *args, **kwargs)
|
return view_func(request, *args, **kwargs)
|
||||||
return _redirect_to_login(request)
|
return _redirect_to_login(request)
|
||||||
|
|
||||||
|
# Attributes used by LoginRequiredMiddleware.
|
||||||
|
_view_wrapper.login_url = login_url
|
||||||
|
_view_wrapper.redirect_field_name = redirect_field_name
|
||||||
|
|
||||||
return wraps(view_func)(_view_wrapper)
|
return wraps(view_func)(_view_wrapper)
|
||||||
|
|
||||||
return decorator
|
return decorator
|
||||||
|
@ -82,6 +86,14 @@ def login_required(
|
||||||
return actual_decorator
|
return actual_decorator
|
||||||
|
|
||||||
|
|
||||||
|
def login_not_required(view_func):
|
||||||
|
"""
|
||||||
|
Decorator for views that allows access to unauthenticated requests.
|
||||||
|
"""
|
||||||
|
view_func.login_required = False
|
||||||
|
return view_func
|
||||||
|
|
||||||
|
|
||||||
def permission_required(perm, login_url=None, raise_exception=False):
|
def permission_required(perm, login_url=None, raise_exception=False):
|
||||||
"""
|
"""
|
||||||
Decorator for views that checks whether a user has a particular permission
|
Decorator for views that checks whether a user has a particular permission
|
||||||
|
|
|
@ -1,9 +1,13 @@
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
|
from django.conf import settings
|
||||||
from django.contrib import auth
|
from django.contrib import auth
|
||||||
from django.contrib.auth import load_backend
|
from django.contrib.auth import REDIRECT_FIELD_NAME, load_backend
|
||||||
from django.contrib.auth.backends import RemoteUserBackend
|
from django.contrib.auth.backends import RemoteUserBackend
|
||||||
|
from django.contrib.auth.views import redirect_to_login
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
|
from django.shortcuts import resolve_url
|
||||||
from django.utils.deprecation import MiddlewareMixin
|
from django.utils.deprecation import MiddlewareMixin
|
||||||
from django.utils.functional import SimpleLazyObject
|
from django.utils.functional import SimpleLazyObject
|
||||||
|
|
||||||
|
@ -34,6 +38,56 @@ class AuthenticationMiddleware(MiddlewareMixin):
|
||||||
request.auser = partial(auser, request)
|
request.auser = partial(auser, request)
|
||||||
|
|
||||||
|
|
||||||
|
class LoginRequiredMiddleware(MiddlewareMixin):
|
||||||
|
"""
|
||||||
|
Middleware that redirects all unauthenticated requests to a login page.
|
||||||
|
|
||||||
|
Views using the login_not_required decorator will not be redirected.
|
||||||
|
"""
|
||||||
|
|
||||||
|
redirect_field_name = REDIRECT_FIELD_NAME
|
||||||
|
|
||||||
|
def process_view(self, request, view_func, view_args, view_kwargs):
|
||||||
|
if request.user.is_authenticated:
|
||||||
|
return None
|
||||||
|
|
||||||
|
if not getattr(view_func, "login_required", True):
|
||||||
|
return None
|
||||||
|
|
||||||
|
return self.handle_no_permission(request, view_func)
|
||||||
|
|
||||||
|
def get_login_url(self, view_func):
|
||||||
|
login_url = getattr(view_func, "login_url", None) or settings.LOGIN_URL
|
||||||
|
if not login_url:
|
||||||
|
raise ImproperlyConfigured(
|
||||||
|
"No login URL to redirect to. Define settings.LOGIN_URL or "
|
||||||
|
"provide a login_url via the 'django.contrib.auth.decorators."
|
||||||
|
"login_required' decorator."
|
||||||
|
)
|
||||||
|
return str(login_url)
|
||||||
|
|
||||||
|
def get_redirect_field_name(self, view_func):
|
||||||
|
return getattr(view_func, "redirect_field_name", self.redirect_field_name)
|
||||||
|
|
||||||
|
def handle_no_permission(self, request, view_func):
|
||||||
|
path = request.build_absolute_uri()
|
||||||
|
resolved_login_url = resolve_url(self.get_login_url(view_func))
|
||||||
|
# If the login url is the same scheme and net location then use the
|
||||||
|
# path as the "next" url.
|
||||||
|
login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
|
||||||
|
current_scheme, current_netloc = urlparse(path)[:2]
|
||||||
|
if (not login_scheme or login_scheme == current_scheme) and (
|
||||||
|
not login_netloc or login_netloc == current_netloc
|
||||||
|
):
|
||||||
|
path = request.get_full_path()
|
||||||
|
|
||||||
|
return redirect_to_login(
|
||||||
|
path,
|
||||||
|
resolved_login_url,
|
||||||
|
self.get_redirect_field_name(view_func),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class RemoteUserMiddleware(MiddlewareMixin):
|
class RemoteUserMiddleware(MiddlewareMixin):
|
||||||
"""
|
"""
|
||||||
Middleware for utilizing web-server-provided authentication.
|
Middleware for utilizing web-server-provided authentication.
|
||||||
|
|
|
@ -7,7 +7,7 @@ from django.contrib.auth import REDIRECT_FIELD_NAME, get_user_model
|
||||||
from django.contrib.auth import login as auth_login
|
from django.contrib.auth import login as auth_login
|
||||||
from django.contrib.auth import logout as auth_logout
|
from django.contrib.auth import logout as auth_logout
|
||||||
from django.contrib.auth import update_session_auth_hash
|
from django.contrib.auth import update_session_auth_hash
|
||||||
from django.contrib.auth.decorators import login_required
|
from django.contrib.auth.decorators import login_not_required, login_required
|
||||||
from django.contrib.auth.forms import (
|
from django.contrib.auth.forms import (
|
||||||
AuthenticationForm,
|
AuthenticationForm,
|
||||||
PasswordChangeForm,
|
PasswordChangeForm,
|
||||||
|
@ -62,6 +62,7 @@ class RedirectURLMixin:
|
||||||
raise ImproperlyConfigured("No URL to redirect to. Provide a next_page.")
|
raise ImproperlyConfigured("No URL to redirect to. Provide a next_page.")
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(login_not_required, name="dispatch")
|
||||||
class LoginView(RedirectURLMixin, FormView):
|
class LoginView(RedirectURLMixin, FormView):
|
||||||
"""
|
"""
|
||||||
Display the login form and handle the login action.
|
Display the login form and handle the login action.
|
||||||
|
@ -210,6 +211,7 @@ class PasswordContextMixin:
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(login_not_required, name="dispatch")
|
||||||
class PasswordResetView(PasswordContextMixin, FormView):
|
class PasswordResetView(PasswordContextMixin, FormView):
|
||||||
email_template_name = "registration/password_reset_email.html"
|
email_template_name = "registration/password_reset_email.html"
|
||||||
extra_email_context = None
|
extra_email_context = None
|
||||||
|
@ -244,11 +246,13 @@ class PasswordResetView(PasswordContextMixin, FormView):
|
||||||
INTERNAL_RESET_SESSION_TOKEN = "_password_reset_token"
|
INTERNAL_RESET_SESSION_TOKEN = "_password_reset_token"
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(login_not_required, name="dispatch")
|
||||||
class PasswordResetDoneView(PasswordContextMixin, TemplateView):
|
class PasswordResetDoneView(PasswordContextMixin, TemplateView):
|
||||||
template_name = "registration/password_reset_done.html"
|
template_name = "registration/password_reset_done.html"
|
||||||
title = _("Password reset sent")
|
title = _("Password reset sent")
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(login_not_required, name="dispatch")
|
||||||
class PasswordResetConfirmView(PasswordContextMixin, FormView):
|
class PasswordResetConfirmView(PasswordContextMixin, FormView):
|
||||||
form_class = SetPasswordForm
|
form_class = SetPasswordForm
|
||||||
post_reset_login = False
|
post_reset_login = False
|
||||||
|
@ -335,6 +339,7 @@ class PasswordResetConfirmView(PasswordContextMixin, FormView):
|
||||||
return context
|
return context
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(login_not_required, name="dispatch")
|
||||||
class PasswordResetCompleteView(PasswordContextMixin, TemplateView):
|
class PasswordResetCompleteView(PasswordContextMixin, TemplateView):
|
||||||
template_name = "registration/password_reset_complete.html"
|
template_name = "registration/password_reset_complete.html"
|
||||||
title = _("Password reset complete")
|
title = _("Password reset complete")
|
||||||
|
|
|
@ -868,6 +868,10 @@ The following checks are performed on the default
|
||||||
for its builtin permission names to be at most 100 characters.
|
for its builtin permission names to be at most 100 characters.
|
||||||
* **auth.E012**: The permission codenamed ``<codename>`` of model ``<model>``
|
* **auth.E012**: The permission codenamed ``<codename>`` of model ``<model>``
|
||||||
is longer than 100 characters.
|
is longer than 100 characters.
|
||||||
|
* **auth.E013**: In order to use
|
||||||
|
:class:`django.contrib.auth.middleware.LoginRequiredMiddleware`,
|
||||||
|
:class:`django.contrib.auth.middleware.AuthenticationMiddleware` must be
|
||||||
|
defined before it in MIDDLEWARE.
|
||||||
|
|
||||||
``contenttypes``
|
``contenttypes``
|
||||||
----------------
|
----------------
|
||||||
|
|
|
@ -495,6 +495,58 @@ Adds the ``user`` attribute, representing the currently-logged-in user, to
|
||||||
every incoming ``HttpRequest`` object. See :ref:`Authentication in web requests
|
every incoming ``HttpRequest`` object. See :ref:`Authentication in web requests
|
||||||
<auth-web-requests>`.
|
<auth-web-requests>`.
|
||||||
|
|
||||||
|
.. class:: LoginRequiredMiddleware
|
||||||
|
|
||||||
|
.. versionadded:: 5.1
|
||||||
|
|
||||||
|
Redirects all unauthenticated requests to a login page. For admin views, this
|
||||||
|
redirects to the admin login. For all other views, this will redirect to
|
||||||
|
:setting:`settings.LOGIN_URL <LOGIN_URL>`. This can be customized by using the
|
||||||
|
:func:`~.django.contrib.auth.decorators.login_required` decorator and setting
|
||||||
|
``login_url`` or ``redirect_field_name`` for the view. For example::
|
||||||
|
|
||||||
|
@method_decorator(
|
||||||
|
login_required(login_url="/login/", redirect_field_name="redirect_to"),
|
||||||
|
name="dispatch",
|
||||||
|
)
|
||||||
|
class MyView(View):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@login_required(login_url="/login/", redirect_field_name="redirect_to")
|
||||||
|
def my_view(request): ...
|
||||||
|
|
||||||
|
Views using the :func:`~django.contrib.auth.decorators.login_not_required`
|
||||||
|
decorator are exempt from this requirement.
|
||||||
|
|
||||||
|
.. admonition:: Ensure that your login view does not require a login.
|
||||||
|
|
||||||
|
To prevent infinite redirects, ensure you have
|
||||||
|
:ref:`enabled unauthenticated requests
|
||||||
|
<disable-login-required-middleware-for-views>` to your login view.
|
||||||
|
|
||||||
|
**Methods and Attributes**
|
||||||
|
|
||||||
|
.. attribute:: redirect_field_name
|
||||||
|
|
||||||
|
Defaults to ``"next"``.
|
||||||
|
|
||||||
|
.. method:: get_login_url()
|
||||||
|
|
||||||
|
Returns the URL that unauthenticated requests will be redirected to. If
|
||||||
|
defined, this returns the ``login_url`` set on the
|
||||||
|
:func:`~.django.contrib.auth.decorators.login_required` decorator. Defaults
|
||||||
|
to :setting:`settings.LOGIN_URL <LOGIN_URL>`.
|
||||||
|
|
||||||
|
.. method:: get_redirect_field_name()
|
||||||
|
|
||||||
|
Returns the name of the query parameter that contains the URL the user
|
||||||
|
should be redirected to after a successful login. If defined, this returns
|
||||||
|
the ``redirect_field_name`` set on the
|
||||||
|
:func:`~.django.contrib.auth.decorators.login_required` decorator. Defaults
|
||||||
|
to :attr:`redirect_field_name`. If ``None`` is returned, a query parameter
|
||||||
|
won't be added.
|
||||||
|
|
||||||
.. class:: RemoteUserMiddleware
|
.. class:: RemoteUserMiddleware
|
||||||
|
|
||||||
Middleware for utilizing web server provided authentication. See
|
Middleware for utilizing web server provided authentication. See
|
||||||
|
@ -597,6 +649,12 @@ Here are some hints about the ordering of various Django middleware classes:
|
||||||
|
|
||||||
After ``SessionMiddleware``: uses session storage.
|
After ``SessionMiddleware``: uses session storage.
|
||||||
|
|
||||||
|
#. :class:`~django.contrib.auth.middleware.LoginRequiredMiddleware`
|
||||||
|
|
||||||
|
.. versionadded:: 5.1
|
||||||
|
|
||||||
|
After ``AuthenticationMiddleware``: uses user object.
|
||||||
|
|
||||||
#. :class:`~django.contrib.messages.middleware.MessageMiddleware`
|
#. :class:`~django.contrib.messages.middleware.MessageMiddleware`
|
||||||
|
|
||||||
After ``SessionMiddleware``: can use session-based storage.
|
After ``SessionMiddleware``: can use session-based storage.
|
||||||
|
|
|
@ -3060,8 +3060,9 @@ Default: ``'/accounts/login/'``
|
||||||
The URL or :ref:`named URL pattern <naming-url-patterns>` where requests are
|
The URL or :ref:`named URL pattern <naming-url-patterns>` where requests are
|
||||||
redirected for login when using the
|
redirected for login when using the
|
||||||
:func:`~django.contrib.auth.decorators.login_required` decorator,
|
:func:`~django.contrib.auth.decorators.login_required` decorator,
|
||||||
:class:`~django.contrib.auth.mixins.LoginRequiredMixin`, or
|
:class:`~django.contrib.auth.mixins.LoginRequiredMixin`,
|
||||||
:class:`~django.contrib.auth.mixins.AccessMixin`.
|
:class:`~django.contrib.auth.mixins.AccessMixin`, or when
|
||||||
|
:class:`~django.contrib.auth.middleware.LoginRequiredMiddleware` is installed.
|
||||||
|
|
||||||
.. setting:: LOGOUT_REDIRECT_URL
|
.. setting:: LOGOUT_REDIRECT_URL
|
||||||
|
|
||||||
|
|
|
@ -26,6 +26,20 @@ only officially support the latest release of each series.
|
||||||
What's new in Django 5.1
|
What's new in Django 5.1
|
||||||
========================
|
========================
|
||||||
|
|
||||||
|
Middleware to require authentication by default
|
||||||
|
-----------------------------------------------
|
||||||
|
|
||||||
|
The new :class:`~django.contrib.auth.middleware.LoginRequiredMiddleware`
|
||||||
|
redirects all unauthenticated requests to a login page. Views can allow
|
||||||
|
unauthenticated requests by using the new
|
||||||
|
:func:`~django.contrib.auth.decorators.login_not_required` decorator.
|
||||||
|
|
||||||
|
The :class:`~django.contrib.auth.middleware.LoginRequiredMiddleware` respects
|
||||||
|
the ``login_url`` and ``redirect_field_name`` values set via the
|
||||||
|
:func:`~.django.contrib.auth.decorators.login_required` decorator, but does not
|
||||||
|
support setting ``login_url`` or ``redirect_field_name`` via the
|
||||||
|
:class:`~django.contrib.auth.mixins.LoginRequiredMixin`.
|
||||||
|
|
||||||
Minor features
|
Minor features
|
||||||
--------------
|
--------------
|
||||||
|
|
||||||
|
|
|
@ -656,8 +656,25 @@ inheritance list.
|
||||||
``is_active`` flag on a user, but the default
|
``is_active`` flag on a user, but the default
|
||||||
:setting:`AUTHENTICATION_BACKENDS` reject inactive users.
|
:setting:`AUTHENTICATION_BACKENDS` reject inactive users.
|
||||||
|
|
||||||
|
.. _disable-login-required-middleware-for-views:
|
||||||
|
|
||||||
.. currentmodule:: django.contrib.auth.decorators
|
.. currentmodule:: django.contrib.auth.decorators
|
||||||
|
|
||||||
|
The ``login_not_required`` decorator
|
||||||
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
.. versionadded:: 5.1
|
||||||
|
|
||||||
|
When :class:`~django.contrib.auth.middleware.LoginRequiredMiddleware` is
|
||||||
|
installed, all views require authentication by default. Some views, such as the
|
||||||
|
login view, may need to disable this behavior.
|
||||||
|
|
||||||
|
.. function:: login_not_required()
|
||||||
|
|
||||||
|
Allows unauthenticated requests without redirecting to the login page when
|
||||||
|
:class:`~django.contrib.auth.middleware.LoginRequiredMiddleware` is
|
||||||
|
installed.
|
||||||
|
|
||||||
Limiting access to logged-in users that pass a test
|
Limiting access to logged-in users that pass a test
|
||||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,14 @@
|
||||||
from django.contrib.auth.checks import check_models_permissions, check_user_model
|
from django.contrib.auth.checks import (
|
||||||
|
check_middleware,
|
||||||
|
check_models_permissions,
|
||||||
|
check_user_model,
|
||||||
|
)
|
||||||
|
from django.contrib.auth.middleware import (
|
||||||
|
AuthenticationMiddleware,
|
||||||
|
LoginRequiredMiddleware,
|
||||||
|
)
|
||||||
from django.contrib.auth.models import AbstractBaseUser
|
from django.contrib.auth.models import AbstractBaseUser
|
||||||
|
from django.contrib.sessions.middleware import SessionMiddleware
|
||||||
from django.core import checks
|
from django.core import checks
|
||||||
from django.db import models
|
from django.db import models
|
||||||
from django.db.models import Q, UniqueConstraint
|
from django.db.models import Q, UniqueConstraint
|
||||||
|
@ -345,3 +354,102 @@ class ModelsPermissionsChecksTests(SimpleTestCase):
|
||||||
default_permissions = ()
|
default_permissions = ()
|
||||||
|
|
||||||
self.assertEqual(checks.run_checks(self.apps.get_app_configs()), [])
|
self.assertEqual(checks.run_checks(self.apps.get_app_configs()), [])
|
||||||
|
|
||||||
|
|
||||||
|
class LoginRequiredMiddlewareSubclass(LoginRequiredMiddleware):
|
||||||
|
redirect_field_name = "redirect_to"
|
||||||
|
|
||||||
|
|
||||||
|
class AuthenticationMiddlewareSubclass(AuthenticationMiddleware):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SessionMiddlewareSubclass(SessionMiddleware):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@override_system_checks([check_middleware])
|
||||||
|
class MiddlewareChecksTests(SimpleTestCase):
|
||||||
|
@override_settings(
|
||||||
|
MIDDLEWARE=[
|
||||||
|
"auth_tests.test_checks.SessionMiddlewareSubclass",
|
||||||
|
"auth_tests.test_checks.AuthenticationMiddlewareSubclass",
|
||||||
|
"auth_tests.test_checks.LoginRequiredMiddlewareSubclass",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_middleware_subclasses(self):
|
||||||
|
errors = checks.run_checks()
|
||||||
|
self.assertEqual(errors, [])
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
MIDDLEWARE=[
|
||||||
|
"auth_tests.test_checks",
|
||||||
|
"auth_tests.test_checks.NotExist",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_invalid_middleware_skipped(self):
|
||||||
|
errors = checks.run_checks()
|
||||||
|
self.assertEqual(errors, [])
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
MIDDLEWARE=[
|
||||||
|
"django.contrib.does.not.Exist",
|
||||||
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
"django.contrib.auth.middleware.LoginRequiredMiddleware",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_check_ignores_import_error_in_middleware(self):
|
||||||
|
errors = checks.run_checks()
|
||||||
|
self.assertEqual(errors, [])
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
MIDDLEWARE=[
|
||||||
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
"django.contrib.auth.middleware.LoginRequiredMiddleware",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_correct_order_with_login_required_middleware(self):
|
||||||
|
errors = checks.run_checks()
|
||||||
|
self.assertEqual(errors, [])
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
MIDDLEWARE=[
|
||||||
|
"django.contrib.auth.middleware.LoginRequiredMiddleware",
|
||||||
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_incorrect_order_with_login_required_middleware(self):
|
||||||
|
errors = checks.run_checks()
|
||||||
|
self.assertEqual(
|
||||||
|
errors,
|
||||||
|
[
|
||||||
|
checks.Error(
|
||||||
|
"In order to use django.contrib.auth.middleware."
|
||||||
|
"LoginRequiredMiddleware, django.contrib.auth.middleware."
|
||||||
|
"AuthenticationMiddleware must be defined before it in MIDDLEWARE.",
|
||||||
|
id="auth.E013",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
MIDDLEWARE=[
|
||||||
|
"django.contrib.auth.middleware.LoginRequiredMiddleware",
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_missing_authentication_with_login_required_middleware(self):
|
||||||
|
errors = checks.run_checks()
|
||||||
|
self.assertEqual(
|
||||||
|
errors,
|
||||||
|
[
|
||||||
|
checks.Error(
|
||||||
|
"In order to use django.contrib.auth.middleware."
|
||||||
|
"LoginRequiredMiddleware, django.contrib.auth.middleware."
|
||||||
|
"AuthenticationMiddleware must be defined before it in MIDDLEWARE.",
|
||||||
|
id="auth.E013",
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
|
@ -5,6 +5,7 @@ from asgiref.sync import sync_to_async
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.contrib.auth import models
|
from django.contrib.auth import models
|
||||||
from django.contrib.auth.decorators import (
|
from django.contrib.auth.decorators import (
|
||||||
|
login_not_required,
|
||||||
login_required,
|
login_required,
|
||||||
permission_required,
|
permission_required,
|
||||||
user_passes_test,
|
user_passes_test,
|
||||||
|
@ -113,6 +114,40 @@ class LoginRequiredTestCase(AuthViewsTestCase):
|
||||||
await self.test_login_required_async_view(login_url="/somewhere/")
|
await self.test_login_required_async_view(login_url="/somewhere/")
|
||||||
|
|
||||||
|
|
||||||
|
class LoginNotRequiredTestCase(TestCase):
|
||||||
|
"""
|
||||||
|
Tests the login_not_required decorators
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_callable(self):
|
||||||
|
"""
|
||||||
|
login_not_required is assignable to callable objects.
|
||||||
|
"""
|
||||||
|
|
||||||
|
class CallableView:
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
pass
|
||||||
|
|
||||||
|
login_not_required(CallableView())
|
||||||
|
|
||||||
|
def test_view(self):
|
||||||
|
"""
|
||||||
|
login_not_required is assignable to normal views.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def normal_view(request):
|
||||||
|
pass
|
||||||
|
|
||||||
|
login_not_required(normal_view)
|
||||||
|
|
||||||
|
def test_decorator_marks_view_as_login_not_required(self):
|
||||||
|
@login_not_required
|
||||||
|
def view(request):
|
||||||
|
return HttpResponse()
|
||||||
|
|
||||||
|
self.assertFalse(view.login_required)
|
||||||
|
|
||||||
|
|
||||||
class PermissionsRequiredDecoratorTest(TestCase):
|
class PermissionsRequiredDecoratorTest(TestCase):
|
||||||
"""
|
"""
|
||||||
Tests for the permission_required decorator
|
Tests for the permission_required decorator
|
||||||
|
|
|
@ -1,8 +1,14 @@
|
||||||
from django.contrib.auth.middleware import AuthenticationMiddleware
|
from django.conf import settings
|
||||||
|
from django.contrib.auth import REDIRECT_FIELD_NAME
|
||||||
|
from django.contrib.auth.middleware import (
|
||||||
|
AuthenticationMiddleware,
|
||||||
|
LoginRequiredMiddleware,
|
||||||
|
)
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.test import TestCase
|
from django.test import TestCase, modify_settings, override_settings
|
||||||
|
from django.urls import reverse
|
||||||
|
|
||||||
|
|
||||||
class TestAuthenticationMiddleware(TestCase):
|
class TestAuthenticationMiddleware(TestCase):
|
||||||
|
@ -50,3 +56,134 @@ class TestAuthenticationMiddleware(TestCase):
|
||||||
self.assertEqual(auser, self.user)
|
self.assertEqual(auser, self.user)
|
||||||
auser_second = await self.request.auser()
|
auser_second = await self.request.auser()
|
||||||
self.assertIs(auser, auser_second)
|
self.assertIs(auser, auser_second)
|
||||||
|
|
||||||
|
|
||||||
|
@override_settings(ROOT_URLCONF="auth_tests.urls")
|
||||||
|
@modify_settings(
|
||||||
|
MIDDLEWARE={"append": "django.contrib.auth.middleware.LoginRequiredMiddleware"}
|
||||||
|
)
|
||||||
|
class TestLoginRequiredMiddleware(TestCase):
|
||||||
|
@classmethod
|
||||||
|
def setUpTestData(cls):
|
||||||
|
cls.user = User.objects.create_user(
|
||||||
|
"test_user", "test@example.com", "test_password"
|
||||||
|
)
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.middleware = LoginRequiredMiddleware(lambda req: HttpResponse())
|
||||||
|
self.request = HttpRequest()
|
||||||
|
|
||||||
|
def test_public_paths(self):
|
||||||
|
paths = ["public_view", "public_function_view"]
|
||||||
|
for path in paths:
|
||||||
|
response = self.client.get(f"/{path}/")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_protected_paths(self):
|
||||||
|
paths = ["protected_view", "protected_function_view"]
|
||||||
|
for path in paths:
|
||||||
|
response = self.client.get(f"/{path}/")
|
||||||
|
self.assertRedirects(
|
||||||
|
response,
|
||||||
|
settings.LOGIN_URL + f"?next=/{path}/",
|
||||||
|
fetch_redirect_response=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_login_required_paths(self):
|
||||||
|
paths = ["login_required_cbv_view", "login_required_decorator_view"]
|
||||||
|
for path in paths:
|
||||||
|
response = self.client.get(f"/{path}/")
|
||||||
|
self.assertRedirects(
|
||||||
|
response,
|
||||||
|
"/custom_login/" + f"?step=/{path}/",
|
||||||
|
fetch_redirect_response=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_admin_path(self):
|
||||||
|
admin_url = reverse("admin:index")
|
||||||
|
response = self.client.get(admin_url)
|
||||||
|
self.assertRedirects(
|
||||||
|
response,
|
||||||
|
reverse("admin:login") + f"?next={admin_url}",
|
||||||
|
target_status_code=200,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_non_existent_path(self):
|
||||||
|
response = self.client.get("/non_existent/")
|
||||||
|
self.assertEqual(response.status_code, 404)
|
||||||
|
|
||||||
|
def test_paths_with_logged_in_user(self):
|
||||||
|
paths = [
|
||||||
|
"public_view",
|
||||||
|
"public_function_view",
|
||||||
|
"protected_view",
|
||||||
|
"protected_function_view",
|
||||||
|
"login_required_cbv_view",
|
||||||
|
"login_required_decorator_view",
|
||||||
|
]
|
||||||
|
self.client.login(username="test_user", password="test_password")
|
||||||
|
for path in paths:
|
||||||
|
response = self.client.get(f"/{path}/")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
def test_get_login_url_from_view_func(self):
|
||||||
|
def view_func(request):
|
||||||
|
return HttpResponse()
|
||||||
|
|
||||||
|
view_func.login_url = "/custom_login/"
|
||||||
|
login_url = self.middleware.get_login_url(view_func)
|
||||||
|
self.assertEqual(login_url, "/custom_login/")
|
||||||
|
|
||||||
|
@override_settings(LOGIN_URL="/settings_login/")
|
||||||
|
def test_get_login_url_from_settings(self):
|
||||||
|
login_url = self.middleware.get_login_url(lambda: None)
|
||||||
|
self.assertEqual(login_url, "/settings_login/")
|
||||||
|
|
||||||
|
@override_settings(LOGIN_URL=None)
|
||||||
|
def test_get_login_url_no_login_url(self):
|
||||||
|
with self.assertRaises(ImproperlyConfigured) as e:
|
||||||
|
self.middleware.get_login_url(lambda: None)
|
||||||
|
self.assertEqual(
|
||||||
|
str(e.exception),
|
||||||
|
"No login URL to redirect to. Define settings.LOGIN_URL or provide "
|
||||||
|
"a login_url via the 'django.contrib.auth.decorators.login_required' "
|
||||||
|
"decorator.",
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_redirect_field_name_from_view_func(self):
|
||||||
|
def view_func(request):
|
||||||
|
return HttpResponse()
|
||||||
|
|
||||||
|
view_func.redirect_field_name = "next_page"
|
||||||
|
redirect_field_name = self.middleware.get_redirect_field_name(view_func)
|
||||||
|
self.assertEqual(redirect_field_name, "next_page")
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
MIDDLEWARE=[
|
||||||
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
"auth_tests.test_checks.LoginRequiredMiddlewareSubclass",
|
||||||
|
],
|
||||||
|
LOGIN_URL="/settings_login/",
|
||||||
|
)
|
||||||
|
def test_login_url_resolve_logic(self):
|
||||||
|
paths = ["login_required_cbv_view", "login_required_decorator_view"]
|
||||||
|
for path in paths:
|
||||||
|
response = self.client.get(f"/{path}/")
|
||||||
|
self.assertRedirects(
|
||||||
|
response,
|
||||||
|
"/custom_login/" + f"?step=/{path}/",
|
||||||
|
fetch_redirect_response=False,
|
||||||
|
)
|
||||||
|
paths = ["protected_view", "protected_function_view"]
|
||||||
|
for path in paths:
|
||||||
|
response = self.client.get(f"/{path}/")
|
||||||
|
self.assertRedirects(
|
||||||
|
response,
|
||||||
|
f"/settings_login/?redirect_to=/{path}/",
|
||||||
|
fetch_redirect_response=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_get_redirect_field_name_default(self):
|
||||||
|
redirect_field_name = self.middleware.get_redirect_field_name(lambda: None)
|
||||||
|
self.assertEqual(redirect_field_name, REDIRECT_FIELD_NAME)
|
||||||
|
|
|
@ -32,7 +32,7 @@ from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.db import connection
|
from django.db import connection
|
||||||
from django.http import HttpRequest, HttpResponse
|
from django.http import HttpRequest, HttpResponse
|
||||||
from django.middleware.csrf import CsrfViewMiddleware, get_token
|
from django.middleware.csrf import CsrfViewMiddleware, get_token
|
||||||
from django.test import Client, TestCase, override_settings
|
from django.test import Client, TestCase, modify_settings, override_settings
|
||||||
from django.test.client import RedirectCycleError
|
from django.test.client import RedirectCycleError
|
||||||
from django.urls import NoReverseMatch, reverse, reverse_lazy
|
from django.urls import NoReverseMatch, reverse, reverse_lazy
|
||||||
from django.utils.http import urlsafe_base64_encode
|
from django.utils.http import urlsafe_base64_encode
|
||||||
|
@ -472,6 +472,29 @@ class PasswordResetTest(AuthViewsTestCase):
|
||||||
with self.assertRaisesMessage(ImproperlyConfigured, msg):
|
with self.assertRaisesMessage(ImproperlyConfigured, msg):
|
||||||
self.client.get("/reset/missing_parameters/")
|
self.client.get("/reset/missing_parameters/")
|
||||||
|
|
||||||
|
@modify_settings(
|
||||||
|
MIDDLEWARE={"append": "django.contrib.auth.middleware.LoginRequiredMiddleware"}
|
||||||
|
)
|
||||||
|
def test_access_under_login_required_middleware(self):
|
||||||
|
reset_urls = [
|
||||||
|
reverse("password_reset"),
|
||||||
|
reverse("password_reset_done"),
|
||||||
|
reverse("password_reset_confirm", kwargs={"uidb64": "abc", "token": "def"}),
|
||||||
|
reverse("password_reset_complete"),
|
||||||
|
]
|
||||||
|
|
||||||
|
for url in reset_urls:
|
||||||
|
with self.subTest(url=url):
|
||||||
|
response = self.client.get(url)
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
"/password_reset/", {"email": "staffmember@example.com"}
|
||||||
|
)
|
||||||
|
self.assertRedirects(
|
||||||
|
response, "/password_reset/done/", fetch_redirect_response=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@override_settings(AUTH_USER_MODEL="auth_tests.CustomUser")
|
@override_settings(AUTH_USER_MODEL="auth_tests.CustomUser")
|
||||||
class CustomUserPasswordResetTest(AuthViewsTestCase):
|
class CustomUserPasswordResetTest(AuthViewsTestCase):
|
||||||
|
@ -661,6 +684,38 @@ class ChangePasswordTest(AuthViewsTestCase):
|
||||||
response, "/password_reset/", fetch_redirect_response=False
|
response, "/password_reset/", fetch_redirect_response=False
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@modify_settings(
|
||||||
|
MIDDLEWARE={"append": "django.contrib.auth.middleware.LoginRequiredMiddleware"}
|
||||||
|
)
|
||||||
|
def test_access_under_login_required_middleware(self):
|
||||||
|
response = self.client.post(
|
||||||
|
"/password_change/",
|
||||||
|
{
|
||||||
|
"old_password": "password",
|
||||||
|
"new_password1": "password1",
|
||||||
|
"new_password2": "password1",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertRedirects(
|
||||||
|
response,
|
||||||
|
settings.LOGIN_URL + "?next=/password_change/",
|
||||||
|
fetch_redirect_response=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.login()
|
||||||
|
|
||||||
|
response = self.client.post(
|
||||||
|
"/password_change/",
|
||||||
|
{
|
||||||
|
"old_password": "password",
|
||||||
|
"new_password1": "password1",
|
||||||
|
"new_password2": "password1",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
self.assertRedirects(
|
||||||
|
response, "/password_change/done/", fetch_redirect_response=False
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
class SessionAuthenticationTests(AuthViewsTestCase):
|
class SessionAuthenticationTests(AuthViewsTestCase):
|
||||||
def test_user_password_change_updates_session(self):
|
def test_user_password_change_updates_session(self):
|
||||||
|
@ -904,6 +959,13 @@ class LoginTest(AuthViewsTestCase):
|
||||||
response = self.login(url="/login/get_default_redirect_url/?next=/test/")
|
response = self.login(url="/login/get_default_redirect_url/?next=/test/")
|
||||||
self.assertRedirects(response, "/test/", fetch_redirect_response=False)
|
self.assertRedirects(response, "/test/", fetch_redirect_response=False)
|
||||||
|
|
||||||
|
@modify_settings(
|
||||||
|
MIDDLEWARE={"append": "django.contrib.auth.middleware.LoginRequiredMiddleware"}
|
||||||
|
)
|
||||||
|
def test_access_under_login_required_middleware(self):
|
||||||
|
response = self.client.get(reverse("login"))
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
class LoginURLSettings(AuthViewsTestCase):
|
class LoginURLSettings(AuthViewsTestCase):
|
||||||
"""Tests for settings.LOGIN_URL."""
|
"""Tests for settings.LOGIN_URL."""
|
||||||
|
@ -1355,6 +1417,22 @@ class LogoutTest(AuthViewsTestCase):
|
||||||
self.assertContains(response, "Logged out")
|
self.assertContains(response, "Logged out")
|
||||||
self.confirm_logged_out()
|
self.confirm_logged_out()
|
||||||
|
|
||||||
|
@modify_settings(
|
||||||
|
MIDDLEWARE={"append": "django.contrib.auth.middleware.LoginRequiredMiddleware"}
|
||||||
|
)
|
||||||
|
def test_access_under_login_required_middleware(self):
|
||||||
|
response = self.client.post("/logout/")
|
||||||
|
self.assertRedirects(
|
||||||
|
response,
|
||||||
|
settings.LOGIN_URL + "?next=/logout/",
|
||||||
|
fetch_redirect_response=False,
|
||||||
|
)
|
||||||
|
|
||||||
|
self.login()
|
||||||
|
|
||||||
|
response = self.client.post("/logout/")
|
||||||
|
self.assertEqual(response.status_code, 200)
|
||||||
|
|
||||||
|
|
||||||
def get_perm(Model, perm):
|
def get_perm(Model, perm):
|
||||||
ct = ContentType.objects.get_for_model(Model)
|
ct = ContentType.objects.get_for_model(Model)
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
from django.contrib import admin
|
from django.contrib import admin
|
||||||
from django.contrib.auth import views
|
from django.contrib.auth import views
|
||||||
from django.contrib.auth.decorators import login_required, permission_required
|
from django.contrib.auth.decorators import (
|
||||||
|
login_not_required,
|
||||||
|
login_required,
|
||||||
|
permission_required,
|
||||||
|
)
|
||||||
from django.contrib.auth.forms import AuthenticationForm
|
from django.contrib.auth.forms import AuthenticationForm
|
||||||
from django.contrib.auth.urls import urlpatterns as auth_urlpatterns
|
from django.contrib.auth.urls import urlpatterns as auth_urlpatterns
|
||||||
from django.contrib.auth.views import LoginView
|
from django.contrib.auth.views import LoginView
|
||||||
|
@ -9,6 +13,8 @@ from django.http import HttpRequest, HttpResponse
|
||||||
from django.shortcuts import render
|
from django.shortcuts import render
|
||||||
from django.template import RequestContext, Template
|
from django.template import RequestContext, Template
|
||||||
from django.urls import path, re_path, reverse_lazy
|
from django.urls import path, re_path, reverse_lazy
|
||||||
|
from django.utils.decorators import method_decorator
|
||||||
|
from django.views import View
|
||||||
from django.views.decorators.cache import never_cache
|
from django.views.decorators.cache import never_cache
|
||||||
from django.views.i18n import set_language
|
from django.views.i18n import set_language
|
||||||
|
|
||||||
|
@ -88,6 +94,42 @@ class CustomDefaultRedirectURLLoginView(LoginView):
|
||||||
return "/custom/"
|
return "/custom/"
|
||||||
|
|
||||||
|
|
||||||
|
class EmptyResponseBaseView(View):
|
||||||
|
def get(self, request, *args, **kwargs):
|
||||||
|
return HttpResponse()
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(login_not_required, name="dispatch")
|
||||||
|
class PublicView(EmptyResponseBaseView):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class ProtectedView(EmptyResponseBaseView):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@method_decorator(
|
||||||
|
login_required(login_url="/custom_login/", redirect_field_name="step"),
|
||||||
|
name="dispatch",
|
||||||
|
)
|
||||||
|
class ProtectedViewWithCustomLoginRequired(EmptyResponseBaseView):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
@login_not_required
|
||||||
|
def public_view(request):
|
||||||
|
return HttpResponse()
|
||||||
|
|
||||||
|
|
||||||
|
def protected_view(request):
|
||||||
|
return HttpResponse()
|
||||||
|
|
||||||
|
|
||||||
|
@login_required(login_url="/custom_login/", redirect_field_name="step")
|
||||||
|
def protected_view_with_login_required_decorator(request):
|
||||||
|
return HttpResponse()
|
||||||
|
|
||||||
|
|
||||||
# special urls for auth test cases
|
# special urls for auth test cases
|
||||||
urlpatterns = auth_urlpatterns + [
|
urlpatterns = auth_urlpatterns + [
|
||||||
path(
|
path(
|
||||||
|
@ -198,7 +240,14 @@ urlpatterns = auth_urlpatterns + [
|
||||||
"login_and_permission_required_exception/",
|
"login_and_permission_required_exception/",
|
||||||
login_and_permission_required_exception,
|
login_and_permission_required_exception,
|
||||||
),
|
),
|
||||||
|
path("public_view/", PublicView.as_view()),
|
||||||
|
path("public_function_view/", public_view),
|
||||||
|
path("protected_view/", ProtectedView.as_view()),
|
||||||
|
path("protected_function_view/", protected_view),
|
||||||
|
path(
|
||||||
|
"login_required_decorator_view/", protected_view_with_login_required_decorator
|
||||||
|
),
|
||||||
|
path("login_required_cbv_view/", ProtectedViewWithCustomLoginRequired.as_view()),
|
||||||
path("setlang/", set_language, name="set_language"),
|
path("setlang/", set_language, name="set_language"),
|
||||||
# This line is only required to render the password reset with is_admin=True
|
|
||||||
path("admin/", admin.site.urls),
|
path("admin/", admin.site.urls),
|
||||||
]
|
]
|
||||||
|
|
|
@ -5,6 +5,7 @@ from asgiref.sync import async_to_sync, iscoroutinefunction
|
||||||
from django.contrib.admindocs.middleware import XViewMiddleware
|
from django.contrib.admindocs.middleware import XViewMiddleware
|
||||||
from django.contrib.auth.middleware import (
|
from django.contrib.auth.middleware import (
|
||||||
AuthenticationMiddleware,
|
AuthenticationMiddleware,
|
||||||
|
LoginRequiredMiddleware,
|
||||||
RemoteUserMiddleware,
|
RemoteUserMiddleware,
|
||||||
)
|
)
|
||||||
from django.contrib.flatpages.middleware import FlatpageFallbackMiddleware
|
from django.contrib.flatpages.middleware import FlatpageFallbackMiddleware
|
||||||
|
@ -34,6 +35,7 @@ from django.utils.deprecation import MiddlewareMixin
|
||||||
class MiddlewareMixinTests(SimpleTestCase):
|
class MiddlewareMixinTests(SimpleTestCase):
|
||||||
middlewares = [
|
middlewares = [
|
||||||
AuthenticationMiddleware,
|
AuthenticationMiddleware,
|
||||||
|
LoginRequiredMiddleware,
|
||||||
BrokenLinkEmailsMiddleware,
|
BrokenLinkEmailsMiddleware,
|
||||||
CacheMiddleware,
|
CacheMiddleware,
|
||||||
CommonMiddleware,
|
CommonMiddleware,
|
||||||
|
|
Loading…
Reference in New Issue