tcmslogin 3.3 KB

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