forked from AkkomaGang/akkoma
Merge branch 'feature/delete-validator' into 'develop'
Move deletions to the common pipeline Closes #1497 See merge request pleroma/pleroma!2441
This commit is contained in:
commit
473b0d9f3d
27 changed files with 710 additions and 385 deletions
|
@ -8,6 +8,8 @@ defmodule Mix.Tasks.Pleroma.User do
|
||||||
alias Ecto.Changeset
|
alias Ecto.Changeset
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.UserInviteToken
|
alias Pleroma.UserInviteToken
|
||||||
|
alias Pleroma.Web.ActivityPub.Builder
|
||||||
|
alias Pleroma.Web.ActivityPub.Pipeline
|
||||||
|
|
||||||
@shortdoc "Manages Pleroma users"
|
@shortdoc "Manages Pleroma users"
|
||||||
@moduledoc File.read!("docs/administration/CLI_tasks/user.md")
|
@moduledoc File.read!("docs/administration/CLI_tasks/user.md")
|
||||||
|
@ -96,8 +98,9 @@ def run(["new", nickname, email | rest]) do
|
||||||
def run(["rm", nickname]) do
|
def run(["rm", nickname]) do
|
||||||
start_pleroma()
|
start_pleroma()
|
||||||
|
|
||||||
with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do
|
with %User{local: true} = user <- User.get_cached_by_nickname(nickname),
|
||||||
User.perform(:delete, user)
|
{:ok, delete_data, _} <- Builder.delete(user, user.ap_id),
|
||||||
|
{:ok, _delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do
|
||||||
shell_info("User #{nickname} deleted.")
|
shell_info("User #{nickname} deleted.")
|
||||||
else
|
else
|
||||||
_ -> shell_error("No local user #{nickname}")
|
_ -> shell_error("No local user #{nickname}")
|
||||||
|
|
|
@ -29,7 +29,9 @@ defmodule Pleroma.User do
|
||||||
alias Pleroma.UserRelationship
|
alias Pleroma.UserRelationship
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.ActivityPub.Builder
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||||
|
alias Pleroma.Web.ActivityPub.Pipeline
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
|
alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
|
||||||
|
@ -1425,8 +1427,6 @@ def perform(:force_password_reset, user), do: force_password_reset(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
|
||||||
user
|
user
|
||||||
|> get_followers()
|
|> get_followers()
|
||||||
|
@ -1536,21 +1536,23 @@ def follow_import(%User{} = follower, followed_identifiers)
|
||||||
})
|
})
|
||||||
end
|
end
|
||||||
|
|
||||||
def delete_user_activities(%User{ap_id: ap_id}) do
|
def delete_user_activities(%User{ap_id: ap_id} = user) do
|
||||||
ap_id
|
ap_id
|
||||||
|> Activity.Queries.by_actor()
|
|> Activity.Queries.by_actor()
|
||||||
|> RepoStreamer.chunk_stream(50)
|
|> RepoStreamer.chunk_stream(50)
|
||||||
|> Stream.each(fn activities -> Enum.each(activities, &delete_activity/1) end)
|
|> Stream.each(fn activities ->
|
||||||
|
Enum.each(activities, fn activity -> delete_activity(activity, user) end)
|
||||||
|
end)
|
||||||
|> Stream.run()
|
|> Stream.run()
|
||||||
end
|
end
|
||||||
|
|
||||||
defp delete_activity(%{data: %{"type" => "Create"}} = activity) do
|
defp delete_activity(%{data: %{"type" => "Create", "object" => object}}, user) do
|
||||||
activity
|
{:ok, delete_data, _} = Builder.delete(user, object)
|
||||||
|> Object.normalize()
|
|
||||||
|> ActivityPub.delete()
|
Pipeline.common_pipeline(delete_data, local: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
|
defp delete_activity(%{data: %{"type" => "Like"}} = activity, _user) do
|
||||||
object = Object.normalize(activity)
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
activity.actor
|
activity.actor
|
||||||
|
@ -1558,7 +1560,7 @@ defp delete_activity(%{data: %{"type" => "Like"}} = activity) do
|
||||||
|> ActivityPub.unlike(object)
|
|> ActivityPub.unlike(object)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
|
defp delete_activity(%{data: %{"type" => "Announce"}} = activity, _user) do
|
||||||
object = Object.normalize(activity)
|
object = Object.normalize(activity)
|
||||||
|
|
||||||
activity.actor
|
activity.actor
|
||||||
|
@ -1566,7 +1568,7 @@ defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do
|
||||||
|> ActivityPub.unannounce(object)
|
|> ActivityPub.unannounce(object)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp delete_activity(_activity), do: "Doing nothing"
|
defp delete_activity(_activity, _user), do: "Doing nothing"
|
||||||
|
|
||||||
def html_filter_policy(%User{no_rich_text: true}) do
|
def html_filter_policy(%User{no_rich_text: true}) do
|
||||||
Pleroma.HTML.Scrubber.TwitterText
|
Pleroma.HTML.Scrubber.TwitterText
|
||||||
|
|
|
@ -519,67 +519,6 @@ defp do_unfollow(follower, followed, activity_id, local) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec delete(User.t() | Object.t(), keyword()) :: {:ok, User.t() | Object.t()} | {:error, any()}
|
|
||||||
def delete(entity, options \\ []) do
|
|
||||||
with {:ok, result} <- Repo.transaction(fn -> do_delete(entity, options) end) do
|
|
||||||
result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_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, true),
|
|
||||||
:ok <- maybe_federate(activity) do
|
|
||||||
{:ok, user}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_delete(%Object{data: %{"id" => id, "actor" => actor}} = object, options) do
|
|
||||||
local = Keyword.get(options, :local, true)
|
|
||||||
activity_id = Keyword.get(options, :activity_id, nil)
|
|
||||||
actor = Keyword.get(options, :actor, actor)
|
|
||||||
|
|
||||||
user = User.get_cached_by_ap_id(actor)
|
|
||||||
to = (object.data["to"] || []) ++ (object.data["cc"] || [])
|
|
||||||
|
|
||||||
with create_activity <- Activity.get_create_by_object_ap_id(id),
|
|
||||||
data <-
|
|
||||||
%{
|
|
||||||
"type" => "Delete",
|
|
||||||
"actor" => actor,
|
|
||||||
"object" => id,
|
|
||||||
"to" => to,
|
|
||||||
"deleted_activity_id" => create_activity && create_activity.id
|
|
||||||
}
|
|
||||||
|> maybe_put("id", activity_id),
|
|
||||||
{:ok, activity} <- insert(data, local, false),
|
|
||||||
{:ok, object, _create_activity} <- Object.delete(object),
|
|
||||||
stream_out_participations(object, user),
|
|
||||||
_ <- decrease_replies_count_if_reply(object),
|
|
||||||
{:ok, _actor} <- decrease_note_count_if_public(user, object),
|
|
||||||
:ok <- maybe_federate(activity) do
|
|
||||||
{:ok, activity}
|
|
||||||
else
|
|
||||||
{:error, error} ->
|
|
||||||
Repo.rollback(error)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_delete(%Object{data: %{"type" => "Tombstone", "id" => ap_id}}, _) do
|
|
||||||
activity =
|
|
||||||
ap_id
|
|
||||||
|> Activity.Queries.by_object_id()
|
|
||||||
|> Activity.Queries.by_type("Delete")
|
|
||||||
|> Repo.one()
|
|
||||||
|
|
||||||
{:ok, activity}
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec block(User.t(), User.t(), String.t() | nil, boolean()) ::
|
@spec block(User.t(), User.t(), String.t() | nil, boolean()) ::
|
||||||
{:ok, Activity.t()} | {:error, any()}
|
{:ok, Activity.t()} | {:error, any()}
|
||||||
def block(blocker, blocked, activity_id \\ nil, local \\ true) do
|
def block(blocker, blocked, activity_id \\ nil, local \\ true) do
|
||||||
|
|
|
@ -415,7 +415,8 @@ defp handle_user_activity(%User{} = user, %{"type" => "Create"} = params) do
|
||||||
defp handle_user_activity(%User{} = user, %{"type" => "Delete"} = params) do
|
defp handle_user_activity(%User{} = user, %{"type" => "Delete"} = params) do
|
||||||
with %Object{} = object <- Object.normalize(params["object"]),
|
with %Object{} = object <- Object.normalize(params["object"]),
|
||||||
true <- user.is_moderator || user.ap_id == object.data["actor"],
|
true <- user.is_moderator || user.ap_id == object.data["actor"],
|
||||||
{:ok, delete} <- ActivityPub.delete(object) do
|
{:ok, delete_data, _} <- Builder.delete(user, object.data["id"]),
|
||||||
|
{:ok, delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do
|
||||||
{:ok, delete}
|
{:ok, delete}
|
||||||
else
|
else
|
||||||
_ -> {:error, dgettext("errors", "Can't delete object")}
|
_ -> {:error, dgettext("errors", "Can't delete object")}
|
||||||
|
|
|
@ -10,6 +10,33 @@ defmodule Pleroma.Web.ActivityPub.Builder do
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
|
|
||||||
|
@spec delete(User.t(), String.t()) :: {:ok, map(), keyword()}
|
||||||
|
def delete(actor, object_id) do
|
||||||
|
object = Object.normalize(object_id, false)
|
||||||
|
|
||||||
|
user = !object && User.get_cached_by_ap_id(object_id)
|
||||||
|
|
||||||
|
to =
|
||||||
|
case {object, user} do
|
||||||
|
{%Object{}, _} ->
|
||||||
|
# We are deleting an object, address everyone who was originally mentioned
|
||||||
|
(object.data["to"] || []) ++ (object.data["cc"] || [])
|
||||||
|
|
||||||
|
{_, %User{follower_address: follower_address}} ->
|
||||||
|
# We are deleting a user, address the followers of that user
|
||||||
|
[follower_address]
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok,
|
||||||
|
%{
|
||||||
|
"id" => Utils.generate_activity_id(),
|
||||||
|
"actor" => actor.ap_id,
|
||||||
|
"object" => object_id,
|
||||||
|
"to" => to,
|
||||||
|
"type" => "Delete"
|
||||||
|
}, []}
|
||||||
|
end
|
||||||
|
|
||||||
@spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
|
@spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
|
||||||
def like(actor, object) do
|
def like(actor, object) do
|
||||||
object_actor = User.get_cached_by_ap_id(object.data["actor"])
|
object_actor = User.get_cached_by_ap_id(object.data["actor"])
|
||||||
|
|
|
@ -11,11 +11,23 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
|
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||||
|
|
||||||
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
|
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
|
||||||
def validate(object, meta)
|
def validate(object, meta)
|
||||||
|
|
||||||
|
def validate(%{"type" => "Delete"} = object, meta) do
|
||||||
|
with cng <- DeleteValidator.cast_and_validate(object),
|
||||||
|
do_not_federate <- DeleteValidator.do_not_federate?(cng),
|
||||||
|
{:ok, object} <- Ecto.Changeset.apply_action(cng, :insert) do
|
||||||
|
object = stringify_keys(object)
|
||||||
|
meta = Keyword.put(meta, :do_not_federate, do_not_federate)
|
||||||
|
{:ok, object, meta}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def validate(%{"type" => "Like"} = object, meta) do
|
def validate(%{"type" => "Like"} = object, meta) do
|
||||||
with {:ok, object} <-
|
with {:ok, object} <-
|
||||||
object |> LikeValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do
|
object |> LikeValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do
|
||||||
|
@ -24,13 +36,25 @@ def validate(%{"type" => "Like"} = object, meta) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def stringify_keys(%{__struct__: _} = object) do
|
||||||
|
object
|
||||||
|
|> Map.from_struct()
|
||||||
|
|> stringify_keys
|
||||||
|
end
|
||||||
|
|
||||||
def stringify_keys(object) do
|
def stringify_keys(object) do
|
||||||
object
|
object
|
||||||
|> Map.new(fn {key, val} -> {to_string(key), val} end)
|
|> Map.new(fn {key, val} -> {to_string(key), val} end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fetch_actor(object) do
|
||||||
|
with {:ok, actor} <- Types.ObjectID.cast(object["actor"]) do
|
||||||
|
User.get_or_fetch_by_ap_id(actor)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def fetch_actor_and_object(object) do
|
def fetch_actor_and_object(object) do
|
||||||
User.get_or_fetch_by_ap_id(object["actor"])
|
fetch_actor(object)
|
||||||
Object.normalize(object["object"])
|
Object.normalize(object["object"])
|
||||||
:ok
|
:ok
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,7 +8,29 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
|
||||||
def validate_actor_presence(cng, field_name \\ :actor) do
|
def validate_recipients_presence(cng, fields \\ [:to, :cc]) do
|
||||||
|
non_empty =
|
||||||
|
fields
|
||||||
|
|> Enum.map(fn field -> get_field(cng, field) end)
|
||||||
|
|> Enum.any?(fn
|
||||||
|
[] -> false
|
||||||
|
_ -> true
|
||||||
|
end)
|
||||||
|
|
||||||
|
if non_empty do
|
||||||
|
cng
|
||||||
|
else
|
||||||
|
fields
|
||||||
|
|> Enum.reduce(cng, fn field, cng ->
|
||||||
|
cng
|
||||||
|
|> add_error(field, "no recipients in any field")
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_actor_presence(cng, options \\ []) do
|
||||||
|
field_name = Keyword.get(options, :field_name, :actor)
|
||||||
|
|
||||||
cng
|
cng
|
||||||
|> validate_change(field_name, fn field_name, actor ->
|
|> validate_change(field_name, fn field_name, actor ->
|
||||||
if User.get_cached_by_ap_id(actor) do
|
if User.get_cached_by_ap_id(actor) do
|
||||||
|
@ -19,14 +41,39 @@ def validate_actor_presence(cng, field_name \\ :actor) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def validate_object_presence(cng, field_name \\ :object) do
|
def validate_object_presence(cng, options \\ []) do
|
||||||
|
field_name = Keyword.get(options, :field_name, :object)
|
||||||
|
allowed_types = Keyword.get(options, :allowed_types, false)
|
||||||
|
|
||||||
cng
|
cng
|
||||||
|> validate_change(field_name, fn field_name, object ->
|
|> validate_change(field_name, fn field_name, object_id ->
|
||||||
if Object.get_cached_by_ap_id(object) do
|
object = Object.get_cached_by_ap_id(object_id)
|
||||||
[]
|
|
||||||
else
|
cond do
|
||||||
|
!object ->
|
||||||
[{field_name, "can't find object"}]
|
[{field_name, "can't find object"}]
|
||||||
|
|
||||||
|
object && allowed_types && object.data["type"] not in allowed_types ->
|
||||||
|
[{field_name, "object not in allowed types"}]
|
||||||
|
|
||||||
|
true ->
|
||||||
|
[]
|
||||||
end
|
end
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def validate_object_or_user_presence(cng, options \\ []) do
|
||||||
|
field_name = Keyword.get(options, :field_name, :object)
|
||||||
|
options = Keyword.put(options, :field_name, field_name)
|
||||||
|
|
||||||
|
actor_cng =
|
||||||
|
cng
|
||||||
|
|> validate_actor_presence(options)
|
||||||
|
|
||||||
|
object_cng =
|
||||||
|
cng
|
||||||
|
|> validate_object_presence(options)
|
||||||
|
|
||||||
|
if actor_cng.valid?, do: actor_cng, else: object_cng
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
|
||||||
|
@primary_key false
|
||||||
|
|
||||||
|
embedded_schema do
|
||||||
|
field(:id, Types.ObjectID, primary_key: true)
|
||||||
|
field(:type, :string)
|
||||||
|
field(:actor, Types.ObjectID)
|
||||||
|
field(:to, Types.Recipients, default: [])
|
||||||
|
field(:cc, Types.Recipients, default: [])
|
||||||
|
field(:deleted_activity_id, Types.ObjectID)
|
||||||
|
field(:object, Types.ObjectID)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_data(data) do
|
||||||
|
%__MODULE__{}
|
||||||
|
|> cast(data, __schema__(:fields))
|
||||||
|
end
|
||||||
|
|
||||||
|
def add_deleted_activity_id(cng) do
|
||||||
|
object =
|
||||||
|
cng
|
||||||
|
|> get_field(:object)
|
||||||
|
|
||||||
|
with %Activity{id: id} <- Activity.get_create_by_object_ap_id(object) do
|
||||||
|
cng
|
||||||
|
|> put_change(:deleted_activity_id, id)
|
||||||
|
else
|
||||||
|
_ -> cng
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@deletable_types ~w{
|
||||||
|
Answer
|
||||||
|
Article
|
||||||
|
Audio
|
||||||
|
Event
|
||||||
|
Note
|
||||||
|
Page
|
||||||
|
Question
|
||||||
|
Video
|
||||||
|
}
|
||||||
|
def validate_data(cng) do
|
||||||
|
cng
|
||||||
|
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
||||||
|
|> validate_inclusion(:type, ["Delete"])
|
||||||
|
|> validate_actor_presence()
|
||||||
|
|> validate_deletion_rights()
|
||||||
|
|> validate_object_or_user_presence(allowed_types: @deletable_types)
|
||||||
|
|> add_deleted_activity_id()
|
||||||
|
end
|
||||||
|
|
||||||
|
def do_not_federate?(cng) do
|
||||||
|
!same_domain?(cng)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp same_domain?(cng) do
|
||||||
|
actor_uri =
|
||||||
|
cng
|
||||||
|
|> get_field(:actor)
|
||||||
|
|> URI.parse()
|
||||||
|
|
||||||
|
object_uri =
|
||||||
|
cng
|
||||||
|
|> get_field(:object)
|
||||||
|
|> URI.parse()
|
||||||
|
|
||||||
|
object_uri.host == actor_uri.host
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_deletion_rights(cng) do
|
||||||
|
actor = User.get_cached_by_ap_id(get_field(cng, :actor))
|
||||||
|
|
||||||
|
if User.superuser?(actor) || same_domain?(cng) do
|
||||||
|
cng
|
||||||
|
else
|
||||||
|
cng
|
||||||
|
|> add_error(:actor, "is not allowed to delete object")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_and_validate(data) do
|
||||||
|
data
|
||||||
|
|> cast_data
|
||||||
|
|> validate_data
|
||||||
|
end
|
||||||
|
end
|
|
@ -20,8 +20,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
|
||||||
field(:object, Types.ObjectID)
|
field(:object, Types.ObjectID)
|
||||||
field(:actor, Types.ObjectID)
|
field(:actor, Types.ObjectID)
|
||||||
field(:context, :string)
|
field(:context, :string)
|
||||||
field(:to, {:array, :string}, default: [])
|
field(:to, Types.Recipients, default: [])
|
||||||
field(:cc, {:array, :string}, default: [])
|
field(:cc, Types.Recipients, default: [])
|
||||||
end
|
end
|
||||||
|
|
||||||
def cast_and_validate(data) do
|
def cast_and_validate(data) do
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.Recipients do
|
||||||
|
use Ecto.Type
|
||||||
|
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID
|
||||||
|
|
||||||
|
def type, do: {:array, ObjectID}
|
||||||
|
|
||||||
|
def cast(object) when is_binary(object) do
|
||||||
|
cast([object])
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast(data) when is_list(data) do
|
||||||
|
data
|
||||||
|
|> Enum.reduce({:ok, []}, fn element, acc ->
|
||||||
|
case {acc, ObjectID.cast(element)} do
|
||||||
|
{:error, _} -> :error
|
||||||
|
{_, :error} -> :error
|
||||||
|
{{:ok, list}, {:ok, id}} -> {:ok, [id | list]}
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast(_) do
|
||||||
|
:error
|
||||||
|
end
|
||||||
|
|
||||||
|
def dump(data) do
|
||||||
|
{:ok, data}
|
||||||
|
end
|
||||||
|
|
||||||
|
def load(data) do
|
||||||
|
{:ok, data}
|
||||||
|
end
|
||||||
|
end
|
|
@ -44,7 +44,9 @@ defp maybe_federate(%Object{}, _), do: {:ok, :not_federated}
|
||||||
|
|
||||||
defp maybe_federate(%Activity{} = activity, meta) do
|
defp maybe_federate(%Activity{} = activity, meta) do
|
||||||
with {:ok, local} <- Keyword.fetch(meta, :local) do
|
with {:ok, local} <- Keyword.fetch(meta, :local) do
|
||||||
if local do
|
do_not_federate = meta[:do_not_federate]
|
||||||
|
|
||||||
|
if !do_not_federate && local do
|
||||||
Federator.publish(activity)
|
Federator.publish(activity)
|
||||||
{:ok, :federated}
|
{:ok, :federated}
|
||||||
else
|
else
|
||||||
|
|
|
@ -7,6 +7,8 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||||
"""
|
"""
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
|
||||||
def handle(object, meta \\ [])
|
def handle(object, meta \\ [])
|
||||||
|
@ -23,6 +25,49 @@ def handle(%{data: %{"type" => "Like"}} = object, meta) do
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Tasks this handles:
|
||||||
|
# - Delete and unpins the create activity
|
||||||
|
# - Replace object with Tombstone
|
||||||
|
# - Set up notification
|
||||||
|
# - Reduce the user note count
|
||||||
|
# - Reduce the reply count
|
||||||
|
# - Stream out the activity
|
||||||
|
def handle(%{data: %{"type" => "Delete", "object" => deleted_object}} = object, meta) do
|
||||||
|
deleted_object =
|
||||||
|
Object.normalize(deleted_object, false) || User.get_cached_by_ap_id(deleted_object)
|
||||||
|
|
||||||
|
result =
|
||||||
|
case deleted_object do
|
||||||
|
%Object{} ->
|
||||||
|
with {:ok, deleted_object, activity} <- Object.delete(deleted_object),
|
||||||
|
%User{} = user <- User.get_cached_by_ap_id(deleted_object.data["actor"]) do
|
||||||
|
User.remove_pinnned_activity(user, activity)
|
||||||
|
|
||||||
|
{:ok, user} = ActivityPub.decrease_note_count_if_public(user, deleted_object)
|
||||||
|
|
||||||
|
if in_reply_to = deleted_object.data["inReplyTo"] do
|
||||||
|
Object.decrease_replies_count(in_reply_to)
|
||||||
|
end
|
||||||
|
|
||||||
|
ActivityPub.stream_out(object)
|
||||||
|
ActivityPub.stream_out_participations(deleted_object, user)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
%User{} ->
|
||||||
|
with {:ok, _} <- User.delete(deleted_object) do
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
if result == :ok do
|
||||||
|
Notification.create_notifications(object)
|
||||||
|
{:ok, object, meta}
|
||||||
|
else
|
||||||
|
{:error, result}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
# Nothing to do
|
# Nothing to do
|
||||||
def handle(object, meta) do
|
def handle(object, meta) do
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
|
|
|
@ -735,36 +735,12 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: We presently assume that any actor on the same origin domain as the object being
|
|
||||||
# deleted has the rights to delete that object. A better way to validate whether or not
|
|
||||||
# the object should be deleted is to refetch the object URI, which should return either
|
|
||||||
# an error or a tombstone. This would allow us to verify that a deletion actually took
|
|
||||||
# place.
|
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => id} = data,
|
%{"type" => "Delete"} = data,
|
||||||
_options
|
_options
|
||||||
) do
|
) do
|
||||||
object_id = Utils.get_ap_id(object_id)
|
with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
|
||||||
|
|
||||||
with actor <- Containment.get_actor(data),
|
|
||||||
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
|
|
||||||
{:ok, object} <- get_obj_helper(object_id),
|
|
||||||
:ok <- Containment.contain_origin(actor.ap_id, object.data),
|
|
||||||
{:ok, activity} <-
|
|
||||||
ActivityPub.delete(object, local: false, activity_id: id, actor: actor.ap_id) do
|
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
|
||||||
nil ->
|
|
||||||
case User.get_cached_by_ap_id(object_id) do
|
|
||||||
%User{ap_id: ^actor} = user ->
|
|
||||||
User.delete(user)
|
|
||||||
|
|
||||||
nil ->
|
|
||||||
:error
|
|
||||||
end
|
|
||||||
|
|
||||||
_e ->
|
|
||||||
:error
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -512,7 +512,7 @@ def get_latest_reaction(internal_activity_id, %{ap_id: ap_id}, emoji) do
|
||||||
#### Announce-related helpers
|
#### Announce-related helpers
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Retruns an existing announce activity if the notice has already been announced
|
Returns an existing announce activity if the notice has already been announced
|
||||||
"""
|
"""
|
||||||
@spec get_existing_announce(String.t(), map()) :: Activity.t() | nil
|
@spec get_existing_announce(String.t(), map()) :: Activity.t() | nil
|
||||||
def get_existing_announce(actor, %{data: %{"id" => ap_id}}) do
|
def get_existing_announce(actor, %{data: %{"id" => ap_id}}) do
|
||||||
|
|
|
@ -17,6 +17,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.UserInviteToken
|
alias Pleroma.UserInviteToken
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.ActivityPub.Builder
|
||||||
|
alias Pleroma.Web.ActivityPub.Pipeline
|
||||||
alias Pleroma.Web.ActivityPub.Relay
|
alias Pleroma.Web.ActivityPub.Relay
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
alias Pleroma.Web.AdminAPI.AccountView
|
alias Pleroma.Web.AdminAPI.AccountView
|
||||||
|
@ -133,23 +135,20 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do
|
||||||
|
|
||||||
action_fallback(:errors)
|
action_fallback(:errors)
|
||||||
|
|
||||||
def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
|
def user_delete(conn, %{"nickname" => nickname}) do
|
||||||
user = User.get_cached_by_nickname(nickname)
|
user_delete(conn, %{"nicknames" => [nickname]})
|
||||||
User.delete(user)
|
|
||||||
|
|
||||||
ModerationLog.insert_log(%{
|
|
||||||
actor: admin,
|
|
||||||
subject: [user],
|
|
||||||
action: "delete"
|
|
||||||
})
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> json(nickname)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
|
def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
|
||||||
users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
|
users =
|
||||||
User.delete(users)
|
nicknames
|
||||||
|
|> Enum.map(&User.get_cached_by_nickname/1)
|
||||||
|
|
||||||
|
users
|
||||||
|
|> Enum.each(fn user ->
|
||||||
|
{:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
|
||||||
|
Pipeline.common_pipeline(delete_data, local: true)
|
||||||
|
end)
|
||||||
|
|
||||||
ModerationLog.insert_log(%{
|
ModerationLog.insert_log(%{
|
||||||
actor: admin,
|
actor: admin,
|
||||||
|
|
|
@ -79,8 +79,8 @@ def delete(activity_id, user) do
|
||||||
{:find_activity, Activity.get_by_id_with_object(activity_id)},
|
{:find_activity, Activity.get_by_id_with_object(activity_id)},
|
||||||
%Object{} = object <- Object.normalize(activity),
|
%Object{} = object <- Object.normalize(activity),
|
||||||
true <- User.superuser?(user) || user.ap_id == object.data["actor"],
|
true <- User.superuser?(user) || user.ap_id == object.data["actor"],
|
||||||
{:ok, _} <- unpin(activity_id, user),
|
{:ok, delete_data, _} <- Builder.delete(user, object.data["id"]),
|
||||||
{:ok, delete} <- ActivityPub.delete(object) do
|
{:ok, delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do
|
||||||
{:ok, delete}
|
{:ok, delete}
|
||||||
else
|
else
|
||||||
{:find_activity, _} -> {:error, :not_found}
|
{:find_activity, _} -> {:error, :not_found}
|
||||||
|
|
|
@ -4,14 +4,17 @@
|
||||||
|
|
||||||
defmodule Mix.Tasks.Pleroma.UserTest do
|
defmodule Mix.Tasks.Pleroma.UserTest do
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.Tests.ObanHelpers
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.OAuth.Authorization
|
alias Pleroma.Web.OAuth.Authorization
|
||||||
alias Pleroma.Web.OAuth.Token
|
alias Pleroma.Web.OAuth.Token
|
||||||
|
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
use Oban.Testing, repo: Pleroma.Repo
|
||||||
|
|
||||||
import Pleroma.Factory
|
|
||||||
import ExUnit.CaptureIO
|
import ExUnit.CaptureIO
|
||||||
|
import Mock
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
setup_all do
|
setup_all do
|
||||||
Mix.shell(Mix.Shell.Process)
|
Mix.shell(Mix.Shell.Process)
|
||||||
|
@ -87,12 +90,17 @@ test "user is not created" do
|
||||||
test "user is deleted" do
|
test "user is deleted" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
|
with_mock Pleroma.Web.Federator,
|
||||||
|
publish: fn _ -> nil end do
|
||||||
Mix.Tasks.Pleroma.User.run(["rm", user.nickname])
|
Mix.Tasks.Pleroma.User.run(["rm", user.nickname])
|
||||||
|
ObanHelpers.perform_all()
|
||||||
|
|
||||||
assert_received {:mix_shell, :info, [message]}
|
assert_received {:mix_shell, :info, [message]}
|
||||||
assert message =~ " deleted"
|
assert message =~ " deleted"
|
||||||
|
|
||||||
assert %{deactivated: true} = User.get_by_nickname(user.nickname)
|
assert %{deactivated: true} = User.get_by_nickname(user.nickname)
|
||||||
|
|
||||||
|
assert called(Pleroma.Web.Federator.publish(:_))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "no user to delete" do
|
test "no user to delete" do
|
||||||
|
|
|
@ -15,7 +15,6 @@ defmodule Pleroma.UserTest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
use Oban.Testing, repo: Pleroma.Repo
|
use Oban.Testing, repo: Pleroma.Repo
|
||||||
|
|
||||||
import Mock
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
import ExUnit.CaptureLog
|
import ExUnit.CaptureLog
|
||||||
|
|
||||||
|
@ -1131,7 +1130,7 @@ test ".delete_user_activities deletes all create activities", %{user: user} do
|
||||||
|
|
||||||
User.delete_user_activities(user)
|
User.delete_user_activities(user)
|
||||||
|
|
||||||
# TODO: Remove favorites, repeats, delete activities.
|
# TODO: Test removal favorites, repeats, delete activities.
|
||||||
refute Activity.get_by_id(activity.id)
|
refute Activity.get_by_id(activity.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -1170,31 +1169,6 @@ test "it deactivates a user, all follow relationships and all activities", %{use
|
||||||
refute Activity.get_by_id(like_two.id)
|
refute Activity.get_by_id(like_two.id)
|
||||||
refute Activity.get_by_id(repeat.id)
|
refute Activity.get_by_id(repeat.id)
|
||||||
end
|
end
|
||||||
|
|
||||||
test_with_mock "it sends out User Delete activity",
|
|
||||||
%{user: user},
|
|
||||||
Pleroma.Web.ActivityPub.Publisher,
|
|
||||||
[:passthrough],
|
|
||||||
[] do
|
|
||||||
Pleroma.Config.put([:instance, :federating], true)
|
|
||||||
|
|
||||||
{:ok, follower} = User.get_or_fetch_by_ap_id("http://mastodon.example.org/users/admin")
|
|
||||||
{:ok, _} = User.follow(follower, user)
|
|
||||||
|
|
||||||
{:ok, job} = User.delete(user)
|
|
||||||
{:ok, _user} = ObanHelpers.perform(job)
|
|
||||||
|
|
||||||
assert ObanHelpers.member?(
|
|
||||||
%{
|
|
||||||
"op" => "publish_one",
|
|
||||||
"params" => %{
|
|
||||||
"inbox" => "http://mastodon.example.org/inbox",
|
|
||||||
"id" => "pleroma:fakeid"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
all_enqueued(worker: Pleroma.Workers.PublisherWorker)
|
|
||||||
)
|
|
||||||
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
|
||||||
|
|
|
@ -1332,143 +1332,6 @@ test "creates an undo activity for the last block" do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "deletion" do
|
|
||||||
setup do: clear_config([:instance, :rewrite_policy])
|
|
||||||
|
|
||||||
test "it reverts deletion on error" do
|
|
||||||
note = insert(:note_activity)
|
|
||||||
object = Object.normalize(note)
|
|
||||||
|
|
||||||
with_mock(Utils, [:passthrough], maybe_federate: fn _ -> {:error, :reverted} end) do
|
|
||||||
assert {:error, :reverted} = ActivityPub.delete(object)
|
|
||||||
end
|
|
||||||
|
|
||||||
assert Repo.aggregate(Activity, :count, :id) == 1
|
|
||||||
assert Repo.get(Object, object.id) == object
|
|
||||||
assert Activity.get_by_id(note.id) == note
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it creates a delete activity and deletes the original object" do
|
|
||||||
note = insert(:note_activity)
|
|
||||||
object = Object.normalize(note)
|
|
||||||
{:ok, delete} = ActivityPub.delete(object)
|
|
||||||
|
|
||||||
assert delete.data["type"] == "Delete"
|
|
||||||
assert delete.data["actor"] == note.data["actor"]
|
|
||||||
assert delete.data["object"] == object.data["id"]
|
|
||||||
|
|
||||||
assert Activity.get_by_id(delete.id) != nil
|
|
||||||
|
|
||||||
assert Repo.get(Object, object.id).data["type"] == "Tombstone"
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it doesn't fail when an activity was already deleted" do
|
|
||||||
{:ok, delete} = insert(:note_activity) |> Object.normalize() |> ActivityPub.delete()
|
|
||||||
|
|
||||||
assert {:ok, ^delete} = delete |> Object.normalize() |> ActivityPub.delete()
|
|
||||||
end
|
|
||||||
|
|
||||||
test "decrements user note count only for public activities" do
|
|
||||||
user = insert(:user, note_count: 10)
|
|
||||||
|
|
||||||
{:ok, a1} =
|
|
||||||
CommonAPI.post(User.get_cached_by_id(user.id), %{
|
|
||||||
"status" => "yeah",
|
|
||||||
"visibility" => "public"
|
|
||||||
})
|
|
||||||
|
|
||||||
{:ok, a2} =
|
|
||||||
CommonAPI.post(User.get_cached_by_id(user.id), %{
|
|
||||||
"status" => "yeah",
|
|
||||||
"visibility" => "unlisted"
|
|
||||||
})
|
|
||||||
|
|
||||||
{:ok, a3} =
|
|
||||||
CommonAPI.post(User.get_cached_by_id(user.id), %{
|
|
||||||
"status" => "yeah",
|
|
||||||
"visibility" => "private"
|
|
||||||
})
|
|
||||||
|
|
||||||
{:ok, a4} =
|
|
||||||
CommonAPI.post(User.get_cached_by_id(user.id), %{
|
|
||||||
"status" => "yeah",
|
|
||||||
"visibility" => "direct"
|
|
||||||
})
|
|
||||||
|
|
||||||
{:ok, _} = Object.normalize(a1) |> ActivityPub.delete()
|
|
||||||
{:ok, _} = Object.normalize(a2) |> ActivityPub.delete()
|
|
||||||
{:ok, _} = Object.normalize(a3) |> ActivityPub.delete()
|
|
||||||
{:ok, _} = Object.normalize(a4) |> ActivityPub.delete()
|
|
||||||
|
|
||||||
user = User.get_cached_by_id(user.id)
|
|
||||||
assert user.note_count == 10
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it creates a delete activity and checks that it is also sent to users mentioned by the deleted object" do
|
|
||||||
user = insert(:user)
|
|
||||||
note = insert(:note_activity)
|
|
||||||
object = Object.normalize(note)
|
|
||||||
|
|
||||||
{:ok, object} =
|
|
||||||
object
|
|
||||||
|> Object.change(%{
|
|
||||||
data: %{
|
|
||||||
"actor" => object.data["actor"],
|
|
||||||
"id" => object.data["id"],
|
|
||||||
"to" => [user.ap_id],
|
|
||||||
"type" => "Note"
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|> Object.update_and_set_cache()
|
|
||||||
|
|
||||||
{:ok, delete} = ActivityPub.delete(object)
|
|
||||||
|
|
||||||
assert user.ap_id in delete.data["to"]
|
|
||||||
end
|
|
||||||
|
|
||||||
test "decreases reply count" do
|
|
||||||
user = insert(:user)
|
|
||||||
user2 = insert(:user)
|
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "1", "visibility" => "public"})
|
|
||||||
reply_data = %{"status" => "1", "in_reply_to_status_id" => activity.id}
|
|
||||||
ap_id = activity.data["id"]
|
|
||||||
|
|
||||||
{:ok, public_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "public"))
|
|
||||||
{:ok, unlisted_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "unlisted"))
|
|
||||||
{:ok, private_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "private"))
|
|
||||||
{:ok, direct_reply} = CommonAPI.post(user2, Map.put(reply_data, "visibility", "direct"))
|
|
||||||
|
|
||||||
_ = CommonAPI.delete(direct_reply.id, user2)
|
|
||||||
assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
|
|
||||||
assert object.data["repliesCount"] == 2
|
|
||||||
|
|
||||||
_ = CommonAPI.delete(private_reply.id, user2)
|
|
||||||
assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
|
|
||||||
assert object.data["repliesCount"] == 2
|
|
||||||
|
|
||||||
_ = CommonAPI.delete(public_reply.id, user2)
|
|
||||||
assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
|
|
||||||
assert object.data["repliesCount"] == 1
|
|
||||||
|
|
||||||
_ = CommonAPI.delete(unlisted_reply.id, user2)
|
|
||||||
assert %{data: data, object: object} = Activity.get_by_ap_id_with_object(ap_id)
|
|
||||||
assert object.data["repliesCount"] == 0
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it passes delete activity through MRF before deleting the object" do
|
|
||||||
Pleroma.Config.put([:instance, :rewrite_policy], Pleroma.Web.ActivityPub.MRF.DropPolicy)
|
|
||||||
|
|
||||||
note = insert(:note_activity)
|
|
||||||
object = Object.normalize(note)
|
|
||||||
|
|
||||||
{:error, {:reject, _}} = ActivityPub.delete(object)
|
|
||||||
|
|
||||||
assert Activity.get_by_id(note.id)
|
|
||||||
assert Repo.get(Object, object.id).data["type"] == object.data["type"]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe "timeline post-processing" do
|
describe "timeline post-processing" do
|
||||||
test "it filters broken threads" do
|
test "it filters broken threads" do
|
||||||
user1 = insert(:user)
|
user1 = insert(:user)
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
|
defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Web.ActivityPub.Builder
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
||||||
alias Pleroma.Web.ActivityPub.Utils
|
alias Pleroma.Web.ActivityPub.Utils
|
||||||
|
@ -8,6 +10,98 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
describe "deletes" do
|
||||||
|
setup do
|
||||||
|
user = insert(:user)
|
||||||
|
{:ok, post_activity} = CommonAPI.post(user, %{"status" => "cancel me daddy"})
|
||||||
|
|
||||||
|
{:ok, valid_post_delete, _} = Builder.delete(user, post_activity.data["object"])
|
||||||
|
{:ok, valid_user_delete, _} = Builder.delete(user, user.ap_id)
|
||||||
|
|
||||||
|
%{user: user, valid_post_delete: valid_post_delete, valid_user_delete: valid_user_delete}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it is valid for a post deletion", %{valid_post_delete: valid_post_delete} do
|
||||||
|
{:ok, valid_post_delete, _} = ObjectValidator.validate(valid_post_delete, [])
|
||||||
|
|
||||||
|
assert valid_post_delete["deleted_activity_id"]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it is invalid if the object isn't in a list of certain types", %{
|
||||||
|
valid_post_delete: valid_post_delete
|
||||||
|
} do
|
||||||
|
object = Object.get_by_ap_id(valid_post_delete["object"])
|
||||||
|
|
||||||
|
data =
|
||||||
|
object.data
|
||||||
|
|> Map.put("type", "Like")
|
||||||
|
|
||||||
|
{:ok, _object} =
|
||||||
|
object
|
||||||
|
|> Ecto.Changeset.change(%{data: data})
|
||||||
|
|> Object.update_and_set_cache()
|
||||||
|
|
||||||
|
{:error, cng} = ObjectValidator.validate(valid_post_delete, [])
|
||||||
|
assert {:object, {"object not in allowed types", []}} in cng.errors
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it is valid for a user deletion", %{valid_user_delete: valid_user_delete} do
|
||||||
|
assert match?({:ok, _, _}, ObjectValidator.validate(valid_user_delete, []))
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it's invalid if the id is missing", %{valid_post_delete: valid_post_delete} do
|
||||||
|
no_id =
|
||||||
|
valid_post_delete
|
||||||
|
|> Map.delete("id")
|
||||||
|
|
||||||
|
{:error, cng} = ObjectValidator.validate(no_id, [])
|
||||||
|
|
||||||
|
assert {:id, {"can't be blank", [validation: :required]}} in cng.errors
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it's invalid if the object doesn't exist", %{valid_post_delete: valid_post_delete} do
|
||||||
|
missing_object =
|
||||||
|
valid_post_delete
|
||||||
|
|> Map.put("object", "http://does.not/exist")
|
||||||
|
|
||||||
|
{:error, cng} = ObjectValidator.validate(missing_object, [])
|
||||||
|
|
||||||
|
assert {:object, {"can't find object", []}} in cng.errors
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it's invalid if the actor of the object and the actor of delete are from different domains",
|
||||||
|
%{valid_post_delete: valid_post_delete} do
|
||||||
|
valid_user = insert(:user)
|
||||||
|
|
||||||
|
valid_other_actor =
|
||||||
|
valid_post_delete
|
||||||
|
|> Map.put("actor", valid_user.ap_id)
|
||||||
|
|
||||||
|
assert match?({:ok, _, _}, ObjectValidator.validate(valid_other_actor, []))
|
||||||
|
|
||||||
|
invalid_other_actor =
|
||||||
|
valid_post_delete
|
||||||
|
|> Map.put("actor", "https://gensokyo.2hu/users/raymoo")
|
||||||
|
|
||||||
|
{:error, cng} = ObjectValidator.validate(invalid_other_actor, [])
|
||||||
|
|
||||||
|
assert {:actor, {"is not allowed to delete object", []}} in cng.errors
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it's valid if the actor of the object is a local superuser",
|
||||||
|
%{valid_post_delete: valid_post_delete} do
|
||||||
|
user =
|
||||||
|
insert(:user, local: true, is_moderator: true, ap_id: "https://gensokyo.2hu/users/raymoo")
|
||||||
|
|
||||||
|
valid_other_actor =
|
||||||
|
valid_post_delete
|
||||||
|
|> Map.put("actor", user.ap_id)
|
||||||
|
|
||||||
|
{:ok, _, meta} = ObjectValidator.validate(valid_other_actor, [])
|
||||||
|
assert meta[:do_not_federate]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "likes" do
|
describe "likes" do
|
||||||
setup do
|
setup do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
defmodule Pleroma.Web.ObjectValidators.Types.RecipientsTest do
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.Types.Recipients
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
test "it asserts that all elements of the list are object ids" do
|
||||||
|
list = ["https://lain.com/users/lain", "invalid"]
|
||||||
|
|
||||||
|
assert :error == Recipients.cast(list)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it works with a list" do
|
||||||
|
list = ["https://lain.com/users/lain"]
|
||||||
|
assert {:ok, list} == Recipients.cast(list)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it works with a list with whole objects" do
|
||||||
|
list = ["https://lain.com/users/lain", %{"id" => "https://gensokyo.2hu/users/raymoo"}]
|
||||||
|
resulting_list = ["https://gensokyo.2hu/users/raymoo", "https://lain.com/users/lain"]
|
||||||
|
assert {:ok, resulting_list} == Recipients.cast(list)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it turns a single string into a list" do
|
||||||
|
recipient = "https://lain.com/users/lain"
|
||||||
|
|
||||||
|
assert {:ok, [recipient]} == Recipients.cast(recipient)
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,17 +3,74 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
|
defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
|
||||||
|
use Oban.Testing, repo: Pleroma.Repo
|
||||||
use Pleroma.DataCase
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
|
alias Pleroma.Tests.ObanHelpers
|
||||||
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
alias Pleroma.Web.ActivityPub.Builder
|
alias Pleroma.Web.ActivityPub.Builder
|
||||||
alias Pleroma.Web.ActivityPub.SideEffects
|
alias Pleroma.Web.ActivityPub.SideEffects
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
import Mock
|
||||||
|
|
||||||
|
describe "delete objects" do
|
||||||
|
setup do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, op} = CommonAPI.post(other_user, %{"status" => "big oof"})
|
||||||
|
{:ok, post} = CommonAPI.post(user, %{"status" => "hey", "in_reply_to_id" => op})
|
||||||
|
object = Object.normalize(post)
|
||||||
|
{:ok, delete_data, _meta} = Builder.delete(user, object.data["id"])
|
||||||
|
{:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id)
|
||||||
|
{:ok, delete, _meta} = ActivityPub.persist(delete_data, local: true)
|
||||||
|
{:ok, delete_user, _meta} = ActivityPub.persist(delete_user_data, local: true)
|
||||||
|
%{user: user, delete: delete, post: post, object: object, delete_user: delete_user, op: op}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it handles object deletions", %{
|
||||||
|
delete: delete,
|
||||||
|
post: post,
|
||||||
|
object: object,
|
||||||
|
user: user,
|
||||||
|
op: op
|
||||||
|
} do
|
||||||
|
with_mock Pleroma.Web.ActivityPub.ActivityPub, [:passthrough],
|
||||||
|
stream_out: fn _ -> nil end,
|
||||||
|
stream_out_participations: fn _, _ -> nil end do
|
||||||
|
{:ok, delete, _} = SideEffects.handle(delete)
|
||||||
|
user = User.get_cached_by_ap_id(object.data["actor"])
|
||||||
|
|
||||||
|
assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out(delete))
|
||||||
|
assert called(Pleroma.Web.ActivityPub.ActivityPub.stream_out_participations(object, user))
|
||||||
|
end
|
||||||
|
|
||||||
|
object = Object.get_by_id(object.id)
|
||||||
|
assert object.data["type"] == "Tombstone"
|
||||||
|
refute Activity.get_by_id(post.id)
|
||||||
|
|
||||||
|
user = User.get_by_id(user.id)
|
||||||
|
assert user.note_count == 0
|
||||||
|
|
||||||
|
object = Object.normalize(op.data["object"], false)
|
||||||
|
|
||||||
|
assert object.data["repliesCount"] == 0
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it handles user deletions", %{delete_user: delete, user: user} do
|
||||||
|
{:ok, _delete, _} = SideEffects.handle(delete)
|
||||||
|
ObanHelpers.perform_all()
|
||||||
|
|
||||||
|
assert User.get_cached_by_ap_id(user.ap_id).deactivated
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "like objects" do
|
describe "like objects" do
|
||||||
setup do
|
setup do
|
||||||
|
|
|
@ -0,0 +1,86 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.Transmogrifier.DeleteHandlingTest do
|
||||||
|
use Oban.Testing, repo: Pleroma.Repo
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Object
|
||||||
|
alias Pleroma.Tests.ObanHelpers
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
|
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
|
setup_all do
|
||||||
|
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it works for incoming deletes" do
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
deleting_user = insert(:user)
|
||||||
|
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/mastodon-delete.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("actor", deleting_user.ap_id)
|
||||||
|
|> put_in(["object", "id"], activity.data["object"])
|
||||||
|
|
||||||
|
{:ok, %Activity{actor: actor, local: false, data: %{"id" => id}}} =
|
||||||
|
Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
assert id == data["id"]
|
||||||
|
|
||||||
|
# We delete the Create activity because we base our timelines on it.
|
||||||
|
# This should be changed after we unify objects and activities
|
||||||
|
refute Activity.get_by_id(activity.id)
|
||||||
|
assert actor == deleting_user.ap_id
|
||||||
|
|
||||||
|
# Objects are replaced by a tombstone object.
|
||||||
|
object = Object.normalize(activity.data["object"])
|
||||||
|
assert object.data["type"] == "Tombstone"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it fails for incoming deletes with spoofed origin" do
|
||||||
|
activity = insert(:note_activity)
|
||||||
|
%{ap_id: ap_id} = insert(:user, ap_id: "https://gensokyo.2hu/users/raymoo")
|
||||||
|
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/mastodon-delete.json")
|
||||||
|
|> Poison.decode!()
|
||||||
|
|> Map.put("actor", ap_id)
|
||||||
|
|> put_in(["object", "id"], activity.data["object"])
|
||||||
|
|
||||||
|
assert match?({:error, _}, Transmogrifier.handle_incoming(data))
|
||||||
|
end
|
||||||
|
|
||||||
|
@tag capture_log: true
|
||||||
|
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)
|
||||||
|
ObanHelpers.perform_all()
|
||||||
|
|
||||||
|
assert User.get_cached_by_ap_id(ap_id).deactivated
|
||||||
|
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 match?({:error, _}, Transmogrifier.handle_incoming(data))
|
||||||
|
|
||||||
|
assert User.get_cached_by_ap_id(ap_id)
|
||||||
|
end
|
||||||
|
end
|
|
@ -766,84 +766,6 @@ test "it works for incoming update activities which lock the account" do
|
||||||
assert user.locked == true
|
assert user.locked == true
|
||||||
end
|
end
|
||||||
|
|
||||||
test "it works for incoming deletes" do
|
|
||||||
activity = insert(:note_activity)
|
|
||||||
deleting_user = insert(:user)
|
|
||||||
|
|
||||||
data =
|
|
||||||
File.read!("test/fixtures/mastodon-delete.json")
|
|
||||||
|> Poison.decode!()
|
|
||||||
|
|
||||||
object =
|
|
||||||
data["object"]
|
|
||||||
|> Map.put("id", activity.data["object"])
|
|
||||||
|
|
||||||
data =
|
|
||||||
data
|
|
||||||
|> Map.put("object", object)
|
|
||||||
|> Map.put("actor", deleting_user.ap_id)
|
|
||||||
|
|
||||||
{:ok, %Activity{actor: actor, local: false, data: %{"id" => id}}} =
|
|
||||||
Transmogrifier.handle_incoming(data)
|
|
||||||
|
|
||||||
assert id == data["id"]
|
|
||||||
refute Activity.get_by_id(activity.id)
|
|
||||||
assert actor == deleting_user.ap_id
|
|
||||||
end
|
|
||||||
|
|
||||||
test "it fails for incoming deletes with spoofed origin" do
|
|
||||||
activity = insert(:note_activity)
|
|
||||||
|
|
||||||
data =
|
|
||||||
File.read!("test/fixtures/mastodon-delete.json")
|
|
||||||
|> Poison.decode!()
|
|
||||||
|
|
||||||
object =
|
|
||||||
data["object"]
|
|
||||||
|> Map.put("id", activity.data["object"])
|
|
||||||
|
|
||||||
data =
|
|
||||||
data
|
|
||||||
|> Map.put("object", object)
|
|
||||||
|
|
||||||
assert capture_log(fn ->
|
|
||||||
:error = Transmogrifier.handle_incoming(data)
|
|
||||||
end) =~
|
|
||||||
"[error] Could not decode user at fetch http://mastodon.example.org/users/gargron, {:error, :nxdomain}"
|
|
||||||
|
|
||||||
assert Activity.get_by_id(activity.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
@tag capture_log: true
|
|
||||||
test "it works for incoming user deletes" do
|
|
||||||
%{ap_id: ap_id} =
|
|
||||||
insert(:user, ap_id: "http://mastodon.example.org/users/admin", local: false)
|
|
||||||
|
|
||||||
data =
|
|
||||||
File.read!("test/fixtures/mastodon-delete-user.json")
|
|
||||||
|> Poison.decode!()
|
|
||||||
|
|
||||||
{:ok, _} = Transmogrifier.handle_incoming(data)
|
|
||||||
ObanHelpers.perform_all()
|
|
||||||
|
|
||||||
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 capture_log(fn ->
|
|
||||||
assert :error == Transmogrifier.handle_incoming(data)
|
|
||||||
end) =~ "Object containment failed"
|
|
||||||
|
|
||||||
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"})
|
||||||
|
|
|
@ -6,8 +6,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
|
||||||
use Pleroma.Web.ConnCase
|
use Pleroma.Web.ConnCase
|
||||||
use Oban.Testing, repo: Pleroma.Repo
|
use Oban.Testing, repo: Pleroma.Repo
|
||||||
|
|
||||||
import Pleroma.Factory
|
|
||||||
import ExUnit.CaptureLog
|
import ExUnit.CaptureLog
|
||||||
|
import Mock
|
||||||
|
import Pleroma.Factory
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
|
@ -147,17 +148,26 @@ test "GET /api/pleroma/admin/users/:nickname requires " <>
|
||||||
test "single user", %{admin: admin, conn: conn} do
|
test "single user", %{admin: admin, conn: conn} do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
|
|
||||||
|
with_mock Pleroma.Web.Federator,
|
||||||
|
publish: fn _ -> nil end do
|
||||||
conn =
|
conn =
|
||||||
conn
|
conn
|
||||||
|> put_req_header("accept", "application/json")
|
|> put_req_header("accept", "application/json")
|
||||||
|> delete("/api/pleroma/admin/users?nickname=#{user.nickname}")
|
|> delete("/api/pleroma/admin/users?nickname=#{user.nickname}")
|
||||||
|
|
||||||
|
ObanHelpers.perform_all()
|
||||||
|
|
||||||
|
assert User.get_by_nickname(user.nickname).deactivated
|
||||||
|
|
||||||
log_entry = Repo.one(ModerationLog)
|
log_entry = Repo.one(ModerationLog)
|
||||||
|
|
||||||
assert ModerationLog.get_log_entry_message(log_entry) ==
|
assert ModerationLog.get_log_entry_message(log_entry) ==
|
||||||
"@#{admin.nickname} deleted users: @#{user.nickname}"
|
"@#{admin.nickname} deleted users: @#{user.nickname}"
|
||||||
|
|
||||||
assert json_response(conn, 200) == user.nickname
|
assert json_response(conn, 200) == [user.nickname]
|
||||||
|
|
||||||
|
assert called(Pleroma.Web.Federator.publish(:_))
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
test "multiple users", %{admin: admin, conn: conn} do
|
test "multiple users", %{admin: admin, conn: conn} do
|
||||||
|
|
|
@ -9,11 +9,13 @@ defmodule Pleroma.Web.CommonAPITest do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.ActivityPub.Transmogrifier
|
||||||
alias Pleroma.Web.ActivityPub.Visibility
|
alias Pleroma.Web.ActivityPub.Visibility
|
||||||
alias Pleroma.Web.AdminAPI.AccountView
|
alias Pleroma.Web.AdminAPI.AccountView
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
|
||||||
import Pleroma.Factory
|
import Pleroma.Factory
|
||||||
|
import Mock
|
||||||
|
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
|
||||||
|
@ -21,6 +23,84 @@ defmodule Pleroma.Web.CommonAPITest do
|
||||||
setup do: clear_config([:instance, :limit])
|
setup do: clear_config([:instance, :limit])
|
||||||
setup do: clear_config([:instance, :max_pinned_statuses])
|
setup do: clear_config([:instance, :max_pinned_statuses])
|
||||||
|
|
||||||
|
describe "deletion" do
|
||||||
|
test "it allows users to delete their posts" do
|
||||||
|
user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, post} = CommonAPI.post(user, %{"status" => "namu amida butsu"})
|
||||||
|
|
||||||
|
with_mock Pleroma.Web.Federator,
|
||||||
|
publish: fn _ -> nil end do
|
||||||
|
assert {:ok, delete} = CommonAPI.delete(post.id, user)
|
||||||
|
assert delete.local
|
||||||
|
assert called(Pleroma.Web.Federator.publish(delete))
|
||||||
|
end
|
||||||
|
|
||||||
|
refute Activity.get_by_id(post.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it does not allow a user to delete their posts" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, post} = CommonAPI.post(user, %{"status" => "namu amida butsu"})
|
||||||
|
|
||||||
|
assert {:error, "Could not delete"} = CommonAPI.delete(post.id, other_user)
|
||||||
|
assert Activity.get_by_id(post.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it allows moderators to delete other user's posts" do
|
||||||
|
user = insert(:user)
|
||||||
|
moderator = insert(:user, is_moderator: true)
|
||||||
|
|
||||||
|
{:ok, post} = CommonAPI.post(user, %{"status" => "namu amida butsu"})
|
||||||
|
|
||||||
|
assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
|
||||||
|
assert delete.local
|
||||||
|
|
||||||
|
refute Activity.get_by_id(post.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "it allows admins to delete other user's posts" do
|
||||||
|
user = insert(:user)
|
||||||
|
moderator = insert(:user, is_admin: true)
|
||||||
|
|
||||||
|
{:ok, post} = CommonAPI.post(user, %{"status" => "namu amida butsu"})
|
||||||
|
|
||||||
|
assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
|
||||||
|
assert delete.local
|
||||||
|
|
||||||
|
refute Activity.get_by_id(post.id)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "superusers deleting non-local posts won't federate the delete" do
|
||||||
|
# This is the user of the ingested activity
|
||||||
|
_user =
|
||||||
|
insert(:user,
|
||||||
|
local: false,
|
||||||
|
ap_id: "http://mastodon.example.org/users/admin",
|
||||||
|
last_refreshed_at: NaiveDateTime.utc_now()
|
||||||
|
)
|
||||||
|
|
||||||
|
moderator = insert(:user, is_admin: true)
|
||||||
|
|
||||||
|
data =
|
||||||
|
File.read!("test/fixtures/mastodon-post-activity.json")
|
||||||
|
|> Jason.decode!()
|
||||||
|
|
||||||
|
{:ok, post} = Transmogrifier.handle_incoming(data)
|
||||||
|
|
||||||
|
with_mock Pleroma.Web.Federator,
|
||||||
|
publish: fn _ -> nil end do
|
||||||
|
assert {:ok, delete} = CommonAPI.delete(post.id, moderator)
|
||||||
|
assert delete.local
|
||||||
|
refute called(Pleroma.Web.Federator.publish(:_))
|
||||||
|
end
|
||||||
|
|
||||||
|
refute Activity.get_by_id(post.id)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
test "favoriting race condition" do
|
test "favoriting race condition" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
users_serial = insert_list(10, :user)
|
users_serial = insert_list(10, :user)
|
||||||
|
|
|
@ -210,6 +210,12 @@ test "it sends to public" do
|
||||||
Worker.push_to_socket(topics, "public", activity)
|
Worker.push_to_socket(topics, "public", activity)
|
||||||
|
|
||||||
Task.await(task)
|
Task.await(task)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "works for deletions" do
|
||||||
|
user = insert(:user)
|
||||||
|
other_user = insert(:user)
|
||||||
|
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "Test"})
|
||||||
|
|
||||||
task =
|
task =
|
||||||
Task.async(fn ->
|
Task.async(fn ->
|
||||||
|
|
Loading…
Reference in a new issue