diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/controllers/users_controller.rb | 23 | ||||
| -rw-r--r-- | app/helpers/node_actions_helper.rb | 14 | ||||
| -rw-r--r-- | app/models/node_action.rb | 5 | ||||
| -rw-r--r-- | app/models/user.rb | 20 | ||||
| -rw-r--r-- | app/views/users/_user.html.erb | 22 |
5 files changed, 71 insertions, 13 deletions
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 95dff220..7bf23f17 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb | |||
| @@ -4,7 +4,7 @@ class UsersController < ApplicationController | |||
| 4 | # Private | 4 | # Private |
| 5 | 5 | ||
| 6 | before_action :login_required | 6 | before_action :login_required |
| 7 | before_action :find_user, :only => [:show, :edit, :update, :destroy, :reset_otp] | 7 | before_action :find_user, :only => [:show, :edit, :update, :reset_otp, :deactivate, :reactivate] |
| 8 | before_action :verify_status, :except => [:index, :show] | 8 | before_action :verify_status, :except => [:index, :show] |
| 9 | 9 | ||
| 10 | layout 'admin' | 10 | layout 'admin' |
| @@ -53,8 +53,25 @@ class UsersController < ApplicationController | |||
| 53 | def show | 53 | def show |
| 54 | end | 54 | end |
| 55 | 55 | ||
| 56 | def destroy | 56 | def deactivate |
| 57 | @user.destroy if @user | 57 | return deny_user_access unless current_user.is_admin? |
| 58 | |||
| 59 | if @user == current_user | ||
| 60 | flash[:error] = t("flash.users.cannot_deactivate_self") | ||
| 61 | elsif @user.deactivate!(:actor => current_user) | ||
| 62 | flash[:notice] = t("flash.users.deactivated", :login => @user.login) | ||
| 63 | end | ||
| 64 | |||
| 65 | redirect_to users_path | ||
| 66 | end | ||
| 67 | |||
| 68 | def reactivate | ||
| 69 | return deny_user_access unless current_user.is_admin? | ||
| 70 | |||
| 71 | if @user.reactivate!(:actor => current_user) | ||
| 72 | flash[:notice] = t("flash.users.reactivated", :login => @user.login) | ||
| 73 | end | ||
| 74 | |||
| 58 | redirect_to users_path | 75 | redirect_to users_path |
| 59 | end | 76 | end |
| 60 | 77 | ||
diff --git a/app/helpers/node_actions_helper.rb b/app/helpers/node_actions_helper.rb index 4cbe741c..f57ef84f 100644 --- a/app/helpers/node_actions_helper.rb +++ b/app/helpers/node_actions_helper.rb | |||
| @@ -18,7 +18,9 @@ module NodeActionsHelper | |||
| 18 | "asset_destroy" => "file-x", | 18 | "asset_destroy" => "file-x", |
| 19 | "otp_enroll" => "shield-lock", | 19 | "otp_enroll" => "shield-lock", |
| 20 | "otp_disable" => "shield-off", | 20 | "otp_disable" => "shield-off", |
| 21 | "otp_reset" => "shield-x" | 21 | "otp_reset" => "shield-x", |
| 22 | "user_deactivate" => "user-off", | ||
| 23 | "user_reactivate" => "user-check" | ||
| 22 | }.freeze | 24 | }.freeze |
| 23 | 25 | ||
| 24 | def verb_icon action | 26 | def verb_icon action |
| @@ -271,4 +273,14 @@ module NodeActionsHelper | |||
| 271 | t("node_actions.otp_reset", :actor => actor_ref(action), | 273 | t("node_actions.otp_reset", :actor => actor_ref(action), |
| 272 | :target => user_participant_ref(action)).html_safe | 274 | :target => user_participant_ref(action)).html_safe |
| 273 | end | 275 | end |
| 276 | |||
| 277 | def summarize_user_deactivate action | ||
| 278 | t("node_actions.user_deactivate", :actor => actor_ref(action), | ||
| 279 | :target => user_participant_ref(action)).html_safe | ||
| 280 | end | ||
| 281 | |||
| 282 | def summarize_user_reactivate action | ||
| 283 | t("node_actions.user_reactivate", :actor => actor_ref(action), | ||
| 284 | :target => user_participant_ref(action)).html_safe | ||
| 285 | end | ||
| 274 | end | 286 | end |
diff --git a/app/models/node_action.rb b/app/models/node_action.rb index fec5a062..d619aac5 100644 --- a/app/models/node_action.rb +++ b/app/models/node_action.rb | |||
| @@ -98,8 +98,9 @@ class NodeAction < ApplicationRecord | |||
| 98 | # "detached_from" -- array of unique_names, only when any | 98 | # "detached_from" -- array of unique_names, only when any |
| 99 | # "headline_removed_from" -- array of unique_names, only when any | 99 | # "headline_removed_from" -- array of unique_names, only when any |
| 100 | # | 100 | # |
| 101 | # "otp_enroll" / "otp_disable" / "otp_reset" (second-factor lifecycle; | 101 | # "otp_enroll" / "otp_disable" / "otp_reset" / "user_deactivate" / |
| 102 | # node column nil; participants: the affected User -- the table's first | 102 | # "user_reactivate" (second-factor lifecycle; node column nil; |
| 103 | # participants: the affected User | ||
| 103 | # User-typed subject. otp_disable is self-service; otp_reset is an | 104 | # User-typed subject. otp_disable is self-service; otp_reset is an |
| 104 | # administrator clearing someone else's factor, where actor and | 105 | # administrator clearing someone else's factor, where actor and |
| 105 | # participant differ): | 106 | # participant differ): |
diff --git a/app/models/user.rb b/app/models/user.rb index 2e9da86c..1728521a 100644 --- a/app/models/user.rb +++ b/app/models/user.rb | |||
| @@ -105,6 +105,26 @@ class User < ApplicationRecord | |||
| 105 | roles.map { |r| I18n.t("users.roles.#{r}", :default => r) } | 105 | roles.map { |r| I18n.t("users.roles.#{r}", :default => r) } |
| 106 | end | 106 | end |
| 107 | 107 | ||
| 108 | def deactivate!(actor:) | ||
| 109 | return false if alumni? | ||
| 110 | transaction do | ||
| 111 | update_column(:roles, (roles | ["alumni"]).sort) | ||
| 112 | NodeAction.record!(:participants => [self], :user => actor, | ||
| 113 | :action => "user_deactivate", :target_login => login) | ||
| 114 | end | ||
| 115 | true | ||
| 116 | end | ||
| 117 | |||
| 118 | def reactivate!(actor:) | ||
| 119 | return false unless alumni? | ||
| 120 | transaction do | ||
| 121 | update_column(:roles, (roles - ["alumni"]).sort) | ||
| 122 | NodeAction.record!(:participants => [self], :user => actor, | ||
| 123 | :action => "user_reactivate", :target_login => login) | ||
| 124 | end | ||
| 125 | true | ||
| 126 | end | ||
| 127 | |||
| 108 | # otp_secret present == enrolled. otp_pending_secret holds the secret | 128 | # otp_secret present == enrolled. otp_pending_secret holds the secret |
| 109 | # between QR display and first-code confirmation. otp_consumed_timestep | 129 | # between QR display and first-code confirmation. otp_consumed_timestep |
| 110 | # makes every accepted code single-use (replay guard within the drift | 130 | # makes every accepted code single-use (replay guard within the drift |
diff --git a/app/views/users/_user.html.erb b/app/views/users/_user.html.erb index 04884be8..ff9d4e37 100644 --- a/app/views/users/_user.html.erb +++ b/app/views/users/_user.html.erb | |||
| @@ -9,14 +9,22 @@ | |||
| 9 | <% end %> | 9 | <% end %> |
| 10 | </td> | 10 | </td> |
| 11 | <td><%= link_to t("admin.common.show"), user_path(user) %></td> | 11 | <td><%= link_to t("admin.common.show"), user_path(user) %></td> |
| 12 | <% if current_user.admin? || current_user == user %> | ||
| 13 | <td><%= link_to t("admin.common.edit"), edit_user_path(user) %></td> | ||
| 14 | <td> | 12 | <td> |
| 15 | <%= button_to user_path(user), method: :delete, | 13 | <% if current_user.admin? || current_user == user %> |
| 16 | form: { data: { confirm: t(".confirm_destroy", :login => user.login) }, class: 'button_to destructive' } do %> | 14 | <%= link_to t("admin.common.edit"), edit_user_path(user) %> |
| 17 | <%= icon("trash", library: "tabler", "aria-hidden": true) %> <%= t("admin.common.destroy") %> | 15 | <% end %> |
| 18 | <% end %> | 16 | </td> |
| 17 | <td> | ||
| 18 | <% if current_user.admin? && current_user != user %> | ||
| 19 | <% if user.alumni? %> | ||
| 20 | <%= button_to t(".reactivate"), reactivate_user_path(user), method: :put, | ||
| 21 | form: { class: 'button_to state_changing' } %> | ||
| 22 | <% else %> | ||
| 23 | <%= button_to t(".deactivate"), deactivate_user_path(user), method: :put, | ||
| 24 | form: { data: { confirm: t(".confirm_deactivate", :login => user.login) }, | ||
| 25 | class: 'button_to destructive' } %> | ||
| 26 | <% end %> | ||
| 27 | <% end %> | ||
| 19 | </td> | 28 | </td> |
| 20 | <% end %> | ||
| 21 | </tr> | 29 | </tr> |
| 22 | <% end %> | 30 | <% end %> |
