aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--perllib/FixMyStreet/App/Controller/Admin.pm84
-rw-r--r--perllib/FixMyStreet/Cobrand/Default.pm83
-rw-r--r--perllib/FixMyStreet/Cobrand/UKCouncils.pm37
-rw-r--r--t/app/controller/admin.t148
-rw-r--r--t/app/controller/admin_permissions.t201
-rw-r--r--t/app/controller/moderate.t1
-rw-r--r--templates/web/base/admin/stats.html18
-rw-r--r--templates/web/base/admin/user-form.html73
8 files changed, 592 insertions, 53 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Admin.pm b/perllib/FixMyStreet/App/Controller/Admin.pm
index ea8633db0..17425ad77 100644
--- a/perllib/FixMyStreet/App/Controller/Admin.pm
+++ b/perllib/FixMyStreet/App/Controller/Admin.pm
@@ -669,6 +669,13 @@ sub report_edit : Path('report_edit') : Args(1) {
$c->detach( '/page_error_404_not_found' )
unless $problem;
+ unless (
+ $c->cobrand->moniker eq 'zurich'
+ || $c->user->has_permission_to(report_edit => $problem->bodies_str)
+ ) {
+ $c->detach( '/page_error_403_access_denied', [] );
+ }
+
$c->stash->{problem} = $problem;
$c->forward('/auth/get_csrf_token');
@@ -913,7 +920,7 @@ sub users: Path('users') : Args(0) {
my $search_n = 0;
$search_n = int($search) if $search =~ /^\d+$/;
- my $users = $c->model('DB::User')->search(
+ my $users = $c->cobrand->users->search(
{
-or => [
email => { ilike => $isearch },
@@ -945,7 +952,7 @@ sub users: Path('users') : Args(0) {
$c->forward('fetch_all_bodies');
# Admin users by default
- my $users = $c->model('DB::User')->search(
+ my $users = $c->cobrand->users->search(
{ from_body => { '!=', undef } },
{ order_by => 'name' }
);
@@ -1113,9 +1120,19 @@ sub user_edit : Path('user_edit') : Args(1) {
$c->forward('/auth/get_csrf_token');
- my $user = $c->model('DB::User')->find( { id => $id } );
+ my $user = $c->cobrand->users->find( { id => $id } );
+ $c->detach( '/page_error_404_not_found' ) unless $user;
+
+ unless ( $c->user->is_superuser || ( $c->user->has_permission_to('user_edit', $c->user->from_body->id) ) ) {
+ $c->detach('/page_error_403_access_denied', []);
+ }
+
$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') ) {
@@ -1126,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;
@@ -1135,10 +1152,37 @@ sub user_edit : Path('user_edit') : Args(1) {
$user->name( $c->get_param('name') );
$user->email( $c->get_param('email') );
$user->phone( $c->get_param('phone') ) if $c->get_param('phone');
- $user->from_body( $c->get_param('body') || undef );
$user->flagged( $c->get_param('flagged') || 0 );
# Only superusers can grant superuser status
$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 ) {
+ $user->from_body( $c->get_param('body') || undef );
+ } 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');
@@ -1229,7 +1273,13 @@ sub stats_fix_rate : Path('stats/fix-rate') : Args(0) {
sub stats : Path('stats') : Args(0) {
my ( $self, $c ) = @_;
- $c->forward('fetch_all_bodies');
+ my $selected_body;
+ if ( $c->user->is_superuser ) {
+ $c->forward('fetch_all_bodies');
+ $selected_body = $c->get_param('body');
+ } else {
+ $selected_body = $c->user->from_body->id;
+ }
if ( $c->cobrand->moniker eq 'seesomething' || $c->cobrand->moniker eq 'zurich' ) {
return $c->cobrand->admin_stats();
@@ -1259,7 +1309,7 @@ sub stats : Path('stats') : Args(0) {
my $bymonth = $c->get_param('bymonth');
$c->stash->{bymonth} = $bymonth;
- $c->stash->{selected_body} = $c->get_param('body');
+ $c->stash->{selected_body} = $selected_body;
my $field = 'confirmed';
@@ -1288,7 +1338,7 @@ sub stats : Path('stats') : Args(0) {
);
}
- my $p = $c->cobrand->problems->to_body($c->get_param('body'))->search(
+ my $p = $c->cobrand->problems->to_body($selected_body)->search(
{
-AND => [
$field => { '>=', $start_date},
@@ -1318,24 +1368,6 @@ sub set_allowed_pages : Private {
my $pages = $c->cobrand->admin_pages;
- if( !$pages ) {
- $pages = {
- 'summary' => [_('Summary'), 0],
- 'bodies' => [_('Bodies'), 1],
- 'reports' => [_('Reports'), 2],
- 'timeline' => [_('Timeline'), 3],
- 'users' => [_('Users'), 5],
- 'flagged' => [_('Flagged'), 6],
- 'stats' => [_('Stats'), 7],
- 'config' => [ _('Configuration'), 8],
- 'user_edit' => [undef, undef],
- 'body' => [undef, undef],
- 'report_edit' => [undef, undef],
- 'update_edit' => [undef, undef],
- 'abuse_edit' => [undef, undef],
- }
- }
-
my @allowed_links = sort {$pages->{$a}[1] <=> $pages->{$b}[1]} grep {$pages->{$_}->[0] } keys %$pages;
$c->stash->{allowed_pages} = $pages;
diff --git a/perllib/FixMyStreet/Cobrand/Default.pm b/perllib/FixMyStreet/Cobrand/Default.pm
index 686684a05..043d0b8e6 100644
--- a/perllib/FixMyStreet/Cobrand/Default.pm
+++ b/perllib/FixMyStreet/Cobrand/Default.pm
@@ -140,6 +140,30 @@ sub problems_on_map_restriction {
return $rs;
}
+=head1 users
+
+Returns a ResultSet of Users, potentially restricted to a subset if we're on
+a cobrand that only wants some of the data.
+
+=cut
+
+sub users {
+ my $self = shift;
+ return $self->users_restriction($self->{c}->model('DB::User'));
+}
+
+=head1 users_restriction
+
+Used to restricts users in the admin in a cobrand in a particular way. Do
+nothing by default.
+
+=cut
+
+sub users_restriction {
+ my ($self, $rs) = @_;
+ return $rs;
+}
+
sub site_key { return 0; }
=head2 restriction
@@ -613,7 +637,33 @@ List of names of pages to display on the admin interface
=cut
-sub admin_pages { 0 }
+sub admin_pages {
+ my $self = shift;
+
+ my $user = $self->{c}->user;
+
+ my $pages = {
+ 'summary' => [_('Summary'), 0],
+ 'bodies' => [_('Bodies'), 1],
+ 'reports' => [_('Reports'), 2],
+ 'timeline' => [_('Timeline'), 3],
+ 'users' => [_('Users'), 5],
+ 'flagged' => [_('Flagged'), 6],
+ 'stats' => [_('Stats'), 7],
+ 'user_edit' => [undef, undef],
+ 'body' => [undef, undef],
+ 'report_edit' => [undef, undef],
+ 'update_edit' => [undef, undef],
+ 'abuse_edit' => [undef, undef],
+ };
+
+ # There are some pages that only super users can see
+ if ( $user->is_superuser ) {
+ $pages->{config} = [ _('Configuration'), 8];
+ };
+
+ return $pages;
+}
=head2 admin_show_creation_graph
@@ -634,6 +684,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 43f10130a..5d72c4962 100644
--- a/perllib/FixMyStreet/Cobrand/UKCouncils.pm
+++ b/perllib/FixMyStreet/Cobrand/UKCouncils.pm
@@ -50,6 +50,33 @@ sub updates_restriction {
return $rs->to_body($self->council_id);
}
+sub users_restriction {
+ my ($self, $rs) = @_;
+
+ # Council admins can only see users who are members of the same council or
+ # users who have sent a report or update to that council.
+
+ my $problem_user_ids = $self->problems->search(
+ undef,
+ {
+ columns => [ 'user_id' ],
+ distinct => 1
+ }
+ )->as_query;
+ my $update_user_ids = $self->updates->search(
+ undef,
+ {
+ columns => [ 'user_id' ],
+ distinct => 1
+ }
+ )->as_query;
+
+ return $rs->search([
+ from_body => $self->council_id,
+ id => [ { -in => $problem_user_ids }, { -in => $update_user_ids } ],
+ ]);
+}
+
sub base_url {
my $self = shift;
my $base_url = FixMyStreet->config('BASE_URL');
@@ -157,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 51307f756..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 ) {
@@ -1102,6 +1104,30 @@ subtest 'user search' => sub {
$mech->content_contains('Haringey');
};
+subtest 'search does not show user from another council' => sub {
+ FixMyStreet::override_config {
+ ALLOWED_COBRANDS => [ 'oxfordshire' ],
+ }, sub {
+ $mech->get_ok('/admin/users');
+ $mech->get_ok('/admin/users?search=' . $user->name);
+
+ $mech->content_contains( "Searching found no users." );
+
+ $mech->get_ok('/admin/users?search=' . $user->email);
+ $mech->content_contains( "Searching found no users." );
+ };
+};
+
+subtest 'user_edit does not show user from another council' => sub {
+ FixMyStreet::override_config {
+ ALLOWED_COBRANDS => [ 'oxfordshire' ],
+ }, sub {
+ $mech->get('/admin/user_edit/' . $user->id);
+ ok !$mech->res->is_success(), "want a bad response";
+ is $mech->res->code, 404, "got 404";
+ };
+};
+
$log_entries = FixMyStreet::App->model('DB::AdminLog')->search(
{
object_type => 'user',
@@ -1129,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',
@@ -1145,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',
@@ -1161,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,
@@ -1177,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',
@@ -1193,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,
@@ -1209,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/],
},
@@ -1229,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/],
},
@@ -1246,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';
@@ -1331,6 +1476,9 @@ subtest "Users with from_body can't access fixmystreet.com admin" => sub {
};
};
+$mech->log_out_ok;
+$user2->user_body_permissions->delete_all;
+$oxfordshireuser->user_body_permissions->delete_all;
$mech->delete_user( $user );
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/stats.html b/templates/web/base/admin/stats.html
index 5aaf59068..897e2fc44 100644
--- a/templates/web/base/admin/stats.html
+++ b/templates/web/base/admin/stats.html
@@ -87,14 +87,16 @@
<input type="checkbox" name="bymonth" id="bymonth"[% bymonth ? ' checked' : '' %] /><label class="inline" for="bymonth">[% loc('By Date') %]</label>
</p>
- <p>
- [% loc('Council:') %] <select id='body' name='body'>
- <option value=''>[% loc('No council') %]</option>
- [% FOR body IN bodies %]
- <option value="[% body.id %]"[% ' selected' IF body.id == selected_body %]>[% body.name %]</option>
- [% END %]
- </select>
- </p>
+ [% IF c.user.is_superuser %]
+ <p>
+ [% loc('Council:') %] <select id='body' name='body'>
+ <option value=''>[% loc('No council') %]</option>
+ [% FOR body IN bodies %]
+ <option value="[% body.id %]"[% ' selected' IF body.id == selected_body %]>[% body.name %]</option>
+ [% END %]
+ </select>
+ </p>
+ [% END %]
<p>
<input type="submit" name="getcounts" size="30" id="getcounts" value="Get Count" />
diff --git a/templates/web/base/admin/user-form.html b/templates/web/base/admin/user-form.html
index 2942494a7..5bca4171a 100644
--- a/templates/web/base/admin/user-form.html
+++ b/templates/web/base/admin/user-form.html
@@ -22,25 +22,37 @@
<input type='text' id='email' name='email' value='[% user.email | html %]'></li>
<li><label for="phone">[% loc('Phone:') %]</label>
<input type='text' id='phone' name='phone' value='[% user.phone | html %]'></li>
- <li>
- <div class="admin-hint">
- <p>
- [% loc(
- "Normal (public) users should not be associated with any <strong>body</strong>.<br>
- Authorised staff users can be associated with the body they represent.<br>
- Depending on the implementation, staff users may have access to the dashboard (summary of
- activity across their body), the ability to hide reports or set special report statuses.")
- %]
- </p>
- </div>
- [% loc('Body:') %] <select id='body' name='body'>
- <option value=''>[% loc('No body') %]</option>
- [% FOR body IN bodies %]
- <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 %]
+
+ [% IF c.user.is_superuser %]
+ <li>
+ <div class="admin-hint">
+ <p>
+ [% loc(
+ "Normal (public) users should not be associated with any <strong>body</strong>.<br>
+ Authorised staff users can be associated with the body they represent.<br>
+ Depending on the implementation, staff users may have access to the dashboard (summary of
+ activity across their body), the ability to hide reports or set special report statuses.")
+ %]
+ </p>
+ </div>
+ [% loc('Body:') %] <select id='body' name='body'>
+ <option value=''>[% loc('No body') %]</option>
+ [% FOR body IN bodies %]
+ <option value="[% body.id %]"[% ' selected' IF body.id == user.from_body.id %]>[% body.name %]</option>
+ [% END %]
+ </select>
</li>
+ [% ELSE %]
+ <li>
+ <div class="admin-hint">
+ <p>
+ [% 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' : '' %] [% 'disabled' UNLESS c.user.has_permission_to('user_assign_body', c.user.from_body.id) %]>
+ </li>
+ [% END %]
+
[% IF c.cobrand.moniker != 'zurich' %]
<li>
<div class="admin-hint">
@@ -66,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') %]" >