diff --git a/lib/pause_2017/PAUSE/Web/Config.pm b/lib/pause_2017/PAUSE/Web/Config.pm index 04d4e1230..ce85d48fe 100644 --- a/lib/pause_2017/PAUSE/Web/Config.pm +++ b/lib/pause_2017/PAUSE/Web/Config.pm @@ -537,11 +537,25 @@ our %Actions = ( cat => "01usr/01look", desc => "Admins can look where email should go", }, + change_user_status => { + x_mojo_to => "admin#change_user_status", + verb => "Change user status", + priv => "admin", + cat => "01usr/03", + desc => "Admins can change the ustatus of a user", + x_csrf_protection => 1, + x_form => { + HIDDENNAME => {form_type => "hidden_field"}, + pause99_change_user_status_user => {form_type => "text_field"}, + pause99_change_user_status_new_ustatus => {form_type => "select_field"}, + pause99_change_user_status_sub => {form_type => "submit_button"}, + }, + }, select_user => { x_mojo_to => "admin#select_user", verb => "Select User/Action", priv => "admin", - cat => "01usr/03", + cat => "01usr/04", desc => "Admins can access PAUSE as-if they were somebody else. Here they select a user/action pair.", method => 'POST', x_form => { diff --git a/lib/pause_2017/PAUSE/Web/Controller/Admin.pm b/lib/pause_2017/PAUSE/Web/Controller/Admin.pm index ed601df7b..5791fdafa 100644 --- a/lib/pause_2017/PAUSE/Web/Controller/Admin.pm +++ b/lib/pause_2017/PAUSE/Web/Controller/Admin.pm @@ -167,6 +167,50 @@ sub edit_ml { } } +sub change_user_status { + my $c = shift; + my $pause = $c->stash(".pause"); + my $mgr = $c->app->pause; + my $req = $c->req; + my $u = $c->active_user_record; + + my %valid_status = map {$_ => 1} qw(active nologin); + + my $user = $req->param("pause99_change_user_status_user"); + my $new_ustatus = $req->param("pause99_change_user_status_new_ustatus"); + if ($user) { + $pause->{user} = uc $user; + my $dbh = $mgr->connect; + my $sql = qq{SELECT ustatus FROM users WHERE userid = ?}; + my $row = $dbh->selectrow_arrayref($sql, undef, uc $user); + if ($row) { + $pause->{ustatus} = $row->[0]; + } else { + $pause->{user_not_found} = 1; + return; + } + + if ($new_ustatus && $valid_status{$new_ustatus} && $new_ustatus ne $pause->{ustatus}) { + my $sql = qq{UPDATE users SET ustatus = ?, changed = ?, changedby = ? WHERE userid = ?}; + my $sth = $dbh->prepare($sql); + my $ret = $sth->execute($new_ustatus, time, $u->{userid}, uc $user); + $sth->finish; + if ($ret) { + $pause->{changed} = 1; + $pause->{new_ustatus} = $new_ustatus; + my $mailblurb = $c->render_to_string("email/admin/change_user_status", format => "email"); + my @to = ($u->{secretemail}||$u->{email}, $mgr->config->mailto_admins); + warn "sending to[@to]"; + warn "mailblurb[$mailblurb]"; + my $header = { + Subject => "User status update for $user" + }; + $mgr->send_mail_multi(\@to, $header, $mailblurb); + } + } + } +} + sub select_user { my $c = shift; my $pause = $c->stash(".pause"); diff --git a/lib/pause_2017/templates/admin/change_user_status.html.ep b/lib/pause_2017/templates/admin/change_user_status.html.ep new file mode 100644 index 000000000..36d51f0bd --- /dev/null +++ b/lib/pause_2017/templates/admin/change_user_status.html.ep @@ -0,0 +1,19 @@ +% layout 'layout'; +% my $pause = stash(".pause") || {}; + + + +% if ($pause->{user_not_found}) { +
+

User <%= $pause->{user} %> is not found.

+
+% } elsif ($pause->{changed}) { +
+

<%= $pause->{user} %>'s status has changed from <%= $pause->{ustatus} %> to <%= $pause->{new_ustatus} %>.

+
+% } + +%= csrf_field +%= text_field "pause99_change_user_status_user" => $pause->{user}; +%= select_field "pause99_change_user_status_new_ustatus" => ['nologin', 'active']; +%= submit_button "Change", name => "pause99_change_user_status_sub"; diff --git a/lib/pause_2017/templates/email/admin/change_user_status.email.ep b/lib/pause_2017/templates/email/admin/change_user_status.email.ep new file mode 100644 index 000000000..a3b87cec3 --- /dev/null +++ b/lib/pause_2017/templates/email/admin/change_user_status.email.ep @@ -0,0 +1,13 @@ +% my $pause = stash(".pause") || {}; +% +%#------------------------------------------------------------------ +% +Record update in the PAUSE users database: + +The ustatus of <%= $pause->{user} %> has changed from <%= $pause->{ustatus} %> to <%= $pause->{new_ustatus} %>. + +Data entered by <%= $pause->{User}{fullname} %>. + +Thanks, +-- +The PAUSE Team diff --git a/t/pause_2017/action/change_user_status.t b/t/pause_2017/action/change_user_status.t new file mode 100644 index 000000000..f5a6fbaa1 --- /dev/null +++ b/t/pause_2017/action/change_user_status.t @@ -0,0 +1,98 @@ +use Mojo::Base -strict; +use FindBin; +use lib "$FindBin::Bin/../lib"; +use Test::PAUSE::Web; + +my $default = { + pause99_change_user_status_user => "TESTUSER", + pause99_change_user_status_new_ustatus => "nologin", + pause99_change_user_status_sub => 1, +}; + +Test::PAUSE::Web->setup; + +subtest 'get' => sub { + for my $test (Test::PAUSE::Web->tests_for('admin')) { + my ($path, $user) = @$test; + my $t = Test::PAUSE::Web->new(user => $user); + $t->get_ok("$path?ACTION=change_user_status"); + # note $t->content; + } +}; + +subtest 'post: basic' => sub { + Test::PAUSE::Web->setup; + for my $test (Test::PAUSE::Web->tests_for('admin')) { + my ($path, $user) = @$test; + my %form = %$default; + my $t = Test::PAUSE::Web->new(user => $user); + my $res = $t->post("$path?ACTION=change_user_status", \%form); + ok !$res->is_success && $res->code == 403, "Forbidden"; + like $res->content => qr/Failed CSRF check/; + # note $t->content; + } +}; + +subtest 'post_with_token: basic' => sub { + Test::PAUSE::Web->setup; + for my $test (Test::PAUSE::Web->tests_for('admin')) { + my ($path, $user) = @$test; + my %form = %$default; + my $t = Test::PAUSE::Web->new(user => $user); + $t->post_with_token_ok("$path?ACTION=change_user_status", \%form) + ->text_like("div.messagebox p", qr/status has changed from \w+ to nologin/); + is $t->deliveries => 2, "two deliveries for admin"; + # note $t->content; + } +}; + +subtest 'post_with_token: user not found' => sub { + Test::PAUSE::Web->setup; + for my $test (Test::PAUSE::Web->tests_for('admin')) { + my ($path, $user) = @$test; + my %form = ( + %$default, + pause99_change_user_status_user => 'UNKNOWN', + ); + my $t = Test::PAUSE::Web->new(user => $user); + $t->post_with_token_ok("$path?ACTION=change_user_status", \%form) + ->text_like("div.messagebox p", qr/User UNKNOWN is not found/); + is $t->deliveries => 0, "no deliveries for admin"; + # note $t->content; + } +}; + +subtest 'post_with_token: ustatus not changed' => sub { + Test::PAUSE::Web->setup; + for my $test (Test::PAUSE::Web->tests_for('admin')) { + my ($path, $user) = @$test; + my %form = %$default; + my $t = Test::PAUSE::Web->new(user => $user); + $t->post_with_token_ok("$path?ACTION=change_user_status", \%form) + ->text_like("div.messagebox p", qr/status has changed from \w+ to nologin/); + is $t->deliveries => 2, "two deliveries for admin"; + # note $t->content; + + # nologin to nologin + $t->post_with_token_ok("$path?ACTION=change_user_status", \%form) + ->dom_not_found("div.messagebox p"); + is $t->deliveries => 0, "no deliveries for admin"; + } +}; + +subtest 'post_with_token: unknown ustatus' => sub { + Test::PAUSE::Web->setup; + for my $test (Test::PAUSE::Web->tests_for('admin')) { + my ($path, $user) = @$test; + my %form = ( + %$default, + pause99_change_user_status_new_ustatus => 'unknown', + ); + my $t = Test::PAUSE::Web->new(user => $user); + $t->post_with_token_ok("$path?ACTION=change_user_status", \%form) + ->dom_not_found("div.messagebox p"); + is $t->deliveries => 0, "no deliveries for admin"; + } +}; + +done_testing; diff --git a/t/pause_2017/lib/Test/PAUSE/Web.pm b/t/pause_2017/lib/Test/PAUSE/Web.pm index 34b6338a1..707e94c67 100644 --- a/t/pause_2017/lib/Test/PAUSE/Web.pm +++ b/t/pause_2017/lib/Test/PAUSE/Web.pm @@ -300,6 +300,17 @@ sub text_unlike { $self; } +sub dom_not_found { + my ($self, $selector) = @_; + my $at = $self->dom->at($selector); + if ($at) { + fail "'$selector' is found"; + } else { + pass "'$selector' is not found"; + } + $self; +} + sub title_is_ok { my ($self, $url) = @_; return if $self->dom->at('p.error_message'); # ignore if error