Fixed #29628 -- Made createsuperuser validate password against username and required fields.

This commit is contained in:
Josh Schneier 2018-08-02 13:29:48 -04:00 committed by Tim Graham
parent f1fbef6cd1
commit 793e9bb35a
3 changed files with 118 additions and 31 deletions

View File

@ -108,6 +108,11 @@ class Command(BaseCommand):
if 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.
for field_name in self.UserModel.REQUIRED_FIELDS:
if not options['interactive']:
@ -134,28 +139,33 @@ class Command(BaseCommand):
# Wrap any foreign keys in fake model instances
if field.remote_field:
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() == '':
self.stderr.write("Error: Blank passwords aren't allowed.")
if options['interactive']:
# 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
# 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
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.")
except KeyboardInterrupt:
self.stderr.write('\nOperation cancelled.')
sys.exit(1)
@ -168,13 +178,6 @@ class Command(BaseCommand):
'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):
"""
Override this method if you want to customize data inputs or

View File

@ -9,7 +9,7 @@ from django.db import models
# that every user provide a date of birth. This lets us test
# changes in username datatype, and non-text required fields.
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.
"""
@ -19,14 +19,15 @@ class CustomUserManager(BaseUserManager):
user = self.model(
email=self.normalize_email(email),
date_of_birth=date_of_birth,
**fields
)
user.set_password(password)
user.save(using=self._db)
return user
def create_superuser(self, email, password, date_of_birth):
u = self.create_user(email, password=password, date_of_birth=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, **fields)
u.is_admin = True
u.save(using=self._db)
return u
@ -37,11 +38,12 @@ class CustomUser(AbstractBaseUser):
is_active = models.BooleanField(default=True)
is_admin = models.BooleanField(default=False)
date_of_birth = models.DateField()
first_name = models.CharField(max_length=50)
custom_objects = CustomUserManager()
USERNAME_FIELD = 'email'
REQUIRED_FIELDS = ['date_of_birth']
REQUIRED_FIELDS = ['date_of_birth', 'first_name']
def __str__(self):
return self.email

View File

@ -29,6 +29,8 @@ MOCK_INPUT_KEY_TO_PROMPTS = {
# @mock_inputs dict key: [expected prompt messages],
'bypass': ['Bypass password validation and create user anyway? [y/N]: '],
'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()],
}
@ -327,6 +329,7 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
interactive=False,
email="joe@somewhere.org",
date_of_birth="1976-04-01",
first_name='Joe',
stdout=new_io,
)
command_output = new_io.getvalue().strip()
@ -552,6 +555,85 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
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):
"""Creation fails if --username is blank."""
new_io = StringIO()