@@ -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) | |||
@@ -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()} | |||
@@ -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"] || []) | |||
@@ -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) | |||
@@ -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 | |||
@@ -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 | |||
import Pleroma.Factory | |||
import Mock | |||
setup_all do | |||
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) | |||
@@ -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 | |||
@@ -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"}) | |||