views.py 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. """
  2. sudo.views
  3. ~~~~~~~~~~
  4. :copyright: (c) 2020 by Matt Robenolt.
  5. :license: BSD, see LICENSE for more details.
  6. """
  7. from __future__ import annotations
  8. from typing import Any
  9. from urllib.parse import urlparse, urlunparse
  10. from django.contrib.auth.decorators import login_required
  11. from django.core.exceptions import ImproperlyConfigured
  12. from django.http import HttpRequest, HttpResponseRedirect, QueryDict
  13. from django.http.response import HttpResponseBase
  14. from django.shortcuts import resolve_url
  15. from django.template.response import TemplateResponse
  16. from django.utils.decorators import method_decorator
  17. from django.utils.http import url_has_allowed_host_and_scheme
  18. from django.utils.module_loading import import_string
  19. from django.views.decorators.cache import never_cache
  20. from django.views.decorators.csrf import csrf_protect
  21. from django.views.decorators.debug import sensitive_post_parameters
  22. from django.views.generic import View
  23. from sudo.forms import SudoForm
  24. from sudo.settings import REDIRECT_FIELD_NAME, REDIRECT_TO_FIELD_NAME, REDIRECT_URL, URL
  25. from sudo.utils import grant_sudo_privileges
  26. class SudoView(View):
  27. """
  28. The default view for the sudo mode page. The role of this page is to
  29. prompt the user for their password again, and if successful, redirect
  30. them back to ``next``.
  31. """
  32. form_class = SudoForm
  33. template_name = "sudo/sudo.html"
  34. extra_context: dict[str, str] | None = None
  35. def handle_sudo(self, request: HttpRequest, context: dict[str, Any]) -> bool:
  36. return request.method == "POST" and context["form"].is_valid()
  37. def grant_sudo_privileges(self, request: HttpRequest, redirect_to: str) -> HttpResponseRedirect:
  38. grant_sudo_privileges(request)
  39. # Restore the redirect destination from the GET request
  40. redirect_to = request.session.pop(REDIRECT_TO_FIELD_NAME, redirect_to)
  41. # Double check we're not redirecting to other sites
  42. if not url_has_allowed_host_and_scheme(redirect_to, allowed_hosts=(request.get_host(),)):
  43. redirect_to = resolve_url(REDIRECT_URL)
  44. return HttpResponseRedirect(redirect_to)
  45. @method_decorator(sensitive_post_parameters())
  46. @method_decorator(never_cache)
  47. @method_decorator(csrf_protect)
  48. @method_decorator(login_required)
  49. def dispatch(self, request: HttpRequest, *args: object, **kwargs: object) -> HttpResponseBase:
  50. redirect_to = request.GET.get(REDIRECT_FIELD_NAME, REDIRECT_URL)
  51. # Make sure we're not redirecting to other sites
  52. if not url_has_allowed_host_and_scheme(redirect_to, allowed_hosts=(request.get_host(),)):
  53. redirect_to = resolve_url(REDIRECT_URL)
  54. if request.is_sudo():
  55. return HttpResponseRedirect(redirect_to)
  56. if request.method == "GET":
  57. request.session[REDIRECT_TO_FIELD_NAME] = redirect_to
  58. context = {
  59. "form": self.form_class(request.user, request.POST or None),
  60. "request": request,
  61. REDIRECT_FIELD_NAME: redirect_to,
  62. }
  63. if self.handle_sudo(request, context):
  64. return self.grant_sudo_privileges(request, redirect_to)
  65. if self.extra_context is not None:
  66. context.update(self.extra_context)
  67. return TemplateResponse(request, self.template_name, context)
  68. def sudo(request: HttpRequest, **kwargs: object) -> HttpResponseBase:
  69. return SudoView(**kwargs).dispatch(request)
  70. def redirect_to_sudo(next_url: str, sudo_url: str | None = None) -> HttpResponseRedirect:
  71. """
  72. Redirects the user to the login page, passing the given 'next' page
  73. """
  74. if sudo_url is None:
  75. sudo_obj = URL
  76. else:
  77. sudo_obj = sudo_url
  78. try:
  79. # django 1.10 and greater can't resolve the string 'sudo.views.sudo' to a URL
  80. # https://docs.djangoproject.com/en/1.10/releases/1.10/#removed-features-1-10
  81. sudo_obj = import_string(sudo_obj)
  82. except (ImportError, ImproperlyConfigured):
  83. pass # wasn't a dotted path
  84. sudo_url_parts = list(urlparse(resolve_url(sudo_obj)))
  85. querystring = QueryDict(sudo_url_parts[4], mutable=True)
  86. querystring[REDIRECT_FIELD_NAME] = next_url
  87. sudo_url_parts[4] = querystring.urlencode(safe="/")
  88. return HttpResponseRedirect(urlunparse(sudo_url_parts))