diff options
| author | erdgeist <erdgeist@erdgeist.org> | 2026-08-04 05:40:54 +0200 |
|---|---|---|
| committer | erdgeist <erdgeist@erdgeist.org> | 2026-08-04 05:40:54 +0200 |
| commit | 974062f16169f6d07f2289564be6d72610b8770e (patch) | |
| tree | b18f297a9d4326a3ce36f5192fbc4992fcb28e7d /app/models/user.rb | |
| parent | be2cdea85177ac016e56dcce6cbe6015d754adf5 (diff) | |
Witness every promotion and demotion made through the roles form
Diffstat (limited to 'app/models/user.rb')
| -rw-r--r-- | app/models/user.rb | 51 |
1 files changed, 51 insertions, 0 deletions
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 |
