@@ -937,6 +937,8 @@ defmodule Pleroma.User do | |||||
@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 @@ defmodule Pleroma.User do | |||||
end) | end) | ||||
delete_user_activities(user) | delete_user_activities(user) | ||||
{:ok, _user} = Repo.delete(user) | |||||
invalidate_cache(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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub 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 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||||
# 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 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do | |||||
{: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 | ||||
@@ -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 @@ defmodule Pleroma.UserTest do | |||||
end | end | ||||
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 | 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 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest 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"}) | ||||