mirror of
https://github.com/django/django.git
synced 2025-05-04 22:17:34 +00:00
Fixed #29628 -- Made createsuperuser validate password against username and required fields.
This commit is contained in:
parent
f1fbef6cd1
commit
793e9bb35a
@ -108,6 +108,11 @@ class Command(BaseCommand):
|
|||||||
if error_msg:
|
if error_msg:
|
||||||
raise CommandError(error_msg)
|
raise CommandError(error_msg)
|
||||||
|
|
||||||
|
user_data[self.UserModel.USERNAME_FIELD] = username
|
||||||
|
fake_user_data[self.UserModel.USERNAME_FIELD] = (
|
||||||
|
self.username_field.remote_field.model(username) if self.username_field.remote_field
|
||||||
|
else username
|
||||||
|
)
|
||||||
# Prompt for required fields.
|
# Prompt for required fields.
|
||||||
for field_name in self.UserModel.REQUIRED_FIELDS:
|
for field_name in self.UserModel.REQUIRED_FIELDS:
|
||||||
if not options['interactive']:
|
if not options['interactive']:
|
||||||
@ -134,28 +139,33 @@ class Command(BaseCommand):
|
|||||||
# Wrap any foreign keys in fake model instances
|
# Wrap any foreign keys in fake model instances
|
||||||
if field.remote_field:
|
if field.remote_field:
|
||||||
fake_user_data[field_name] = field.remote_field.model(input_value)
|
fake_user_data[field_name] = field.remote_field.model(input_value)
|
||||||
# Prompt for a password.
|
|
||||||
while password is None:
|
|
||||||
password = getpass.getpass()
|
|
||||||
password2 = getpass.getpass('Password (again): ')
|
|
||||||
if password != password2:
|
|
||||||
self.stderr.write("Error: Your passwords didn't match.")
|
|
||||||
password = None
|
|
||||||
# Don't validate passwords that don't match.
|
|
||||||
continue
|
|
||||||
|
|
||||||
if password.strip() == '':
|
if options['interactive']:
|
||||||
self.stderr.write("Error: Blank passwords aren't allowed.")
|
# Prompt for a password.
|
||||||
|
while password is None:
|
||||||
|
password = getpass.getpass()
|
||||||
|
password2 = getpass.getpass('Password (again): ')
|
||||||
|
if password != password2:
|
||||||
|
self.stderr.write("Error: Your passwords didn't match.")
|
||||||
|
password = None
|
||||||
|
# Don't validate passwords that don't match.
|
||||||
|
continue
|
||||||
|
if password.strip() == '':
|
||||||
|
self.stderr.write("Error: Blank passwords aren't allowed.")
|
||||||
|
password = None
|
||||||
|
# Don't validate blank passwords.
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
validate_password(password2, self.UserModel(**fake_user_data))
|
||||||
|
except exceptions.ValidationError as err:
|
||||||
|
self.stderr.write('\n'.join(err.messages))
|
||||||
|
response = input('Bypass password validation and create user anyway? [y/N]: ')
|
||||||
|
if response.lower() != 'y':
|
||||||
password = None
|
password = None
|
||||||
# Don't validate blank passwords.
|
user_data['password'] = password
|
||||||
continue
|
self.UserModel._default_manager.db_manager(database).create_superuser(**user_data)
|
||||||
try:
|
if options['verbosity'] >= 1:
|
||||||
validate_password(password2, self.UserModel(**fake_user_data))
|
self.stdout.write("Superuser created successfully.")
|
||||||
except exceptions.ValidationError as err:
|
|
||||||
self.stderr.write('\n'.join(err.messages))
|
|
||||||
response = input('Bypass password validation and create user anyway? [y/N]: ')
|
|
||||||
if response.lower() != 'y':
|
|
||||||
password = None
|
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
self.stderr.write('\nOperation cancelled.')
|
self.stderr.write('\nOperation cancelled.')
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
@ -168,13 +178,6 @@ class Command(BaseCommand):
|
|||||||
'to create one manually.'
|
'to create one manually.'
|
||||||
)
|
)
|
||||||
|
|
||||||
if username:
|
|
||||||
user_data[self.UserModel.USERNAME_FIELD] = username
|
|
||||||
user_data['password'] = password
|
|
||||||
self.UserModel._default_manager.db_manager(database).create_superuser(**user_data)
|
|
||||||
if options['verbosity'] >= 1:
|
|
||||||
self.stdout.write("Superuser created successfully.")
|
|
||||||
|
|
||||||
def get_input_data(self, field, message, default=None):
|
def get_input_data(self, field, message, default=None):
|
||||||
"""
|
"""
|
||||||
Override this method if you want to customize data inputs or
|
Override this method if you want to customize data inputs or
|
||||||
|
@ -9,7 +9,7 @@ from django.db import models
|
|||||||
# that every user provide a date of birth. This lets us test
|
# that every user provide a date of birth. This lets us test
|
||||||
# changes in username datatype, and non-text required fields.
|
# changes in username datatype, and non-text required fields.
|
||||||
class CustomUserManager(BaseUserManager):
|
class CustomUserManager(BaseUserManager):
|
||||||
def create_user(self, email, date_of_birth, password=None):
|
def create_user(self, email, date_of_birth, password=None, **fields):
|
||||||
"""
|
"""
|
||||||
Creates and saves a User with the given email and password.
|
Creates and saves a User with the given email and password.
|
||||||
"""
|
"""
|
||||||
@ -19,14 +19,15 @@ class CustomUserManager(BaseUserManager):
|
|||||||
user = self.model(
|
user = self.model(
|
||||||
email=self.normalize_email(email),
|
email=self.normalize_email(email),
|
||||||
date_of_birth=date_of_birth,
|
date_of_birth=date_of_birth,
|
||||||
|
**fields
|
||||||
)
|
)
|
||||||
|
|
||||||
user.set_password(password)
|
user.set_password(password)
|
||||||
user.save(using=self._db)
|
user.save(using=self._db)
|
||||||
return user
|
return user
|
||||||
|
|
||||||
def create_superuser(self, email, password, date_of_birth):
|
def create_superuser(self, email, password, date_of_birth, **fields):
|
||||||
u = self.create_user(email, password=password, date_of_birth=date_of_birth)
|
u = self.create_user(email, password=password, date_of_birth=date_of_birth, **fields)
|
||||||
u.is_admin = True
|
u.is_admin = True
|
||||||
u.save(using=self._db)
|
u.save(using=self._db)
|
||||||
return u
|
return u
|
||||||
@ -37,11 +38,12 @@ class CustomUser(AbstractBaseUser):
|
|||||||
is_active = models.BooleanField(default=True)
|
is_active = models.BooleanField(default=True)
|
||||||
is_admin = models.BooleanField(default=False)
|
is_admin = models.BooleanField(default=False)
|
||||||
date_of_birth = models.DateField()
|
date_of_birth = models.DateField()
|
||||||
|
first_name = models.CharField(max_length=50)
|
||||||
|
|
||||||
custom_objects = CustomUserManager()
|
custom_objects = CustomUserManager()
|
||||||
|
|
||||||
USERNAME_FIELD = 'email'
|
USERNAME_FIELD = 'email'
|
||||||
REQUIRED_FIELDS = ['date_of_birth']
|
REQUIRED_FIELDS = ['date_of_birth', 'first_name']
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return self.email
|
return self.email
|
||||||
|
@ -29,6 +29,8 @@ MOCK_INPUT_KEY_TO_PROMPTS = {
|
|||||||
# @mock_inputs dict key: [expected prompt messages],
|
# @mock_inputs dict key: [expected prompt messages],
|
||||||
'bypass': ['Bypass password validation and create user anyway? [y/N]: '],
|
'bypass': ['Bypass password validation and create user anyway? [y/N]: '],
|
||||||
'email': ['Email address: '],
|
'email': ['Email address: '],
|
||||||
|
'date_of_birth': ['Date of birth: '],
|
||||||
|
'first_name': ['First name: '],
|
||||||
'username': ['Username: ', lambda: "Username (leave blank to use '%s'): " % get_default_username()],
|
'username': ['Username: ', lambda: "Username (leave blank to use '%s'): " % get_default_username()],
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -327,6 +329,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
|
|||||||
interactive=False,
|
interactive=False,
|
||||||
email="joe@somewhere.org",
|
email="joe@somewhere.org",
|
||||||
date_of_birth="1976-04-01",
|
date_of_birth="1976-04-01",
|
||||||
|
first_name='Joe',
|
||||||
stdout=new_io,
|
stdout=new_io,
|
||||||
)
|
)
|
||||||
command_output = new_io.getvalue().strip()
|
command_output = new_io.getvalue().strip()
|
||||||
@ -552,6 +555,85 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
|
|||||||
|
|
||||||
test(self)
|
test(self)
|
||||||
|
|
||||||
|
@override_settings(AUTH_PASSWORD_VALIDATORS=[
|
||||||
|
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
|
||||||
|
])
|
||||||
|
def test_validate_password_against_username(self):
|
||||||
|
new_io = StringIO()
|
||||||
|
username = 'supremelycomplex'
|
||||||
|
|
||||||
|
def bad_then_good_password(index=[0]):
|
||||||
|
"""Return username the first two times, then a valid password."""
|
||||||
|
index[0] += 1
|
||||||
|
if index[0] <= 2:
|
||||||
|
return username
|
||||||
|
return 'superduperunguessablepassword'
|
||||||
|
|
||||||
|
@mock_inputs({
|
||||||
|
'password': bad_then_good_password,
|
||||||
|
'username': username,
|
||||||
|
'email': '',
|
||||||
|
'bypass': 'n',
|
||||||
|
})
|
||||||
|
def test(self):
|
||||||
|
call_command(
|
||||||
|
'createsuperuser',
|
||||||
|
interactive=True,
|
||||||
|
stdin=MockTTY(),
|
||||||
|
stdout=new_io,
|
||||||
|
stderr=new_io,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
new_io.getvalue().strip(),
|
||||||
|
'The password is too similar to the username.\n'
|
||||||
|
'Superuser created successfully.'
|
||||||
|
)
|
||||||
|
|
||||||
|
test(self)
|
||||||
|
|
||||||
|
@override_settings(
|
||||||
|
AUTH_USER_MODEL='auth_tests.CustomUser',
|
||||||
|
AUTH_PASSWORD_VALIDATORS=[
|
||||||
|
{'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator'},
|
||||||
|
]
|
||||||
|
)
|
||||||
|
def test_validate_password_against_required_fields(self):
|
||||||
|
new_io = StringIO()
|
||||||
|
username = 'josephine'
|
||||||
|
|
||||||
|
# Returns the username the first two times it's called, then a valid
|
||||||
|
# password.
|
||||||
|
def bad_then_good_password(index=[0]):
|
||||||
|
"""Return username the first two times, then a valid password."""
|
||||||
|
index[0] += 1
|
||||||
|
if index[0] <= 2:
|
||||||
|
return username
|
||||||
|
return 'superduperunguessablepassword'
|
||||||
|
|
||||||
|
@mock_inputs({
|
||||||
|
'password': bad_then_good_password,
|
||||||
|
'username': username,
|
||||||
|
'first_name': 'josephine',
|
||||||
|
'date_of_birth': '1970-01-01',
|
||||||
|
'email': 'joey@example.com',
|
||||||
|
'bypass': 'n',
|
||||||
|
})
|
||||||
|
def test(self):
|
||||||
|
call_command(
|
||||||
|
'createsuperuser',
|
||||||
|
interactive=True,
|
||||||
|
stdin=MockTTY(),
|
||||||
|
stdout=new_io,
|
||||||
|
stderr=new_io,
|
||||||
|
)
|
||||||
|
self.assertEqual(
|
||||||
|
new_io.getvalue().strip(),
|
||||||
|
"The password is too similar to the first name.\n"
|
||||||
|
"Superuser created successfully."
|
||||||
|
)
|
||||||
|
|
||||||
|
test(self)
|
||||||
|
|
||||||
def test_blank_username(self):
|
def test_blank_username(self):
|
||||||
"""Creation fails if --username is blank."""
|
"""Creation fails if --username is blank."""
|
||||||
new_io = StringIO()
|
new_io = StringIO()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user