diff --git a/.travis.yml b/.travis.yml index 05d3f83..6f3dc0f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,11 @@ language: python python: - 2.6 - 2.7 -# command to install dependencies, e.g. pip install -r requirements.txt --use-mirrors -install: pip install -r requirements.txt -# command to run tests, e.g. python setup.py test +env: + - DJANGO_VERSION=1.3.4 + - DJANGO_VERSION=1.4.2 + - DJANGO_VERSION=1.5a1 +install: + - pip install -e git+git://github.com/django/django.git@${DJANGO_VERSION}#egg=django + - pip install -r requirements.txt --use-mirrors script: fab test diff --git a/django_browserid/auth.py b/django_browserid/auth.py index b9a6afb..583dda5 100644 --- a/django_browserid/auth.py +++ b/django_browserid/auth.py @@ -17,10 +17,12 @@ try: from django.contrib.auth import get_user_model - User = get_user_model() except ImportError: from django.contrib.auth.models import User + def get_user_model(*args, **kwargs): + return User + log = logging.getLogger(__name__) @@ -45,6 +47,14 @@ class BrowserIDBackend(object): 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 verify(self, *args): warn('Deprecated, please use the standalone function ' 'django_browserid.verify instead.', DeprecationWarning) @@ -52,7 +62,7 @@ def verify(self, *args): def filter_users_by_email(self, email): """Return all users matching the specified email.""" - return User.objects.filter(email=email) + return self.User.objects.filter(email=email) def create_user(self, email): """Return object for a newly created user account.""" @@ -62,7 +72,7 @@ def create_user(self, email): else: username = default_username_algo(email) - return User.objects.create_user(username, email) + return self.User.objects.create_user(username, email) def authenticate(self, assertion=None, audience=None): """``django.contrib.auth`` compatible authentication method. @@ -86,7 +96,8 @@ def authenticate(self, assertion=None, audience=None): # log and bail. randomly selecting one seems really wrong. users = self.filter_users_by_email(email=email) if len(users) > 1: - log.warn('{0} users with email address {1}.'.format(len(users), email)) + log.warn('{0} users with email address {1}.'.format(len(users), + email)) return None if len(users) == 1: return users[0] @@ -107,8 +118,8 @@ def authenticate(self, assertion=None, audience=None): def get_user(self, user_id): try: - return User.objects.get(pk=user_id) - except User.DoesNotExist: + return self.User.objects.get(pk=user_id) + except self.User.DoesNotExist: return None def _load_module(self, path): diff --git a/django_browserid/tests/models.py b/django_browserid/tests/models.py new file mode 100644 index 0000000..70ecbbb --- /dev/null +++ b/django_browserid/tests/models.py @@ -0,0 +1,23 @@ +""" +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/. +""" +from django.db import models + +try: + from django.contrib.auth.models import AbstractBaseUser +except ImportError: + AbstractBaseUser = object + + +class CustomUser(AbstractBaseUser): + USERNAME_FIELD = 'email' + + email = models.EmailField(unique=True, db_index=True) + + def get_full_name(self): + return self.email + + def get_short_name(self): + return self.email diff --git a/django_browserid/tests/settings.py b/django_browserid/tests/settings.py index 2b30b24..a4cb642 100644 --- a/django_browserid/tests/settings.py +++ b/django_browserid/tests/settings.py @@ -5,6 +5,8 @@ """ TEST_RUNNER = 'django_nose.runner.NoseTestSuiteRunner' +SECRET_KEY = 'asdf' + DATABASES = { 'default': { 'NAME': 'test.db', @@ -15,6 +17,7 @@ INSTALLED_APPS = ( 'django_nose', 'django_browserid', + 'django_browserid.tests', 'django.contrib.auth', 'django.contrib.contenttypes', diff --git a/django_browserid/tests/test_auth.py b/django_browserid/tests/test_auth.py index 803f787..463e22a 100644 --- a/django_browserid/tests/test_auth.py +++ b/django_browserid/tests/test_auth.py @@ -12,6 +12,12 @@ from django_browserid.auth import BrowserIDBackend, default_username_algo from django_browserid.tests import mock_browserid +try: + from django.contrib.auth import get_user_model + from django_browserid.tests.models import CustomUser +except ImportError: + get_user_model = False + def new_user(email, username=None): """Creates a user with the specified email for testing.""" @@ -87,3 +93,36 @@ def test_user_created_signal(self, user_created): # created. user = self.auth('a@b.com') assert user_created.call.called_with(user=user) + + +# Only run custom user model tests if we're using a version of Django that +# supports it. +if get_user_model: + @patch.object(settings, 'AUTH_USER_MODEL', 'tests.CustomUser') + class CustomUserModelTests(TestCase): + def _auth(self, backend=None, verified_email=None): + if backend is None: + backend = BrowserIDBackend() + + with mock_browserid(verified_email): + return backend.authenticate(assertion='asdf', audience='asdf') + + def test_existing_user(self): + """If a custom user exists with the given email, return them.""" + user = CustomUser.objects.create(email='a@test.com') + authed_user = self._auth(verified_email='a@test.com') + assert user == authed_user + + @patch.object(settings, 'BROWSERID_CREATE_USER', True) + def test_create_new_user(self): + """ + If a custom user does not exist with the given email, create a new + user and return them. + """ + class CustomUserBrowserIDBackend(BrowserIDBackend): + def create_user(self, email): + return CustomUser.objects.create(email=email) + user = self._auth(backend=CustomUserBrowserIDBackend(), + verified_email='b@test.com') + assert isinstance(user, CustomUser) + assert user.email == 'b@test.com' diff --git a/docs/details/advanced.rst b/docs/details/advanced.rst index 5517e90..1ec27d0 100644 --- a/docs/details/advanced.rst +++ b/docs/details/advanced.rst @@ -128,3 +128,13 @@ Signals * **sender**: The function that created the user instance. * **user**: The user instance that was created. + + +Custom User Model +----------------- + +Django 1.5 allows you to specify a custom model to use in place of the built-in +User model with the ``AUTH_USER_MODEL`` setting. ``django-browserid`` supports +custom User models, however you will most likely need to subclass +``django-browserid.BrowserIDBackend`` and override the ``create_user``, +``get_user``, and ``filter_users_by_email`` functions to work with your class.