diff options
-rw-r--r-- | perllib/FixMyStreet/App/Controller/Admin/Users.pm | 52 | ||||
-rw-r--r-- | perllib/FixMyStreet/DB/Result/User.pm | 15 | ||||
-rw-r--r-- | t/app/controller/admin/roles.t | 29 | ||||
-rw-r--r-- | templates/web/base/admin/users/form.html | 14 | ||||
-rw-r--r-- | web/cobrands/fixmystreet/admin.js | 26 |
5 files changed, 126 insertions, 10 deletions
diff --git a/perllib/FixMyStreet/App/Controller/Admin/Users.pm b/perllib/FixMyStreet/App/Controller/Admin/Users.pm index 1edddd960..26f81992a 100644 --- a/perllib/FixMyStreet/App/Controller/Admin/Users.pm +++ b/perllib/FixMyStreet/App/Controller/Admin/Users.pm @@ -136,6 +136,18 @@ sub add : Local : Args(0) { $c->res->redirect( $c->uri_for_action( 'admin/users/edit', $user->id ) ); } +sub fetch_body_roles : Private { + my ($self, $c, $body ) = @_; + + my $roles = $body->roles->search(undef, { order_by => 'name' }); + unless ($roles) { + delete $c->stash->{roles}; # Body doesn't have any roles + return; + } + + $c->stash->{roles} = [ $roles->all ]; +} + sub edit : Path : Args(1) { my ( $self, $c, $id ) = @_; @@ -157,6 +169,7 @@ sub edit : Path : Args(1) { $c->forward('/admin/fetch_all_bodies'); $c->forward('/admin/fetch_body_areas', [ $user->from_body ]) if $user->from_body; + $c->forward('fetch_body_roles', [ $user->from_body ]) if $user->from_body; $c->cobrand->call_hook('admin_user_edit_extra_data'); if ( defined $c->flash->{status_message} ) { @@ -269,26 +282,45 @@ sub edit : Path : Args(1) { # If so, we need to re-fetch areas so the UI is up to date. if ( $user->from_body && $user->from_body->id ne $c->stash->{fetched_areas_body_id} ) { $c->forward('/admin/fetch_body_areas', [ $user->from_body ]); + $c->forward('fetch_body_roles', [ $user->from_body ]); } if (!$user->from_body) { # Non-staff users aren't allowed any permissions or to be in an area $user->admin_user_body_permissions->delete; + $user->user_roles->delete; $user->area_ids(undef); delete $c->stash->{areas}; + delete $c->stash->{roles}; delete $c->stash->{fetched_areas_body_id}; } 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->admin_user_body_permissions->search({ - body_id => $user->from_body->id, - permission_type => { -not_in => \@user_permissions }, - })->delete; - foreach my $permission_type (@user_permissions) { - $user->user_body_permissions->find_or_create({ + my %valid_roles = map { $_->id => 1 } @{$c->stash->{roles}}; + my @role_ids = grep { $valid_roles{$_} } $c->get_param_list('roles'); + if (@role_ids) { + # Roles take precedence over permissions + $user->admin_user_body_permissions->delete; + $user->user_roles->search({ + role_id => { -not_in => \@role_ids }, + })->delete; + foreach my $role (@role_ids) { + $user->user_roles->find_or_create({ + role_id => $role, + }); + } + } else { + $user->user_roles->delete; + my @all_permissions = map { keys %$_ } values %{ $c->stash->{available_permissions} }; + my @user_permissions = grep { $c->get_param("permissions[$_]") ? 1 : undef } @all_permissions; + $user->admin_user_body_permissions->search({ body_id => $user->from_body->id, - permission_type => $permission_type, - }); + permission_type => { -not_in => \@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, + }); + } } } diff --git a/perllib/FixMyStreet/DB/Result/User.pm b/perllib/FixMyStreet/DB/Result/User.pm index edcd20fdf..9dc5df856 100644 --- a/perllib/FixMyStreet/DB/Result/User.pm +++ b/perllib/FixMyStreet/DB/Result/User.pm @@ -628,4 +628,19 @@ sub in_area { return $self->areas_hash->{$area}; } +has roles_hash => ( + is => 'ro', + lazy => 1, + default => sub { + my $self = shift; + my %ids = map { $_->role_id => 1 } $self->user_roles->all; + return \%ids; + }, +); + +sub in_role { + my ($self, $role) = @_; + return $self->roles_hash->{$role}; +} + 1; diff --git a/t/app/controller/admin/roles.t b/t/app/controller/admin/roles.t index a7740a572..77c5b0033 100644 --- a/t/app/controller/admin/roles.t +++ b/t/app/controller/admin/roles.t @@ -7,10 +7,20 @@ my $superuser = $mech->create_user_ok('superuser@example.com', name => 'Super Us my $body = $mech->create_body_ok(2237, 'Oxfordshire County Council'); my $body2 = $mech->create_body_ok(2482, 'Bromley Council'); my $editor = $mech->create_user_ok('counciluser@example.com', name => 'Council User', from_body => $body); +my $user = $mech->create_user_ok('staffuser@example.com', name => 'Other Council User', from_body => $body); + $editor->user_body_permissions->create({ body => $body, permission_type => 'user_edit', }); +$editor->user_body_permissions->create({ + body => $body, + permission_type => 'user_manage_permissions', +}); +$user->user_body_permissions->create({ + body => $body, + permission_type => 'report_edit_priority', +}); FixMyStreet::DB->resultset("Role")->create({ body => $body, @@ -25,6 +35,7 @@ FixMyStreet::DB->resultset("Role")->create({ FixMyStreet::override_config { ALLOWED_COBRANDS => 'oxfordshire', + MAPIT_URL => 'http://mapit.uk', }, sub { $mech->log_in_ok( $editor->email ); @@ -68,6 +79,24 @@ FixMyStreet::override_config { $mech->content_lacks('Role A'); }; + subtest 'assign a user to a role' => sub { + $mech->get_ok('/admin/users/' . $user->id); + $mech->content_contains('Role B'); + $mech->content_lacks('Role Z'); + $mech->submit_form_ok({ with_fields => { + roles => 'Role B', + }}); + $mech->content_like(qr/<option[^>]*selected>Role B/); + is $user->roles->count, 1, 'in one role'; + is $user->user_body_permissions->count, 0, 'permissions removed'; + }; + + subtest 'remove user from role' => sub { + $mech->submit_form_ok({ with_fields => { + roles => undef, + }}, 'remove role'); + }; + }; subtest 'superuser can see all bodies' => sub { diff --git a/templates/web/base/admin/users/form.html b/templates/web/base/admin/users/form.html index 944de802c..c9cc6463b 100644 --- a/templates/web/base/admin/users/form.html +++ b/templates/web/base/admin/users/form.html @@ -155,6 +155,20 @@ [% IF available_permissions AND NOT user.is_superuser %] <li> + <div class="admin-hint"> + <p> + [% loc("Users can be assigned one or more roles to give them all the permissions of those roles. Selecting a role or roles will disable manual permission selection.") %] + </p> + </div> + <label for="roles">[% loc('Role:') %]</label> + <select class="form-control js-multiple" id="roles" name="roles" multiple> + [% FOREACH role IN roles %] + <option data-permissions='["[% role.permissions.join('","') | html %]"]' value="[% role.id %]"[% ' selected' IF user.in_role(role.id) %]>[% role.name | html %]</option> + [% END %] + </select> + </li> + + <li> <fieldset> <legend> <div class="admin-hint"> diff --git a/web/cobrands/fixmystreet/admin.js b/web/cobrands/fixmystreet/admin.js index b947b3e49..8bc956c57 100644 --- a/web/cobrands/fixmystreet/admin.js +++ b/web/cobrands/fixmystreet/admin.js @@ -80,6 +80,32 @@ $(function(){ $("form#user_edit .js-user-categories").toggle(show_area); }); + $('form#user_edit select#roles').change(function() { + var $perms = $('.permissions-checkboxes'); + if ($(this).val()) { + var selected_perms = {}; + $(this).find(':selected').each(function() { + $.each($(this).data('permissions'), function(i, p) { + selected_perms['permissions[' + p + ']'] = 1; + }); + }); + console.log(selected_perms); + $perms.css('color', '#666'); + $perms.find('a').css('color', '#666'); + $perms.find('input').each(function() { + this.checked = selected_perms[this.name] || false; + }); + $perms.find('input').prop('disabled', true); + } else { + $perms.css('color', ''); + $perms.find('a').css('color', ''); + $perms.find('input').each(function() { + this.checked = this.hasAttribute('checked'); + }); + $perms.find('input').prop('disabled', false); + } + }).change(); + // On category edit page, hide the reputation input if inspection isn't required $("form#category_edit #inspection_required").change(function() { var $p = $("form#category_edit #reputation_threshold").closest("p"); |