summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJosh Schneier <josh.schneier@gmail.com>2018-08-02 13:29:48 -0400
committerTim Graham <timograham@gmail.com>2018-08-04 08:44:25 -0400
commit793e9bb35af34aa11320866bcc6ad8edaf1480a7 (patch)
tree7491d0df04353c14a5cb1148bef8ea8a802e38b1
parentf1fbef6cd171ddfae41fcc901f1f60ccad039f51 (diff)
Fixed #29628 -- Made createsuperuser validate password against username and required fields.
-rw-r--r--django/contrib/auth/management/commands/createsuperuser.py57
-rw-r--r--tests/auth_tests/models/custom_user.py10
-rw-r--r--tests/auth_tests/test_management.py82
3 files changed, 118 insertions, 31 deletions
diff --git a/django/contrib/auth/management/commands/createsuperuser.py b/django/contrib/auth/management/commands/createsuperuser.py
index fb3cda7662..541711606c 100644
--- a/django/contrib/auth/management/commands/createsuperuser.py
+++ b/django/contrib/auth/management/commands/createsuperuser.py
@@ -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
diff --git a/tests/auth_tests/models/custom_user.py b/tests/auth_tests/models/custom_user.py
index ad8b4c1212..a46f1d5a9c 100644
--- a/tests/auth_tests/models/custom_user.py
+++ b/tests/auth_tests/models/custom_user.py
@@ -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
diff --git a/tests/auth_tests/test_management.py b/tests/auth_tests/test_management.py
index da0ec73626..0c9ad81f3f 100644
--- a/tests/auth_tests/test_management.py
+++ b/tests/auth_tests/test_management.py
@@ -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()