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

FEATURE: Implement SiteSetting to Allow Anonymous Likes #22131

Merged
merged 5 commits into from Jul 21, 2023
Merged
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
6 changes: 6 additions & 0 deletions app/serializers/post_serializer.rb
Expand Up @@ -315,8 +315,14 @@ def actions_summary
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

Expand Down
1 change: 1 addition & 0 deletions config/locales/server.en.yml
Expand Up @@ -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: <a href='https://meta.discourse.org/t/customize-specific-email-templates/88323' target='_blank'>Customize subject format for standard emails</a>"
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."

Expand Down
3 changes: 3 additions & 0 deletions config/site_settings.yml
Expand Up @@ -671,6 +671,9 @@ users:
allow_anonymous_posting:
default: false
client: true
allow_anonymous_likes:
meltingmettle marked this conversation as resolved.
Show resolved Hide resolved
default: false
client: true
anonymous_posting_min_trust_level:
default: 1
enum: "TrustLevelSetting"
Expand Down
10 changes: 7 additions & 3 deletions lib/guardian.rb
Expand Up @@ -620,10 +620,14 @@ def is_me?(other)
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
)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

syntax_tree really wanted this format. :/

Originally this looked like:
return SiteSetting.allow_anonymous_likes? && obj.class == PostAction && obj.is_like? && obj.user_id == @user.id if anonymous?

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
Expand Down
5 changes: 4 additions & 1 deletion lib/guardian/post_guardian.rb
Expand Up @@ -43,7 +43,10 @@ def post_can_act?(post, action_key, opts: {}, can_see_post: nil)
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?

Expand Down
148 changes: 148 additions & 0 deletions spec/lib/guardian_spec.rb
Expand Up @@ -98,6 +98,38 @@
fab!(:user) { Fabricate(:user) }
fab!(:post) { Fabricate(:post) }

describe "an anonymous user" do
before { SiteSetting.allow_anonymous_posting = true }
pmusaraj marked this conversation as resolved.
Show resolved Hide resolved

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
Expand Down Expand Up @@ -2443,6 +2475,122 @@
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
Expand Down
52 changes: 52 additions & 0 deletions spec/serializers/post_serializer_spec.rb
Expand Up @@ -310,6 +310,58 @@ def json_for_user(user)
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) }
Expand Down