discourse/plugins/discourse-policy/lib/post_validator.rb
Alan Guo Xiang Tan 451ed6aa98 SECURITY: Validate policy permissions on post save
The `create_policy_allowed_groups` setting controls who can create
policies, but it only gates the composer UI and the
`post_process_cooked` event handler. It does not prevent unauthorized
users from injecting `[policy]` markup directly into post raw — for
example, by editing a wiki post.

Add `DiscoursePolicy::PostValidator` as an ActiveRecord validation on
`Post`. When policies are added, removed, or modified, both the post
owner and the acting user must belong to `create_policy_allowed_groups`
or the save is rejected. Policies inside blockquotes are ignored.
2026-03-19 15:21:28 +00:00

44 lines
1 KiB
Ruby
Vendored

# frozen_string_literal: true
module DiscoursePolicy
class PostValidator
def initialize(post)
@post = post
end
def validate_post
old_raw = @post.changes[:raw]&.first
new_raw = @post.raw
return true if !old_raw&.include?("[/policy]") && !new_raw.include?("[/policy]")
old_policies = extract_policies(@post.cooked)
new_policies = extract_policies(PrettyText.cook(new_raw, {}))
return true if old_policies == new_policies
if !user_allowed?(@post.acting_user) || !user_allowed?(@post.user)
@post.errors.add(:base, I18n.t("discourse_policy.errors.no_policy_permission"))
return false
end
true
end
private
def extract_policies(cooked)
return [] if cooked.blank?
Nokogiri::HTML5
.fragment(cooked)
.css("div.policy")
.reject { |p| p.ancestors("blockquote").any? }
.map(&:to_html)
end
def user_allowed?(user)
user&.in_any_groups?(SiteSetting.create_policy_allowed_groups_map)
end
end
end