Send and handle "Delete" activity for deleted users
This commit is contained in:
parent
75be90a6d1
commit
2d2b50ccca
|
@ -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()}
|
||||||
|
|
|
@ -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"] || [])
|
||||||
|
|
|
@ -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
24
test/fixtures/mastodon-delete-user.json
vendored
Normal 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"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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"})
|
||||||
|
|
Loading…
Reference in a new issue