__init__.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. import inspect
  2. import re
  3. from django.apps import apps as django_apps
  4. from django.conf import settings
  5. from django.core.exceptions import ImproperlyConfigured, PermissionDenied
  6. from django.utils.module_loading import import_string
  7. from django.utils.translation import LANGUAGE_SESSION_KEY
  8. from django.middleware.csrf import rotate_token
  9. from .signals import user_logged_in, user_logged_out, user_login_failed
  10. SESSION_KEY = '_auth_user_id'
  11. BACKEND_SESSION_KEY = '_auth_user_backend'
  12. HASH_SESSION_KEY = '_auth_user_hash'
  13. REDIRECT_FIELD_NAME = 'next'
  14. def load_backend(path):
  15. return import_string(path)()
  16. def get_backends():
  17. backends = []
  18. for backend_path in settings.AUTHENTICATION_BACKENDS:
  19. backends.append(load_backend(backend_path))
  20. if not backends:
  21. raise ImproperlyConfigured('No authentication backends have been defined. Does AUTHENTICATION_BACKENDS contain anything?')
  22. return backends
  23. def _clean_credentials(credentials):
  24. """
  25. Cleans a dictionary of credentials of potentially sensitive info before
  26. sending to less secure functions.
  27. Not comprehensive - intended for user_login_failed signal
  28. """
  29. SENSITIVE_CREDENTIALS = re.compile('api|token|key|secret|password|signature', re.I)
  30. CLEANSED_SUBSTITUTE = '********************'
  31. for key in credentials:
  32. if SENSITIVE_CREDENTIALS.search(key):
  33. credentials[key] = CLEANSED_SUBSTITUTE
  34. return credentials
  35. def authenticate(**credentials):
  36. """
  37. If the given credentials are valid, return a User object.
  38. """
  39. for backend in get_backends():
  40. try:
  41. inspect.getcallargs(backend.authenticate, **credentials)
  42. except TypeError:
  43. # This backend doesn't accept these credentials as arguments. Try the next one.
  44. continue
  45. try:
  46. user = backend.authenticate(**credentials)
  47. except PermissionDenied:
  48. # This backend says to stop in our tracks - this user should not be allowed in at all.
  49. return None
  50. if user is None:
  51. continue
  52. # Annotate the user object with the path of the backend.
  53. user.backend = "%s.%s" % (backend.__module__, backend.__class__.__name__)
  54. return user
  55. # The credentials supplied are invalid to all backends, fire signal
  56. user_login_failed.send(sender=__name__,
  57. credentials=_clean_credentials(credentials))
  58. def login(request, user):
  59. """
  60. Persist a user id and a backend in the request. This way a user doesn't
  61. have to reauthenticate on every request. Note that data set during
  62. the anonymous session is retained when the user logs in.
  63. """
  64. session_auth_hash = ''
  65. if user is None:
  66. user = request.user
  67. if hasattr(user, 'get_session_auth_hash'):
  68. session_auth_hash = user.get_session_auth_hash()
  69. if SESSION_KEY in request.session:
  70. if request.session[SESSION_KEY] != user.pk or (
  71. session_auth_hash and
  72. request.session.get(HASH_SESSION_KEY) != session_auth_hash):
  73. # To avoid reusing another user's session, create a new, empty
  74. # session if the existing session corresponds to a different
  75. # authenticated user.
  76. request.session.flush()
  77. else:
  78. request.session.cycle_key()
  79. request.session[SESSION_KEY] = user.pk
  80. request.session[BACKEND_SESSION_KEY] = user.backend
  81. request.session[HASH_SESSION_KEY] = session_auth_hash
  82. if hasattr(request, 'user'):
  83. request.user = user
  84. rotate_token(request)
  85. user_logged_in.send(sender=user.__class__, request=request, user=user)
  86. def logout(request):
  87. """
  88. Removes the authenticated user's ID from the request and flushes their
  89. session data.
  90. """
  91. # Dispatch the signal before the user is logged out so the receivers have a
  92. # chance to find out *who* logged out.
  93. user = getattr(request, 'user', None)
  94. if hasattr(user, 'is_authenticated') and not user.is_authenticated():
  95. user = None
  96. user_logged_out.send(sender=user.__class__, request=request, user=user)
  97. # remember language choice saved to session
  98. # for backwards compatibility django_language is also checked (remove in 1.8)
  99. language = request.session.get(LANGUAGE_SESSION_KEY, request.session.get('django_language'))
  100. request.session.flush()
  101. if language is not None:
  102. request.session[LANGUAGE_SESSION_KEY] = language
  103. if hasattr(request, 'user'):
  104. from django.contrib.auth.models import AnonymousUser
  105. request.user = AnonymousUser()
  106. def get_user_model():
  107. """
  108. Returns the User model that is active in this project.
  109. """
  110. try:
  111. return django_apps.get_model(settings.AUTH_USER_MODEL)
  112. except ValueError:
  113. raise ImproperlyConfigured("AUTH_USER_MODEL must be of the form 'app_label.model_name'")
  114. except LookupError:
  115. raise ImproperlyConfigured("AUTH_USER_MODEL refers to model '%s' that has not been installed" % settings.AUTH_USER_MODEL)
  116. def get_user(request):
  117. """
  118. Returns the user model instance associated with the given request session.
  119. If no user is retrieved an instance of `AnonymousUser` is returned.
  120. """
  121. from .models import AnonymousUser
  122. user = None
  123. try:
  124. user_id = request.session[SESSION_KEY]
  125. backend_path = request.session[BACKEND_SESSION_KEY]
  126. except KeyError:
  127. pass
  128. else:
  129. if backend_path in settings.AUTHENTICATION_BACKENDS:
  130. backend = load_backend(backend_path)
  131. user = backend.get_user(user_id)
  132. return user or AnonymousUser()
  133. def get_permission_codename(action, opts):
  134. """
  135. Returns the codename of the permission for the specified action.
  136. """
  137. return '%s_%s' % (action, opts.model_name)
  138. def update_session_auth_hash(request, user):
  139. """
  140. Updating a user's password logs out all sessions for the user if
  141. django.contrib.auth.middleware.SessionAuthenticationMiddleware is enabled.
  142. This function takes the current request and the updated user object from
  143. which the new session hash will be derived and updates the session hash
  144. appropriately to prevent a password change from logging out the session
  145. from which the password was changed.
  146. """
  147. if hasattr(user, 'get_session_auth_hash') and request.user == user:
  148. request.session[HASH_SESSION_KEY] = user.get_session_auth_hash()
  149. default_app_config = 'django.contrib.auth.apps.AuthConfig'