Fixed #14354 -- Normalized the handling of empty/null passwords in contrib.auth. This also updates the createsuperuser command to be more testable, and migrates some auth doctests. Thanks to berryp for the report, and Laurent Luce for the patch.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@14053 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
Russell Keith-Magee 2010-10-09 03:34:08 +00:00
parent 71a4c472ab
commit 8755fb1549
5 changed files with 109 additions and 88 deletions

View File

@ -310,6 +310,7 @@ answer newbie questions, and generally made Django that much better:
Simon Litchfield <simon@quo.com.au>
Daniel Lindsley <polarcowz@gmail.com>
Trey Long <trey@ktrl.com>
Laurent Luce <http://www.laurentluce.com>
Martin Mahner <http://www.mahner.org/>
Matt McClanahan <http://mmcc.cx/>
Stanislaus Madueke

View File

@ -41,7 +41,8 @@ class Command(BaseCommand):
username = options.get('username', None)
email = options.get('email', None)
interactive = options.get('interactive')
verbosity = int(options.get('verbosity', 1))
# Do quick and dirty validation if --noinput
if not interactive:
if not username or not email:
@ -79,7 +80,7 @@ class Command(BaseCommand):
# try/except to trap for a keyboard interrupt and exit gracefully.
if interactive:
try:
# Get a username
while 1:
if not username:
@ -100,7 +101,7 @@ class Command(BaseCommand):
else:
sys.stderr.write("Error: That username is already taken.\n")
username = None
# Get an email
while 1:
if not email:
@ -112,7 +113,7 @@ class Command(BaseCommand):
email = None
else:
break
# Get a password
while 1:
if not password:
@ -130,6 +131,8 @@ class Command(BaseCommand):
except KeyboardInterrupt:
sys.stderr.write("\nOperation cancelled.\n")
sys.exit(1)
User.objects.create_superuser(username, email, password)
print "Superuser created successfully."
if verbosity >= 1:
self.stdout.write("Superuser created successfully.\n")

View File

@ -106,7 +106,6 @@ class UserManager(models.Manager):
"""
Creates and saves a User with the given username, e-mail and password.
"""
now = datetime.datetime.now()
# Normalize the address by lowercasing the domain part of the email
@ -122,10 +121,7 @@ class UserManager(models.Manager):
is_active=True, is_superuser=False, last_login=now,
date_joined=now)
if password:
user.set_password(password)
else:
user.set_unusable_password()
user.set_password(password)
user.save(using=self._db)
return user
@ -238,11 +234,14 @@ class User(models.Model):
return full_name.strip()
def set_password(self, raw_password):
import random
algo = 'sha1'
salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5]
hsh = get_hexdigest(algo, salt, raw_password)
self.password = '%s$%s$%s' % (algo, salt, hsh)
if raw_password is None:
self.set_unusable_password()
else:
import random
algo = 'sha1'
salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5]
hsh = get_hexdigest(algo, salt, raw_password)
self.password = '%s$%s$%s' % (algo, salt, hsh)
def check_password(self, raw_password):
"""
@ -265,7 +264,11 @@ class User(models.Model):
self.password = UNUSABLE_PASSWORD
def has_usable_password(self):
return self.password != UNUSABLE_PASSWORD
if self.password is None \
or self.password == UNUSABLE_PASSWORD:
return False
else:
return True
def get_group_permissions(self, obj=None):
"""

View File

@ -1,5 +1,5 @@
from django.contrib.auth.tests.auth_backends import BackendTest, RowlevelBackendTest, AnonymousUserBackendTest, NoAnonymousUserBackendTest
from django.contrib.auth.tests.basic import BASIC_TESTS
from django.contrib.auth.tests.basic import BasicTestCase
from django.contrib.auth.tests.decorators import LoginRequiredTestCase
from django.contrib.auth.tests.forms import UserCreationFormTest, AuthenticationFormTest, SetPasswordFormTest, PasswordChangeFormTest, UserChangeFormTest, PasswordResetFormTest
from django.contrib.auth.tests.remote_user \
@ -12,6 +12,5 @@ from django.contrib.auth.tests.views \
# The password for the fixture data users is 'password'
__test__ = {
'BASIC_TESTS': BASIC_TESTS,
'TOKEN_GENERATOR_TESTS': TOKEN_GENERATOR_TESTS,
}

View File

@ -1,77 +1,92 @@
from django.test import TestCase
from django.contrib.auth.models import User, AnonymousUser
from django.core.management import call_command
from StringIO import StringIO
BASIC_TESTS = """
>>> from django.contrib.auth.models import User, AnonymousUser
>>> u = User.objects.create_user('testuser', 'test@example.com', 'testpw')
>>> u.has_usable_password()
True
>>> u.check_password('bad')
False
>>> u.check_password('testpw')
True
>>> u.set_unusable_password()
>>> u.save()
>>> u.check_password('testpw')
False
>>> u.has_usable_password()
False
>>> u2 = User.objects.create_user('testuser2', 'test2@example.com')
>>> u2.has_usable_password()
False
class BasicTestCase(TestCase):
def test_user(self):
"Check that users can be created and can set their password"
u = User.objects.create_user('testuser', 'test@example.com', 'testpw')
self.assertTrue(u.has_usable_password())
self.assertFalse(u.check_password('bad'))
self.assertTrue(u.check_password('testpw'))
>>> u.is_authenticated()
True
>>> u.is_staff
False
>>> u.is_active
True
>>> u.is_superuser
False
# Check we can manually set an unusable password
u.set_unusable_password()
u.save()
self.assertFalse(u.check_password('testpw'))
self.assertFalse(u.has_usable_password())
u.set_password('testpw')
self.assertTrue(u.check_password('testpw'))
u.set_password(None)
self.assertFalse(u.has_usable_password())
>>> a = AnonymousUser()
>>> a.is_authenticated()
False
>>> a.is_staff
False
>>> a.is_active
False
>>> a.is_superuser
False
>>> a.groups.all()
[]
>>> a.user_permissions.all()
[]
# Check authentication/permissions
self.assertTrue(u.is_authenticated())
self.assertFalse(u.is_staff)
self.assertTrue(u.is_active)
self.assertFalse(u.is_superuser)
# superuser tests.
>>> super = User.objects.create_superuser('super', 'super@example.com', 'super')
>>> super.is_superuser
True
>>> super.is_active
True
>>> super.is_staff
True
# Check API-based user creation with no password
u2 = User.objects.create_user('testuser2', 'test2@example.com')
self.assertFalse(u.has_usable_password())
#
# Tests for createsuperuser management command.
# It's nearly impossible to test the interactive mode -- a command test helper
# would be needed (and *awesome*) -- so just test the non-interactive mode.
# This covers most of the important validation, but not all.
#
>>> from django.core.management import call_command
def test_anonymous_user(self):
"Check the properties of the anonymous user"
a = AnonymousUser()
self.assertFalse(a.is_authenticated())
self.assertFalse(a.is_staff)
self.assertFalse(a.is_active)
self.assertFalse(a.is_superuser)
self.assertEqual(a.groups.all().count(), 0)
self.assertEqual(a.user_permissions.all().count(), 0)
>>> call_command("createsuperuser", interactive=False, username="joe", email="joe@somewhere.org")
Superuser created successfully.
def test_superuser(self):
"Check the creation and properties of a superuser"
super = User.objects.create_superuser('super', 'super@example.com', 'super')
self.assertTrue(super.is_superuser)
self.assertTrue(super.is_active)
self.assertTrue(super.is_staff)
>>> u = User.objects.get(username="joe")
>>> u.email
u'joe@somewhere.org'
>>> u.password
u'!'
>>> call_command("createsuperuser", interactive=False, username="joe+admin@somewhere.org", email="joe@somewhere.org")
Superuser created successfully.
def test_createsuperuser_management_command(self):
"Check the operation of the createsuperuser management command"
# We can use the management command to create a superuser
new_io = StringIO()
call_command("createsuperuser",
interactive=False,
username="joe",
email="joe@somewhere.org",
stdout=new_io
)
command_output = new_io.getvalue().strip()
self.assertEqual(command_output, 'Superuser created successfully.')
u = User.objects.get(username="joe")
self.assertEquals(u.email, 'joe@somewhere.org')
self.assertTrue(u.check_password(''))
# We can supress output on the management command
new_io = StringIO()
call_command("createsuperuser",
interactive=False,
username="joe2",
email="joe2@somewhere.org",
verbosity=0,
stdout=new_io
)
command_output = new_io.getvalue().strip()
self.assertEqual(command_output, '')
u = User.objects.get(username="joe2")
self.assertEquals(u.email, 'joe2@somewhere.org')
self.assertTrue(u.check_password(''))
new_io = StringIO()
call_command("createsuperuser",
interactive=False,
username="joe+admin@somewhere.org",
email="joe@somewhere.org",
stdout=new_io
)
u = User.objects.get(username="joe+admin@somewhere.org")
self.assertEquals(u.email, 'joe@somewhere.org')
self.assertTrue(u.check_password(''))
>>> u = User.objects.get(username="joe+admin@somewhere.org")
>>> u.email
u'joe@somewhere.org'
>>> u.password
u'!'
"""