diff --git a/lib/discourse.rb b/lib/discourse.rb index c6280786eb2..e62af531912 100644 --- a/lib/discourse.rb +++ b/lib/discourse.rb @@ -873,6 +873,10 @@ module Discourse @system_users[current_db] ||= User.find_by(id: SYSTEM_USER_ID) end + def self.basic_user + Guardian.basic_user + end + def self.store if SiteSetting.Upload.enable_s3_uploads @s3_store_loaded ||= require "file_store/s3_store" diff --git a/lib/guardian.rb b/lib/guardian.rb index 6af80ee6233..3232c1fd684 100644 --- a/lib/guardian.rb +++ b/lib/guardian.rb @@ -78,6 +78,64 @@ class Guardian end end + # In some cases, we want a user that is not totally anonymous, but has + # the absolute baseline of access to the forum, acting like a TL0 user + # that is logged in. This represents someone who cannot access any secure + # categories or PMs but can read public topics. + class BasicUser + def blank? + false + end + def admin? + false + end + def staff? + false + end + def moderator? + false + end + def anonymous? + false + end + def approved? + true + end + def staged? + false + end + def silenced? + false + end + def is_system_user? + false + end + def bot? + false + end + def secure_category_ids + [] + end + def groups + @groups ||= Group.where(id: Group::AUTO_GROUPS[:trust_level_0]) + end + def has_trust_level?(level) + level == TrustLevel[0] + end + def has_trust_level_or_staff?(level) + has_trust_level?(level) + end + def email + nil + end + def whisperer? + false + end + def in_any_groups?(group_ids) + group_ids.include?(Group::AUTO_GROUPS[:everyone]) || (group_ids & groups.map(&:id)).any? + end + end + attr_reader :request def initialize(user = nil, request = nil) @@ -85,6 +143,14 @@ class Guardian @request = request end + def self.anon_user(request: nil) + new(AnonymousUser.new, request) + end + + def self.basic_user(request: nil) + new(BasicUser.new, request) + end + def user @user.presence end diff --git a/lib/oneboxer.rb b/lib/oneboxer.rb index 5349b8a1d05..dada3904ec3 100644 --- a/lib/oneboxer.rb +++ b/lib/oneboxer.rb @@ -385,19 +385,22 @@ module Oneboxer def self.local_topic(url, route, opts) if current_user = User.find_by(id: opts[:user_id]) if current_category = Category.find_by(id: opts[:category_id]) - return unless Guardian.new(current_user).can_see_category?(current_category) + return if !current_user.guardian.can_see_category?(current_category) end if current_topic = Topic.find_by(id: opts[:topic_id]) - return unless Guardian.new(current_user).can_see_topic?(current_topic) + return if !current_user.guardian.can_see_topic?(current_topic) end end - return unless topic = Topic.find_by(id: route[:id] || route[:topic_id]) - return if topic.private_message? + topic = Topic.find_by(id: route[:id] || route[:topic_id]) + return if topic.blank? || topic.private_message? + # If we are oneboxing from one category to another, we need to use a basic guardian + # because some users can see more than others and we need the absolute baseline of + # access control here. if current_category.blank? || current_category.id != topic.category_id - return unless Guardian.new.can_see_topic?(topic) + return if !Guardian.basic_user.can_see_topic?(topic) end topic diff --git a/spec/lib/oneboxer_spec.rb b/spec/lib/oneboxer_spec.rb index 8912b91adf2..e595e311ed4 100644 --- a/spec/lib/oneboxer_spec.rb +++ b/spec/lib/oneboxer_spec.rb @@ -932,4 +932,92 @@ RSpec.describe Oneboxer do expect(Oneboxer.preview(url)).to eq("Custom Onebox for Wizard") end end + + describe ".local_topic" do + fab!(:topic) + fab!(:user) + + let(:url) { topic.url } + let(:route) { Discourse.route_for(url) } + + context "when user_id is not provided" do + let(:opts) { {} } + + it "returns nil if the topic is a private message" do + topic.update!(archetype: Archetype.private_message, category: nil) + expect(Oneboxer.local_topic(url, route, opts)).to eq(nil) + end + + it "returns nil if basic user cannot see the topic" do + topic.update!(category: Fabricate(:private_category, group: Fabricate(:group))) + expect(Oneboxer.local_topic(url, route, opts)).to eq(nil) + end + + it "returns topic if basic user can see the topic" do + expect(Oneboxer.local_topic(url, route, opts)).to eq(topic) + end + end + + context "when user_id is provided" do + context "when category_id is provided" do + fab!(:category) + + let(:opts) { { category_id: category.id, user_id: user.id } } + + before { topic.update!(category: category) } + + it "returns nil if the user cannot see the category" do + category.update!(read_restricted: true) + expect(Oneboxer.local_topic(url, route, opts)).to eq(nil) + end + + it "returns the topic if the user can see the category" do + expect(Oneboxer.local_topic(url, route, opts)).to eq(topic) + end + + it "returns nil if basic user users cannot see the topic" do + topic.update!(category: Fabricate(:private_category, group: Fabricate(:group))) + expect(Oneboxer.local_topic(url, route, opts)).to eq(nil) + end + + it "returns nil if the topic is a private message" do + topic.update!(archetype: Archetype.private_message, category: nil) + expect(Oneboxer.local_topic(url, route, opts)).to eq(nil) + end + + context "when category_id is mismatched" do + fab!(:other_category) { Fabricate(:private_category, group: Fabricate(:group)) } + + before { topic.update!(category: other_category) } + + it "returns nil if the basic user cannot see the topic" do + expect(Oneboxer.local_topic(url, route, opts)).to eq(nil) + end + + it "returns topic if the basic user can see the topic" do + other_category.update!(read_restricted: false) + expect(Oneboxer.local_topic(url, route, opts)).to eq(topic) + end + end + end + + context "when topic_id is provided" do + let(:opts) { { topic_id: topic.id, user_id: user.id } } + + it "returns nil if the user cannot see the topic" do + topic.update!(category: Fabricate(:private_category, group: Fabricate(:group))) + expect(Oneboxer.local_topic(url, route, opts)).to eq(nil) + end + + it "returns the topic if the user can see the topic" do + expect(Oneboxer.local_topic(url, route, opts)).to eq(topic) + end + + it "returns nil if the topic is a private message" do + topic.update!(archetype: Archetype.private_message, category: nil) + expect(Oneboxer.local_topic(url, route, opts)).to eq(nil) + end + end + end + end end