From 2d2b50cccaa99b551b88be36a4b33b271300d3c8 Mon Sep 17 00:00:00 2001 From: Sergey Suprunenko Date: Wed, 10 Jul 2019 05:16:08 +0000 Subject: [PATCH] Send and handle "Delete" activity for deleted users --- lib/pleroma/user.ex | 6 +- lib/pleroma/web/activity_pub/activity_pub.ex | 13 +++ .../web/activity_pub/transmogrifier.ex | 27 +++++- test/fixtures/mastodon-delete-user.json | 24 +++++ test/user_test.exs | 92 +++++++++++++------ test/web/activity_pub/transmogrifier_test.exs | 24 +++++ 6 files changed, 152 insertions(+), 34 deletions(-) create mode 100644 test/fixtures/mastodon-delete-user.json diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index d03810d1a..034c414bf 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -937,6 +937,8 @@ def delete(%User{} = user), @spec perform(atom(), User.t()) :: {:ok, User.t()} def perform(:delete, %User{} = user) do + {:ok, _user} = ActivityPub.delete(user) + # Remove all relationships {:ok, followers} = User.get_followers(user) @@ -953,8 +955,8 @@ def perform(:delete, %User{} = user) do end) delete_user_activities(user) - - {:ok, _user} = Repo.delete(user) + invalidate_cache(user) + Repo.delete(user) end @spec perform(atom(), User.t()) :: {:ok, User.t()} diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 55315d66e..41b55bbab 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -405,6 +405,19 @@ def unfollow(follower, followed, activity_id \\ nil, local \\ true) do end end + def delete(%User{ap_id: ap_id, follower_address: follower_address} = user) do + with data <- %{ + "to" => [follower_address], + "type" => "Delete", + "actor" => ap_id, + "object" => %{"type" => "Person", "id" => ap_id} + }, + {:ok, activity} <- insert(data, true, true), + :ok <- maybe_federate(activity) do + {:ok, user} + end + end + def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do user = User.get_cached_by_ap_id(actor) to = (object.data["to"] || []) ++ (object.data["cc"] || []) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 543d4bb7d..e34fe6611 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -641,7 +641,7 @@ def handle_incoming( # an error or a tombstone. This would allow us to verify that a deletion actually took # place. def handle_incoming( - %{"type" => "Delete", "object" => object_id, "actor" => _actor, "id" => _id} = data, + %{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => _id} = data, _options ) do object_id = Utils.get_ap_id(object_id) @@ -653,7 +653,30 @@ def handle_incoming( {:ok, activity} <- ActivityPub.delete(object, false) do {:ok, activity} else - _e -> :error + nil -> + case User.get_cached_by_ap_id(object_id) do + %User{ap_id: ^actor} = user -> + {:ok, followers} = User.get_followers(user) + + Enum.each(followers, fn follower -> + User.unfollow(follower, user) + end) + + {:ok, friends} = User.get_friends(user) + + Enum.each(friends, fn followed -> + User.unfollow(user, followed) + end) + + User.invalidate_cache(user) + Repo.delete(user) + + nil -> + :error + end + + _e -> + :error end end diff --git a/test/fixtures/mastodon-delete-user.json b/test/fixtures/mastodon-delete-user.json new file mode 100644 index 000000000..f19088fec --- /dev/null +++ b/test/fixtures/mastodon-delete-user.json @@ -0,0 +1,24 @@ +{ + "type": "Delete", + "object": { + "type": "Person", + "id": "http://mastodon.example.org/users/admin", + "atomUri": "http://mastodon.example.org/users/admin" + }, + "id": "http://mastodon.example.org/users/admin#delete", + "actor": "http://mastodon.example.org/users/admin", + "@context": [ + { + "toot": "http://joinmastodon.org/ns#", + "sensitive": "as:sensitive", + "ostatus": "http://ostatus.org#", + "movedTo": "as:movedTo", + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "inReplyToAtomUri": "ostatus:inReplyToAtomUri", + "conversation": "ostatus:conversation", + "atomUri": "ostatus:atomUri", + "Hashtag": "as:Hashtag", + "Emoji": "toot:Emoji" + } + ] +} diff --git a/test/user_test.exs b/test/user_test.exs index 0f27d73f7..62be79b4f 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -14,6 +14,7 @@ defmodule Pleroma.UserTest do use Pleroma.DataCase import Pleroma.Factory + import Mock setup_all do Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -915,49 +916,80 @@ test "hide a user's statuses from timelines and notifications" do end end - test ".delete_user_activities deletes all create activities" do - user = insert(:user) + describe "delete" do + setup do + {:ok, user} = insert(:user) |> User.set_cache() - {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"}) + [user: user] + end - {:ok, _} = User.delete_user_activities(user) + test ".delete_user_activities deletes all create activities", %{user: user} do + {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"}) - # TODO: Remove favorites, repeats, delete activities. - refute Activity.get_by_id(activity.id) - end + {:ok, _} = User.delete_user_activities(user) - test ".delete deactivates a user, all follow relationships and all activities" do - user = insert(:user) - follower = insert(:user) + # TODO: Remove favorites, repeats, delete activities. + refute Activity.get_by_id(activity.id) + end - {:ok, follower} = User.follow(follower, user) + test "it deletes a user, all follow relationships and all activities", %{user: user} do + follower = insert(:user) + {:ok, follower} = User.follow(follower, user) - {:ok, activity} = CommonAPI.post(user, %{"status" => "2hu"}) - {:ok, activity_two} = CommonAPI.post(follower, %{"status" => "3hu"}) + object = insert(:note, user: user) + activity = insert(:note_activity, user: user, note: object) - {:ok, like, _} = CommonAPI.favorite(activity_two.id, user) - {:ok, like_two, _} = CommonAPI.favorite(activity.id, follower) - {:ok, repeat, _} = CommonAPI.repeat(activity_two.id, user) + object_two = insert(:note, user: follower) + activity_two = insert(:note_activity, user: follower, note: object_two) - {:ok, _} = User.delete(user) + {:ok, like, _} = CommonAPI.favorite(activity_two.id, user) + {:ok, like_two, _} = CommonAPI.favorite(activity.id, follower) + {:ok, repeat, _} = CommonAPI.repeat(activity_two.id, user) - follower = User.get_cached_by_id(follower.id) + {:ok, _} = User.delete(user) - refute User.following?(follower, user) - refute User.get_by_id(user.id) + follower = User.get_cached_by_id(follower.id) - user_activities = - user.ap_id - |> Activity.query_by_actor() - |> Repo.all() - |> Enum.map(fn act -> act.data["type"] end) + refute User.following?(follower, user) + refute User.get_by_id(user.id) + assert {:ok, nil} == Cachex.get(:user_cache, "ap_id:#{user.ap_id}") - assert Enum.all?(user_activities, fn act -> act in ~w(Delete Undo) end) + user_activities = + user.ap_id + |> Activity.query_by_actor() + |> Repo.all() + |> Enum.map(fn act -> act.data["type"] end) - refute Activity.get_by_id(activity.id) - refute Activity.get_by_id(like.id) - refute Activity.get_by_id(like_two.id) - refute Activity.get_by_id(repeat.id) + assert Enum.all?(user_activities, fn act -> act in ~w(Delete Undo) end) + + refute Activity.get_by_id(activity.id) + refute Activity.get_by_id(like.id) + refute Activity.get_by_id(like_two.id) + refute Activity.get_by_id(repeat.id) + end + + test_with_mock "it sends out User Delete activity", + %{user: user}, + Pleroma.Web.ActivityPub.Publisher, + [:passthrough], + [] do + config_path = [:instance, :federating] + initial_setting = Pleroma.Config.get(config_path) + Pleroma.Config.put(config_path, true) + + {:ok, follower} = User.get_or_fetch_by_ap_id("http://mastodon.example.org/users/admin") + {:ok, _} = User.follow(follower, user) + + {:ok, _user} = User.delete(user) + + assert called( + Pleroma.Web.ActivityPub.Publisher.publish_one(%{ + inbox: "http://mastodon.example.org/inbox" + }) + ) + + Pleroma.Config.put(config_path, initial_setting) + end end test "get_public_key_for_ap_id fetches a user that's not in the db" do diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index d152169b8..825e99879 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -553,6 +553,30 @@ test "it fails for incoming deletes with spoofed origin" do assert Activity.get_by_id(activity.id) end + test "it works for incoming user deletes" do + %{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin") + + data = + File.read!("test/fixtures/mastodon-delete-user.json") + |> Poison.decode!() + + {:ok, _} = Transmogrifier.handle_incoming(data) + + refute User.get_cached_by_ap_id(ap_id) + end + + test "it fails for incoming user deletes with spoofed origin" do + %{ap_id: ap_id} = insert(:user) + + data = + File.read!("test/fixtures/mastodon-delete-user.json") + |> Poison.decode!() + |> Map.put("actor", ap_id) + + assert :error == Transmogrifier.handle_incoming(data) + assert User.get_cached_by_ap_id(ap_id) + end + test "it works for incoming unannounces with an existing notice" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})