Fixed #29616 -- Fixed createsuperuser for user models that don't have a password field.

This commit is contained in:
Josh Schneier 2018-08-04 18:16:47 -04:00 committed by Tim Graham
parent 89d4d41240
commit 8b43e9b1af
4 changed files with 71 additions and 10 deletions

View File

@ -17,6 +17,9 @@ class NotRunningInTTYException(Exception):
pass pass
PASSWORD_FIELD = 'password'
class Command(BaseCommand): class Command(BaseCommand):
help = 'Used to create a superuser.' help = 'Used to create a superuser.'
requires_migrations_checks = True requires_migrations_checks = True
@ -60,11 +63,15 @@ class Command(BaseCommand):
def handle(self, *args, **options): def handle(self, *args, **options):
username = options[self.UserModel.USERNAME_FIELD] username = options[self.UserModel.USERNAME_FIELD]
database = options['database'] database = options['database']
# If not provided, create the user with an unusable password
password = None
user_data = {} user_data = {}
verbose_field_name = self.username_field.verbose_name verbose_field_name = self.username_field.verbose_name
try:
self.UserModel._meta.get_field(PASSWORD_FIELD)
except exceptions.FieldDoesNotExist:
pass
else:
# If not provided, create the user with an unusable password.
user_data[PASSWORD_FIELD] = None
try: try:
if options['interactive']: if options['interactive']:
# Same as user_data but with foreign keys as fake model # Same as user_data but with foreign keys as fake model
@ -109,18 +116,16 @@ class Command(BaseCommand):
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. # Prompt for a password if the model has one.
while password is None: while PASSWORD_FIELD in user_data and user_data[PASSWORD_FIELD] is None:
password = getpass.getpass() password = getpass.getpass()
password2 = getpass.getpass('Password (again): ') password2 = getpass.getpass('Password (again): ')
if password != password2: if password != password2:
self.stderr.write("Error: Your passwords didn't match.") self.stderr.write("Error: Your passwords didn't match.")
password = None
# Don't validate passwords that don't match. # Don't validate passwords that don't match.
continue continue
if password.strip() == '': if password.strip() == '':
self.stderr.write("Error: Blank passwords aren't allowed.") self.stderr.write("Error: Blank passwords aren't allowed.")
password = None
# Don't validate blank passwords. # Don't validate blank passwords.
continue continue
try: try:
@ -129,7 +134,8 @@ class Command(BaseCommand):
self.stderr.write('\n'.join(err.messages)) self.stderr.write('\n'.join(err.messages))
response = input('Bypass password validation and create user anyway? [y/N]: ') response = input('Bypass password validation and create user anyway? [y/N]: ')
if response.lower() != 'y': if response.lower() != 'y':
password = None continue
user_data[PASSWORD_FIELD] = password
else: else:
# Non-interactive mode. # Non-interactive mode.
if username is None: if username is None:
@ -147,7 +153,6 @@ class Command(BaseCommand):
else: else:
raise CommandError('You must use --%s with --noinput.' % field_name) raise CommandError('You must use --%s with --noinput.' % field_name)
user_data['password'] = password
self.UserModel._default_manager.db_manager(database).create_superuser(**user_data) self.UserModel._default_manager.db_manager(database).create_superuser(**user_data)
if options['verbosity'] >= 1: if options['verbosity'] >= 1:
self.stdout.write("Superuser created successfully.") self.stdout.write("Superuser created successfully.")

View File

@ -5,6 +5,7 @@ from .custom_user import (
from .invalid_models import CustomUserNonUniqueUsername from .invalid_models import CustomUserNonUniqueUsername
from .is_active import IsActiveTestUser1 from .is_active import IsActiveTestUser1
from .minimal import MinimalUser from .minimal import MinimalUser
from .no_password import NoPasswordUser
from .uuid_pk import UUIDUser from .uuid_pk import UUIDUser
from .with_foreign_key import CustomUserWithFK, Email from .with_foreign_key import CustomUserWithFK, Email
from .with_integer_username import IntegerUsernameUser from .with_integer_username import IntegerUsernameUser
@ -13,6 +14,6 @@ from .with_last_login_attr import UserWithDisabledLastLoginField
__all__ = ( __all__ = (
'CustomUser', 'CustomUserWithoutIsActiveField', 'CustomPermissionsUser', 'CustomUser', 'CustomUserWithoutIsActiveField', 'CustomPermissionsUser',
'CustomUserWithFK', 'Email', 'ExtensionUser', 'IsActiveTestUser1', 'CustomUserWithFK', 'Email', 'ExtensionUser', 'IsActiveTestUser1',
'MinimalUser', 'UUIDUser', 'CustomUserNonUniqueUsername', 'MinimalUser', 'NoPasswordUser', 'UUIDUser', 'CustomUserNonUniqueUsername',
'IntegerUsernameUser', 'UserWithDisabledLastLoginField', 'IntegerUsernameUser', 'UserWithDisabledLastLoginField',
) )

View File

@ -0,0 +1,21 @@
from django.contrib.auth.base_user import AbstractBaseUser, BaseUserManager
from django.db import models
class UserManager(BaseUserManager):
def _create_user(self, username, **extra_fields):
user = self.model(username=username, **extra_fields)
user.save(using=self._db)
return user
def create_superuser(self, username=None, **extra_fields):
return self._create_user(username, **extra_fields)
class NoPasswordUser(AbstractBaseUser):
password = None
last_login = None
username = models.CharField(max_length=50, unique=True)
USERNAME_FIELD = 'username'
objects = UserManager()

View File

@ -879,6 +879,40 @@ class CreatesuperuserManagementCommandTestCase(TestCase):
test(self) test(self)
@override_settings(AUTH_USER_MODEL='auth_tests.NoPasswordUser')
def test_usermodel_without_password(self):
new_io = StringIO()
def test(self):
call_command(
'createsuperuser',
interactive=False,
stdin=MockTTY(),
stdout=new_io,
stderr=new_io,
username='username',
)
self.assertEqual(new_io.getvalue().strip(), 'Superuser created successfully.')
test(self)
@override_settings(AUTH_USER_MODEL='auth_tests.NoPasswordUser')
def test_usermodel_without_password_interactive(self):
new_io = StringIO()
@mock_inputs({'username': 'username'})
def test(self):
call_command(
'createsuperuser',
interactive=True,
stdin=MockTTY(),
stdout=new_io,
stderr=new_io,
)
self.assertEqual(new_io.getvalue().strip(), 'Superuser created successfully.')
test(self)
class MultiDBCreatesuperuserTestCase(TestCase): class MultiDBCreatesuperuserTestCase(TestCase):
multi_db = True multi_db = True