diff options
author | Dave Arter <davea@mysociety.org> | 2016-08-17 15:31:56 +0100 |
---|---|---|
committer | Dave Arter <davea@mysociety.org> | 2016-08-17 15:38:07 +0100 |
commit | d2a00747fc56342ed262804d8f268335e6ec1dfa (patch) | |
tree | 81e06668a97416ac9ae0d537380fc247e1f91c3c | |
parent | 4eb4658ad589d01d58b239993e201c47325a2eb4 (diff) |
Allow user permissions to be granted/revoked in admin
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Admin.pm | 29 | ||||
-rw-r--r-- | perllib/FixMyStreet/Cobrand/Default.pm | 31 | ||||
-rw-r--r-- | perllib/FixMyStreet/Cobrand/UKCouncils.pm | 10 | ||||
-rw-r--r-- | t/app/controller/admin.t | 167 | ||||
-rw-r--r-- | t/app/controller/admin_permissions.t | 201 | ||||
-rw-r--r-- | t/app/controller/moderate.t | 1 | ||||
-rw-r--r-- | templates/web/base/admin/user-form.html | 30 |
7 files changed, 418 insertions, 51 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm index ce2a653a2..c8432df0c 100644 --- a/perllib/FixMyStreet/App/Controller/Admin.pm +++ b/perllib/FixMyStreet/App/Controller/Admin.pm @@ -1129,6 +1129,10 @@ sub user_edit : Path('user_edit') : Args(1) { $c->stash->{user} = $user; + if ( $user->from_body && $c->user->has_permission_to('user_manage_permissions', $user->from_body->id) ) { + $c->stash->{available_permissions} = $c->cobrand->available_permissions; + } + $c->forward('fetch_all_bodies'); if ( $c->get_param('submit') ) { @@ -1139,7 +1143,7 @@ sub user_edit : Path('user_edit') : Args(1) { if ( $user->email ne $c->get_param('email') || $user->name ne $c->get_param('name') || ($user->phone || "") ne $c->get_param('phone') || - ($user->from_body && $user->from_body->id ne $c->get_param('body')) || + ($user->from_body && $c->get_param('body') && $user->from_body->id ne $c->get_param('body')) || (!$user->from_body && $c->get_param('body')) ) { $edited = 1; @@ -1153,14 +1157,33 @@ sub user_edit : Path('user_edit') : Args(1) { $user->is_superuser( ( $c->user->is_superuser && $c->get_param('is_superuser') ) || 0 ); # Superusers can set from_body to any value, but other staff can only # set from_body to the same value as their own from_body. - if ($c->user->is_superuser) { + if ( $c->user->is_superuser ) { $user->from_body( $c->get_param('body') || undef ); - } elsif ($c->get_param('body') eq $c->user->from_body->id) { + } elsif ( $c->user->has_permission_to('user_assign_body', $c->user->from_body->id ) && + $c->get_param('body') && $c->get_param('body') eq $c->user->from_body->id ) { $user->from_body( $c->user->from_body ); } else { $user->from_body( undef ); } + if (!$user->from_body) { + # Non-staff users aren't allowed any permissions + $user->user_body_permissions->delete_all; + } elsif ($c->stash->{available_permissions}) { + my @all_permissions = map { keys %$_ } values %{ $c->stash->{available_permissions} }; + my @user_permissions = grep { $c->get_param("permissions[$_]") ? 1 : undef } @all_permissions; + $user->user_body_permissions->search({ + body_id => $user->from_body->id, + permission_type => { '!=' => \@user_permissions }, + })->delete; + foreach my $permission_type (@user_permissions) { + $user->user_body_permissions->find_or_create({ + body_id => $user->from_body->id, + permission_type => $permission_type, + }); + } + } + unless ($user->email) { $c->stash->{field_errors}->{email} = _('Please enter a valid email'); return; diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm index 8c75a1234..326919654 100644 --- a/perllib/FixMyStreet/Cobrand/Default.pm +++ b/perllib/FixMyStreet/Cobrand/Default.pm @@ -658,6 +658,37 @@ sub admin_allow_user { return 1 if $user->is_superuser; } +=head2 available_permissions + +Grouped lists of permission types available for use in the admin + +=cut + +sub available_permissions { + my $self = shift; + + return { + _("Problems") => { + moderate => _("Moderate report details"), + report_edit => _("Edit reports"), + report_edit_category => _("Edit report category"), # future use + report_edit_priority => _("Edit report priority"), # future use + report_inspect => _("Markup problem details"), + report_instruct => _("Instruct contractors to fix problems"), # future use + planned_reports => _("Manage planned reports list"), + contribute_as_another_user => _("Create reports/updates on a user's behalf"), + contribute_as_body => _("Create reports/updates as the council"), + }, + _("Users") => { + user_edit => _("Edit other users' details"), + user_manage_permissions => _("Edit other users' permissions"), + user_assign_body => _("Grant access to the admin"), + user_assign_areas => _("Assign users to areas"), # future use + }, + }; +} + + =head2 area_types The MaPit types this site handles diff --git a/perllib/FixMyStreet/Cobrand/UKCouncils.pm b/perllib/FixMyStreet/Cobrand/UKCouncils.pm index 701a4ca1c..5d72c4962 100644 --- a/perllib/FixMyStreet/Cobrand/UKCouncils.pm +++ b/perllib/FixMyStreet/Cobrand/UKCouncils.pm @@ -184,4 +184,14 @@ sub admin_allow_user { return $user->from_body->id == $self->council_id; } +sub available_permissions { + my $self = shift; + + my $perms = $self->next::method(); + $perms->{Problems}->{contribute_as_body} = "Create reports/updates as " . $self->council_name; + $perms->{Users}->{user_assign_areas} = "Assign users to areas in " . $self->council_name; + + return $perms; +} + 1; diff --git a/t/app/controller/admin.t b/t/app/controller/admin.t index 531fa7726..df4a72c4b 100644 --- a/t/app/controller/admin.t +++ b/t/app/controller/admin.t @@ -15,6 +15,8 @@ my $superuser = $mech->create_user_ok('superuser@example.com', name => 'Super Us my $oxfordshire = $mech->create_body_ok(2237, 'Oxfordshire County Council', id => 2237); my $oxfordshireuser = $mech->create_user_ok('counciluser@example.com', name => 'Council User', from_body => $oxfordshire); +my $bromley = $mech->create_body_ok(2482, 'Bromley Council', id => 2482); + my $user3 = $mech->create_user_ok('test3@example.com', name => 'Test User 2'); if ( $user3 ) { @@ -1153,6 +1155,19 @@ for my $test ( phone => '', flagged => undef, is_superuser => undef, + "permissions[moderate]" => undef, + "permissions[planned_reports]" => undef, + "permissions[report_edit]" => undef, + "permissions[report_edit_category]" => undef, + "permissions[report_edit_priority]" => undef, + "permissions[report_inspect]" => undef, + "permissions[report_instruct]" => undef, + "permissions[contribute_as_another_user]" => undef, + "permissions[contribute_as_body]" => undef, + "permissions[user_edit]" => undef, + "permissions[user_manage_permissions]" => undef, + "permissions[user_assign_body]" => undef, + "permissions[user_assign_areas]" => undef, }, changes => { name => 'Changed User', @@ -1169,6 +1184,19 @@ for my $test ( phone => '', flagged => undef, is_superuser => undef, + "permissions[moderate]" => undef, + "permissions[planned_reports]" => undef, + "permissions[report_edit]" => undef, + "permissions[report_edit_category]" => undef, + "permissions[report_edit_priority]" => undef, + "permissions[report_inspect]" => undef, + "permissions[report_instruct]" => undef, + "permissions[contribute_as_another_user]" => undef, + "permissions[contribute_as_body]" => undef, + "permissions[user_edit]" => undef, + "permissions[user_manage_permissions]" => undef, + "permissions[user_assign_body]" => undef, + "permissions[user_assign_areas]" => undef, }, changes => { email => 'changed@example.com', @@ -1185,6 +1213,19 @@ for my $test ( phone => '', flagged => undef, is_superuser => undef, + "permissions[moderate]" => undef, + "permissions[planned_reports]" => undef, + "permissions[report_edit]" => undef, + "permissions[report_edit_category]" => undef, + "permissions[report_edit_priority]" => undef, + "permissions[report_inspect]" => undef, + "permissions[report_instruct]" => undef, + "permissions[contribute_as_another_user]" => undef, + "permissions[contribute_as_body]" => undef, + "permissions[user_edit]" => undef, + "permissions[user_manage_permissions]" => undef, + "permissions[user_assign_body]" => undef, + "permissions[user_assign_areas]" => undef, }, changes => { body => $southend->id, @@ -1201,6 +1242,19 @@ for my $test ( phone => '', flagged => undef, is_superuser => undef, + "permissions[moderate]" => undef, + "permissions[planned_reports]" => undef, + "permissions[report_edit]" => undef, + "permissions[report_edit_category]" => undef, + "permissions[report_edit_priority]" => undef, + "permissions[report_inspect]" => undef, + "permissions[report_instruct]" => undef, + "permissions[contribute_as_another_user]" => undef, + "permissions[contribute_as_body]" => undef, + "permissions[user_edit]" => undef, + "permissions[user_manage_permissions]" => undef, + "permissions[user_assign_body]" => undef, + "permissions[user_assign_areas]" => undef, }, changes => { flagged => 'on', @@ -1217,6 +1271,19 @@ for my $test ( phone => '', flagged => 'on', is_superuser => undef, + "permissions[moderate]" => undef, + "permissions[planned_reports]" => undef, + "permissions[report_edit]" => undef, + "permissions[report_edit_category]" => undef, + "permissions[report_edit_priority]" => undef, + "permissions[report_inspect]" => undef, + "permissions[report_instruct]" => undef, + "permissions[contribute_as_another_user]" => undef, + "permissions[contribute_as_body]" => undef, + "permissions[user_edit]" => undef, + "permissions[user_manage_permissions]" => undef, + "permissions[user_assign_body]" => undef, + "permissions[user_assign_areas]" => undef, }, changes => { flagged => undef, @@ -1233,10 +1300,38 @@ for my $test ( phone => '', flagged => undef, is_superuser => undef, + "permissions[moderate]" => undef, + "permissions[planned_reports]" => undef, + "permissions[report_edit]" => undef, + "permissions[report_edit_category]" => undef, + "permissions[report_edit_priority]" => undef, + "permissions[report_inspect]" => undef, + "permissions[report_instruct]" => undef, + "permissions[contribute_as_another_user]" => undef, + "permissions[contribute_as_body]" => undef, + "permissions[user_edit]" => undef, + "permissions[user_manage_permissions]" => undef, + "permissions[user_assign_body]" => undef, + "permissions[user_assign_areas]" => undef, }, changes => { is_superuser => 'on', }, + removed => [ + "permissions[moderate]", + "permissions[planned_reports]", + "permissions[report_edit]", + "permissions[report_edit_category]", + "permissions[report_edit_priority]", + "permissions[report_inspect]", + "permissions[report_instruct]", + "permissions[contribute_as_another_user]", + "permissions[contribute_as_body]", + "permissions[user_edit]", + "permissions[user_manage_permissions]", + "permissions[user_assign_body]", + "permissions[user_assign_areas]", + ], log_count => 5, log_entries => [qw/edit edit edit edit edit/], }, @@ -1253,6 +1348,21 @@ for my $test ( changes => { is_superuser => undef, }, + added => { + "permissions[moderate]" => undef, + "permissions[planned_reports]" => undef, + "permissions[report_edit]" => undef, + "permissions[report_edit_category]" => undef, + "permissions[report_edit_priority]" => undef, + "permissions[report_inspect]" => undef, + "permissions[report_instruct]" => undef, + "permissions[contribute_as_another_user]" => undef, + "permissions[contribute_as_body]" => undef, + "permissions[user_edit]" => undef, + "permissions[user_manage_permissions]" => undef, + "permissions[user_assign_body]" => undef, + "permissions[user_assign_areas]" => undef, + }, log_count => 5, log_entries => [qw/edit edit edit edit edit/], }, @@ -1270,6 +1380,17 @@ for my $test ( $mech->submit_form_ok( { with_fields => $expected } ); + # Some actions cause visible fields to be added/removed + foreach my $x (@{ $test->{removed} }) { + delete $expected->{$x}; + } + if ( $test->{added} ) { + $expected = { + %$expected, + %{ $test->{added} } + }; + } + $visible = $mech->visible_form_values; is_deeply $visible, $expected, 'user updated'; @@ -1355,52 +1476,8 @@ subtest "Users with from_body can't access fixmystreet.com admin" => sub { }; }; -$report->bodies_str(2237); -$report->cobrand('oxfordshire'); -$report->update; - -$mech->log_in_ok( $oxfordshireuser->email ); - -subtest "Users can't edit report without report_edit permission" => sub { - FixMyStreet::override_config { - ALLOWED_COBRANDS => [ 'oxfordshire' ], - }, sub { - $mech->get("/admin/report_edit/$report_id"); - ok !$mech->res->is_success(), "want a bad response"; - is $mech->res->code, 404, "got 404, can't edit report without report_edit permission"; - }; -}; - -subtest "Users can edit report with report_edit permission" => sub { - FixMyStreet::override_config { - ALLOWED_COBRANDS => [ 'oxfordshire' ], - }, sub { - $oxfordshireuser->user_body_permissions->create({ - body => $oxfordshire, - permission_type => 'report_edit', - }); - - $mech->get_ok("/admin/report_edit/$report_id"); - $mech->content_contains( $report->title ); - }; -}; - -subtest "Users can't edit another council's reports with their own council's report_edit permission" => sub { - FixMyStreet::override_config { - ALLOWED_COBRANDS => [ 'oxfordshire' ], - }, sub { - $report->bodies_str(2482); - $report->cobrand('bromley'); - $report->update; - - $mech->get("/admin/report_edit/$report_id"); - ok !$mech->res->is_success(), "want a bad response"; - is $mech->res->code, 404, "got 404, can't edit report with incorrect body in report_edit permission"; - }; -}; - - $mech->log_out_ok; +$user2->user_body_permissions->delete_all; $oxfordshireuser->user_body_permissions->delete_all; diff --git a/t/app/controller/admin_permissions.t b/t/app/controller/admin_permissions.t new file mode 100644 index 000000000..63b753ff3 --- /dev/null +++ b/t/app/controller/admin_permissions.t @@ -0,0 +1,201 @@ +use strict; +use warnings; +use Test::More; +use LWP::Protocol::PSGI; + +use t::Mock::MapIt; +use FixMyStreet::TestMech; + +my $mech = FixMyStreet::TestMech->new; + +my $user = $mech->create_user_ok('test@example.com', name => 'Test User'); +my $user2 = $mech->create_user_ok('test2@example.com', name => 'Test User 2'); +my $superuser = $mech->create_user_ok('superuser@example.com', name => 'Super User', is_superuser => 1); + +my $oxfordshire = $mech->create_body_ok(2237, 'Oxfordshire County Council', id => 2237); +my $oxfordshireuser = $mech->create_user_ok('counciluser@example.com', name => 'Council User', from_body => $oxfordshire); + +my $bromley = $mech->create_body_ok(2482, 'Bromley Council', id => 2482); + +END { + $mech->delete_user( $user ); + $mech->delete_user( $user2 ); + $mech->delete_user( $superuser ); + $mech->delete_user( $oxfordshireuser ); +} + +my $dt = DateTime->new( + year => 2011, + month => 04, + day => 16, + hour => 15, + minute => 47, + second => 23 +); + +my ($report) = $mech->create_problems_for_body(1, $oxfordshire->id, 'Test'); +my $report_id = $report->id; +ok $report, "created test report - $report_id"; + +$mech->log_in_ok( $oxfordshireuser->email ); + +subtest "Users can't edit report without report_edit permission" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'oxfordshire' ], + }, sub { + $mech->get("/admin/report_edit/$report_id"); + ok !$mech->res->is_success(), "want a bad response"; + is $mech->res->code, 403, "got 403, can't edit report without report_edit permission"; + }; +}; + +subtest "Users can edit report with report_edit permission" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'oxfordshire' ], + }, sub { + $oxfordshireuser->user_body_permissions->create({ + body => $oxfordshire, + permission_type => 'report_edit', + }); + + $mech->get_ok("/admin/report_edit/$report_id"); + $mech->content_contains( $report->title ); + }; +}; + +subtest "Users can't edit another council's reports with their own council's report_edit permission" => sub { + FixMyStreet::override_config { + ALLOWED_COBRANDS => [ 'oxfordshire' ], + }, sub { + $report->bodies_str($bromley->id); + $report->cobrand('bromley'); + $report->update; + + $mech->get("/admin/report_edit/$report_id"); + ok !$mech->res->is_success(), "want a bad response"; + is $mech->res->code, 404, "got 404, can't edit report with incorrect body in report_edit permission"; + }; +}; + + +FixMyStreet::override_config { + MAPIT_URL => 'http://mapit.uk/', + ALLOWED_COBRANDS => [ 'oxfordshire' ], +}, sub { + LWP::Protocol::PSGI->register(t::Mock::MapIt->run_if_script, host => 'mapit.uk'); + + my $user2_id = $user2->id; + $report->update({ bodies_str => $oxfordshire->id }); + + foreach my $perm (0, 1) { + if ($perm) { + $oxfordshireuser->user_body_permissions->find_or_create({ + body => $oxfordshire, + permission_type => 'user_edit', + }); + } + foreach my $report_user ($user, $user2) { + $report->update({ user => $report_user }); + foreach my $from_body (undef, $bromley, $oxfordshire) { + $user2->update({ from_body => $from_body }); + my $result = ($from_body || '') eq $oxfordshire || $report->user eq $user2 ? ($perm ? 200 : 403 ) : 404; + my $u = $result == 200 ? 'can' : 'cannot'; + my $b = $from_body ? $from_body->name : 'no body'; + my $p = $perm ? 'with' : 'without'; + my $r = $report->user eq $user2 ? 'with' : 'without'; + subtest "User $u edit user for $b $p permission, $r cobrand relation" => sub { + $mech->get("/admin/user_edit/$user2_id"); + my $success = $mech->res->is_success(); + ok $result == 200 ? $success : !$success, "got correct response"; + is $mech->res->code, $result, "got $result"; + }; + } + } + } + + $oxfordshireuser->user_body_permissions->create({ + body => $oxfordshire, + permission_type => 'user_assign_body', + }); + + subtest "Users can edit users of their own council" => sub { + $mech->get_ok("/admin/user_edit/$user2_id"); + $mech->content_contains( $user2->name ); + + # We shouldn't be able to see the permissions tick boxes + $mech->content_lacks('Moderate report details'); + + $mech->submit_form_ok( { with_fields => { + name => 'Test Updated User 2', + email => $user2->email, + body => $user2->from_body->id, + phone => '', + flagged => undef, + } } ); + $user2->discard_changes; + is $user2->name, 'Test Updated User 2', 'name changed'; + }; + + $oxfordshireuser->user_body_permissions->create({ + body => $oxfordshire, + permission_type => 'user_manage_permissions', + }); + + subtest "Users can edit permissions" => sub { + is $user2->user_body_permissions->count, 0, 'user2 has no permissions'; + + $mech->get_ok("/admin/user_edit/$user2_id"); + $mech->content_contains('Moderate report details'); + + $mech->submit_form_ok( { with_fields => { + name => $user2->name, + email => $user2->email, + body => $user2->from_body->id, + phone => '', + flagged => undef, + "permissions[moderate]" => 'on', + "permissions[report_edit_category]" => undef, + "permissions[report_edit_priority]" => undef, + "permissions[report_inspect]" => undef, + "permissions[report_instruct]" => undef, + "permissions[contribute_as_another_user]" => undef, + "permissions[contribute_as_body]" => undef, + "permissions[user_edit]" => undef, + "permissions[user_manage_permissions]" => undef, + "permissions[user_assign_areas]" => undef, + } } ); + + ok $user2->has_permission_to("moderate", $user2->from_body->id), "user2 has been granted moderate permission"; + }; + + subtest "Unsetting user from_body removes all permissions " => sub { + is $user2->user_body_permissions->count, 1, 'user2 has 1 permission'; + + $mech->get_ok("/admin/user_edit/$user2_id"); + $mech->content_contains('Moderate report details'); + + $mech->submit_form_ok( { with_fields => { + name => $user2->name, + email => $user2->email, + body => undef, + phone => '', + flagged => undef, + "permissions[moderate]" => 'on', # NB tick box is left on deliberately + "permissions[report_edit_category]" => undef, + "permissions[report_edit_priority]" => undef, + "permissions[report_inspect]" => undef, + "permissions[report_instruct]" => undef, + "permissions[contribute_as_another_user]" => undef, + "permissions[contribute_as_body]" => undef, + "permissions[user_edit]" => undef, + "permissions[user_manage_permissions]" => undef, + "permissions[user_assign_areas]" => undef, + } } ); + + is $user2->user_body_permissions->count, 0, 'user2 has had permissions removed'; + }; +}; + +$mech->log_out_ok; + +done_testing(); diff --git a/t/app/controller/moderate.t b/t/app/controller/moderate.t index 52201c63b..0ccfcf2c2 100644 --- a/t/app/controller/moderate.t +++ b/t/app/controller/moderate.t @@ -334,5 +334,6 @@ $update2->delete; $report->moderation_original_data->delete; $report->delete; $report2->delete; +$mech->delete_user($user); done_testing(); diff --git a/templates/web/base/admin/user-form.html b/templates/web/base/admin/user-form.html index 96a51486b..5bca4171a 100644 --- a/templates/web/base/admin/user-form.html +++ b/templates/web/base/admin/user-form.html @@ -41,8 +41,7 @@ <option value="[% body.id %]"[% ' selected' IF body.id == user.from_body.id %]>[% body.name %]</option> [% END %] </select> - [% IF user.from_body AND user.has_permission_to('moderate', user.from_body.id) %]*[% END %] - </li> + </li> [% ELSE %] <li> <div class="admin-hint"> @@ -50,7 +49,7 @@ [% loc("Staff users have permission to log in to the admin.") %] </p> </div> - [% loc('Staff:') %] <input type="checkbox" id="body" name="body" value="[% c.user.from_body.id %]" [% user.from_body.id == c.user.from_body.id ? ' checked' : '' %]> + [% loc('Staff:') %] <input type="checkbox" id="body" name="body" value="[% c.user.from_body.id %]" [% user.from_body.id == c.user.from_body.id ? ' checked' : '' %] [% 'disabled' UNLESS c.user.has_permission_to('user_assign_body', c.user.from_body.id) %]> </li> [% END %] @@ -79,6 +78,31 @@ [% loc('Superuser:') %] <input type="checkbox" id="is_superuser" name="is_superuser"[% user.is_superuser ? ' checked' : '' %]> </li> [% END %] + + [% IF available_permissions AND NOT user.is_superuser %] + <li> + <div class="admin-hint"> + <p> + [% loc("Users can perform the following actions within their assigned body or area.") %] + </p> + </div> + [% loc('Permissions:') %] + </li> + [% FOREACH group IN available_permissions.pairs %] + <li> + [% group.key %] + <ul> + [% FOREACH permission IN group.value %] + <li> + <label> + <input type="checkbox" id="perms_[% permission.key %]" name="permissions[[% permission.key %]]" [% "checked" IF user.has_permission_to(permission.key, user.from_body.id) %]> + [% permission.value %] + </label> + </li> + [% END %] + </ul> + [% END %] + [% END %] [% END %] </ul> <input type="submit" name="Submit changes" value="[% loc('Submit changes') %]" > |