diff options
27 files changed, 635 insertions, 388 deletions
| diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 705c4c15e..2524918d4 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -8,6 +8,10 @@ For from source installations Pleroma configuration works by first importing the  To add configuration to your config file, you can copy it from the base config. The latest version of it can be viewed [here](https://git.pleroma.social/pleroma/pleroma/blob/develop/config/config.exs). You can also use this file if you don't know how an option is supposed to be formatted. +## :chat + +* `enabled` - Enables the backend chat. Defaults to `true`. +  ## :instance  * `name`: The instance’s name.  * `email`: Email used to reach an Administrator/Moderator of the instance. diff --git a/installation/pleroma-apache.conf b/installation/pleroma-apache.conf index b5640ac3d..0d627f2d7 100644 --- a/installation/pleroma-apache.conf +++ b/installation/pleroma-apache.conf @@ -32,9 +32,8 @@ CustomLog ${APACHE_LOG_DIR}/access.log combined  <VirtualHost *:443>      SSLEngine on -    SSLCertificateFile      /etc/letsencrypt/live/${servername}/cert.pem +    SSLCertificateFile      /etc/letsencrypt/live/${servername}/fullchain.pem      SSLCertificateKeyFile   /etc/letsencrypt/live/${servername}/privkey.pem -    SSLCertificateChainFile /etc/letsencrypt/live/${servername}/fullchain.pem      # Mozilla modern configuration, tweak to your needs      SSLProtocol             all -SSLv3 -TLSv1 -TLSv1.1 diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 40dd9bdc0..da140ac86 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -8,6 +8,8 @@ defmodule Mix.Tasks.Pleroma.User do    alias Ecto.Changeset    alias Pleroma.User    alias Pleroma.UserInviteToken +  alias Pleroma.Web.ActivityPub.Builder +  alias Pleroma.Web.ActivityPub.Pipeline    @shortdoc "Manages Pleroma users"    @moduledoc File.read!("docs/administration/CLI_tasks/user.md") @@ -96,8 +98,9 @@ defmodule Mix.Tasks.Pleroma.User do    def run(["rm", nickname]) do      start_pleroma() -    with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do -      User.perform(:delete, user) +    with %User{local: true} = user <- User.get_cached_by_nickname(nickname), +         {:ok, delete_data, _} <- Builder.delete(user, user.ap_id), +         {:ok, _delete, _} <- Pipeline.common_pipeline(delete_data, local: true) do        shell_info("User #{nickname} deleted.")      else        _ -> shell_error("No local user #{nickname}") diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 2c343eb22..323eb2a41 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -29,7 +29,9 @@ defmodule Pleroma.User do    alias Pleroma.UserRelationship    alias Pleroma.Web    alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.ActivityPub.Builder    alias Pleroma.Web.ActivityPub.ObjectValidators.Types +  alias Pleroma.Web.ActivityPub.Pipeline    alias Pleroma.Web.ActivityPub.Utils    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils @@ -1425,8 +1427,6 @@ 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      user      |> get_followers() @@ -1536,21 +1536,23 @@ defmodule Pleroma.User do      })    end -  def delete_user_activities(%User{ap_id: ap_id}) do +  def delete_user_activities(%User{ap_id: ap_id} = user) do      ap_id      |> Activity.Queries.by_actor()      |> 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()    end -  defp delete_activity(%{data: %{"type" => "Create"}} = activity) do -    activity -    |> Object.normalize() -    |> ActivityPub.delete() +  defp delete_activity(%{data: %{"type" => "Create", "object" => object}}, user) do +    {:ok, delete_data, _} = Builder.delete(user, object) + +    Pipeline.common_pipeline(delete_data, local: true)    end -  defp delete_activity(%{data: %{"type" => "Like"}} = activity) do +  defp delete_activity(%{data: %{"type" => "Like"}} = activity, _user) do      object = Object.normalize(activity)      activity.actor @@ -1558,7 +1560,7 @@ defmodule Pleroma.User do      |> ActivityPub.unlike(object)    end -  defp delete_activity(%{data: %{"type" => "Announce"}} = activity) do +  defp delete_activity(%{data: %{"type" => "Announce"}} = activity, _user) do      object = Object.normalize(activity)      activity.actor @@ -1566,7 +1568,7 @@ defmodule Pleroma.User do      |> ActivityPub.unannounce(object)    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      Pleroma.HTML.Scrubber.TwitterText diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 697ace488..697336019 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -526,67 +526,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do      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()) ::            {:ok, Activity.t()} | {:error, any()}    def block(blocker, blocked, activity_id \\ nil, local \\ true) do diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index ab9d1e0b7..976ff243e 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -415,7 +415,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do    defp handle_user_activity(%User{} = user, %{"type" => "Delete"} = params) do      with %Object{} = object <- Object.normalize(params["object"]),           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}      else        _ -> {:error, dgettext("errors", "Can't delete object")} diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 67e65c7b9..6e3a375e7 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -11,6 +11,33 @@ defmodule Pleroma.Web.ActivityPub.Builder do    alias Pleroma.Web.ActivityPub.Utils    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 +    def create(actor, object, recipients) do      {:ok,       %{ diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index d6c14f7b8..cc5ca1d9e 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -13,12 +13,23 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator    alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator +  alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator    alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator    alias Pleroma.Web.ActivityPub.ObjectValidators.Types    @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}    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      with {:ok, object} <-             object diff --git a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex index 02f3a6438..4e6ee2034 100644 --- a/lib/pleroma/web/activity_pub/object_validators/common_validations.ex +++ b/lib/pleroma/web/activity_pub/object_validators/common_validations.ex @@ -8,11 +8,29 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do    alias Pleroma.Object    alias Pleroma.User -  def validate_actor_presence(cng) do -    validate_user_presence(cng, :actor) +  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_user_presence(cng, field_name) do +  def validate_actor_presence(cng, options \\ []) do +    field_name = Keyword.get(options, :field_name, :actor) +      cng      |> validate_change(field_name, fn field_name, actor ->        if User.get_cached_by_ap_id(actor) do @@ -23,14 +41,39 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do      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 -    |> validate_change(field_name, fn field_name, object -> -      if Object.get_cached_by_ap_id(object) do -        [] -      else -        [{field_name, "can't find object"}] +    |> validate_change(field_name, fn field_name, object_id -> +      object = Object.get_cached_by_ap_id(object_id) + +      cond do +        !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 + +  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 diff --git a/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex new file mode 100644 index 000000000..e06de3dff --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/delete_validator.ex @@ -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 diff --git a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex index 1bce739bd..034f25492 100644 --- a/lib/pleroma/web/activity_pub/object_validators/like_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/like_validator.ex @@ -20,8 +20,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do      field(:object, Types.ObjectID)      field(:actor, Types.ObjectID)      field(:context, :string) -    field(:to, {:array, :string}, default: []) -    field(:cc, {:array, :string}, default: []) +    field(:to, Types.Recipients, default: []) +    field(:cc, Types.Recipients, default: [])    end    def cast_and_validate(data) do diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex index d5abb7567..657cdfdb1 100644 --- a/lib/pleroma/web/activity_pub/pipeline.ex +++ b/lib/pleroma/web/activity_pub/pipeline.ex @@ -44,7 +44,9 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do    defp maybe_federate(%Activity{} = activity, meta) 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)          {:ok, :federated}        else diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index e394c75d7..8bdc433ff 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -10,6 +10,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do    alias Pleroma.Object    alias Pleroma.Repo    alias Pleroma.User +  alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Pipeline    alias Pleroma.Web.ActivityPub.Utils @@ -40,6 +41,49 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do      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    def handle(object, meta) do      {:ok, object, meta} diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 40a505b91..55e0df283 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -745,36 +745,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do      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( -        %{"type" => "Delete", "object" => object_id, "actor" => actor, "id" => id} = data, +        %{"type" => "Delete"} = data,          _options        ) do -    object_id = Utils.get_ap_id(object_id) - -    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 +    with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do        {: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 diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 2d685ecc0..1a3b0b3c1 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -512,7 +512,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do    #### Announce-related helpers    @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    def get_existing_announce(actor, %{data: %{"id" => ap_id}}) do diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index da71e63d9..80a4ebaac 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -17,6 +17,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    alias Pleroma.User    alias Pleroma.UserInviteToken    alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.ActivityPub.Builder +  alias Pleroma.Web.ActivityPub.Pipeline    alias Pleroma.Web.ActivityPub.Relay    alias Pleroma.Web.ActivityPub.Utils    alias Pleroma.Web.AdminAPI.AccountView @@ -133,23 +135,20 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do    action_fallback(:errors) -  def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do -    user = User.get_cached_by_nickname(nickname) -    User.delete(user) - -    ModerationLog.insert_log(%{ -      actor: admin, -      subject: [user], -      action: "delete" -    }) - -    conn -    |> json(nickname) +  def user_delete(conn, %{"nickname" => nickname}) do +    user_delete(conn, %{"nicknames" => [nickname]})    end    def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do -    users = nicknames |> Enum.map(&User.get_cached_by_nickname/1) -    User.delete(users) +    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(%{        actor: admin, diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 38b5c6f7c..d7d934683 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -110,8 +110,8 @@ defmodule Pleroma.Web.CommonAPI do             {:find_activity, Activity.get_by_id_with_object(activity_id)},           %Object{} = object <- Object.normalize(activity),           true <- User.superuser?(user) || user.ap_id == object.data["actor"], -         {:ok, _} <- unpin(activity_id, user), -         {: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}      else        {:find_activity, _} -> {:error, :not_found} diff --git a/test/tasks/user_test.exs b/test/tasks/user_test.exs index 0f6ffb2b1..e0fee7290 100644 --- a/test/tasks/user_test.exs +++ b/test/tasks/user_test.exs @@ -4,14 +4,17 @@  defmodule Mix.Tasks.Pleroma.UserTest do    alias Pleroma.Repo +  alias Pleroma.Tests.ObanHelpers    alias Pleroma.User    alias Pleroma.Web.OAuth.Authorization    alias Pleroma.Web.OAuth.Token    use Pleroma.DataCase +  use Oban.Testing, repo: Pleroma.Repo -  import Pleroma.Factory    import ExUnit.CaptureIO +  import Mock +  import Pleroma.Factory    setup_all do      Mix.shell(Mix.Shell.Process) @@ -87,12 +90,17 @@ defmodule Mix.Tasks.Pleroma.UserTest do      test "user is deleted" do        user = insert(:user) -      Mix.Tasks.Pleroma.User.run(["rm", user.nickname]) +      with_mock Pleroma.Web.Federator, +        publish: fn _ -> nil end do +        Mix.Tasks.Pleroma.User.run(["rm", user.nickname]) +        ObanHelpers.perform_all() -      assert_received {:mix_shell, :info, [message]} -      assert message =~ " deleted" +        assert_received {:mix_shell, :info, [message]} +        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      test "no user to delete" do diff --git a/test/user_test.exs b/test/user_test.exs index bff337d3e..a3c75aa9b 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -15,7 +15,6 @@ defmodule Pleroma.UserTest do    use Pleroma.DataCase    use Oban.Testing, repo: Pleroma.Repo -  import Mock    import Pleroma.Factory    import ExUnit.CaptureLog @@ -1131,7 +1130,7 @@ defmodule Pleroma.UserTest do        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)      end @@ -1170,31 +1169,6 @@ defmodule Pleroma.UserTest do        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 -      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    test "get_public_key_for_ap_id fetches a user that's not in the db" do diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index 84ead93bb..4dc9c0f0a 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -1332,143 +1332,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do      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      test "it filters broken threads" do        user1 = insert(:user) diff --git a/test/web/activity_pub/object_validator_test.exs b/test/web/activity_pub/object_validator_test.exs index fcc54c8a1..c9eace866 100644 --- a/test/web/activity_pub/object_validator_test.exs +++ b/test/web/activity_pub/object_validator_test.exs @@ -156,6 +156,98 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do      end    end +  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      setup do        user = insert(:user) diff --git a/test/web/activity_pub/side_effects_test.exs b/test/web/activity_pub/side_effects_test.exs index 19abac6a6..a631e5c6b 100644 --- a/test/web/activity_pub/side_effects_test.exs +++ b/test/web/activity_pub/side_effects_test.exs @@ -3,18 +3,75 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.ActivityPub.SideEffectsTest do +  use Oban.Testing, repo: Pleroma.Repo    use Pleroma.DataCase +  alias Pleroma.Activity    alias Pleroma.Chat    alias Pleroma.Notification    alias Pleroma.Object    alias Pleroma.Repo +  alias Pleroma.Tests.ObanHelpers +  alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.ActivityPub.Builder    alias Pleroma.Web.ActivityPub.SideEffects    alias Pleroma.Web.CommonAPI    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      setup do diff --git a/test/web/activity_pub/transmogrifier/delete_handling_test.exs b/test/web/activity_pub/transmogrifier/delete_handling_test.exs new file mode 100644 index 000000000..f235a8e63 --- /dev/null +++ b/test/web/activity_pub/transmogrifier/delete_handling_test.exs @@ -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 diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 23efa4be6..6d43c3365 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -766,84 +766,6 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do        assert user.locked == true      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        user = insert(:user)        {:ok, activity} = CommonAPI.post(user, %{"status" => "hey"}) diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs index 78c79bb07..7ab7cc15c 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/admin_api_controller_test.exs @@ -6,8 +6,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do    use Pleroma.Web.ConnCase    use Oban.Testing, repo: Pleroma.Repo -  import Pleroma.Factory    import ExUnit.CaptureLog +  import Mock +  import Pleroma.Factory    alias Pleroma.Activity    alias Pleroma.Config @@ -147,17 +148,26 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do      test "single user", %{admin: admin, conn: conn} do        user = insert(:user) -      conn = -        conn -        |> put_req_header("accept", "application/json") -        |> delete("/api/pleroma/admin/users?nickname=#{user.nickname}") +      with_mock Pleroma.Web.Federator, +        publish: fn _ -> nil end do +        conn = +          conn +          |> put_req_header("accept", "application/json") +          |> delete("/api/pleroma/admin/users?nickname=#{user.nickname}") -      log_entry = Repo.one(ModerationLog) +        ObanHelpers.perform_all() -      assert ModerationLog.get_log_entry_message(log_entry) == -               "@#{admin.nickname} deleted users: @#{user.nickname}" +        assert User.get_by_nickname(user.nickname).deactivated + +        log_entry = Repo.one(ModerationLog) -      assert json_response(conn, 200) == user.nickname +        assert ModerationLog.get_log_entry_message(log_entry) == +                 "@#{admin.nickname} deleted users: @#{user.nickname}" + +        assert json_response(conn, 200) == [user.nickname] + +        assert called(Pleroma.Web.Federator.publish(:_)) +      end      end      test "multiple users", %{admin: admin, conn: conn} do diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 58ffda72f..ef7c479c0 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -10,11 +10,13 @@ defmodule Pleroma.Web.CommonAPITest do    alias Pleroma.Object    alias Pleroma.User    alias Pleroma.Web.ActivityPub.ActivityPub +  alias Pleroma.Web.ActivityPub.Transmogrifier    alias Pleroma.Web.ActivityPub.Visibility    alias Pleroma.Web.AdminAPI.AccountView    alias Pleroma.Web.CommonAPI    import Pleroma.Factory +  import Mock    require Pleroma.Constants @@ -71,6 +73,84 @@ defmodule Pleroma.Web.CommonAPITest do      end    end +  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      user = insert(:user)      users_serial = insert_list(10, :user) diff --git a/test/web/streamer/streamer_test.exs b/test/web/streamer/streamer_test.exs index 8b8d8af6c..3c0f240f5 100644 --- a/test/web/streamer/streamer_test.exs +++ b/test/web/streamer/streamer_test.exs @@ -210,6 +210,12 @@ defmodule Pleroma.Web.StreamerTest do      Worker.push_to_socket(topics, "public", activity)      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.async(fn -> | 
