middleware.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191
  1. from functools import partial
  2. from urllib.parse import urlparse
  3. from django.conf import settings
  4. from django.contrib import auth
  5. from django.contrib.auth import REDIRECT_FIELD_NAME, load_backend
  6. from django.contrib.auth.backends import RemoteUserBackend
  7. from django.contrib.auth.views import redirect_to_login
  8. from django.core.exceptions import ImproperlyConfigured
  9. from django.shortcuts import resolve_url
  10. from django.utils.deprecation import MiddlewareMixin
  11. from django.utils.functional import SimpleLazyObject
  12. def get_user(request):
  13. if not hasattr(request, "_cached_user"):
  14. request._cached_user = auth.get_user(request)
  15. return request._cached_user
  16. async def auser(request):
  17. if not hasattr(request, "_acached_user"):
  18. request._acached_user = await auth.aget_user(request)
  19. return request._acached_user
  20. class AuthenticationMiddleware(MiddlewareMixin):
  21. def process_request(self, request):
  22. if not hasattr(request, "session"):
  23. raise ImproperlyConfigured(
  24. "The Django authentication middleware requires session "
  25. "middleware to be installed. Edit your MIDDLEWARE setting to "
  26. "insert "
  27. "'django.contrib.sessions.middleware.SessionMiddleware' before "
  28. "'django.contrib.auth.middleware.AuthenticationMiddleware'."
  29. )
  30. request.user = SimpleLazyObject(lambda: get_user(request))
  31. request.auser = partial(auser, request)
  32. class LoginRequiredMiddleware(MiddlewareMixin):
  33. """
  34. Middleware that redirects all unauthenticated requests to a login page.
  35. Views using the login_not_required decorator will not be redirected.
  36. """
  37. redirect_field_name = REDIRECT_FIELD_NAME
  38. def process_view(self, request, view_func, view_args, view_kwargs):
  39. if request.user.is_authenticated:
  40. return None
  41. if not getattr(view_func, "login_required", True):
  42. return None
  43. return self.handle_no_permission(request, view_func)
  44. def get_login_url(self, view_func):
  45. login_url = getattr(view_func, "login_url", None) or settings.LOGIN_URL
  46. if not login_url:
  47. raise ImproperlyConfigured(
  48. "No login URL to redirect to. Define settings.LOGIN_URL or "
  49. "provide a login_url via the 'django.contrib.auth.decorators."
  50. "login_required' decorator."
  51. )
  52. return str(login_url)
  53. def get_redirect_field_name(self, view_func):
  54. return getattr(view_func, "redirect_field_name", self.redirect_field_name)
  55. def handle_no_permission(self, request, view_func):
  56. path = request.build_absolute_uri()
  57. resolved_login_url = resolve_url(self.get_login_url(view_func))
  58. # If the login url is the same scheme and net location then use the
  59. # path as the "next" url.
  60. login_scheme, login_netloc = urlparse(resolved_login_url)[:2]
  61. current_scheme, current_netloc = urlparse(path)[:2]
  62. if (not login_scheme or login_scheme == current_scheme) and (
  63. not login_netloc or login_netloc == current_netloc
  64. ):
  65. path = request.get_full_path()
  66. return redirect_to_login(
  67. path,
  68. resolved_login_url,
  69. self.get_redirect_field_name(view_func),
  70. )
  71. class RemoteUserMiddleware(MiddlewareMixin):
  72. """
  73. Middleware for utilizing web-server-provided authentication.
  74. If request.user is not authenticated, then this middleware attempts to
  75. authenticate the username passed in the ``REMOTE_USER`` request header.
  76. If authentication is successful, the user is automatically logged in to
  77. persist the user in the session.
  78. The header used is configurable and defaults to ``REMOTE_USER``. Subclass
  79. this class and change the ``header`` attribute if you need to use a
  80. different header.
  81. """
  82. # Name of request header to grab username from. This will be the key as
  83. # used in the request.META dictionary, i.e. the normalization of headers to
  84. # all uppercase and the addition of "HTTP_" prefix apply.
  85. header = "REMOTE_USER"
  86. force_logout_if_no_header = True
  87. def process_request(self, request):
  88. # AuthenticationMiddleware is required so that request.user exists.
  89. if not hasattr(request, "user"):
  90. raise ImproperlyConfigured(
  91. "The Django remote user auth middleware requires the"
  92. " authentication middleware to be installed. Edit your"
  93. " MIDDLEWARE setting to insert"
  94. " 'django.contrib.auth.middleware.AuthenticationMiddleware'"
  95. " before the RemoteUserMiddleware class."
  96. )
  97. try:
  98. username = request.META[self.header]
  99. except KeyError:
  100. # If specified header doesn't exist then remove any existing
  101. # authenticated remote-user, or return (leaving request.user set to
  102. # AnonymousUser by the AuthenticationMiddleware).
  103. if self.force_logout_if_no_header and request.user.is_authenticated:
  104. self._remove_invalid_user(request)
  105. return
  106. # If the user is already authenticated and that user is the user we are
  107. # getting passed in the headers, then the correct user is already
  108. # persisted in the session and we don't need to continue.
  109. if request.user.is_authenticated:
  110. if request.user.get_username() == self.clean_username(username, request):
  111. return
  112. else:
  113. # An authenticated user is associated with the request, but
  114. # it does not match the authorized user in the header.
  115. self._remove_invalid_user(request)
  116. # We are seeing this user for the first time in this session, attempt
  117. # to authenticate the user.
  118. user = auth.authenticate(request, remote_user=username)
  119. if user:
  120. # User is valid. Set request.user and persist user in the session
  121. # by logging the user in.
  122. request.user = user
  123. auth.login(request, user)
  124. def clean_username(self, username, request):
  125. """
  126. Allow the backend to clean the username, if the backend defines a
  127. clean_username method.
  128. """
  129. backend_str = request.session[auth.BACKEND_SESSION_KEY]
  130. backend = auth.load_backend(backend_str)
  131. try:
  132. username = backend.clean_username(username)
  133. except AttributeError: # Backend has no clean_username method.
  134. pass
  135. return username
  136. def _remove_invalid_user(self, request):
  137. """
  138. Remove the current authenticated user in the request which is invalid
  139. but only if the user is authenticated via the RemoteUserBackend.
  140. """
  141. try:
  142. stored_backend = load_backend(
  143. request.session.get(auth.BACKEND_SESSION_KEY, "")
  144. )
  145. except ImportError:
  146. # backend failed to load
  147. auth.logout(request)
  148. else:
  149. if isinstance(stored_backend, RemoteUserBackend):
  150. auth.logout(request)
  151. class PersistentRemoteUserMiddleware(RemoteUserMiddleware):
  152. """
  153. Middleware for web-server provided authentication on logon pages.
  154. Like RemoteUserMiddleware but keeps the user authenticated even if
  155. the header (``REMOTE_USER``) is not found in the request. Useful
  156. for setups when the external authentication via ``REMOTE_USER``
  157. is only expected to happen on some "logon" URL and the rest of
  158. the application wants to use Django's authentication mechanism.
  159. """
  160. force_logout_if_no_header = False