2
0
Fork 0
mirror of https://github.com/discourse/discourse.git synced 2026-03-03 23:54:20 +08:00
discourse/spec/models/user_history_spec.rb
Régis Hanol 9892628a50 SECURITY: Restrict staff action logs visibility for moderators
Previously, moderators had full access to all staff action logs, which
exposed sensitive information including webhook secrets, API keys, site
settings, private messages, and restricted categories.

This change implements an allowlist approach where moderators can only
see actions relevant to their role (user management, posts, topics,
badges, etc.) while admin-only actions (site settings, webhooks, API
keys, themes, etc.) are hidden.

Additionally, content-level redaction ensures moderators cannot see
details of logs referencing private topics, restricted categories, or
deleted content they don't have access to.

Site setting gates control visibility of category, trust level, and
email actions based on existing moderator permission settings.

Ref - t/171137
2026-01-28 17:11:14 +00:00

179 lines
5.7 KiB
Ruby

# frozen_string_literal: true
RSpec.describe UserHistory do
describe "#actions" do
context "when verifying enum sequence" do
let!(:actions) { described_class.actions }
it "'delete_user' should be at 1st position" do
expect(actions[:delete_user]).to eq(1)
end
it "'change_site_text' should be at 29th position" do
expect(actions[:change_site_text]).to eq(29)
end
end
end
describe "#staff_action_records" do
context "with some records" do
fab!(:admin)
let(:custom_type) { "confirmed_ham" }
let!(:change_site_setting) do
UserHistory.create!(
action: UserHistory.actions[:change_site_setting],
subject: "title",
previous_value: "Old",
new_value: "New",
)
end
let!(:change_trust_level) do
UserHistory.create!(
action: UserHistory.actions[:change_trust_level],
target_user_id: Fabricate(:user).id,
details: "stuff happened",
)
end
let!(:custom_history) do
StaffActionLogger.new(admin).log_custom("confirmed_ham", admin_only: true)
end
it "returns all records for admins" do
records = described_class.staff_action_records(admin).to_a
expect(records.size).to eq(3)
end
it "only returns moderator-visible actions to moderators (allowlist approach)" do
records = described_class.staff_action_records(Fabricate(:moderator)).to_a
expect(records).to contain_exactly(change_trust_level)
expect(records).not_to include(change_site_setting, custom_history)
end
it "filters by action" do
records =
described_class.staff_action_records(
admin,
action_id: change_site_setting.action_before_type_cast,
).to_a
expect(records.size).to eq(1)
expect(records.first).to eq(change_site_setting)
end
it "filters by action_name" do
records =
described_class.staff_action_records(admin, action_name: "change_site_setting").to_a
expect(records.size).to eq(1)
expect(records.first).to eq(change_site_setting)
end
it "Uses action_name as custom_type when searching for custom_staff logs" do
records =
described_class.staff_action_records(
admin,
action_name: custom_type,
action_id: described_class.actions[:custom_staff],
).to_a
expect(records.size).to eq(1)
expect(records.first).to eq(custom_history)
end
it "filters by start and/or end date" do
freeze_time
10.times do |i|
Fabricate(
:user_history,
action: UserHistory.actions[:suspend_user],
created_at: i.days.ago,
)
end
records =
described_class.staff_action_records(admin, start_date: 7.days.ago, end_date: 2.days.ago)
expect(records.size).to eq(7 - 2 + 1)
end
end
end
describe ".site_setting_excluded_actions" do
it "excludes category actions when moderators_manage_categories is disabled" do
SiteSetting.moderators_manage_categories = false
expect(described_class.site_setting_excluded_actions).to include(
:create_category,
:change_category_settings,
:delete_category,
)
end
it "includes category actions when moderators_manage_categories is enabled" do
SiteSetting.moderators_manage_categories = true
expect(described_class.site_setting_excluded_actions).not_to include(
:create_category,
:change_category_settings,
:delete_category,
)
end
it "excludes trust level actions when moderators_change_trust_levels is disabled" do
SiteSetting.moderators_change_trust_levels = false
expect(described_class.site_setting_excluded_actions).to include(
:change_trust_level,
:lock_trust_level,
:unlock_trust_level,
)
end
it "includes trust level actions when moderators_change_trust_levels is enabled" do
SiteSetting.moderators_change_trust_levels = true
expect(described_class.site_setting_excluded_actions).not_to include(
:change_trust_level,
:lock_trust_level,
:unlock_trust_level,
)
end
it "excludes email actions when moderators_view_emails is disabled" do
SiteSetting.moderators_view_emails = false
expect(described_class.site_setting_excluded_actions).to include(
:check_email,
:add_email,
:update_email,
:destroy_email,
)
end
it "includes email actions when moderators_view_emails is enabled" do
SiteSetting.moderators_view_emails = true
expect(described_class.site_setting_excluded_actions).not_to include(
:check_email,
:add_email,
:update_email,
:destroy_email,
)
end
end
describe ".moderator_visible_action_ids" do
it "returns action IDs for moderator-visible actions" do
ids = described_class.moderator_visible_action_ids
expect(ids).to include(described_class.actions[:suspend_user])
expect(ids).to include(described_class.actions[:delete_topic])
expect(ids).not_to include(described_class.actions[:change_site_setting])
end
it "responds dynamically to site setting changes" do
SiteSetting.moderators_manage_categories = true
expect(described_class.moderator_visible_action_ids).to include(
described_class.actions[:create_category],
)
SiteSetting.moderators_manage_categories = false
expect(described_class.moderator_visible_action_ids).not_to include(
described_class.actions[:create_category],
)
end
end
end