From bb893d0bd5dbcbbd41cc3a0b1bbc8331bff9efa2 Mon Sep 17 00:00:00 2001 From: Graham Christensen Date: Fri, 21 Jan 2022 10:37:20 -0500 Subject: [PATCH] hydra-create-user: support prompting for passwords I'm not sure this is a good implementation as-is. It does work, but the password gets echo'd to the screen. I tried to use IO::Prompt but IO::Prompt really seems to want to read the password from ARGV. --- src/script/hydra-create-user | 36 ++++++++++++++++++++++++++++++++--- t/scripts/hydra-create-user.t | 24 ++++++++++++++++++++++- 2 files changed, 56 insertions(+), 4 deletions(-) diff --git a/src/script/hydra-create-user b/src/script/hydra-create-user index ec14de95..839f742b 100755 --- a/src/script/hydra-create-user +++ b/src/script/hydra-create-user @@ -14,6 +14,7 @@ Usage: hydra-create-user NAME [--type hydra|google|github] [--full-name FULLNAME] [--email-address EMAIL-ADDRESS] + [--password-prompt] [--password-hash HASH] [--password PASSWORD (dangerous)] [--wipe-roles] @@ -27,6 +28,14 @@ renamed. * Specifying Passwords +** Interactively + +Pass `--password-prompt` to collect the password on stdin. + +Example: + + $ hydra-create-user alice --password-prompt --role admin + ** Specifying a Hash You can generate a password hash and provide the hash as well. This @@ -63,7 +72,7 @@ Example: exit 0; } -my ($renameFrom, $type, $fullName, $emailAddress, $password, $passwordHash); +my ($renameFrom, $type, $fullName, $emailAddress, $password, $passwordHash, $passwordPrompt); my $wipeRoles = 0; my @roles; @@ -72,6 +81,7 @@ GetOptions("rename-from=s" => \$renameFrom, "full-name=s" => \$fullName, "email-address=s" => \$emailAddress, "password=s" => \$password, + "password-prompt" => \$passwordPrompt, "password-hash=s" => \$passwordHash, "wipe-roles" => \$wipeRoles, "role=s" => \@roles, @@ -81,9 +91,9 @@ GetOptions("rename-from=s" => \$renameFrom, die "$0: one user name required\n" if scalar @ARGV != 1; my $userName = $ARGV[0]; -my $chosenPasswordOptions = grep { defined($_) } ($passwordHash, $password); +my $chosenPasswordOptions = grep { defined($_) } ($passwordPrompt, $passwordHash, $password); if ($chosenPasswordOptions > 1) { - die "$0: please specify only one --password* option. See --help for more information.\n"; + die "$0: please specify one of --password-prompt or --password-hash. See --help for more information.\n"; } die "$0: type must be `hydra', `google' or `github'\n" @@ -118,6 +128,8 @@ $db->txn_do(sub { if defined $password; die "$0: Google and GitHub accounts do not have a password.\n" if defined $passwordHash; + die "$0: Google and GitHub accounts do not have a password.\n" + if defined $passwordPrompt; $user->update({ emailaddress => $userName, password => "!" }); } else { $user->update({ emailaddress => $emailAddress }) if defined $emailAddress; @@ -129,6 +141,24 @@ $db->txn_do(sub { if (defined $passwordHash) { $user->setPasswordHash($passwordHash); } + + if (defined $passwordPrompt) { + print STDERR "Password: "; + my $password = // ""; + chomp $password; + + print STDERR "Password Confirmation: "; + my $passwordConfirm = // ""; + chomp $passwordConfirm; + + if ($password ne $passwordConfirm) { + die "Passwords don't match." + } elsif ($password eq "") { + die "Password cannot be empty." + } + + $user->setPassword($password); + } } $user->userroles->delete if $wipeRoles; diff --git a/t/scripts/hydra-create-user.t b/t/scripts/hydra-create-user.t index 597f4ebc..4b382acd 100644 --- a/t/scripts/hydra-create-user.t +++ b/t/scripts/hydra-create-user.t @@ -46,15 +46,37 @@ subtest "Handling password and password hash creation" => sub { is($storedPassword, $user->password, "The password was not upgraded."); }; + subtest "Creating a user by prompting for the password" => sub { + subtest "with the same password twice" => sub { + my ($res, $stdout, $stderr) = captureStdoutStderrWithStdin(5, ["hydra-create-user", "prompted-pass-user", "--password-prompt"], "my-password\nmy-password\n"); + is($res, 0, "hydra-create-user should exit zero"); + + my $user = $db->resultset('Users')->find({ username => "prompted-pass-user" }); + isnt($user, undef, "The user exists"); + like($user->password, qr/^\$argon2id\$v=/, "The password was saved, hashed with argon2id."); + + my $storedPassword = $user->password; + ok($user->check_password("my-password"), "Their password validates"); + }; + + subtest "With mismatched password confirmation" => sub { + my ($res, $stdout, $stderr) = captureStdoutStderrWithStdin(5, ["hydra-create-user", "prompted-pass-user", "--password-prompt"], "my-password\nnot-my-password\n"); + isnt($res, 0, "hydra-create-user should exit non-zero"); + }; + }; + subtest "Specifying conflicting password options fails" => sub { my @cases = ( + [ "--password=foo", "--password-hash=8843d7f92416211de9ebb963ff4ce28125932878", "--password-prompt" ], + [ "--password=foo", "--password-prompt" ], [ "--password=foo", "--password-hash=8843d7f92416211de9ebb963ff4ce28125932878" ], + [ "--password-hash=8843d7f92416211de9ebb963ff4ce28125932878", "--password-prompt" ], ); for my $case (@cases) { my ($res, $stdout, $stderr) = captureStdoutStderr(5, ( "hydra-create-user", "bogus-password-options", @{$case})); - like($stderr, qr/please specify only one --password\* option/, "We get an error about specifying the password"); + like($stderr, qr/please specify one of --password-prompt or --password-hash/, "We get an error about specifying the password"); isnt($res, 0, "hydra-create-user should exit non-zero with conflicting " . join(" ", @{$case})); } };