From d9a018a75002bba009e60eaa87a5fb8276233c4e Mon Sep 17 00:00:00 2001 From: erdgeist Date: Sun, 2 Aug 2026 18:59:08 +0200 Subject: Witness account creation --- app/controllers/users_controller.rb | 2 +- app/helpers/node_actions_helper.rb | 10 ++++++++-- app/models/node_action.rb | 13 +++++++------ app/models/user.rb | 11 +++++++++++ config/locales/de.yml | 1 + config/locales/en.yml | 1 + test/controllers/users_controller_test.rb | 17 +++++++++++++++++ 7 files changed, 46 insertions(+), 9 deletions(-) diff --git a/app/controllers/users_controller.rb b/app/controllers/users_controller.rb index 9b9d64e5..7932e28b 100644 --- a/app/controllers/users_controller.rb +++ b/app/controllers/users_controller.rb @@ -31,7 +31,7 @@ class UsersController < ApplicationController def create @user = User.new user_params - if @user.save + if @user.save_witnessed(:actor => current_user) flash[:notice] = t("flash.users.created", :login => @user.login) redirect_to user_path(@user) else diff --git a/app/helpers/node_actions_helper.rb b/app/helpers/node_actions_helper.rb index 53f6ecd0..019a9cf7 100644 --- a/app/helpers/node_actions_helper.rb +++ b/app/helpers/node_actions_helper.rb @@ -19,10 +19,11 @@ module NodeActionsHelper "otp_enroll" => "shield-lock", "otp_disable" => "shield-off", "otp_reset" => "shield-x", + "user_create" => "user-plus", "user_deactivate" => "user-off", "user_reactivate" => "user-check", - "redaktion_grant" => "user-plus", - "redaktion_revoke" => "user-minus" + "redaktion_grant" => "users-plus", + "redaktion_revoke" => "users-minus" }.freeze def verb_icon action @@ -295,4 +296,9 @@ module NodeActionsHelper t("node_actions.redaktion_revoke", :actor => actor_ref(action), :target => user_participant_ref(action)).html_safe end + + def summarize_user_create action + t("node_actions.user_create", :actor => actor_ref(action), + :target => user_participant_ref(action)).html_safe + end end diff --git a/app/models/node_action.rb b/app/models/node_action.rb index d619aac5..82a3ef44 100644 --- a/app/models/node_action.rb +++ b/app/models/node_action.rb @@ -98,12 +98,13 @@ class NodeAction < ApplicationRecord # "detached_from" -- array of unique_names, only when any # "headline_removed_from" -- array of unique_names, only when any # - # "otp_enroll" / "otp_disable" / "otp_reset" / "user_deactivate" / - # "user_reactivate" (second-factor lifecycle; node column nil; - # participants: the affected User - # User-typed subject. otp_disable is self-service; otp_reset is an - # administrator clearing someone else's factor, where actor and - # participant differ): + # "otp_enroll" / "otp_disable" / "otp_reset" (second-factor + # lifecycle) and "user_create" / "user_deactivate" / + # "user_reactivate" (account lifecycle). Node column nil; + # participants: the affected User, a User-typed subject. + # otp_disable is self-service; otp_reset and all three account + # verbs are an administrator acting on someone else, so actor and + # participant differ: # "target_login" -- flat string, the affected account's login # # Reserved: "demote" (via "trash" | "depublish") for an explicit diff --git a/app/models/user.rb b/app/models/user.rb index 4b70ebe5..49c22584 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -117,6 +117,17 @@ class User < ApplicationRecord redaktion? end + def save_witnessed(actor:) + saved = false + transaction do + saved = save + raise ActiveRecord::Rollback unless saved + NodeAction.record!(:participants => [self], :user => actor, + :action => "user_create", :target_login => login) + end + saved + end + def deactivate!(actor:) return false if alumni? transaction do diff --git a/config/locales/de.yml b/config/locales/de.yml index 63f133f1..9ea828ed 100644 --- a/config/locales/de.yml +++ b/config/locales/de.yml @@ -226,6 +226,7 @@ de: otp_enroll: "%{actor} hat einen zweiten Faktor eingerichtet" otp_disable: "%{actor} hat den zweiten Faktor entfernt" otp_reset: "%{actor} hat den zweiten Faktor von %{target} zurückgesetzt" + user_create: "%{actor} hat das Konto %{target} angelegt" user_deactivate: "%{actor} hat %{target} deaktiviert" user_reactivate: "%{actor} hat %{target} reaktiviert" redaktion_grant: "%{actor} hat %{target} in die Redaktion aufgenommen" diff --git a/config/locales/en.yml b/config/locales/en.yml index 27098e87..7b5b1b86 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -173,6 +173,7 @@ en: otp_enroll: "%{actor} set up a second factor" otp_disable: "%{actor} removed their second factor" otp_reset: "%{actor} reset the second factor of %{target}" + user_create: "%{actor} created the account %{target}" user_deactivate: "%{actor} deactivated %{target}" user_reactivate: "%{actor} reactivated %{target}" redaktion_grant: "%{actor} added %{target} to Redaktion" diff --git a/test/controllers/users_controller_test.rb b/test/controllers/users_controller_test.rb index 0c517647..a1812cb7 100644 --- a/test/controllers/users_controller_test.rb +++ b/test/controllers/users_controller_test.rb @@ -285,4 +285,21 @@ class UsersControllerTest < ActionController::TestCase get :new, params: { :locale => "de" } assert_redirected_to new_elevation_path end + + test "creating a user is witnessed" do + login_as :aaron + elevate_session! + + assert_difference -> { NodeAction.where(:action => "user_create").count }, 1 do + post :create, params: { :locale => "de", :user => { + :login => "newcomer", :email => "n@example.org", + :password => "secret123", :password_confirmation => "secret123" } } + end + + entry = NodeAction.where(:action => "user_create").last + assert_equal users(:aaron).id, entry.user_id + assert_equal "newcomer", entry.metadata["target_login"] + assert_equal User.find_by(:login => "newcomer").id, + entry.action_participants.first.subject_id + end end -- cgit v1.3