Skip to content

Commit

Permalink
Fixed #14354 -- Normalized the handling of empty/null passwords in co…
Browse files Browse the repository at this point in the history
…ntrib.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
  • Loading branch information
freakboy3742 committed Oct 9, 2010
1 parent 71a4c47 commit 8755fb1
Show file tree
Hide file tree
Showing 5 changed files with 109 additions and 88 deletions.
1 change: 1 addition & 0 deletions AUTHORS
Expand Up @@ -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
Expand Down
15 changes: 9 additions & 6 deletions django/contrib/auth/management/commands/createsuperuser.py
Expand Up @@ -41,7 +41,8 @@ def handle(self, *args, **options):
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:
if not username or not email: if not username or not email:
Expand Down Expand Up @@ -79,7 +80,7 @@ def handle(self, *args, **options):
# try/except to trap for a keyboard interrupt and exit gracefully. # try/except to trap for a keyboard interrupt and exit gracefully.
if interactive: if interactive:
try: try:

# Get a username # Get a username
while 1: while 1:
if not username: if not username:
Expand All @@ -100,7 +101,7 @@ def handle(self, *args, **options):
else: else:
sys.stderr.write("Error: That username is already taken.\n") sys.stderr.write("Error: That username is already taken.\n")
username = None username = None

# Get an email # Get an email
while 1: while 1:
if not email: if not email:
Expand All @@ -112,7 +113,7 @@ def handle(self, *args, **options):
email = None email = None
else: else:
break break

# Get a password # Get a password
while 1: while 1:
if not password: if not password:
Expand All @@ -130,6 +131,8 @@ def handle(self, *args, **options):
except KeyboardInterrupt: except KeyboardInterrupt:
sys.stderr.write("\nOperation cancelled.\n") sys.stderr.write("\nOperation cancelled.\n")
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")

25 changes: 14 additions & 11 deletions django/contrib/auth/models.py
Expand Up @@ -106,7 +106,6 @@ def create_user(self, username, email, password=None):
""" """
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
Expand All @@ -122,10 +121,7 @@ def create_user(self, username, email, password=None):
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


Expand Down Expand Up @@ -238,11 +234,14 @@ def get_full_name(self):
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):
""" """
Expand All @@ -265,7 +264,11 @@ def set_unusable_password(self):
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):
""" """
Expand Down
3 changes: 1 addition & 2 deletions django/contrib/auth/tests/__init__.py
@@ -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 \
Expand All @@ -12,6 +12,5 @@
# 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,
} }
153 changes: 84 additions & 69 deletions django/contrib/auth/tests/basic.py
@@ -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'!'
"""

0 comments on commit 8755fb1

Please sign in to comment.