Skip to content

Commit

Permalink
Merge branch 'feature/send-delete-user-activity' into 'develop'
Browse files Browse the repository at this point in the history
Send and handle "Delete" activity for deleted users

Closes #1071 and #1059

See merge request pleroma/pleroma!1384
  • Loading branch information
kaniini committed Jul 10, 2019
2 parents 75be90a + 2d2b50c commit b00620b
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 34 deletions.
6 changes: 4 additions & 2 deletions lib/pleroma/user.ex
Expand Up @@ -937,6 +937,8 @@ defmodule Pleroma.User do

@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)

Expand All @@ -953,8 +955,8 @@ defmodule Pleroma.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()}
Expand Down
13 changes: 13 additions & 0 deletions lib/pleroma/web/activity_pub/activity_pub.ex
Expand Up @@ -405,6 +405,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub 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"] || [])
Expand Down
27 changes: 25 additions & 2 deletions lib/pleroma/web/activity_pub/transmogrifier.ex
Expand Up @@ -641,7 +641,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
# 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)
Expand All @@ -653,7 +653,30 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
{: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

Expand Down
24 changes: 24 additions & 0 deletions 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"
}
]
}
92 changes: 62 additions & 30 deletions test/user_test.exs
Expand Up @@ -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)
Expand Down Expand Up @@ -915,49 +916,80 @@ defmodule Pleroma.UserTest 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)

follower = User.get_cached_by_id(follower.id)

refute User.following?(follower, user)
refute User.get_by_id(user.id)
assert {:ok, nil} == Cachex.get(:user_cache, "ap_id:#{user.ap_id}")

user_activities =
user.ap_id
|> Activity.query_by_actor()
|> Repo.all()
|> Enum.map(fn act -> act.data["type"] end)

assert Enum.all?(user_activities, fn act -> act in ~w(Delete Undo) end)

refute User.following?(follower, user)
refute User.get_by_id(user.id)
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)

user_activities =
user.ap_id
|> Activity.query_by_actor()
|> Repo.all()
|> Enum.map(fn act -> act.data["type"] end)
{:ok, follower} = User.get_or_fetch_by_ap_id("http://mastodon.example.org/users/admin")
{:ok, _} = User.follow(follower, user)

assert Enum.all?(user_activities, fn act -> act in ~w(Delete Undo) end)
{:ok, _user} = User.delete(user)

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 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
Expand Down
24 changes: 24 additions & 0 deletions test/web/activity_pub/transmogrifier_test.exs
Expand Up @@ -553,6 +553,30 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest 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"})
Expand Down

0 comments on commit b00620b

Please sign in to comment.