mirror of
https://github.com/django/django.git
synced 2025-11-18 02:56:45 +00:00
Merge 33f3e67031 into 1ce6e78dd4
This commit is contained in:
commit
b0bae9a48e
5 changed files with 171 additions and 7 deletions
1
AUTHORS
1
AUTHORS
|
|
@ -1083,6 +1083,7 @@ answer newbie questions, and generally made Django that much better:
|
|||
Vinny Do <vdo.code@gmail.com>
|
||||
Vitaly Babiy <vbabiy86@gmail.com>
|
||||
Vitaliy Yelnik <velnik@gmail.com>
|
||||
Viviès Denis <legnonpi@gmail.com>
|
||||
Vladimir Kuzma <vladimirkuzma.ch@gmail.com>
|
||||
Vlado <vlado@labath.org>
|
||||
Vsevolod Solovyov
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import getpass
|
||||
import sys
|
||||
|
||||
from django.contrib.auth import get_user_model
|
||||
from django.contrib.auth.password_validation import validate_password
|
||||
|
|
@ -20,6 +21,13 @@ class Command(BaseCommand):
|
|||
raise CommandError("aborted")
|
||||
return p
|
||||
|
||||
def _get_stdin(self):
|
||||
try:
|
||||
stdin_content = sys.stdin.readline()
|
||||
return stdin_content, stdin_content
|
||||
except Exception:
|
||||
raise CommandError("aborted")
|
||||
|
||||
def add_arguments(self, parser):
|
||||
parser.add_argument(
|
||||
"username",
|
||||
|
|
@ -35,13 +43,48 @@ class Command(BaseCommand):
|
|||
choices=tuple(connections),
|
||||
help='Specifies the database to use. Default is "default".',
|
||||
)
|
||||
parser.add_argument(
|
||||
"--stdin",
|
||||
action="store_true",
|
||||
help="Read new password from stdin rather than prompting. Default is False",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--noinput",
|
||||
"--no-input",
|
||||
action="store_false",
|
||||
dest="interactive",
|
||||
help=(
|
||||
"Tells Django to NOT prompt the user for input of any kind. "
|
||||
"You must use --stdin with --noinput."
|
||||
),
|
||||
)
|
||||
|
||||
def handle(self, *args, **options):
|
||||
def _input_getter_getpass():
|
||||
p1 = self._get_pass()
|
||||
p2 = self._get_pass("Password (again): ")
|
||||
return p1, p2
|
||||
|
||||
def _input_getter_stdin():
|
||||
return self._get_stdin()
|
||||
|
||||
if options["username"]:
|
||||
username = options["username"]
|
||||
else:
|
||||
username = getpass.getuser()
|
||||
|
||||
if not options["interactive"] ^ options["stdin"]:
|
||||
raise CommandError(
|
||||
"The '--no-input' option must be used " "with the '--stdin' option."
|
||||
)
|
||||
|
||||
if options["stdin"]:
|
||||
input_getter = _input_getter_stdin
|
||||
max_tries = 1
|
||||
else:
|
||||
input_getter = _input_getter_getpass
|
||||
max_tries = 3
|
||||
|
||||
try:
|
||||
u = UserModel._default_manager.using(options["database"]).get(
|
||||
**{UserModel.USERNAME_FIELD: username}
|
||||
|
|
@ -51,13 +94,11 @@ class Command(BaseCommand):
|
|||
|
||||
self.stdout.write("Changing password for user '%s'" % u)
|
||||
|
||||
MAX_TRIES = 3
|
||||
count = 0
|
||||
p1, p2 = 1, 2 # To make them initially mismatch.
|
||||
password_validated = False
|
||||
while (p1 != p2 or not password_validated) and count < MAX_TRIES:
|
||||
p1 = self._get_pass()
|
||||
p2 = self._get_pass("Password (again): ")
|
||||
while (p1 != p2 or not password_validated) and count < max_tries:
|
||||
p1, p2 = input_getter()
|
||||
if p1 != p2:
|
||||
self.stdout.write("Passwords do not match. Please try again.")
|
||||
count += 1
|
||||
|
|
@ -71,9 +112,10 @@ class Command(BaseCommand):
|
|||
else:
|
||||
password_validated = True
|
||||
|
||||
if count == MAX_TRIES:
|
||||
if count == max_tries:
|
||||
raise CommandError(
|
||||
"Aborting password change for user '%s' after %s attempts" % (u, count)
|
||||
"Aborting password change for user '%s' after %s attempt%s"
|
||||
% (u, count, "s" if count >= 2 else "")
|
||||
)
|
||||
|
||||
u.set_password(p1)
|
||||
|
|
|
|||
|
|
@ -106,6 +106,9 @@ Minor features
|
|||
* The default iteration count for the PBKDF2 password hasher is increased from
|
||||
1,200,000 to 1,500,000.
|
||||
|
||||
* The new :option:`changepassword --stdin` option accept a new password value
|
||||
from stdin.
|
||||
|
||||
:mod:`django.contrib.contenttypes`
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
|
|
|||
|
|
@ -94,7 +94,13 @@ of changing a user's password from the command line. It prompts you to
|
|||
change the password of a given user which you must enter twice. If
|
||||
they both match, the new password will be changed immediately. If you
|
||||
do not supply a user, the command will attempt to change the password
|
||||
whose username matches the current system user.
|
||||
whose username matches the current system user. You can also use the
|
||||
:option:`--stdin` to pass the new password via a pipe:
|
||||
|
||||
.. console::
|
||||
|
||||
$ echo "new_password" | python manage.py changepassword admin --stdin
|
||||
|
||||
|
||||
You can also change a password programmatically, using
|
||||
:meth:`~django.contrib.auth.models.User.set_password`:
|
||||
|
|
|
|||
|
|
@ -261,6 +261,118 @@ class ChangepasswordManagementCommandTestCase(TestCase):
|
|||
User.objects.create_user(username="J\xfalia", password="qwerty")
|
||||
call_command("changepassword", username="J\xfalia", stdout=self.stdout)
|
||||
|
||||
@mock.patch(
|
||||
"django.contrib.auth.management.commands.changepassword.sys.stdin.readline",
|
||||
return_value="not qwerty",
|
||||
)
|
||||
def test_that_stdin_pipe_is_allowed(self, _):
|
||||
"""
|
||||
Executing the changepassword command with the --stdin option
|
||||
should change joe's password.
|
||||
"""
|
||||
joe = User.objects.get(username="joe")
|
||||
self.assertFalse(joe.check_password("not qwerty"))
|
||||
|
||||
call_command(
|
||||
"changepassword",
|
||||
username="joe",
|
||||
stdout=self.stdout,
|
||||
stdin=True,
|
||||
interactive=False,
|
||||
)
|
||||
command_output = self.stdout.getvalue().strip()
|
||||
|
||||
self.assertEqual(
|
||||
command_output,
|
||||
"Changing password for user 'joe'\n"
|
||||
"Password changed successfully for user 'joe'",
|
||||
)
|
||||
joe.refresh_from_db()
|
||||
self.assertTrue(joe.check_password("not qwerty"))
|
||||
|
||||
@mock.patch(
|
||||
"django.contrib.auth.management.commands.changepassword.sys.stdin.readline",
|
||||
return_value="1234",
|
||||
)
|
||||
def test_that_stdin_pipe_validates_only_once(self, _):
|
||||
"""
|
||||
A CommandError should be raised if the password value read with --stdin
|
||||
fail validation, with only one error message.
|
||||
"""
|
||||
joe = User.objects.get(username="joe")
|
||||
self.assertTrue(joe.check_password("qwerty"))
|
||||
|
||||
abort_msg = "Aborting password change for user 'joe' after 1 attempt"
|
||||
with self.assertRaisesMessage(CommandError, abort_msg):
|
||||
call_command(
|
||||
"changepassword",
|
||||
username="joe",
|
||||
stdout=self.stdout,
|
||||
stderr=self.stderr,
|
||||
stdin=True,
|
||||
interactive=False,
|
||||
)
|
||||
|
||||
self.assertEqual(
|
||||
self.stdout.getvalue().strip(),
|
||||
"Changing password for user 'joe'",
|
||||
)
|
||||
self.assertIn(
|
||||
"This password is entirely numeric.",
|
||||
self.stderr.getvalue(),
|
||||
)
|
||||
|
||||
joe.refresh_from_db()
|
||||
self.assertTrue(joe.check_password("qwerty"))
|
||||
|
||||
@mock.patch(
|
||||
"django.contrib.auth.management.commands.changepassword.sys.stdin.readline",
|
||||
side_effect=RuntimeError("stop"),
|
||||
)
|
||||
def test_that_stdin_pipe_can_fail_gracefully(self, _):
|
||||
"""
|
||||
Executing the changepassword command with the --stdin option
|
||||
with the call to stdin failing,
|
||||
should raise a CommandError and leave the password unchanged.
|
||||
"""
|
||||
joe = User.objects.get(username="joe")
|
||||
self.assertFalse(joe.check_password("not qwerty"))
|
||||
|
||||
msg = "aborted"
|
||||
with self.assertRaisesMessage(CommandError, msg):
|
||||
call_command(
|
||||
"changepassword",
|
||||
username="joe",
|
||||
stdout=self.stdout,
|
||||
stderr=self.stderr,
|
||||
stdin=True,
|
||||
interactive=False,
|
||||
)
|
||||
|
||||
joe.refresh_from_db()
|
||||
self.assertFalse(joe.check_password("not qwerty"))
|
||||
|
||||
def test_that_stdin_and_noinput_options_must_be_used_together(self):
|
||||
with self.assertRaisesMessage(CommandError, ""):
|
||||
call_command(
|
||||
"changepassword",
|
||||
username="joe",
|
||||
stdout=self.stdout,
|
||||
stderr=self.stderr,
|
||||
stdin=True,
|
||||
interactive=True,
|
||||
)
|
||||
|
||||
with self.assertRaisesMessage(CommandError, ""):
|
||||
call_command(
|
||||
"changepassword",
|
||||
username="joe",
|
||||
stdout=self.stdout,
|
||||
stderr=self.stderr,
|
||||
stdin=False,
|
||||
interactive=False,
|
||||
)
|
||||
|
||||
|
||||
class MultiDBChangepasswordManagementCommandTestCase(TestCase):
|
||||
databases = {"default", "other"}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue