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
+ )
3
10
from bolt .db import models
4
11
from bolt .packages import packages
12
+ from bolt .runtime import settings
5
13
from bolt .utils import timezone
14
+ from bolt .utils .crypto import get_random_string , salted_hmac
15
+ from bolt .utils .deprecation import RemovedInDjango51Warning
6
16
7
17
from .validators import UnicodeUsernameValidator
8
18
@@ -16,7 +26,7 @@ def update_last_login(sender, user, **kwargs):
16
26
user .save (update_fields = ["last_login" ])
17
27
18
28
19
- class UserManager (BaseUserManager ):
29
+ class UserManager (models . Manager ):
20
30
use_in_migrations = True
21
31
22
32
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):
36
46
user .save (using = self ._db )
37
47
return user
38
48
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 })
39
82
40
- class AbstractUser (AbstractBaseUser ):
83
+
84
+ class AbstractUser (models .Model ):
41
85
"""
42
86
An abstract base class implementing a fully featured User model with
43
87
admin-compliant permissions.
@@ -70,6 +114,13 @@ class AbstractUser(AbstractBaseUser):
70
114
)
71
115
date_joined = models .DateTimeField ("date joined" , default = timezone .now )
72
116
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
+
73
124
objects = UserManager ()
74
125
75
126
USERNAME_FIELD = "username"
@@ -81,9 +132,80 @@ class Meta:
81
132
abstract = True
82
133
83
134
def clean (self ):
84
- super (). clean ( )
135
+ setattr ( self , self . USERNAME_FIELD , self . normalize_username ( self . get_username ()) )
85
136
self .email = self .__class__ .objects .normalize_email (self .email )
86
137
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
+
87
209
88
210
class User (AbstractUser ):
89
211
"""
0 commit comments