Send and handle "Delete" activity for deleted users

This commit is contained in:
Sergey Suprunenko 2019-07-10 05:16:08 +00:00 committed by kaniini
parent 75be90a6d1
commit 2d2b50ccca
6 changed files with 152 additions and 34 deletions

View file

@ -937,6 +937,8 @@ def delete(%User{} = user),
@spec perform(atom(), User.t()) :: {:ok, User.t()} @spec perform(atom(), User.t()) :: {:ok, User.t()}
def perform(:delete, %User{} = user) do def perform(:delete, %User{} = user) do
{:ok, _user} = ActivityPub.delete(user)
# Remove all relationships # Remove all relationships
{:ok, followers} = User.get_followers(user) {:ok, followers} = User.get_followers(user)
@ -953,8 +955,8 @@ def perform(:delete, %User{} = user) do
end) end)
delete_user_activities(user) delete_user_activities(user)
invalidate_cache(user)
{:ok, _user} = Repo.delete(user) Repo.delete(user)
end end
@spec perform(atom(), User.t()) :: {:ok, User.t()} @spec perform(atom(), User.t()) :: {:ok, User.t()}

View file

@ -405,6 +405,19 @@ def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
end end
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 def delete(%Object{data: %{"id" => id, "actor" => actor}} = object, local \\ true) do
user = User.get_cached_by_ap_id(actor) user = User.get_cached_by_ap_id(actor)
to = (object.data["to"] || []) ++ (object.data["cc"] || []) to = (object.data["to"] || []) ++ (object.data["cc"] || [])

View file

@ -641,7 +641,7 @@ def handle_incoming(
# an error or a tombstone. This would allow us to verify that a deletion actually took # an error or a tombstone. This would allow us to verify that a deletion actually took
# place. # place.
def handle_incoming( def handle_incoming(
%{"type" => "Delete", "object" => object_id, "actor" => _actor, "id" => _id} = data, %{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => _id} = data,
_options _options
) do ) do
object_id = Utils.get_ap_id(object_id) object_id = Utils.get_ap_id(object_id)
@ -653,7 +653,30 @@ def handle_incoming(
{:ok, activity} <- ActivityPub.delete(object, false) do {:ok, activity} <- ActivityPub.delete(object, false) do
{:ok, activity} {:ok, activity}
else 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
end end

24
test/fixtures/mastodon-delete-user.json vendored Normal file
View file

@ -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"
}
]
}

View file

@ -14,6 +14,7 @@ defmodule Pleroma.UserTest do
use Pleroma.DataCase use Pleroma.DataCase
import Pleroma.Factory import Pleroma.Factory
import Mock
setup_all do setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) 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
end end
test ".delete_user_activities deletes all create activities" do describe "delete" do
user = insert(:user) 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. {:ok, _} = User.delete_user_activities(user)
refute Activity.get_by_id(activity.id)
end
test ".delete deactivates a user, all follow relationships and all activities" do # TODO: Remove favorites, repeats, delete activities.
user = insert(:user) refute Activity.get_by_id(activity.id)
follower = insert(:user) 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"}) object = insert(:note, user: user)
{:ok, activity_two} = CommonAPI.post(follower, %{"status" => "3hu"}) activity = insert(:note_activity, user: user, note: object)
{:ok, like, _} = CommonAPI.favorite(activity_two.id, user) object_two = insert(:note, user: follower)
{:ok, like_two, _} = CommonAPI.favorite(activity.id, follower) activity_two = insert(:note_activity, user: follower, note: object_two)
{:ok, repeat, _} = CommonAPI.repeat(activity_two.id, user)
{: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) follower = User.get_cached_by_id(follower.id)
refute User.get_by_id(user.id)
user_activities = refute User.following?(follower, user)
user.ap_id refute User.get_by_id(user.id)
|> Activity.query_by_actor() assert {:ok, nil} == Cachex.get(:user_cache, "ap_id:#{user.ap_id}")
|> Repo.all()
|> Enum.map(fn act -> act.data["type"] end)
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) assert Enum.all?(user_activities, fn act -> act in ~w(Delete Undo) end)
refute Activity.get_by_id(like.id)
refute Activity.get_by_id(like_two.id) refute Activity.get_by_id(activity.id)
refute Activity.get_by_id(repeat.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 end
test "get_public_key_for_ap_id fetches a user that's not in the db" do test "get_public_key_for_ap_id fetches a user that's not in the db" do

View file

@ -553,6 +553,30 @@ test "it fails for incoming deletes with spoofed origin" do
assert Activity.get_by_id(activity.id) assert Activity.get_by_id(activity.id)
end 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 test "it works for incoming unannounces with an existing notice" do
user = insert(:user) user = insert(:user)
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey"}) {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"})