Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add before and after guardian checks #26936

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/discourse_plugin_registry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ def self.define_filtered_register(register_name)
define_register :demon_processes, Set
define_register :groups_callback_for_users_search_controller_action, Hash
define_register :mail_pollers, Set
define_register :guardian_checks, Hash

define_filtered_register :staff_user_custom_fields
define_filtered_register :public_user_custom_fields
Expand Down
2 changes: 2 additions & 0 deletions lib/guardian.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
require "guardian/tag_guardian"
require "guardian/topic_guardian"
require "guardian/user_guardian"
require "guardian/plugin_checks"

# The guardian is responsible for confirming access to various site resources and operations
class Guardian
Expand All @@ -23,6 +24,7 @@ class Guardian
include TagGuardian
include TopicGuardian
include UserGuardian
include PluginChecks

class AnonymousUser
def blank?
Expand Down
26 changes: 26 additions & 0 deletions lib/guardian/plugin_checks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
# frozen_string_literal: true

module PluginChecks
def self.included(mod)
mod.instance_methods.each do |method_name|
if method_name.to_s =~ /\Acan_(.*)/
method = mod.instance_method(method_name)
check_name = Regexp.last_match[1].chomp("?")
mod.define_method(method_name) do |*args, **kwargs|
result = true
return false unless PluginChecks.pass?(self, :before, check_name, result, *args, **kwargs)
result = method.bind(self).call(*args, **kwargs)
PluginChecks.pass?(self, :after, check_name, result, *args, **kwargs)
end
end
end
end

def self.pass?(instance, check_type, check_name, result, *args, **kwargs)
return result unless DiscoursePluginRegistry.guardian_checks[check_type].present?
checks = DiscoursePluginRegistry.guardian_checks[check_type][check_name.to_sym]
return result if checks.blank?
checks.each { |check| result = check[:proc].call(instance, result, *args, **kwargs) }
!!result
end
end
14 changes: 14 additions & 0 deletions lib/plugin/instance.rb
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,20 @@ def register_modifier(modifier_name, &blk)
DiscoursePluginRegistry.register_modifier(self, modifier_name, &blk)
end

def add_guardian_check(check_type, check_name, priority = 0, &block)
raise ArgumentError.new("Check type is not valid") unless %i[before after].include?(check_type)

reloadable_patch do |plugin|
DiscoursePluginRegistry.guardian_checks[check_type] ||= {}
DiscoursePluginRegistry.guardian_checks[check_type][check_name] ||= []
DiscoursePluginRegistry.guardian_checks[check_type][check_name] << {
priority: priority,
proc: Proc.new { |*args| block.call(*args) if plugin.enabled? },
}
DiscoursePluginRegistry.guardian_checks[check_type][check_name].sort_by! { |h| -h[:priority] }
end
end

# Applies to all sites in a multisite environment. Ignores plugin.enabled?
def add_report(name, &block)
reloadable_patch { |plugin| Report.add_report(name, &block) }
Expand Down
49 changes: 49 additions & 0 deletions spec/lib/plugin/instance_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -998,4 +998,53 @@ def create_notification!
expect(sum).to eq(3)
end
end

describe "#add_guardian_check" do
let(:plugin) { Plugin::Instance.new }
let!(:admin) { Fabricate(:admin, refresh_auto_groups: true) }
let!(:user) { Fabricate(:user) }
let!(:post) { Fabricate(:post) }

after { DiscoursePluginRegistry.reset! }

context "with before check" do
context "when check is true" do
it "guardian method returns method value" do
plugin.add_guardian_check(:before, :edit_post) { true }
expect(Guardian.new(admin).can_edit_post?(post)).to be_truthy
expect(Guardian.new(user).can_edit_post?(post)).to be_falsey
end
end

context "when check is false" do
it "guardian method returns false" do
plugin.add_guardian_check(:before, :edit_post) { false }
expect(Guardian.new(admin).can_edit_post?(post)).to be_falsey
end
end
end

context "with after check" do
context "when method value is true" do
it "guardian method returns check value" do
plugin.add_guardian_check(:after, :edit_post) { false }
expect(Guardian.new(admin).can_edit_post?(post)).to be_falsey
end
end

context "when method value is false" do
it "guardian method returns check value" do
plugin.add_guardian_check(:after, :edit_post) { true }
expect(Guardian.new(user).can_edit_post?(post)).to be_truthy
end
end
end

it "respects check priority" do
plugin.add_guardian_check(:after, :edit_post, 2) { false }
plugin.add_guardian_check(:after, :edit_post, 0) { true }
plugin.add_guardian_check(:after, :edit_post, 1) { false }
expect(Guardian.new(user).can_edit_post?(post)).to be_truthy
end
end
end