diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/controllers/users_controller.rb | 20 | ||||
| -rw-r--r-- | app/helpers/node_actions_helper.rb | 12 | ||||
| -rw-r--r-- | app/models/node_action.rb | 6 | ||||
| -rw-r--r-- | app/models/user.rb | 51 |
4 files changed, 86 insertions, 3 deletions
diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 1bd436e5..b06d11fd 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb | |||
| @@ -50,12 +50,26 @@ class UsersController < ApplicationController | |||
| 50 | end | 50 | end |
| 51 | 51 | ||
| 52 | permitted = user_params | 52 | permitted = user_params |
| 53 | desired = permitted.key?(:roles) ? permitted.delete(:roles) : nil | ||
| 54 | refusals = [] | ||
| 55 | saved = false | ||
| 53 | 56 | ||
| 54 | if @user.update(permitted) | 57 | User.transaction do |
| 58 | saved = @user.update(permitted) | ||
| 59 | raise ActiveRecord::Rollback unless saved | ||
| 60 | |||
| 61 | refusals = desired ? @user.update_roles!(desired, :actor => current_user) : [] | ||
| 62 | raise ActiveRecord::Rollback if refusals.any? | ||
| 63 | end | ||
| 64 | |||
| 65 | if !saved | ||
| 66 | render :edit | ||
| 67 | elsif refusals.any? | ||
| 68 | flash.now[:error] = refusals.map { |r| t("flash.users.#{r}", :login => @user.login) }.to_sentence | ||
| 69 | render :edit | ||
| 70 | else | ||
| 55 | flash[:notice] = t("flash.users.updated", :login => @user.login) | 71 | flash[:notice] = t("flash.users.updated", :login => @user.login) |
| 56 | redirect_to user_path(@user) | 72 | redirect_to user_path(@user) |
| 57 | else | ||
| 58 | render :edit | ||
| 59 | end | 73 | end |
| 60 | end | 74 | end |
| 61 | 75 | ||
diff --git a/app/helpers/node_actions_helper.rb b/app/helpers/node_actions_helper.rb index 7dd55bdb..4cf990b8 100644 --- a/app/helpers/node_actions_helper.rb +++ b/app/helpers/node_actions_helper.rb | |||
| @@ -24,6 +24,8 @@ module NodeActionsHelper | |||
| 24 | "user_reactivate" => "user-check", | 24 | "user_reactivate" => "user-check", |
| 25 | "redaktion_grant" => "users-plus", | 25 | "redaktion_grant" => "users-plus", |
| 26 | "redaktion_revoke" => "users-minus", | 26 | "redaktion_revoke" => "users-minus", |
| 27 | "admin_grant" => "shield-plus", | ||
| 28 | "admin_revoke" => "shield-minus", | ||
| 27 | "event_create" => "calendar-plus", | 29 | "event_create" => "calendar-plus", |
| 28 | "event_update" => "calendar-event", | 30 | "event_update" => "calendar-event", |
| 29 | "event_destroy" => "calendar-x" | 31 | "event_destroy" => "calendar-x" |
| @@ -371,6 +373,16 @@ module NodeActionsHelper | |||
| 371 | :target => user_participant_ref(action)).html_safe | 373 | :target => user_participant_ref(action)).html_safe |
| 372 | end | 374 | end |
| 373 | 375 | ||
| 376 | def summarize_admin_grant action | ||
| 377 | t("node_actions.admin_grant", :actor => actor_ref(action), | ||
| 378 | :target => user_participant_ref(action)).html_safe | ||
| 379 | end | ||
| 380 | |||
| 381 | def summarize_admin_revoke action | ||
| 382 | t("node_actions.admin_revoke", :actor => actor_ref(action), | ||
| 383 | :target => user_participant_ref(action)).html_safe | ||
| 384 | end | ||
| 385 | |||
| 374 | def summarize_user_create action | 386 | def summarize_user_create action |
| 375 | t("node_actions.user_create", :actor => actor_ref(action), | 387 | t("node_actions.user_create", :actor => actor_ref(action), |
| 376 | :target => user_participant_ref(action)).html_safe | 388 | :target => user_participant_ref(action)).html_safe |
diff --git a/app/models/node_action.rb b/app/models/node_action.rb index 0167762b..bfa469b1 100644 --- a/app/models/node_action.rb +++ b/app/models/node_action.rb | |||
| @@ -105,6 +105,12 @@ class NodeAction < ApplicationRecord | |||
| 105 | # otp_disable is self-service; otp_reset and all three account | 105 | # otp_disable is self-service; otp_reset and all three account |
| 106 | # verbs are an administrator acting on someone else, so actor and | 106 | # verbs are an administrator acting on someone else, so actor and |
| 107 | # participant differ: | 107 | # participant differ: |
| 108 | # "redaktion_grant" / "redaktion_revoke" / "admin_grant" / | ||
| 109 | # "admin_revoke" -- role changes. Both pairs come from | ||
| 110 | # User#grant_* / #revoke_*, so the roles form reaches them through | ||
| 111 | # update_roles! rather than writing the attribute: witnessing is the | ||
| 112 | # reason the form does not touch roles directly. Alumni changes record | ||
| 113 | # as user_deactivate / user_reactivate, not as a role verb. | ||
| 108 | # "target_login" -- flat string, the affected account's login | 114 | # "target_login" -- flat string, the affected account's login |
| 109 | # | 115 | # |
| 110 | # "event_create" / "event_update" / "event_destroy" (calendar | 116 | # "event_create" / "event_update" / "event_destroy" (calendar |
diff --git a/app/models/user.rb b/app/models/user.rb index adfdc564..786f8d14 100644 --- a/app/models/user.rb +++ b/app/models/user.rb | |||
| @@ -132,6 +132,7 @@ class User < ApplicationRecord | |||
| 132 | 132 | ||
| 133 | def deactivate!(actor:) | 133 | def deactivate!(actor:) |
| 134 | return false if alumni? | 134 | return false if alumni? |
| 135 | return false if actor == self | ||
| 135 | transaction do | 136 | transaction do |
| 136 | update_column(:roles, (roles | ["alumni"]).sort) | 137 | update_column(:roles, (roles | ["alumni"]).sort) |
| 137 | NodeAction.record!(:participants => [self], :user => actor, | 138 | NodeAction.record!(:participants => [self], :user => actor, |
| @@ -174,6 +175,56 @@ class User < ApplicationRecord | |||
| 174 | :revoked | 175 | :revoked |
| 175 | end | 176 | end |
| 176 | 177 | ||
| 178 | def grant_admin!(actor:) | ||
| 179 | return :already if is_admin? | ||
| 180 | return :no_second_factor unless otp_enrolled? | ||
| 181 | |||
| 182 | transaction do | ||
| 183 | update_column(:roles, (roles | ["admin"]).sort) | ||
| 184 | NodeAction.record!(:participants => [self], :user => actor, | ||
| 185 | :action => "admin_grant", :target_login => login) | ||
| 186 | end | ||
| 187 | :granted | ||
| 188 | end | ||
| 189 | |||
| 190 | def revoke_admin!(actor:) | ||
| 191 | return :already unless is_admin? | ||
| 192 | return :self unless actor != self | ||
| 193 | |||
| 194 | transaction do | ||
| 195 | update_column(:roles, (roles - ["admin"]).sort) | ||
| 196 | NodeAction.record!(:participants => [self], :user => actor, | ||
| 197 | :action => "admin_revoke", :target_login => login) | ||
| 198 | end | ||
| 199 | :revoked | ||
| 200 | end | ||
| 201 | |||
| 202 | def update_roles!(desired, actor:) | ||
| 203 | desired = Array(desired).map(&:to_s) & ROLES | ||
| 204 | refusals = [] | ||
| 205 | |||
| 206 | transaction do | ||
| 207 | refusals << :admin_not_self if is_admin? && !desired.include?("admin") && actor == self | ||
| 208 | refusals << :redaktion_not_self if redaktion? && !desired.include?("redaktion") && actor == self | ||
| 209 | refusals << :cannot_deactivate_self if !alumni? && desired.include?("alumni") && actor == self | ||
| 210 | refusals << :admin_needs_otp if !is_admin? && desired.include?("admin") && !otp_enrolled? | ||
| 211 | refusals << :redaktion_needs_otp if !redaktion? && desired.include?("redaktion") && !otp_enrolled? | ||
| 212 | |||
| 213 | raise ActiveRecord::Rollback if refusals.any? | ||
| 214 | |||
| 215 | revoke_admin!(:actor => actor) if is_admin? && !desired.include?("admin") | ||
| 216 | revoke_redaktion!(:actor => actor) if redaktion? && !desired.include?("redaktion") | ||
| 217 | reactivate!(:actor => actor) if alumni? && !desired.include?("alumni") | ||
| 218 | |||
| 219 | grant_admin!(:actor => actor) if !is_admin? && desired.include?("admin") | ||
| 220 | grant_redaktion!(:actor => actor) if !redaktion? && desired.include?("redaktion") | ||
| 221 | |||
| 222 | deactivate!(:actor => actor) if !alumni? && desired.include?("alumni") | ||
| 223 | end | ||
| 224 | |||
| 225 | refusals | ||
| 226 | end | ||
| 227 | |||
| 177 | # otp_secret present == enrolled. otp_pending_secret holds the secret | 228 | # otp_secret present == enrolled. otp_pending_secret holds the secret |
| 178 | # between QR display and first-code confirmation. otp_consumed_timestep | 229 | # between QR display and first-code confirmation. otp_consumed_timestep |
| 179 | # makes every accepted code single-use (replay guard within the drift | 230 | # makes every accepted code single-use (replay guard within the drift |
