Merge branch 'develop' of git.pleroma.social:pleroma/pleroma into remake-remodel-dms

This commit is contained in:
lain 2020-05-11 17:14:58 +02:00
commit f28ed36b4d
10 changed files with 185 additions and 15 deletions

View file

@ -29,7 +29,7 @@
key: :filters, key: :filters,
type: {:list, :module}, type: {:list, :module},
description: description:
"List of filter modules for uploads. Module names are shortened (removed leading `Pleroma.Upload.Filter.` part), but on adding custom MRF module you need to use full name.", "List of filter modules for uploads. Module names are shortened (removed leading `Pleroma.Upload.Filter.` part), but on adding custom module you need to use full name.",
suggestions: suggestions:
Generator.list_modules_in_dir( Generator.list_modules_in_dir(
"lib/pleroma/upload/filter", "lib/pleroma/upload/filter",
@ -683,7 +683,7 @@
key: :federation_publisher_modules, key: :federation_publisher_modules,
type: {:list, :module}, type: {:list, :module},
description: description:
"List of modules for federation publishing. Module names are shortened (removed leading `Pleroma.Web.` part), but on adding custom MRF module you need to use full name.", "List of modules for federation publishing. Module names are shortened (removed leading `Pleroma.Web.` part), but on adding custom module you need to use full name.",
suggestions: [ suggestions: [
Pleroma.Web.ActivityPub.Publisher Pleroma.Web.ActivityPub.Publisher
] ]
@ -697,7 +697,7 @@
key: :rewrite_policy, key: :rewrite_policy,
type: [:module, {:list, :module}], type: [:module, {:list, :module}],
description: description:
"A list of enabled MRF policies. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom MRF module you need to use full name.", "A list of enabled MRF policies. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.",
suggestions: suggestions:
Generator.list_modules_in_dir( Generator.list_modules_in_dir(
"lib/pleroma/web/activity_pub/mrf", "lib/pleroma/web/activity_pub/mrf",
@ -2035,7 +2035,7 @@
key: :parsers, key: :parsers,
type: {:list, :module}, type: {:list, :module},
description: description:
"List of Rich Media parsers. Module names are shortened (removed leading `Pleroma.Web.RichMedia.Parsers.` part), but on adding custom MRF module you need to use full name.", "List of Rich Media parsers. Module names are shortened (removed leading `Pleroma.Web.RichMedia.Parsers.` part), but on adding custom module you need to use full name.",
suggestions: [ suggestions: [
Pleroma.Web.RichMedia.Parsers.MetaTagsParser, Pleroma.Web.RichMedia.Parsers.MetaTagsParser,
Pleroma.Web.RichMedia.Parsers.OEmbed, Pleroma.Web.RichMedia.Parsers.OEmbed,
@ -2048,7 +2048,7 @@
label: "TTL setters", label: "TTL setters",
type: {:list, :module}, type: {:list, :module},
description: description:
"List of rich media TTL setters. Module names are shortened (removed leading `Pleroma.Web.RichMedia.Parser.` part), but on adding custom MRF module you need to use full name.", "List of rich media TTL setters. Module names are shortened (removed leading `Pleroma.Web.RichMedia.Parser.` part), but on adding custom module you need to use full name.",
suggestions: [ suggestions: [
Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl
] ]
@ -2723,7 +2723,7 @@
key: :scrub_policy, key: :scrub_policy,
type: {:list, :module}, type: {:list, :module},
description: description:
"Module names are shortened (removed leading `Pleroma.HTML.` part), but on adding custom MRF module you need to use full name.", "Module names are shortened (removed leading `Pleroma.HTML.` part), but on adding custom module you need to use full name.",
suggestions: [Pleroma.HTML.Transform.MediaProxy, Pleroma.HTML.Scrubber.Default] suggestions: [Pleroma.HTML.Transform.MediaProxy, Pleroma.HTML.Scrubber.Default]
} }
] ]

View file

@ -1554,10 +1554,23 @@ def delete_user_activities(%User{ap_id: ap_id} = user) do
|> Stream.run() |> Stream.run()
end end
defp delete_activity(%{data: %{"type" => "Create", "object" => object}}, user) do defp delete_activity(%{data: %{"type" => "Create", "object" => object}} = activity, user) do
{:ok, delete_data, _} = Builder.delete(user, object) with {_, %Object{}} <- {:find_object, Object.get_by_ap_id(object)},
{:ok, delete_data, _} <- Builder.delete(user, object) do
Pipeline.common_pipeline(delete_data, local: user.local) Pipeline.common_pipeline(delete_data, local: user.local)
else
{:find_object, nil} ->
# We have the create activity, but not the object, it was probably pruned.
# Insert a tombstone and try again
with {:ok, tombstone_data, _} <- Builder.tombstone(user.ap_id, object),
{:ok, _tombstone} <- Object.create(tombstone_data) do
delete_activity(activity, user)
end
e ->
Logger.error("Could not delete #{object} created by #{activity.data["ap_id"]}")
Logger.error("Error: #{inspect(e)}")
end
end end
defp delete_activity(%{data: %{"type" => type}} = activity, user) defp delete_activity(%{data: %{"type" => type}} = activity, user)

View file

@ -99,6 +99,16 @@ def chat_message(actor, recipient, content, opts \\ []) do
end end
end end
@spec tombstone(String.t(), String.t()) :: {:ok, map(), keyword()}
def tombstone(actor, id) do
{:ok,
%{
"id" => id,
"actor" => actor,
"type" => "Tombstone"
}, []}
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
with {:ok, data, meta} <- object_action(actor, object) do with {:ok, data, meta} <- object_action(actor, object) do

View file

@ -51,6 +51,7 @@ def add_deleted_activity_id(cng) do
Page Page
Question Question
Video Video
Tombstone
} }
def validate_data(cng) do def validate_data(cng) do
cng cng

View file

@ -14,7 +14,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.ObjectValidator alias Pleroma.Web.ActivityPub.ObjectValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.ActivityPub.Visibility
@ -730,6 +732,19 @@ def handle_incoming(
) do ) do
with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do with {:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity} {:ok, activity}
else
{:error, {:validate_object, _}} = e ->
# Check if we have a create activity for this
with {:ok, object_id} <- Types.ObjectID.cast(data["object"]),
%Activity{data: %{"actor" => actor}} <-
Activity.create_by_object_ap_id(object_id) |> Repo.one(),
# We have one, insert a tombstone and retry
{:ok, tombstone_data, _} <- Builder.tombstone(actor, object_id),
{:ok, _tombstone} <- Object.create(tombstone_data) do
handle_incoming(data)
else
_ -> e
end
end end
end end

View file

@ -114,16 +114,35 @@ def reject_follow_request(follower, followed) do
end end
def delete(activity_id, user) do def delete(activity_id, user) do
with {_, %Activity{data: %{"object" => _}} = activity} <- with {_, %Activity{data: %{"object" => _, "type" => "Create"}} = activity} <-
{:find_activity, Activity.get_by_id_with_object(activity_id)}, {:find_activity, Activity.get_by_id(activity_id)},
%Object{} = object <- Object.normalize(activity), {_, %Object{} = object, _} <-
{:find_object, Object.normalize(activity, false), activity},
true <- User.superuser?(user) || user.ap_id == object.data["actor"], true <- User.superuser?(user) || user.ap_id == object.data["actor"],
{:ok, delete_data, _} <- Builder.delete(user, object.data["id"]), {:ok, delete_data, _} <- Builder.delete(user, object.data["id"]),
{:ok, delete, _} <- Pipeline.common_pipeline(delete_data, local: true) 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, dgettext("errors", "Could not delete")} {:error, :not_found}
{:find_object, nil, %Activity{data: %{"actor" => actor, "object" => object}}} ->
# We have the create activity, but not the object, it was probably pruned.
# Insert a tombstone and try again
with {:ok, tombstone_data, _} <- Builder.tombstone(actor, object),
{:ok, _tombstone} <- Object.create(tombstone_data) do
delete(activity_id, user)
else
_ ->
Logger.error(
"Could not insert tombstone for missing object on deletion. Object is #{object}."
)
{:error, dgettext("errors", "Could not delete")}
end
_ ->
{:error, dgettext("errors", "Could not delete")}
end end
end end

View file

@ -3,9 +3,12 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Mix.Tasks.Pleroma.UserTest do defmodule Mix.Tasks.Pleroma.UserTest do
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Repo alias Pleroma.Repo
alias Pleroma.Tests.ObanHelpers alias Pleroma.Tests.ObanHelpers
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.Authorization
alias Pleroma.Web.OAuth.Token alias Pleroma.Web.OAuth.Token
@ -103,6 +106,28 @@ test "user is deleted" do
end end
end end
test "a remote user's create activity is deleted when the object has been pruned" do
user = insert(:user)
{:ok, post} = CommonAPI.post(user, %{"status" => "uguu"})
object = Object.normalize(post)
Object.prune(object)
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 %{deactivated: true} = User.get_by_nickname(user.nickname)
assert called(Pleroma.Web.Federator.publish(:_))
end
refute Activity.get_by_id(post.id)
end
test "no user to delete" do test "no user to delete" do
Mix.Tasks.Pleroma.User.run(["rm", "nonexistent"]) Mix.Tasks.Pleroma.User.run(["rm", "nonexistent"])

View file

@ -28,15 +28,56 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
{:ok, op} = CommonAPI.post(other_user, %{"status" => "big oof"}) {:ok, op} = CommonAPI.post(other_user, %{"status" => "big oof"})
{:ok, post} = CommonAPI.post(user, %{"status" => "hey", "in_reply_to_id" => op}) {:ok, post} = CommonAPI.post(user, %{"status" => "hey", "in_reply_to_id" => op})
{:ok, favorite} = CommonAPI.favorite(user, post.id)
object = Object.normalize(post) object = Object.normalize(post)
{:ok, delete_data, _meta} = Builder.delete(user, object.data["id"]) {:ok, delete_data, _meta} = Builder.delete(user, object.data["id"])
{:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id) {:ok, delete_user_data, _meta} = Builder.delete(user, user.ap_id)
{:ok, delete, _meta} = ActivityPub.persist(delete_data, local: true) {:ok, delete, _meta} = ActivityPub.persist(delete_data, local: true)
{:ok, delete_user, _meta} = ActivityPub.persist(delete_user_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}
%{
user: user,
delete: delete,
post: post,
object: object,
delete_user: delete_user,
op: op,
favorite: favorite
}
end end
test "it handles object deletions", %{ test "it handles object deletions", %{
delete: delete,
post: post,
object: object,
user: user,
op: op,
favorite: favorite
} 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)
refute Activity.get_by_id(favorite.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 object deletions when the object itself has been pruned", %{
delete: delete, delete: delete,
post: post, post: post,
object: object, object: object,

View file

@ -44,6 +44,34 @@ test "it works for incoming deletes" do
assert object.data["type"] == "Tombstone" assert object.data["type"] == "Tombstone"
end end
test "it works for incoming when the object has been pruned" do
activity = insert(:note_activity)
{:ok, object} =
Object.normalize(activity.data["object"])
|> Repo.delete()
Cachex.del(:object_cache, "object:#{object.data["id"]}")
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
end
test "it fails for incoming deletes with spoofed origin" do test "it fails for incoming deletes with spoofed origin" do
activity = insert(:note_activity) activity = insert(:note_activity)
%{ap_id: ap_id} = insert(:user, ap_id: "https://gensokyo.2hu/users/raymoo") %{ap_id: ap_id} = insert(:user, ap_id: "https://gensokyo.2hu/users/raymoo")

View file

@ -76,6 +76,24 @@ test "it reject messages over the local limit" do
end end
describe "deletion" do describe "deletion" do
test "it works with pruned objects" do
user = insert(:user)
{:ok, post} = CommonAPI.post(user, %{"status" => "namu amida butsu"})
Object.normalize(post, false)
|> Object.prune()
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 allows users to delete their posts" do test "it allows users to delete their posts" do
user = insert(:user) user = insert(:user)