utils.py 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. """
  2. sudo.utils
  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 cast
  9. from django.core.signing import BadSignature
  10. from django.http.request import HttpRequest
  11. from django.utils.crypto import constant_time_compare, get_random_string
  12. from sudo.settings import COOKIE_AGE, COOKIE_NAME, COOKIE_SALT
  13. class _SudoRequest(HttpRequest):
  14. _sudo: bool
  15. _sudo_token: str
  16. _sudo_max_age: int
  17. def _allow_sudo_attribute_stuffing(request: HttpRequest) -> _SudoRequest:
  18. # cast to our fake type which allows typesafe attribute stuffing
  19. return cast(_SudoRequest, request)
  20. def grant_sudo_privileges(request: HttpRequest, max_age: int = COOKIE_AGE) -> str | None:
  21. """
  22. Assigns a random token to the user's session
  23. that allows them to have elevated permissions
  24. """
  25. request = _allow_sudo_attribute_stuffing(request)
  26. user = getattr(request, "user", None)
  27. # If there's not a user on the request, just noop
  28. if user is None:
  29. return None
  30. if not user.is_authenticated:
  31. raise ValueError("User needs to be logged in to be elevated to sudo")
  32. # Token doesn't need to be unique,
  33. # just needs to be unpredictable and match the cookie and the session
  34. token = get_random_string(12)
  35. request.session[COOKIE_NAME] = token
  36. request._sudo = True
  37. request._sudo_token = token
  38. request._sudo_max_age = max_age
  39. return token
  40. def revoke_sudo_privileges(request: HttpRequest) -> None:
  41. """
  42. Revoke sudo privileges from a request explicitly
  43. """
  44. request = _allow_sudo_attribute_stuffing(request)
  45. request._sudo = False
  46. if COOKIE_NAME in request.session:
  47. del request.session[COOKIE_NAME]
  48. def has_sudo_privileges(request: HttpRequest) -> bool:
  49. """
  50. Check if a request is allowed to perform sudo actions
  51. """
  52. request = _allow_sudo_attribute_stuffing(request)
  53. if getattr(request, "_sudo", None) is None:
  54. try:
  55. request._sudo = request.user.is_authenticated and constant_time_compare(
  56. request.get_signed_cookie(COOKIE_NAME, salt=COOKIE_SALT, max_age=COOKIE_AGE) or "",
  57. request.session[COOKIE_NAME],
  58. )
  59. except (KeyError, BadSignature):
  60. request._sudo = False
  61. return request._sudo