From bd977a3c3f4ede45c8f911cf26fd81561ecd8b7f Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Thu, 17 Jun 2021 18:43:46 +0200 Subject: Ingestion Pipeline: Undo-Follow aka Unfollow --- lib/pleroma/user.ex | 12 ++---- lib/pleroma/web/activity_pub/activity_pub.ex | 22 +++++----- lib/pleroma/web/activity_pub/side_effects.ex | 47 ++++++++++++++-------- lib/pleroma/web/activity_pub/transmogrifier.ex | 44 +++++--------------- lib/pleroma/web/activity_pub/utils.ex | 31 -------------- .../transmogrifier/undo_handling_test.exs | 4 +- test/pleroma/web/common_api_test.exs | 17 ++++---- 7 files changed, 66 insertions(+), 111 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 62506f37a..5c0efb38d 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -983,7 +983,7 @@ def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do end def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do - {:error, "Not subscribed!"} + {:error, "Can't unfollow yourself!"} end @spec unfollow(User.t(), User.t()) :: {:ok, User.t(), Activity.t()} | {:error, String.t()} @@ -1771,17 +1771,11 @@ def perform(:delete, %User{} = user) do # Remove all relationships user |> get_followers() - |> Enum.each(fn follower -> - ActivityPub.unfollow(follower, user) - unfollow(follower, user) - end) + |> Enum.each(fn follower -> ActivityPub.unfollow(follower, user) end) user |> get_friends() - |> Enum.each(fn followed -> - ActivityPub.unfollow(user, followed) - unfollow(user, followed) - end) + |> Enum.each(fn followed -> ActivityPub.unfollow(user, followed) end) delete_user_activities(user) delete_notifications_from_user_activities(user) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 4c29dda35..446355c1e 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -20,8 +20,11 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.Repo alias Pleroma.Upload alias Pleroma.User + alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.MRF + alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.Streamer alias Pleroma.Web.WebFinger alias Pleroma.Workers.BackgroundWorker @@ -322,22 +325,17 @@ def listen(%{to: to, actor: actor, context: context, object: object} = params) d end end - @spec unfollow(User.t(), User.t(), String.t() | nil, boolean()) :: - {:ok, Activity.t()} | nil | {:error, any()} - def unfollow(follower, followed, activity_id \\ nil, local \\ true) do - with {:ok, result} <- - Repo.transaction(fn -> do_unfollow(follower, followed, activity_id, local) end) do + @spec unfollow(User.t(), User.t()) :: {:ok, Activity.t()} | nil | {:error, any()} + def unfollow(follower, followed) do + with {:ok, result} <- Repo.transaction(fn -> do_unfollow(follower, followed) end) do result end end - defp do_unfollow(follower, followed, activity_id, local) do - with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed), - {:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"), - unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id), - {:ok, activity} <- insert(unfollow_data, local), - _ <- notify_and_stream(activity), - :ok <- maybe_federate(activity) do + defp do_unfollow(follower, followed) do + with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), + {:ok, unfollow_data, _meta} <- Builder.undo(follower, follow_activity), + {:ok, activity, _meta} <- Pipeline.common_pipeline(unfollow_data, local: true) do {:ok, activity} else nil -> nil diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index b0ec84ade..b9ec33a55 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -254,10 +254,12 @@ def handle(%{data: %{"type" => "Announce"}} = object, meta) do end @impl true - def handle(%{data: %{"type" => "Undo", "object" => undone_object}} = object, meta) do - with undone_object <- Activity.get_by_ap_id(undone_object), - :ok <- handle_undoing(undone_object) do + def handle(%{data: %{"type" => "Undo", "object" => undone_activity}} = object, meta) do + with undone_activity <- Activity.get_by_ap_id(undone_activity), + :ok <- handle_undoing(undone_activity) do {:ok, object, meta} + else + e -> {:error, e} end end @@ -456,35 +458,48 @@ defp undo_like(%Object{} = liked_object, object) do end end - def handle_undoing(%{data: %{"type" => "Like"}} = object) do - object.data["object"] + def handle_undoing(%{data: %{"type" => "Like"}} = activity) do + activity.data["object"] |> Object.get_by_ap_id() - |> undo_like(object) + |> undo_like(activity) end - def handle_undoing(%{data: %{"type" => "EmojiReact"}} = object) do - with %Object{} = reacted_object <- Object.get_by_ap_id(object.data["object"]), - {:ok, _} <- Utils.remove_emoji_reaction_from_object(object, reacted_object), - {:ok, _} <- Repo.delete(object) do + def handle_undoing(%{data: %{"type" => "EmojiReact"}} = activity) do + with %Object{} = reacted_object <- Object.get_by_ap_id(activity.data["object"]), + {:ok, _} <- Utils.remove_emoji_reaction_from_object(activity, reacted_object), + {:ok, _} <- Repo.delete(activity) do :ok end end - def handle_undoing(%{data: %{"type" => "Announce"}} = object) do - with %Object{} = liked_object <- Object.get_by_ap_id(object.data["object"]), - {:ok, _} <- Utils.remove_announce_from_object(object, liked_object), - {:ok, _} <- Repo.delete(object) do + def handle_undoing(%{data: %{"type" => "Announce"}} = activity) do + with %Object{} = liked_object <- Object.get_by_ap_id(activity.data["object"]), + {:ok, _} <- Utils.remove_announce_from_object(activity, liked_object), + {:ok, _} <- Repo.delete(activity) do :ok end end def handle_undoing( - %{data: %{"type" => "Block", "actor" => blocker, "object" => blocked}} = object + %{data: %{"type" => "Block", "actor" => blocker, "object" => blocked}} = activity ) do with %User{} = blocker <- User.get_cached_by_ap_id(blocker), %User{} = blocked <- User.get_cached_by_ap_id(blocked), {:ok, _} <- User.unblock(blocker, blocked), - {:ok, _} <- Repo.delete(object) do + {:ok, _} <- Repo.delete(activity) do + :ok + end + end + + def handle_undoing( + %{data: %{"type" => "Follow", "object" => followed, "actor" => follower}} = activity + ) do + with %User{} = follower <- User.get_cached_by_ap_id(follower), + %User{} = followed <- User.get_cached_by_ap_id(followed), + {:ok, _activity} <- Utils.update_follow_state_for_all(activity, "reject"), + {:ok, nil} <- FollowingRelationship.update(follower, followed, :follow_reject) do + Notification.dismiss(activity) + :ok end end diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 142af1a13..6d49f0141 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -516,52 +516,30 @@ def handle_incoming( end def handle_incoming( - %{ - "type" => "Undo", - "object" => %{"type" => "Follow", "object" => followed}, - "actor" => follower, - "id" => id - } = _data, - _options - ) do - with %User{local: true} = followed <- User.get_cached_by_ap_id(followed), - {:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower), - {:ok, activity} <- ActivityPub.unfollow(follower, followed, id, false) do - User.unfollow(follower, followed) - {:ok, activity} - else - _e -> :error - end - end - - def handle_incoming( - %{ - "type" => "Undo", - "object" => %{"type" => type} - } = data, + %{"type" => "Undo", "object" => %{"type" => objtype, "id" => object_id}} = data, _options ) - when type in ["Like", "EmojiReact", "Announce", "Block"] do - with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do + when objtype in ~w[Like EmojiReact Announce Block Follow] do + with {:ok, %User{}} <- ObjectValidator.fetch_actor(data), + {_, %Activity{}} <- {:exists, Activity.get_by_ap_id(object_id)}, + {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do {:ok, activity} + else + {:error, _} = e -> e + e -> {:error, e} end end # For Undos that don't have the complete object attached, try to find it in our database. - def handle_incoming( - %{ - "type" => "Undo", - "object" => object - } = activity, - options - ) + def handle_incoming(%{"type" => "Undo", "object" => object} = activity, options) when is_binary(object) do with %Activity{data: data} <- Activity.get_by_ap_id(object) do activity |> Map.put("object", data) |> handle_incoming(options) else - _e -> :error + {:error, _} = e -> e + e -> {:error, e} end end diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 1df53f79a..6bed3dffa 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -572,25 +572,6 @@ def make_announce_data( |> Maps.put_if_present("id", activity_id) end - def make_undo_data( - %User{ap_id: actor, follower_address: follower_address}, - %Activity{ - data: %{"id" => undone_activity_id, "context" => context}, - actor: undone_activity_actor - }, - activity_id \\ nil - ) do - %{ - "type" => "Undo", - "actor" => actor, - "object" => undone_activity_id, - "to" => [follower_address, undone_activity_actor], - "cc" => [Pleroma.Constants.as_public()], - "context" => context - } - |> Maps.put_if_present("id", activity_id) - end - @spec add_announce_to_object(Activity.t(), Object.t()) :: {:ok, Object.t()} | {:error, Ecto.Changeset.t()} def add_announce_to_object( @@ -624,18 +605,6 @@ defp take_announcements(%{data: %{"announcements" => announcements}} = _) defp take_announcements(_), do: [] - #### Unfollow-related helpers - - def make_unfollow_data(follower, followed, follow_activity, activity_id) do - %{ - "type" => "Undo", - "actor" => follower.ap_id, - "to" => [followed.ap_id], - "object" => follow_activity.data - } - |> Maps.put_if_present("id", activity_id) - end - #### Block-related helpers @spec fetch_latest_block(User.t(), User.t()) :: Activity.t() | nil def fetch_latest_block(%User{ap_id: blocker_id}, %User{ap_id: blocked_id}) do diff --git a/test/pleroma/web/activity_pub/transmogrifier/undo_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/undo_handling_test.exs index f6e40722c..29b1cc027 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/undo_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/undo_handling_test.exs @@ -32,7 +32,7 @@ test "it works for incoming emoji reaction undos" do assert activity.data["type"] == "Undo" end - test "it returns an error for incoming unlikes wihout a like activity" do + test "it returns an error for incoming unlikes without a like activity" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{status: "leave a like pls"}) @@ -41,7 +41,7 @@ test "it returns an error for incoming unlikes wihout a like activity" do |> Jason.decode!() |> Map.put("object", activity.data["object"]) - assert Transmogrifier.handle_incoming(data) == :error + assert Transmogrifier.handle_incoming(data) == {:error, nil} end test "it works for incoming unlikes with an existing like activity" do diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index a5dfd3934..9ec8677dc 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -15,6 +15,7 @@ defmodule Pleroma.Web.CommonAPITest do alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.AdminAPI.AccountView alias Pleroma.Web.CommonAPI @@ -1213,15 +1214,15 @@ test "cancels a pending follow for a local user" do assert {:ok, follower} = CommonAPI.unfollow(follower, followed) assert User.get_follow_state(follower, followed) == nil - assert %{id: ^activity_id, data: %{"state" => "cancelled"}} = - Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed) + assert %{id: ^activity_id, data: %{"state" => "reject"}} = + Utils.fetch_latest_follow(follower, followed) assert %{ data: %{ "type" => "Undo", - "object" => %{"type" => "Follow", "state" => "cancelled"} + "object" => %{"type" => "Follow", "state" => "reject"} } - } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower) + } = Utils.fetch_latest_undo(follower) end test "cancels a pending follow for a remote user" do @@ -1235,15 +1236,15 @@ test "cancels a pending follow for a remote user" do assert {:ok, follower} = CommonAPI.unfollow(follower, followed) assert User.get_follow_state(follower, followed) == nil - assert %{id: ^activity_id, data: %{"state" => "cancelled"}} = - Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(follower, followed) + assert %{id: ^activity_id, data: %{"state" => "reject"}} = + Utils.fetch_latest_follow(follower, followed) assert %{ data: %{ "type" => "Undo", - "object" => %{"type" => "Follow", "state" => "cancelled"} + "object" => %{"type" => "Follow", "state" => "reject"} } - } = Pleroma.Web.ActivityPub.Utils.fetch_latest_undo(follower) + } = Utils.fetch_latest_undo(follower) end end -- cgit v1.2.3