auth.py 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206
  1. # This Source Code Form is subject to the terms of the Mozilla Public
  2. # License, v. 2.0. If a copy of the MPL was not distributed with this
  3. # file, You can obtain one at http://mozilla.org/MPL/2.0/.
  4. import base64
  5. import hashlib
  6. import logging
  7. from django.conf import settings
  8. try:
  9. from django.utils.encoding import smart_bytes
  10. except ImportError:
  11. from django.utils.encoding import smart_str as smart_bytes
  12. from django_browserid.base import get_audience, LocalVerifier, RemoteVerifier
  13. from django_browserid.signals import user_created
  14. from django_browserid.util import import_from_setting
  15. try:
  16. from django.contrib.auth import get_user_model
  17. except ImportError:
  18. from django.contrib.auth.models import User
  19. def get_user_model(*args, **kwargs):
  20. return User
  21. logger = logging.getLogger(__name__)
  22. def default_username_algo(email):
  23. # store the username as a base64 encoded sha1 of the email address
  24. # this protects against data leakage because usernames are often
  25. # treated as public identifiers (so we can't use the email address).
  26. return base64.urlsafe_b64encode(
  27. hashlib.sha1(smart_bytes(email)).digest()
  28. ).rstrip(b'=')
  29. class BrowserIDBackend(object):
  30. supports_anonymous_user = False
  31. supports_inactive_user = True
  32. supports_object_permissions = False
  33. def __init__(self):
  34. # Store the current user model on creation to avoid issues if settings.AUTH_USER_MODEL
  35. # changes, which usually only happens during tests.
  36. self.User = get_user_model()
  37. def get_verifier(self):
  38. """
  39. Create a verifier for verifying assertions. Uses a
  40. :class:`django_browserid.base.RemoteVerifier` by default.
  41. """
  42. return RemoteVerifier()
  43. def filter_users_by_email(self, email):
  44. """Return all users matching the specified email."""
  45. return self.User.objects.filter(email=email)
  46. def create_user(self, email):
  47. """Return object for a newly created user account."""
  48. from django.db import IntegrityError # Importing at the top causes issues on DB init.
  49. username_algo = getattr(settings, 'BROWSERID_USERNAME_ALGO', None)
  50. if username_algo is not None:
  51. username = username_algo(email)
  52. else:
  53. username = default_username_algo(email)
  54. try:
  55. return self.User.objects.create_user(username, email)
  56. except IntegrityError as err:
  57. # Race condition! Attempt to re-fetch from the database.
  58. logger.warning('IntegrityError during user creation: {0}'.format(err))
  59. try:
  60. return self.User.objects.get(email=email)
  61. except self.User.DoesNotExist:
  62. # Whatevs, let's re-raise the error.
  63. raise err
  64. def is_valid_email(self, email):
  65. """Return True if the email address is ok to log in."""
  66. # This method is basically for your overriding pleasures.
  67. return True
  68. def verify(self, assertion=None, audience=None, request=None, **kwargs):
  69. """
  70. Verify the given assertion and audience. See ``authenticate``
  71. for accepted arguments.
  72. """
  73. if audience is None and request:
  74. audience = get_audience(request)
  75. if audience is None or assertion is None:
  76. return None
  77. verifier = self.get_verifier()
  78. try:
  79. result = verifier.verify(assertion, audience, **kwargs)
  80. except Exception as e:
  81. result = None
  82. logger.warn('Error while verifying assertion %s with audience %s.', assertion,
  83. audience)
  84. logger.warn(e)
  85. if not result:
  86. return None
  87. else:
  88. return result.email
  89. def authenticate(self, assertion=None, audience=None, request=None, **kwargs):
  90. """
  91. Authenticate a user by verifying a BrowserID assertion. Defers
  92. to the verifier returned by
  93. :func:`BrowserIDBackend.get_verifier` for verification.
  94. You may either pass the ``request`` parameter to determine the
  95. audience from the request, or pass the ``audience`` parameter
  96. explicitly.
  97. :param assertion:
  98. Assertion submitted by the user. This asserts that the user
  99. controls a specific email address.
  100. :param audience:
  101. The audience to use when verifying the assertion; this
  102. prevents another site using an assertion for their site to
  103. login to yours. This value takes precedence over the
  104. audience pulled from the request parameter, if given.
  105. :param request:
  106. The request that generated this authentication attempt. This
  107. is used to determine the audience to use during
  108. verification, using the
  109. :func:`django_browserid.base.get_audience` function. If the
  110. audience parameter is also passed, it will be used instead
  111. of the audience from the request.
  112. :param kwargs:
  113. All remaining keyword arguments are passed to the ``verify``
  114. function on the verifier.
  115. """
  116. email = self.verify(assertion, audience, request, **kwargs)
  117. if not email or not self.is_valid_email(email):
  118. return None
  119. # In the rare case that two user accounts have the same email address,
  120. # log and bail. Randomly selecting one seems really wrong.
  121. users = self.filter_users_by_email(email=email)
  122. if len(users) > 1:
  123. logger.warn('%s users with email address %s.', len(users), email)
  124. return None
  125. if len(users) == 1:
  126. return users[0]
  127. create_user = getattr(settings, 'BROWSERID_CREATE_USER', True)
  128. if not create_user:
  129. logger.debug('Login failed: No user with email %s found, and '
  130. 'BROWSERID_CREATE_USER is False', email)
  131. return None
  132. else:
  133. if create_user is True:
  134. create_function = self.create_user
  135. else:
  136. # Find the function to call.
  137. create_function = import_from_setting('BROWSERID_CREATE_USER')
  138. user = create_function(email)
  139. user_created.send(create_function, user=user)
  140. return user
  141. def get_user(self, user_id):
  142. try:
  143. user = self.User.objects.get(pk=user_id)
  144. return user
  145. except self.User.DoesNotExist:
  146. return None
  147. class LocalBrowserIDBackend(BrowserIDBackend):
  148. """
  149. BrowserID authentication backend that uses local verification
  150. instead of remote verification.
  151. """
  152. def get_verifier(self):
  153. return LocalVerifier()
  154. class AutoLoginBackend(BrowserIDBackend):
  155. """
  156. Auto-auths everyone as the user with an email matching the
  157. BROWSERID_AUTOLOGIN_EMAIL setting. If BROWSERID_AUTOLOGIN_ENABLED
  158. is False (the default value), auth-auth is disabled.
  159. .. warning:: Use of this backend creates an insecure site. Do not
  160. use on a publicly-visible server!
  161. """
  162. def verify(self, *args, **kwargs):
  163. autologin_enabled = getattr(settings, 'BROWSERID_AUTOLOGIN_ENABLED', False)
  164. email = getattr(settings, 'BROWSERID_AUTOLOGIN_EMAIL', None)
  165. if autologin_enabled and email:
  166. return email
  167. else:
  168. return None