diff --git a/changelog.d/mention-notify-access.security b/changelog.d/mention-notify-access.security new file mode 100644 index 000000000..2f1830079 --- /dev/null +++ b/changelog.d/mention-notify-access.security @@ -0,0 +1 @@ +Do not notify tagged users who cannot see the post diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 255803c14..b66e0ace6 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -435,13 +435,56 @@ defmodule Pleroma.Web.CommonAPI.Utils do %{} end - tagged_mentions = maybe_extract_mentions(object_data) + tagged_mentions = + maybe_extract_mentions(object_data) + |> filter_tags_to_qualified(activity) recipients ++ tagged_mentions end def maybe_notify_mentioned_recipients(recipients, _), do: recipients + # Filter tagged mentions to ensure they can actually access the status + defp filter_tags_to_qualified(tagged_mentions, %Activity{ + recipients: recipients, + data: data, + actor: actor + }) do + recipients = + if recipients && recipients != [], + do: recipients, + else: + (data["to"] || []) ++ (data["cc"] || []) ++ (data["bto"] || []) ++ (data["bcc"] || []) + + cond do + recipients == [] -> + [] + + Pleroma.Constants.as_public() in recipients -> + tagged_mentions + + true -> + author = User.get_cached_by_ap_id(actor) + to_followers = match?(%User{}, author) and author.follower_address in recipients + + Enum.filter(tagged_mentions, fn tagged -> + cond do + tagged in recipients -> + true + + to_followers -> + case User.get_cached_by_ap_id(tagged) do + %User{} = tagged_user -> User.following?(tagged_user, author) + _ -> false + end + + true -> + false + end + end) + end + end + def maybe_notify_followers(recipients, %Activity{data: %{"type" => "Move"}} = activity) do with %User{} = user <- User.get_cached_by_ap_id(activity.actor) do user diff --git a/test/pleroma/notification_test.exs b/test/pleroma/notification_test.exs index 1ff014a8d..091b9affc 100644 --- a/test/pleroma/notification_test.exs +++ b/test/pleroma/notification_test.exs @@ -7,6 +7,8 @@ defmodule Pleroma.NotificationTest do import Pleroma.Factory + require Pleroma.Constants + alias Pleroma.FollowingRelationship alias Pleroma.Notification alias Pleroma.Object @@ -628,6 +630,102 @@ defmodule Pleroma.NotificationTest do assert other_user in enabled_receivers end + defp incoming_note_tagging(author, to, tagged_ap_ids) do + tags = Enum.map(tagged_ap_ids, &%{"type" => "Mention", "href" => &1, "name" => &1}) + + {:ok, activity} = + Transmogrifier.handle_incoming(%{ + "@context" => "https://www.w3.org/ns/activitystreams", + "type" => "Create", + "to" => to, + "actor" => author.ap_id, + "object" => %{ + "type" => "Note", + "id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(), + "to" => to, + "content" => "tagged", + "tag" => tags, + "attributedTo" => author.ap_id + } + }) + + activity + end + + defp notified_ap_ids(activity), + do: activity |> Notification.get_notified_from_activity() |> Enum.map(& &1.ap_id) + + test "it does not notify tagged users who cannot see a followers-only post" do + author = insert(:user) + tagged = insert(:user) + + activity = incoming_note_tagging(author, [author.follower_address], [tagged.ap_id]) + + refute tagged.ap_id in notified_ap_ids(activity) + end + + test "it notifies tagged followers of a followers-only post" do + author = insert(:user) + tagged = insert(:user) + {:ok, _, _} = Pleroma.User.follow(tagged, author) + + activity = incoming_note_tagging(author, [author.follower_address], [tagged.ap_id]) + + assert tagged.ap_id in notified_ap_ids(activity) + end + + test "it does not notify tagged users who are not addressed by a direct message" do + author = insert(:user) + addressed = insert(:user) + tagged = insert(:user) + + activity = incoming_note_tagging(author, [addressed.ap_id], [tagged.ap_id]) + receivers = notified_ap_ids(activity) + + assert addressed.ap_id in receivers + refute tagged.ap_id in receivers + end + + test "it notifies tagged users of a public post even if they are not addressed" do + author = insert(:user) + tagged = insert(:user) + + activity = + incoming_note_tagging(author, [Pleroma.Constants.as_public()], [tagged.ap_id]) + + assert tagged.ap_id in notified_ap_ids(activity) + end + + test "it does not notify tagged users with a pending follow request" do + author = insert(:user, is_locked: true) + tagged = insert(:user) + {:ok, _, _, _} = CommonAPI.follow(author, tagged) + + activity = incoming_note_tagging(author, [author.follower_address], [tagged.ap_id]) + + refute tagged.ap_id in notified_ap_ids(activity) + end + + test "it ignores tags of unknown users in a followers-only post" do + author = insert(:user) + tagged = insert(:user) + + follower = insert(:user) + {:ok, _, _} = Pleroma.User.follow(follower, author) + + activity = + incoming_note_tagging(author, [author.follower_address], [ + "https://unknown.example/users/nobody", + tagged.ap_id, + follower.ap_id + ]) + + receivers = notified_ap_ids(activity) + + refute tagged.ap_id in receivers + assert follower.ap_id in receivers + end + test "it does not send notifications to users who are only cc in new messages" do user = insert(:user) other_user = insert(:user)