summaryrefslogtreecommitdiff
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/controllers/users_controller.rb20
-rw-r--r--app/helpers/node_actions_helper.rb12
-rw-r--r--app/models/node_action.rb6
-rw-r--r--app/models/user.rb51
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