diff --git a/app/serializers/post_serializer.rb b/app/serializers/post_serializer.rb index e2a849d8704..2998a074e5f 100644 --- a/app/serializers/post_serializer.rb +++ b/app/serializers/post_serializer.rb @@ -315,8 +315,14 @@ class PostSerializer < BasicPostSerializer summary.delete(:can_act) end + if actions.present? && SiteSetting.allow_anonymous_likes && sym == :like && + !scope.can_delete_post_action?(actions[id]) + summary.delete(:can_act) + end + if actions.present? && actions.has_key?(id) summary[:acted] = true + summary[:can_undo] = true if scope.can_delete?(actions[id]) end diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 70428a480ac..4360c531dd6 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -2164,6 +2164,7 @@ en: enable_category_group_moderation: "Allow groups to moderate content in specific categories" group_in_subject: "Set %%{optional_pm} in email subject to name of first group in PM, see: Customize subject format for standard emails" allow_anonymous_posting: "Allow users to switch to anonymous mode" + allow_anonymous_likes: "Allow anonymous users to like posts" anonymous_posting_min_trust_level: "Minimum trust level required to enable anonymous posting" anonymous_account_duration_minutes: "To protect anonymity create a new anonymous account every N minutes for each user. Example: if set to 600, as soon as 600 minutes elapse from last post AND user switches to anon, a new anonymous account is created." diff --git a/config/site_settings.yml b/config/site_settings.yml index 51b3a72f2cd..3196e8a10bd 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -671,6 +671,9 @@ users: allow_anonymous_posting: default: false client: true + allow_anonymous_likes: + default: false + client: true anonymous_posting_min_trust_level: default: 1 enum: "TrustLevelSetting" diff --git a/lib/guardian.rb b/lib/guardian.rb index d4323575951..cfc9e345490 100644 --- a/lib/guardian.rb +++ b/lib/guardian.rb @@ -620,10 +620,14 @@ class Guardian private def is_my_own?(obj) - unless anonymous? - return obj.user_id == @user.id if obj.respond_to?(:user_id) && obj.user_id && @user.id - return obj.user == @user if obj.respond_to?(:user) + if anonymous? + return( + SiteSetting.allow_anonymous_likes? && obj.class == PostAction && obj.is_like? && + obj.user_id == @user.id + ) end + return obj.user_id == @user.id if obj.respond_to?(:user_id) && obj.user_id && @user.id + return obj.user == @user if obj.respond_to?(:user) false end diff --git a/lib/guardian/post_guardian.rb b/lib/guardian/post_guardian.rb index daa36992caa..7c775b5453a 100644 --- a/lib/guardian/post_guardian.rb +++ b/lib/guardian/post_guardian.rb @@ -43,7 +43,10 @@ module PostGuardian already_did_flagging = taken.any? && (taken & PostActionType.notify_flag_types.values).any? result = - if authenticated? && post && !@user.anonymous? + if authenticated? && post + # Allow anonymous users to like if feature is enabled and short-circuit otherwise + return SiteSetting.allow_anonymous_likes? && (action_key == :like) if @user.anonymous? + # Silenced users can't flag return false if is_flag && @user.silenced? diff --git a/spec/lib/guardian_spec.rb b/spec/lib/guardian_spec.rb index 6bd190fe488..82d85785504 100644 --- a/spec/lib/guardian_spec.rb +++ b/spec/lib/guardian_spec.rb @@ -98,6 +98,38 @@ RSpec.describe Guardian do fab!(:user) { Fabricate(:user) } fab!(:post) { Fabricate(:post) } + describe "an anonymous user" do + before { SiteSetting.allow_anonymous_posting = true } + + context "when allow_anonymous_likes is enabled" do + before { SiteSetting.allow_anonymous_likes = true } + + it "returns true when liking" do + expect(Guardian.new(anonymous_user).post_can_act?(post, :like)).to be_truthy + end + + it "cannot perform any other action" do + expect(Guardian.new(anonymous_user).post_can_act?(post, :flag)).to be_falsey + expect(Guardian.new(anonymous_user).post_can_act?(post, :bookmark)).to be_falsey + expect(Guardian.new(anonymous_user).post_can_act?(post, :notify_user)).to be_falsey + end + end + + context "when allow_anonymous_likes is disabled" do + before { SiteSetting.allow_anonymous_likes = false } + + it "returns false when liking" do + expect(Guardian.new(anonymous_user).post_can_act?(post, :like)).to be_falsey + end + + it "cannot perform any other action" do + expect(Guardian.new(anonymous_user).post_can_act?(post, :flag)).to be_falsey + expect(Guardian.new(anonymous_user).post_can_act?(post, :bookmark)).to be_falsey + expect(Guardian.new(anonymous_user).post_can_act?(post, :notify_user)).to be_falsey + end + end + end + it "returns false when the user is nil" do expect(Guardian.new(nil).post_can_act?(post, :like)).to be_falsey end @@ -2443,6 +2475,122 @@ RSpec.describe Guardian do end end + describe "#can_delete_post_action" do + before do + SiteSetting.allow_anonymous_posting = true + Guardian.any_instance.stubs(:anonymous?).returns(true) + end + + context "with allow_anonymous_likes enabled" do + before { SiteSetting.allow_anonymous_likes = true } + describe "an anonymous user" do + let(:post_action) do + user.id = anonymous_user.id + post.id = 1 + + a = + PostAction.new( + user: anonymous_user, + post: post, + post_action_type_id: PostActionType.types[:like], + ) + a.created_at = 1.minute.ago + a + end + + let(:non_like_post_action) do + user.id = anonymous_user.id + post.id = 1 + + a = + PostAction.new( + user: anonymous_user, + post: post, + post_action_type_id: PostActionType.types[:reply], + ) + a.created_at = 1.minute.ago + a + end + + let(:other_users_post_action) do + user.id = user.id + post.id = 1 + + a = + PostAction.new(user: user, post: post, post_action_type_id: PostActionType.types[:like]) + a.created_at = 1.minute.ago + a + end + + it "returns true if the post belongs to the anonymous user" do + expect(Guardian.new(anonymous_user).can_delete_post_action?(post_action)).to be_truthy + end + + it "return false if the post belongs to another user" do + expect( + Guardian.new(anonymous_user).can_delete_post_action?(other_users_post_action), + ).to be_falsey + end + + it "returns false for any other action" do + expect( + Guardian.new(anonymous_user).can_delete_post_action?(non_like_post_action), + ).to be_falsey + end + + it "returns false if the window has expired" do + post_action.created_at = 20.minutes.ago + SiteSetting.post_undo_action_window_mins = 10 + + expect(Guardian.new(anonymous_user).can_delete?(post_action)).to be_falsey + end + end + end + + context "with allow_anonymous_likes disabled" do + before do + SiteSetting.allow_anonymous_likes = false + SiteSetting.allow_anonymous_posting = true + end + describe "an anonymous user" do + let(:post_action) do + user.id = anonymous_user.id + post.id = 1 + + a = + PostAction.new( + user: anonymous_user, + post: post, + post_action_type_id: PostActionType.types[:like], + ) + a.created_at = 1.minute.ago + a + end + + let(:non_like_post_action) do + user.id = anonymous_user.id + post.id = 1 + + a = + PostAction.new( + user: anonymous_user, + post: post, + post_action_type_id: PostActionType.types[:reply], + ) + a.created_at = 1.minute.ago + a + end + + it "any action returns false" do + expect(Guardian.new(anonymous_user).can_delete_post_action?(post_action)).to be_falsey + expect( + Guardian.new(anonymous_user).can_delete_post_action?(non_like_post_action), + ).to be_falsey + end + end + end + end + describe "#can_see_deleted_posts?" do it "returns true if the user is an admin" do expect(Guardian.new(admin).can_see_deleted_posts?(post.topic.category)).to be_truthy diff --git a/spec/serializers/post_serializer_spec.rb b/spec/serializers/post_serializer_spec.rb index 0b728696c83..14e5f175030 100644 --- a/spec/serializers/post_serializer_spec.rb +++ b/spec/serializers/post_serializer_spec.rb @@ -310,6 +310,58 @@ RSpec.describe PostSerializer do end end + context "with allow_anonymous_likes enabled" do + fab!(:user) { Fabricate(:user) } + fab!(:topic) { Fabricate(:topic, user: user) } + fab!(:post) { Fabricate(:post, topic: topic, user: topic.user) } + fab!(:anonymous_user) { Fabricate(:anonymous) } + + let(:serializer) { PostSerializer.new(post, scope: Guardian.new(anonymous_user), root: false) } + let(:post_action) do + user.id = anonymous_user.id + post.id = 1 + + a = + PostAction.new( + user: anonymous_user, + post: post, + post_action_type_id: PostActionType.types[:like], + ) + a.created_at = 1.minute.ago + a + end + + before do + SiteSetting.allow_anonymous_posting = true + SiteSetting.allow_anonymous_likes = true + SiteSetting.post_undo_action_window_mins = 10 + PostSerializer.any_instance.stubs(:post_actions).returns({ 2 => post_action }) + end + + context "when post_undo_action_window_mins has not passed" do + before { post_action.created_at = 5.minutes.ago } + + it "allows anonymous users to unlike posts" do + like_actions_summary = + serializer.actions_summary.find { |a| a[:id] == PostActionType.types[:like] } + + #When :can_act is present, the JavaScript allows the user to click the unlike button + expect(like_actions_summary[:can_act]).to eq(true) + end + end + + context "when post_undo_action_window_mins has passed" do + before { post_action.created_at = 20.minutes.ago } + + it "disallows anonymous users from unliking posts" do + # There are no other post actions available to anonymous users so the action_summary will be an empty array + expect(serializer.actions_summary.find { |a| a[:id] == PostActionType.types[:like] }).to eq( + nil, + ) + end + end + end + describe "#user_status" do fab!(:user_status) { Fabricate(:user_status) } fab!(:user) { Fabricate(:user, user_status: user_status) }