Funktionierender Prototyp des Serious Games zur Vermittlung von Wissen zu Software-Engineering-Arbeitsmodellen.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

auth.py 6.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. from django.conf import settings
  2. from django.contrib.auth import (
  3. BACKEND_SESSION_KEY,
  4. HASH_SESSION_KEY,
  5. SESSION_KEY,
  6. _get_backends,
  7. get_user_model,
  8. load_backend,
  9. user_logged_in,
  10. user_logged_out,
  11. )
  12. from django.utils.crypto import constant_time_compare
  13. from django.utils.functional import LazyObject
  14. from channels.db import database_sync_to_async
  15. from channels.middleware import BaseMiddleware
  16. from channels.sessions import CookieMiddleware, SessionMiddleware
  17. @database_sync_to_async
  18. def get_user(scope):
  19. """
  20. Return the user model instance associated with the given scope.
  21. If no user is retrieved, return an instance of `AnonymousUser`.
  22. """
  23. # postpone model import to avoid ImproperlyConfigured error before Django
  24. # setup is complete.
  25. from django.contrib.auth.models import AnonymousUser
  26. if "session" not in scope:
  27. raise ValueError(
  28. "Cannot find session in scope. You should wrap your consumer in "
  29. "SessionMiddleware."
  30. )
  31. session = scope["session"]
  32. user = None
  33. try:
  34. user_id = _get_user_session_key(session)
  35. backend_path = session[BACKEND_SESSION_KEY]
  36. except KeyError:
  37. pass
  38. else:
  39. if backend_path in settings.AUTHENTICATION_BACKENDS:
  40. backend = load_backend(backend_path)
  41. user = backend.get_user(user_id)
  42. # Verify the session
  43. if hasattr(user, "get_session_auth_hash"):
  44. session_hash = session.get(HASH_SESSION_KEY)
  45. session_hash_verified = session_hash and constant_time_compare(
  46. session_hash, user.get_session_auth_hash()
  47. )
  48. if not session_hash_verified:
  49. session.flush()
  50. user = None
  51. return user or AnonymousUser()
  52. @database_sync_to_async
  53. def login(scope, user, backend=None):
  54. """
  55. Persist a user id and a backend in the request.
  56. This way a user doesn't have to re-authenticate on every request.
  57. Note that data set during the anonymous session is retained when the user
  58. logs in.
  59. """
  60. if "session" not in scope:
  61. raise ValueError(
  62. "Cannot find session in scope. You should wrap your consumer in "
  63. "SessionMiddleware."
  64. )
  65. session = scope["session"]
  66. session_auth_hash = ""
  67. if user is None:
  68. user = scope.get("user", None)
  69. if user is None:
  70. raise ValueError(
  71. "User must be passed as an argument or must be present in the scope."
  72. )
  73. if hasattr(user, "get_session_auth_hash"):
  74. session_auth_hash = user.get_session_auth_hash()
  75. if SESSION_KEY in session:
  76. if _get_user_session_key(session) != user.pk or (
  77. session_auth_hash
  78. and not constant_time_compare(
  79. session.get(HASH_SESSION_KEY, ""), session_auth_hash
  80. )
  81. ):
  82. # To avoid reusing another user's session, create a new, empty
  83. # session if the existing session corresponds to a different
  84. # authenticated user.
  85. session.flush()
  86. else:
  87. session.cycle_key()
  88. try:
  89. backend = backend or user.backend
  90. except AttributeError:
  91. backends = _get_backends(return_tuples=True)
  92. if len(backends) == 1:
  93. _, backend = backends[0]
  94. else:
  95. raise ValueError(
  96. "You have multiple authentication backends configured and "
  97. "therefore must provide the `backend` "
  98. "argument or set the `backend` attribute on the user."
  99. )
  100. session[SESSION_KEY] = user._meta.pk.value_to_string(user)
  101. session[BACKEND_SESSION_KEY] = backend
  102. session[HASH_SESSION_KEY] = session_auth_hash
  103. scope["user"] = user
  104. # note this does not reset the CSRF_COOKIE/Token
  105. user_logged_in.send(sender=user.__class__, request=None, user=user)
  106. @database_sync_to_async
  107. def logout(scope):
  108. """
  109. Remove the authenticated user's ID from the request and flush their session
  110. data.
  111. """
  112. # postpone model import to avoid ImproperlyConfigured error before Django
  113. # setup is complete.
  114. from django.contrib.auth.models import AnonymousUser
  115. if "session" not in scope:
  116. raise ValueError(
  117. "Login cannot find session in scope. You should wrap your "
  118. "consumer in SessionMiddleware."
  119. )
  120. session = scope["session"]
  121. # Dispatch the signal before the user is logged out so the receivers have a
  122. # chance to find out *who* logged out.
  123. user = scope.get("user", None)
  124. if hasattr(user, "is_authenticated") and not user.is_authenticated:
  125. user = None
  126. if user is not None:
  127. user_logged_out.send(sender=user.__class__, request=None, user=user)
  128. session.flush()
  129. if "user" in scope:
  130. scope["user"] = AnonymousUser()
  131. def _get_user_session_key(session):
  132. # This value in the session is always serialized to a string, so we need
  133. # to convert it back to Python whenever we access it.
  134. return get_user_model()._meta.pk.to_python(session[SESSION_KEY])
  135. class UserLazyObject(LazyObject):
  136. """
  137. Throw a more useful error message when scope['user'] is accessed before
  138. it's resolved
  139. """
  140. def _setup(self):
  141. raise ValueError("Accessing scope user before it is ready.")
  142. class AuthMiddleware(BaseMiddleware):
  143. """
  144. Middleware which populates scope["user"] from a Django session.
  145. Requires SessionMiddleware to function.
  146. """
  147. def populate_scope(self, scope):
  148. # Make sure we have a session
  149. if "session" not in scope:
  150. raise ValueError(
  151. "AuthMiddleware cannot find session in scope. "
  152. "SessionMiddleware must be above it."
  153. )
  154. # Add it to the scope if it's not there already
  155. if "user" not in scope:
  156. scope["user"] = UserLazyObject()
  157. async def resolve_scope(self, scope):
  158. scope["user"]._wrapped = await get_user(scope)
  159. async def __call__(self, scope, receive, send):
  160. scope = dict(scope)
  161. # Scope injection/mutation per this middleware's needs.
  162. self.populate_scope(scope)
  163. # Grab the finalized/resolved scope
  164. await self.resolve_scope(scope)
  165. return await super().__call__(scope, receive, send)
  166. # Handy shortcut for applying all three layers at once
  167. def AuthMiddlewareStack(inner):
  168. return CookieMiddleware(SessionMiddleware(AuthMiddleware(inner)))