Skip to content

Commit d3399ef

Browse files
committed
Remove AbstractBaseUser
1 parent 0873764 commit d3399ef

File tree

2 files changed

+127
-148
lines changed

2 files changed

+127
-148
lines changed

bolt-auth/bolt/auth/base_user.py

-143
This file was deleted.

bolt-auth/bolt/auth/models.py

+127-5
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,18 @@
1-
from bolt.auth.base_user import AbstractBaseUser, BaseUserManager
2-
from bolt.auth.hashers import make_password
1+
import unicodedata
2+
import warnings
3+
4+
from bolt.auth import password_validation
5+
from bolt.auth.hashers import (
6+
check_password,
7+
is_password_usable,
8+
make_password,
9+
)
310
from bolt.db import models
411
from bolt.packages import packages
12+
from bolt.runtime import settings
513
from bolt.utils import timezone
14+
from bolt.utils.crypto import get_random_string, salted_hmac
15+
from bolt.utils.deprecation import RemovedInDjango51Warning
616

717
from .validators import UnicodeUsernameValidator
818

@@ -16,7 +26,7 @@ def update_last_login(sender, user, **kwargs):
1626
user.save(update_fields=["last_login"])
1727

1828

19-
class UserManager(BaseUserManager):
29+
class UserManager(models.Manager):
2030
use_in_migrations = True
2131

2232
def create_user(self, username, email=None, password=None, **extra_fields):
@@ -36,8 +46,42 @@ def create_user(self, username, email=None, password=None, **extra_fields):
3646
user.save(using=self._db)
3747
return user
3848

49+
@classmethod
50+
def normalize_email(cls, email):
51+
"""
52+
Normalize the email address by lowercasing the domain part of it.
53+
"""
54+
email = email or ""
55+
try:
56+
email_name, domain_part = email.strip().rsplit("@", 1)
57+
except ValueError:
58+
pass
59+
else:
60+
email = email_name + "@" + domain_part.lower()
61+
return email
62+
63+
def make_random_password(
64+
self,
65+
length=10,
66+
allowed_chars="abcdefghjkmnpqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ23456789",
67+
):
68+
"""
69+
Generate a random password with the given length and given
70+
allowed_chars. The default value of allowed_chars does not have "I" or
71+
"O" or letters and digits that look similar -- just to avoid confusion.
72+
"""
73+
warnings.warn(
74+
"BaseUserManager.make_random_password() is deprecated.",
75+
category=RemovedInDjango51Warning,
76+
stacklevel=2,
77+
)
78+
return get_random_string(length, allowed_chars)
79+
80+
def get_by_natural_key(self, username):
81+
return self.get(**{self.model.USERNAME_FIELD: username})
3982

40-
class AbstractUser(AbstractBaseUser):
83+
84+
class AbstractUser(models.Model):
4185
"""
4286
An abstract base class implementing a fully featured User model with
4387
admin-compliant permissions.
@@ -70,6 +114,13 @@ class AbstractUser(AbstractBaseUser):
70114
)
71115
date_joined = models.DateTimeField("date joined", default=timezone.now)
72116

117+
password = models.CharField("password", max_length=128)
118+
last_login = models.DateTimeField("last login", blank=True, null=True)
119+
120+
# Stores the raw password if set_password() is called so that it can
121+
# be passed to password_changed() after the model is saved.
122+
_password = None
123+
73124
objects = UserManager()
74125

75126
USERNAME_FIELD = "username"
@@ -81,9 +132,80 @@ class Meta:
81132
abstract = True
82133

83134
def clean(self):
84-
super().clean()
135+
setattr(self, self.USERNAME_FIELD, self.normalize_username(self.get_username()))
85136
self.email = self.__class__.objects.normalize_email(self.email)
86137

138+
def __str__(self):
139+
return self.get_username()
140+
141+
def save(self, *args, **kwargs):
142+
super().save(*args, **kwargs)
143+
if self._password is not None:
144+
password_validation.password_changed(self._password, self)
145+
self._password = None
146+
147+
def get_username(self):
148+
"""Return the username for this User."""
149+
return getattr(self, self.USERNAME_FIELD)
150+
151+
def natural_key(self):
152+
return (self.get_username(),)
153+
154+
def set_password(self, raw_password):
155+
self.password = make_password(raw_password)
156+
self._password = raw_password
157+
158+
def check_password(self, raw_password):
159+
"""
160+
Return a boolean of whether the raw_password was correct. Handles
161+
hashing formats behind the scenes.
162+
"""
163+
164+
def setter(raw_password):
165+
self.set_password(raw_password)
166+
# Password hash upgrades shouldn't be considered password changes.
167+
self._password = None
168+
self.save(update_fields=["password"])
169+
170+
return check_password(raw_password, self.password, setter)
171+
172+
def set_unusable_password(self):
173+
# Set a value that will never be a valid hash
174+
self.password = make_password(None)
175+
176+
def has_usable_password(self):
177+
"""
178+
Return False if set_unusable_password() has been called for this user.
179+
"""
180+
return is_password_usable(self.password)
181+
182+
def get_session_auth_hash(self):
183+
"""
184+
Return an HMAC of the password field.
185+
"""
186+
return self._get_session_auth_hash()
187+
188+
def get_session_auth_fallback_hash(self):
189+
for fallback_secret in settings.SECRET_KEY_FALLBACKS:
190+
yield self._get_session_auth_hash(secret=fallback_secret)
191+
192+
def _get_session_auth_hash(self, secret=None):
193+
key_salt = "bolt.auth.models.AbstractBaseUser.get_session_auth_hash"
194+
return salted_hmac(
195+
key_salt,
196+
self.password,
197+
secret=secret,
198+
algorithm="sha256",
199+
).hexdigest()
200+
201+
@classmethod
202+
def normalize_username(cls, username):
203+
return (
204+
unicodedata.normalize("NFKC", username)
205+
if isinstance(username, str)
206+
else username
207+
)
208+
87209

88210
class User(AbstractUser):
89211
"""

0 commit comments

Comments
 (0)