123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206 |
- # This Source Code Form is subject to the terms of the Mozilla Public
- # License, v. 2.0. If a copy of the MPL was not distributed with this
- # file, You can obtain one at http://mozilla.org/MPL/2.0/.
- import base64
- import hashlib
- import logging
- from django.conf import settings
- try:
- from django.utils.encoding import smart_bytes
- except ImportError:
- from django.utils.encoding import smart_str as smart_bytes
- from django_browserid.base import get_audience, LocalVerifier, RemoteVerifier
- from django_browserid.signals import user_created
- from django_browserid.util import import_from_setting
- try:
- from django.contrib.auth import get_user_model
- except ImportError:
- from django.contrib.auth.models import User
- def get_user_model(*args, **kwargs):
- return User
- logger = logging.getLogger(__name__)
- def default_username_algo(email):
- # store the username as a base64 encoded sha1 of the email address
- # this protects against data leakage because usernames are often
- # treated as public identifiers (so we can't use the email address).
- return base64.urlsafe_b64encode(
- hashlib.sha1(smart_bytes(email)).digest()
- ).rstrip(b'=')
- class BrowserIDBackend(object):
- supports_anonymous_user = False
- supports_inactive_user = True
- supports_object_permissions = False
- def __init__(self):
- # Store the current user model on creation to avoid issues if settings.AUTH_USER_MODEL
- # changes, which usually only happens during tests.
- self.User = get_user_model()
- def get_verifier(self):
- """
- Create a verifier for verifying assertions. Uses a
- :class:`django_browserid.base.RemoteVerifier` by default.
- """
- return RemoteVerifier()
- def filter_users_by_email(self, email):
- """Return all users matching the specified email."""
- return self.User.objects.filter(email=email)
- def create_user(self, email):
- """Return object for a newly created user account."""
- from django.db import IntegrityError # Importing at the top causes issues on DB init.
- username_algo = getattr(settings, 'BROWSERID_USERNAME_ALGO', None)
- if username_algo is not None:
- username = username_algo(email)
- else:
- username = default_username_algo(email)
- try:
- return self.User.objects.create_user(username, email)
- except IntegrityError as err:
- # Race condition! Attempt to re-fetch from the database.
- logger.warning('IntegrityError during user creation: {0}'.format(err))
- try:
- return self.User.objects.get(email=email)
- except self.User.DoesNotExist:
- # Whatevs, let's re-raise the error.
- raise err
- def is_valid_email(self, email):
- """Return True if the email address is ok to log in."""
- # This method is basically for your overriding pleasures.
- return True
- def verify(self, assertion=None, audience=None, request=None, **kwargs):
- """
- Verify the given assertion and audience. See ``authenticate``
- for accepted arguments.
- """
- if audience is None and request:
- audience = get_audience(request)
- if audience is None or assertion is None:
- return None
- verifier = self.get_verifier()
- try:
- result = verifier.verify(assertion, audience, **kwargs)
- except Exception as e:
- result = None
- logger.warn('Error while verifying assertion %s with audience %s.', assertion,
- audience)
- logger.warn(e)
- if not result:
- return None
- else:
- return result.email
- def authenticate(self, assertion=None, audience=None, request=None, **kwargs):
- """
- Authenticate a user by verifying a BrowserID assertion. Defers
- to the verifier returned by
- :func:`BrowserIDBackend.get_verifier` for verification.
- You may either pass the ``request`` parameter to determine the
- audience from the request, or pass the ``audience`` parameter
- explicitly.
- :param assertion:
- Assertion submitted by the user. This asserts that the user
- controls a specific email address.
- :param audience:
- The audience to use when verifying the assertion; this
- prevents another site using an assertion for their site to
- login to yours. This value takes precedence over the
- audience pulled from the request parameter, if given.
- :param request:
- The request that generated this authentication attempt. This
- is used to determine the audience to use during
- verification, using the
- :func:`django_browserid.base.get_audience` function. If the
- audience parameter is also passed, it will be used instead
- of the audience from the request.
- :param kwargs:
- All remaining keyword arguments are passed to the ``verify``
- function on the verifier.
- """
- email = self.verify(assertion, audience, request, **kwargs)
- if not email or not self.is_valid_email(email):
- return None
- # In the rare case that two user accounts have the same email address,
- # log and bail. Randomly selecting one seems really wrong.
- users = self.filter_users_by_email(email=email)
- if len(users) > 1:
- logger.warn('%s users with email address %s.', len(users), email)
- return None
- if len(users) == 1:
- return users[0]
- create_user = getattr(settings, 'BROWSERID_CREATE_USER', True)
- if not create_user:
- logger.debug('Login failed: No user with email %s found, and '
- 'BROWSERID_CREATE_USER is False', email)
- return None
- else:
- if create_user is True:
- create_function = self.create_user
- else:
- # Find the function to call.
- create_function = import_from_setting('BROWSERID_CREATE_USER')
- user = create_function(email)
- user_created.send(create_function, user=user)
- return user
- def get_user(self, user_id):
- try:
- user = self.User.objects.get(pk=user_id)
- return user
- except self.User.DoesNotExist:
- return None
- class LocalBrowserIDBackend(BrowserIDBackend):
- """
- BrowserID authentication backend that uses local verification
- instead of remote verification.
- """
- def get_verifier(self):
- return LocalVerifier()
- class AutoLoginBackend(BrowserIDBackend):
- """
- Auto-auths everyone as the user with an email matching the
- BROWSERID_AUTOLOGIN_EMAIL setting. If BROWSERID_AUTOLOGIN_ENABLED
- is False (the default value), auth-auth is disabled.
- .. warning:: Use of this backend creates an insecure site. Do not
- use on a publicly-visible server!
- """
- def verify(self, *args, **kwargs):
- autologin_enabled = getattr(settings, 'BROWSERID_AUTOLOGIN_ENABLED', False)
- email = getattr(settings, 'BROWSERID_AUTOLOGIN_EMAIL', None)
- if autologin_enabled and email:
- return email
- else:
- return None
|