Fixed #24564 -- Moved AbstractBaseUser and BaseUserManager so they can be used without auth in INSTALLED_APPS

This commit is contained in:
Dan Watson 2015-04-02 12:52:45 -04:00 committed by Tim Graham
parent 7b05d2fdae
commit fe914341c8
4 changed files with 131 additions and 107 deletions

View File

@ -0,0 +1,114 @@
"""
This module allows importing AbstractBaseUser even when django.contrib.auth is
not in INSTALLED_APPS.
"""
from __future__ import unicode_literals
from django.contrib.auth.hashers import (
check_password, is_password_usable, make_password,
)
from django.db import models
from django.utils.crypto import get_random_string, salted_hmac
from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _
class BaseUserManager(models.Manager):
@classmethod
def normalize_email(cls, email):
"""
Normalize the email address by lowercasing the domain part of the it.
"""
email = email or ''
try:
email_name, domain_part = email.strip().rsplit('@', 1)
except ValueError:
pass
else:
email = '@'.join([email_name, domain_part.lower()])
return email
def make_random_password(self, length=10,
allowed_chars='abcdefghjkmnpqrstuvwxyz'
'ABCDEFGHJKLMNPQRSTUVWXYZ'
'23456789'):
"""
Generate a random password with the given length and given
allowed_chars. The default value of allowed_chars does not have "I" or
"O" or letters and digits that look similar -- just to avoid confusion.
"""
return get_random_string(length, allowed_chars)
def get_by_natural_key(self, username):
return self.get(**{self.model.USERNAME_FIELD: username})
@python_2_unicode_compatible
class AbstractBaseUser(models.Model):
password = models.CharField(_('password'), max_length=128)
last_login = models.DateTimeField(_('last login'), blank=True, null=True)
is_active = True
REQUIRED_FIELDS = []
class Meta:
abstract = True
def get_username(self):
"Return the identifying username for this User"
return getattr(self, self.USERNAME_FIELD)
def __str__(self):
return self.get_username()
def natural_key(self):
return (self.get_username(),)
def is_anonymous(self):
"""
Always return False. This is a way of comparing User objects to
anonymous users.
"""
return False
def is_authenticated(self):
"""
Always return True. This is a way to tell if the user has been
authenticated in templates.
"""
return True
def set_password(self, raw_password):
self.password = make_password(raw_password)
def check_password(self, raw_password):
"""
Return a boolean of whether the raw_password was correct. Handles
hashing formats behind the scenes.
"""
def setter(raw_password):
self.set_password(raw_password)
self.save(update_fields=["password"])
return check_password(raw_password, self.password, setter)
def set_unusable_password(self):
# Set a value that will never be a valid hash
self.password = make_password(None)
def has_usable_password(self):
return is_password_usable(self.password)
def get_full_name(self):
raise NotImplementedError('subclasses of AbstractBaseUser must provide a get_full_name() method')
def get_short_name(self):
raise NotImplementedError('subclasses of AbstractBaseUser must provide a get_short_name() method.')
def get_session_auth_hash(self):
"""
Return an HMAC of the password field.
"""
key_salt = "django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash"
return salted_hmac(key_salt, self.password).hexdigest()

View File

@ -1,9 +1,7 @@
from __future__ import unicode_literals from __future__ import unicode_literals
from django.contrib import auth from django.contrib import auth
from django.contrib.auth.hashers import ( from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
check_password, is_password_usable, make_password,
)
from django.contrib.auth.signals import user_logged_in from django.contrib.auth.signals import user_logged_in
from django.contrib.contenttypes.models import ContentType from django.contrib.contenttypes.models import ContentType
from django.core import validators from django.core import validators
@ -12,7 +10,6 @@ from django.core.mail import send_mail
from django.db import models from django.db import models
from django.db.models.manager import EmptyManager from django.db.models.manager import EmptyManager
from django.utils import six, timezone from django.utils import six, timezone
from django.utils.crypto import get_random_string, salted_hmac
from django.utils.encoding import python_2_unicode_compatible from django.utils.encoding import python_2_unicode_compatible
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
@ -129,39 +126,6 @@ class Group(models.Model):
return (self.name,) return (self.name,)
class BaseUserManager(models.Manager):
@classmethod
def normalize_email(cls, email):
"""
Normalize the address by lowercasing the domain part of the email
address.
"""
email = email or ''
try:
email_name, domain_part = email.strip().rsplit('@', 1)
except ValueError:
pass
else:
email = '@'.join([email_name, domain_part.lower()])
return email
def make_random_password(self, length=10,
allowed_chars='abcdefghjkmnpqrstuvwxyz'
'ABCDEFGHJKLMNPQRSTUVWXYZ'
'23456789'):
"""
Generates a random password with the given length and given
allowed_chars. Note that the default value of allowed_chars does not
have "I" or "O" or letters and digits that look similar -- just to
avoid confusion.
"""
return get_random_string(length, allowed_chars)
def get_by_natural_key(self, username):
return self.get(**{self.model.USERNAME_FIELD: username})
class UserManager(BaseUserManager): class UserManager(BaseUserManager):
use_in_migrations = True use_in_migrations = True
@ -189,76 +153,6 @@ class UserManager(BaseUserManager):
**extra_fields) **extra_fields)
@python_2_unicode_compatible
class AbstractBaseUser(models.Model):
password = models.CharField(_('password'), max_length=128)
last_login = models.DateTimeField(_('last login'), blank=True, null=True)
is_active = True
REQUIRED_FIELDS = []
class Meta:
abstract = True
def get_username(self):
"Return the identifying username for this User"
return getattr(self, self.USERNAME_FIELD)
def __str__(self):
return self.get_username()
def natural_key(self):
return (self.get_username(),)
def is_anonymous(self):
"""
Always returns False. This is a way of comparing User objects to
anonymous users.
"""
return False
def is_authenticated(self):
"""
Always return True. This is a way to tell if the user has been
authenticated in templates.
"""
return True
def set_password(self, raw_password):
self.password = make_password(raw_password)
def check_password(self, raw_password):
"""
Returns a boolean of whether the raw_password was correct. Handles
hashing formats behind the scenes.
"""
def setter(raw_password):
self.set_password(raw_password)
self.save(update_fields=["password"])
return check_password(raw_password, self.password, setter)
def set_unusable_password(self):
# Sets a value that will never be a valid hash
self.password = make_password(None)
def has_usable_password(self):
return is_password_usable(self.password)
def get_full_name(self):
raise NotImplementedError('subclasses of AbstractBaseUser must provide a get_full_name() method')
def get_short_name(self):
raise NotImplementedError('subclasses of AbstractBaseUser must provide a get_short_name() method.')
def get_session_auth_hash(self):
"""
Returns an HMAC of the password field.
"""
key_salt = "django.contrib.auth.models.AbstractBaseUser.get_session_auth_hash"
return salted_hmac(key_salt, self.password).hexdigest()
# A few helper functions for common logic between User and AnonymousUser. # A few helper functions for common logic between User and AnonymousUser.
def _user_get_all_permissions(user, obj): def _user_get_all_permissions(user, obj):
permissions = set() permissions = set()

View File

@ -59,6 +59,12 @@ Minor features
* The ``BCryptSHA256PasswordHasher`` will now update passwords if its * The ``BCryptSHA256PasswordHasher`` will now update passwords if its
``rounds`` attribute is changed. ``rounds`` attribute is changed.
* ``AbstractBaseUser`` and ``BaseUserManager`` were moved to a new
``django.contrib.auth.base_user`` module so that they can be imported without
including ``django.contrib.auth`` in :setting:`INSTALLED_APPS` (this raised
a deprecation warning in older versions and is no longer supported in
Django 1.9).
:mod:`django.contrib.gis` :mod:`django.contrib.gis`
^^^^^^^^^^^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^

View File

@ -586,6 +586,16 @@ password resets. You must then provide some key implementation details:
identifies the user in an informal way. It may also return the same identifies the user in an informal way. It may also return the same
value as :meth:`django.contrib.auth.models.User.get_full_name()`. value as :meth:`django.contrib.auth.models.User.get_full_name()`.
.. admonition:: Importing ``AbstractBaseUser``
.. versionadded:: 1.9
``AbstractBaseUser`` and ``BaseUserManager`` are importable from
``django.contrib.auth.base_user`` so that they can be imported without
including ``django.contrib.auth`` in :setting:`INSTALLED_APPS` (this
raised a deprecation warning in older versions and is no longer
supported in Django 1.9).
The following methods are available on any subclass of The following methods are available on any subclass of
:class:`~django.contrib.auth.models.AbstractBaseUser`: :class:`~django.contrib.auth.models.AbstractBaseUser`: