tcmslogin 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128
  1. #!/usr/bin/env perl
  2. package tcms::login;
  3. use 5.040;
  4. use re '/aasx';
  5. use warnings FATAL => 'all';
  6. use FindBin::libs;
  7. use Getopt::Long;
  8. use Pod::Usage;
  9. use IO::Prompter();
  10. use Trog::Auth;
  11. use Trog::Log;
  12. =head1 SYNOPSIS
  13. Log a user in and print the resulting session cookie, so that you can hand it
  14. to call.pl and exercise authenticated routes without a browser.
  15. =head2 USAGE
  16. tcmslogin --user foo --password bar
  17. # Which is meant to be used like this:
  18. call.pl GET /admin/wyzzerdd '' "$(tcmslogin --user foo --password bar)"
  19. The cookie is printed to STDOUT and nothing else is, so it is safe to capture.
  20. Everything else the script has to say goes to STDERR.
  21. Note that logging in wastes any session the user already holds -- the session
  22. table keeps one row per user -- so running this will log that user out of their
  23. browser.
  24. =head2 OPTIONS
  25. =over 4
  26. =item --user
  27. The user to log in. Required.
  28. =item --password
  29. That user's password. Leaving the option out, or passing it with no value,
  30. reads it from STDIN instead, which keeps it out of your shell history:
  31. tcmslogin --user foo # prompts, with echo off
  32. tcmslogin --user foo --password # same
  33. echo hunter2 | tcmslogin --user foo
  34. =item --token
  35. The user's current TOTP code, if they have 2FA enabled.
  36. =item --raw
  37. Print just the session id, without the C<tcmslogin=> prefix.
  38. =item --help, --man
  39. Display this output.
  40. =back
  41. =cut
  42. # The logger is set up per-request by the server; a CLI run has to do it itself,
  43. # or the first thing Trog::Auth logs blows up on an undefined $log.
  44. my sub init_logs {
  45. my ($who) = @_;
  46. Trog::Log::log_init( 'logs/tcms.log', 'info' );
  47. $Trog::Log::user = $who // 'nobody';
  48. return;
  49. }
  50. my sub read_password ($prompt) {
  51. my $password = IO::Prompter::prompt( $prompt, -echo => q{}, -in => *STDIN, -out => *STDERR );
  52. return $password ? "$password" : undef;
  53. }
  54. sub main (@args) {
  55. my %options;
  56. Getopt::Long::GetOptionsFromArray(
  57. \@args,
  58. 'user=s' => \$options{user},
  59. 'password:s' => \$options{password},
  60. 'token=s' => \$options{token},
  61. 'raw' => \$options{raw},
  62. 'help|?' => \$options{help},
  63. );
  64. pod2usage( -exitval => 0, -verbose => 1 ) if $options{help};
  65. unless ( $options{user} ) {
  66. print {*STDERR} "--user is required.\n"; ## no critic (ProhibitPrintSTDERR) -- for whoever ran this
  67. return 2;
  68. }
  69. init_logs( $options{user} );
  70. # Omitted entirely or passed with no value both mean "ask me".
  71. $options{password} = read_password("Password for '$options{user}': ")
  72. unless $options{password};
  73. unless ( $options{password} ) {
  74. print {*STDERR} "No password provided.\n"; ## no critic (ProhibitPrintSTDERR) -- for whoever ran this
  75. return 2;
  76. }
  77. # mksession() tells us nothing about *why* it said no, by design -- it is
  78. # the same '' for an unknown user, a bad password and a bad TOTP code.
  79. my $session = Trog::Auth::mksession( $options{user}, $options{password}, $options{token}, 'tcmslogin' );
  80. unless ($session) {
  81. print {*STDERR} <<"SAID"; ## no critic (ProhibitPrintSTDERR) -- for whoever ran this
  82. Login failed for '$options{user}'.
  83. Check the password, and pass --token if this user has 2FA enabled.
  84. Use bin/tcms-useradd --user $options{user} --password ... to reset it.
  85. SAID
  86. return 1;
  87. }
  88. print $options{raw} ? "$session\n" : "tcmslogin=$session\n";
  89. return 0;
  90. }
  91. exit main(@ARGV) unless caller;
  92. 1;