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> Simon Litchfield <simon@quo.com.au>
Daniel Lindsley <polarcowz@gmail.com> Daniel Lindsley <polarcowz@gmail.com>
Trey Long <trey@ktrl.com> Trey Long <trey@ktrl.com>
Laurent Luce <http://www.laurentluce.com>
Martin Mahner <http://www.mahner.org/> Martin Mahner <http://www.mahner.org/>
Matt McClanahan <http://mmcc.cx/> Matt McClanahan <http://mmcc.cx/>
Stanislaus Madueke Stanislaus Madueke

View File

@ -41,6 +41,7 @@ class Command(BaseCommand):
username = options.get('username', None) username = options.get('username', None)
email = options.get('email', None) email = options.get('email', None)
interactive = options.get('interactive') interactive = options.get('interactive')
verbosity = int(options.get('verbosity', 1))
# Do quick and dirty validation if --noinput # Do quick and dirty validation if --noinput
if not interactive: if not interactive:
@ -132,4 +133,6 @@ class Command(BaseCommand):
sys.exit(1) sys.exit(1)
User.objects.create_superuser(username, email, password) 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. Creates and saves a User with the given username, e-mail and password.
""" """
now = datetime.datetime.now() now = datetime.datetime.now()
# Normalize the address by lowercasing the domain part of the email # 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, is_active=True, is_superuser=False, last_login=now,
date_joined=now) date_joined=now)
if password: user.set_password(password)
user.set_password(password)
else:
user.set_unusable_password()
user.save(using=self._db) user.save(using=self._db)
return user return user
@ -238,11 +234,14 @@ class User(models.Model):
return full_name.strip() return full_name.strip()
def set_password(self, raw_password): def set_password(self, raw_password):
import random if raw_password is None:
algo = 'sha1' self.set_unusable_password()
salt = get_hexdigest(algo, str(random.random()), str(random.random()))[:5] else:
hsh = get_hexdigest(algo, salt, raw_password) import random
self.password = '%s$%s$%s' % (algo, salt, hsh) 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): def check_password(self, raw_password):
""" """
@ -265,7 +264,11 @@ class User(models.Model):
self.password = UNUSABLE_PASSWORD self.password = UNUSABLE_PASSWORD
def has_usable_password(self): 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): 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.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.decorators import LoginRequiredTestCase
from django.contrib.auth.tests.forms import UserCreationFormTest, AuthenticationFormTest, SetPasswordFormTest, PasswordChangeFormTest, UserChangeFormTest, PasswordResetFormTest from django.contrib.auth.tests.forms import UserCreationFormTest, AuthenticationFormTest, SetPasswordFormTest, PasswordChangeFormTest, UserChangeFormTest, PasswordResetFormTest
from django.contrib.auth.tests.remote_user \ 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' # The password for the fixture data users is 'password'
__test__ = { __test__ = {
'BASIC_TESTS': BASIC_TESTS,
'TOKEN_GENERATOR_TESTS': TOKEN_GENERATOR_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 = """ class BasicTestCase(TestCase):
>>> from django.contrib.auth.models import User, AnonymousUser def test_user(self):
>>> u = User.objects.create_user('testuser', 'test@example.com', 'testpw') "Check that users can be created and can set their password"
>>> u.has_usable_password() u = User.objects.create_user('testuser', 'test@example.com', 'testpw')
True self.assertTrue(u.has_usable_password())
>>> u.check_password('bad') self.assertFalse(u.check_password('bad'))
False self.assertTrue(u.check_password('testpw'))
>>> 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
>>> u.is_authenticated() # Check we can manually set an unusable password
True u.set_unusable_password()
>>> u.is_staff u.save()
False self.assertFalse(u.check_password('testpw'))
>>> u.is_active self.assertFalse(u.has_usable_password())
True u.set_password('testpw')
>>> u.is_superuser self.assertTrue(u.check_password('testpw'))
False u.set_password(None)
self.assertFalse(u.has_usable_password())
>>> a = AnonymousUser() # Check authentication/permissions
>>> a.is_authenticated() self.assertTrue(u.is_authenticated())
False self.assertFalse(u.is_staff)
>>> a.is_staff self.assertTrue(u.is_active)
False self.assertFalse(u.is_superuser)
>>> a.is_active
False
>>> a.is_superuser
False
>>> a.groups.all()
[]
>>> a.user_permissions.all()
[]
# superuser tests. # Check API-based user creation with no password
>>> super = User.objects.create_superuser('super', 'super@example.com', 'super') u2 = User.objects.create_user('testuser2', 'test2@example.com')
>>> super.is_superuser self.assertFalse(u.has_usable_password())
True
>>> super.is_active
True
>>> super.is_staff
True
# def test_anonymous_user(self):
# Tests for createsuperuser management command. "Check the properties of the anonymous user"
# It's nearly impossible to test the interactive mode -- a command test helper a = AnonymousUser()
# would be needed (and *awesome*) -- so just test the non-interactive mode. self.assertFalse(a.is_authenticated())
# This covers most of the important validation, but not all. self.assertFalse(a.is_staff)
# self.assertFalse(a.is_active)
>>> from django.core.management import call_command 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") def test_superuser(self):
Superuser created successfully. "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") def test_createsuperuser_management_command(self):
>>> u.email "Check the operation of the createsuperuser management command"
u'joe@somewhere.org' # We can use the management command to create a superuser
>>> u.password new_io = StringIO()
u'!' call_command("createsuperuser",
>>> call_command("createsuperuser", interactive=False, username="joe+admin@somewhere.org", email="joe@somewhere.org") interactive=False,
Superuser created successfully. 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'!'
"""