FIX: properly ban non human users from draft system

Previously we had a partial fix in place where non human users
were not allowed draft sequences, this left edges around where non
human users asked for drafts yet had none.

For example system could already have a few drafts in place.

This also removes and extensibility point we added that is not in use
This commit is contained in:
Sam Saffron 2020-05-26 10:07:00 +10:00
parent 979093787f
commit fc97f7e0e7
No known key found for this signature in database
GPG Key ID: B9606168D2FFD9F5
5 changed files with 31 additions and 23 deletions

View File

@ -10,6 +10,8 @@ class Draft < ActiveRecord::Base
class OutOfSequence < StandardError; end class OutOfSequence < StandardError; end
def self.set(user, key, sequence, data, owner = nil) def self.set(user, key, sequence, data, owner = nil)
return 0 if !User.human_user_id?(user.id)
if SiteSetting.backup_drafts_to_pm_length > 0 && SiteSetting.backup_drafts_to_pm_length < data.length if SiteSetting.backup_drafts_to_pm_length > 0 && SiteSetting.backup_drafts_to_pm_length < data.length
backup_draft(user, key, sequence, data) backup_draft(user, key, sequence, data)
end end
@ -94,6 +96,7 @@ class Draft < ActiveRecord::Base
end end
def self.get(user, key, sequence) def self.get(user, key, sequence)
return if !user || !user.id || !User.human_user_id?(user.id)
opts = { opts = {
user_id: user.id, user_id: user.id,
@ -128,6 +131,8 @@ class Draft < ActiveRecord::Base
end end
def self.clear(user, key, sequence) def self.clear(user, key, sequence)
return if !user || !user.id || !User.human_user_id?(user.id)
current_sequence = DraftSequence.current(user, key) current_sequence = DraftSequence.current(user, key)
# bad caller is a reason to complain # bad caller is a reason to complain

View File

@ -2,10 +2,12 @@
class DraftSequence < ActiveRecord::Base class DraftSequence < ActiveRecord::Base
def self.next!(user, key) def self.next!(user, key)
return nil if !user
user_id = user user_id = user
user_id = user.id unless user.is_a?(Integer) user_id = user.id unless user.is_a?(Integer)
return 0 if invalid_user_id?(user_id) return 0 if !User.human_user_id?(user_id)
h = { user_id: user_id, draft_key: key } h = { user_id: user_id, draft_key: key }
c = DraftSequence.find_by(h) c = DraftSequence.find_by(h)
@ -18,27 +20,17 @@ class DraftSequence < ActiveRecord::Base
end end
def self.current(user, key) def self.current(user, key)
return nil unless user return nil if !user
user_id = user user_id = user
user_id = user.id unless user.is_a?(Integer) user_id = user.id unless user.is_a?(Integer)
return 0 if invalid_user_id?(user_id) return 0 if !User.human_user_id?(user_id)
# perf critical path # perf critical path
r, _ = DB.query_single('select sequence from draft_sequences where user_id = ? and draft_key = ?', user_id, key) r, _ = DB.query_single('select sequence from draft_sequences where user_id = ? and draft_key = ?', user_id, key)
r.to_i r.to_i
end end
cattr_accessor :plugin_ignore_draft_sequence_callbacks
self.plugin_ignore_draft_sequence_callbacks = {}
def self.invalid_user_id?(user_id)
user_id < 0 || self.plugin_ignore_draft_sequence_callbacks.any? do |plugin, callback|
plugin.enabled? ? callback.call(user_id) : false
end
end
private_class_method :invalid_user_id?
end end
# == Schema Information # == Schema Information

View File

@ -306,8 +306,12 @@ class User < ActiveRecord::Base
fields.uniq fields.uniq
end end
def self.human_user_id?(user_id)
user_id > 0
end
def human? def human?
self.id > 0 User.human_user_id?(self.id)
end end
def bot? def bot?

View File

@ -405,15 +405,6 @@ class Plugin::Instance
SeedFu.fixture_paths.concat(paths) SeedFu.fixture_paths.concat(paths)
end end
# Applies to all sites in a multisite environment. Block is not called if
# plugin is not enabled. Block is called with `user_id` and has to return a
# boolean based on whether the given `user_id` should be ignored.
def register_ignore_draft_sequence_callback(&block)
reloadable_patch do |plugin|
::DraftSequence.plugin_ignore_draft_sequence_callbacks[plugin] = block
end
end
def listen_for(event_name) def listen_for(event_name)
return unless self.respond_to?(event_name) return unless self.respond_to?(event_name)
DiscourseEvent.on(event_name, &self.method(event_name)) DiscourseEvent.on(event_name, &self.method(event_name))

View File

@ -12,6 +12,22 @@ describe Draft do
Fabricate(:post) Fabricate(:post)
end end
context 'system user' do
it "can not set drafts" do
# fake a sequence
DraftSequence.create!(user_id: Discourse.system_user.id, draft_key: "abc", sequence: 10)
seq = Draft.set(Discourse.system_user, "abc", 0, { reply: 'hi' }.to_json)
expect(seq).to eq(0)
draft = Draft.get(Discourse.system_user, "abc", 0)
expect(draft).to eq(nil)
draft = Draft.get(Discourse.system_user, "abc", 1)
expect(draft).to eq(nil)
end
end
context 'backup_drafts_to_pm_length' do context 'backup_drafts_to_pm_length' do
it "correctly backs up drafts to a personal message" do it "correctly backs up drafts to a personal message" do
SiteSetting.backup_drafts_to_pm_length = 1 SiteSetting.backup_drafts_to_pm_length = 1