Skip to content

Commit

Permalink
Add password prompt for the create-user command #458
Browse files Browse the repository at this point in the history
Signed-off-by: Thomas Druez <tdruez@nexb.com>
  • Loading branch information
tdruez committed Jun 24, 2022
1 parent c3b6e84 commit 0b1b70f
Show file tree
Hide file tree
Showing 3 changed files with 50 additions and 6 deletions.
2 changes: 1 addition & 1 deletion scancodeio/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@
{
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
"OPTIONS": {
"min_length": 14,
"min_length": env.int("SCANCODEIO_PASSWORD_MIN_LENGTH", default=12),
},
},
{
Expand Down
45 changes: 44 additions & 1 deletion scanpipe/management/commands/create-user.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,10 @@
# ScanCode.io is a free software code scanning tool from nexB Inc. and others.
# Visit https://github.com/nexB/scancode.io for support and download.

import getpass

from django.contrib.auth import get_user_model
from django.contrib.auth.password_validation import validate_password
from django.core import exceptions
from django.core.management.base import BaseCommand
from django.core.management.base import CommandError
Expand All @@ -41,6 +44,12 @@ def __init__(self, *args, **kwargs):

def add_arguments(self, parser):
parser.add_argument("username", help="Specifies the username for the user.")
parser.add_argument(
"--no-input",
action="store_false",
dest="interactive",
help="Do not prompt the user for input of any kind.",
)

def handle(self, *args, **options):
username = options["username"]
Expand All @@ -49,13 +58,47 @@ def handle(self, *args, **options):
if error_msg:
raise CommandError(error_msg)

user = self.UserModel._default_manager.create_user(username=username)
password = None
if options["interactive"]:
password = self.get_password_from_stdin(username)

user = self.UserModel._default_manager.create_user(username, password=password)
token, _ = Token._default_manager.get_or_create(user=user)

if options["verbosity"] >= 1:
msg = f"User {username} created with API key: {token.key}"
self.stdout.write(msg, self.style.SUCCESS)

def get_password_from_stdin(self, username):
# Validators, such as UserAttributeSimilarityValidator, depends on other user's
# fields data for password validation.
fake_user_data = {
self.UserModel.USERNAME_FIELD: username,
}

password = None
while password is None:
password1 = getpass.getpass()
password2 = getpass.getpass("Password (again): ")
if password1 != password2:
self.stderr.write("Error: Your passwords didn't match.")
continue
if password1.strip() == "":
self.stderr.write("Error: Blank passwords aren't allowed.")
continue
try:
validate_password(password2, self.UserModel(**fake_user_data))
except exceptions.ValidationError as err:
self.stderr.write("\n".join(err.messages))
response = input(
"Bypass password validation and create user anyway? [y/N]: "
)
if response.lower() != "y":
continue
password = password1

return password

def _validate_username(self, username):
"""
Validate username. If invalid, return a string error message.
Expand Down
9 changes: 5 additions & 4 deletions scanpipe/tests/test_commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -511,22 +511,23 @@ def test_scanpipe_management_command_create_user(self):

expected = "Error: the following arguments are required: username"
with self.assertRaisesMessage(CommandError, expected):
call_command("create-user")
call_command("create-user", "--no-input")

username = "my_username"
call_command("create-user", username, stdout=out)
call_command("create-user", "--no-input", username, stdout=out)
self.assertIn(f"User {username} created with API key:", out.getvalue())
user = get_user_model().objects.get(username=username)
self.assertTrue(user.auth_token)

expected = "Error: That username is already taken."
with self.assertRaisesMessage(CommandError, expected):
call_command("create-user", username)
call_command("create-user", "--no-input", username)

username = "^&*"
expected = (
"Enter a valid username. This value may contain only letters, numbers, "
"and @/./+/-/_ characters."
)
with self.assertRaisesMessage(CommandError, expected):
call_command("create-user", username)
call_command("create-user", "--no-input", username)

0 comments on commit 0b1b70f

Please # to comment.