diff options
Diffstat (limited to 'app/models')
| -rw-r--r-- | app/models/node_action.rb | 6 | ||||
| -rw-r--r-- | app/models/user.rb | 51 |
2 files changed, 57 insertions, 0 deletions
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 |
