Fix user deletion problems See merge request pleroma/pleroma!3476features/ingestion-unfollow
@@ -26,6 +26,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). | |||
### Fixed | |||
- Don't crash so hard when email settings are invalid. | |||
- Checking activated Upload Filters for required commands. | |||
- Remote users can no longer reappear after being deleted. | |||
- Deactivated users may now be deleted. | |||
- Mix task `pleroma.database prune_objects` | |||
### Removed | |||
@@ -1695,8 +1695,6 @@ defmodule Pleroma.User do | |||
email: nil, | |||
name: nil, | |||
password_hash: nil, | |||
keys: nil, | |||
public_key: nil, | |||
avatar: %{}, | |||
tags: [], | |||
last_refreshed_at: nil, | |||
@@ -1707,9 +1705,7 @@ defmodule Pleroma.User do | |||
follower_count: 0, | |||
following_count: 0, | |||
is_locked: false, | |||
is_confirmed: true, | |||
password_reset_pending: false, | |||
is_approved: true, | |||
registration_reason: nil, | |||
confirmation_token: nil, | |||
domain_blocks: [], | |||
@@ -1725,45 +1721,53 @@ defmodule Pleroma.User do | |||
raw_fields: [], | |||
is_discoverable: false, | |||
also_known_as: [] | |||
# id: preserved | |||
# ap_id: preserved | |||
# nickname: preserved | |||
}) | |||
end | |||
# Purge doesn't delete the user from the database. | |||
# It just nulls all its fields and deactivates it. | |||
# See `User.purge_user_changeset/1` above. | |||
defp purge(%User{} = user) do | |||
user | |||
|> purge_user_changeset() | |||
|> update_and_set_cache() | |||
end | |||
def delete(users) when is_list(users) do | |||
for user <- users, do: delete(user) | |||
end | |||
def delete(%User{} = user) do | |||
# Purge the user immediately | |||
purge(user) | |||
BackgroundWorker.enqueue("delete_user", %{"user_id" => user.id}) | |||
end | |||
defp delete_and_invalidate_cache(%User{} = user) do | |||
# *Actually* delete the user from the DB | |||
defp delete_from_db(%User{} = user) do | |||
invalidate_cache(user) | |||
Repo.delete(user) | |||
end | |||
defp delete_or_deactivate(%User{local: false} = user), do: delete_and_invalidate_cache(user) | |||
# If the user never finalized their account, it's safe to delete them. | |||
defp maybe_delete_from_db(%User{local: true, is_confirmed: false} = user), | |||
do: delete_from_db(user) | |||
defp delete_or_deactivate(%User{local: true} = user) do | |||
status = account_status(user) | |||
case status do | |||
:confirmation_pending -> | |||
delete_and_invalidate_cache(user) | |||
defp maybe_delete_from_db(%User{local: true, is_approved: false} = user), | |||
do: delete_from_db(user) | |||
:approval_pending -> | |||
delete_and_invalidate_cache(user) | |||
_ -> | |||
user | |||
|> purge_user_changeset() | |||
|> update_and_set_cache() | |||
end | |||
end | |||
defp maybe_delete_from_db(user), do: {:ok, user} | |||
def perform(:force_password_reset, user), do: force_password_reset(user) | |||
@spec perform(atom(), User.t()) :: {:ok, User.t()} | |||
def perform(:delete, %User{} = user) do | |||
# Purge the user again, in case perform/2 is called directly | |||
purge(user) | |||
# Remove all relationships | |||
user | |||
|> get_followers() | |||
@@ -1781,10 +1785,9 @@ defmodule Pleroma.User do | |||
delete_user_activities(user) | |||
delete_notifications_from_user_activities(user) | |||
delete_outgoing_pending_follow_requests(user) | |||
delete_or_deactivate(user) | |||
maybe_delete_from_db(user) | |||
end | |||
def perform(:set_activation_async, user, status), do: set_activation(user, status) | |||
@@ -53,15 +53,18 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
{recipients, to, cc} | |||
end | |||
defp check_actor_is_active(nil), do: true | |||
defp check_actor_can_insert(%{"type" => "Delete"}), do: true | |||
defp check_actor_can_insert(%{"type" => "Undo"}), do: true | |||
defp check_actor_is_active(actor) when is_binary(actor) do | |||
defp check_actor_can_insert(%{"actor" => actor}) when is_binary(actor) do | |||
case User.get_cached_by_ap_id(actor) do | |||
%User{is_active: true} -> true | |||
_ -> false | |||
end | |||
end | |||
defp check_actor_can_insert(_), do: true | |||
defp check_remote_limit(%{"object" => %{"content" => content}}) when not is_nil(content) do | |||
limit = Config.get([:instance, :remote_limit]) | |||
String.length(content) <= limit | |||
@@ -117,7 +120,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do | |||
def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do | |||
with nil <- Activity.normalize(map), | |||
map <- lazy_put_activity_defaults(map, fake), | |||
{_, true} <- {:actor_check, bypass_actor_check || check_actor_is_active(map["actor"])}, | |||
{_, true} <- {:actor_check, bypass_actor_check || check_actor_can_insert(map)}, | |||
{_, true} <- {:remote_limit_pass, check_remote_limit(map)}, | |||
{:ok, map} <- MRF.filter(map), | |||
{recipients, _, _} = get_recipients(map), | |||
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do | |||
alias Pleroma.Activity | |||
alias Pleroma.EctoType.ActivityPub.ObjectValidators | |||
alias Pleroma.User | |||
import Ecto.Changeset | |||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations | |||
@@ -57,7 +58,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do | |||
cng | |||
|> validate_required([:id, :type, :actor, :to, :cc, :object]) | |||
|> validate_inclusion(:type, ["Delete"]) | |||
|> validate_actor_presence() | |||
|> validate_delete_actor(:actor) | |||
|> validate_modification_rights() | |||
|> validate_object_or_user_presence(allowed_types: @deletable_types) | |||
|> add_deleted_activity_id() | |||
@@ -72,4 +73,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do | |||
|> cast_data | |||
|> validate_data | |||
end | |||
defp validate_delete_actor(cng, field_name) do | |||
validate_change(cng, field_name, fn field_name, actor -> | |||
case User.get_cached_by_ap_id(actor) do | |||
%User{} -> [] | |||
_ -> [{field_name, "can't find user"}] | |||
end | |||
end) | |||
end | |||
end |
@@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do | |||
alias Pleroma.Activity | |||
alias Pleroma.EctoType.ActivityPub.ObjectValidators | |||
alias Pleroma.User | |||
import Ecto.Changeset | |||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations | |||
@@ -42,7 +43,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do | |||
data_cng | |||
|> validate_inclusion(:type, ["Undo"]) | |||
|> validate_required([:id, :type, :object, :actor, :to, :cc]) | |||
|> validate_actor_presence() | |||
|> validate_undo_actor(:actor) | |||
|> validate_object_presence() | |||
|> validate_undo_rights() | |||
end | |||
@@ -59,4 +60,13 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator do | |||
_ -> cng | |||
end | |||
end | |||
defp validate_undo_actor(cng, field_name) do | |||
validate_change(cng, field_name, fn field_name, actor -> | |||
case User.get_cached_by_ap_id(actor) do | |||
%User{} -> [] | |||
_ -> [{field_name, "can't find user"}] | |||
end | |||
end) | |||
end | |||
end |
@@ -1639,9 +1639,9 @@ defmodule Pleroma.UserTest do | |||
follower_count: 9, | |||
following_count: 9001, | |||
is_locked: true, | |||
is_confirmed: false, | |||
is_confirmed: true, | |||
password_reset_pending: true, | |||
is_approved: false, | |||
is_approved: true, | |||
registration_reason: "ahhhhh", | |||
confirmation_token: "qqqq", | |||
domain_blocks: ["lain.com"], | |||
@@ -1669,8 +1669,8 @@ defmodule Pleroma.UserTest do | |||
email: nil, | |||
name: nil, | |||
password_hash: nil, | |||
keys: nil, | |||
public_key: nil, | |||
keys: "RSA begin buplic key", | |||
public_key: "--PRIVATE KEYE--", | |||
avatar: %{}, | |||
tags: [], | |||
last_refreshed_at: nil, | |||
@@ -1702,6 +1702,24 @@ defmodule Pleroma.UserTest do | |||
} = user | |||
end | |||
test "delete/1 purges a remote user" do | |||
user = | |||
insert(:user, %{ | |||
name: "qqqqqqq", | |||
avatar: %{"a" => "b"}, | |||
banner: %{"a" => "b"}, | |||
local: false | |||
}) | |||
{:ok, job} = User.delete(user) | |||
{:ok, _} = ObanHelpers.perform(job) | |||
user = User.get_by_id(user.id) | |||
assert user.name == nil | |||
assert user.avatar == %{} | |||
assert user.banner == %{} | |||
end | |||
test "get_public_key_for_ap_id fetches a user that's not in the db" do | |||
assert {:ok, _key} = User.get_public_key_for_ap_id("http://mastodon.example.org/users/admin") | |||
end | |||