Merge branch 'features/validators-note' into 'develop'

Pipeline Ingestion: Note

Closes #290

See merge request pleroma/pleroma!2984
This commit is contained in:
Haelwenn 2021-06-01 01:51:38 +00:00
commit c4b4258374
31 changed files with 389 additions and 318 deletions

View file

@ -13,21 +13,33 @@ def cast(object) when is_binary(object) do
cast([object]) cast([object])
end end
def cast(object) when is_map(object) do
case ObjectID.cast(object) do
{:ok, data} -> {:ok, [data]}
_ -> :error
end
end
def cast(data) when is_list(data) do def cast(data) when is_list(data) do
data =
data data
|> Enum.reduce_while({:ok, []}, fn element, {:ok, list} -> |> Enum.reduce_while([], fn element, list ->
case ObjectID.cast(element) do case ObjectID.cast(element) do
{:ok, id} -> {:ok, id} ->
{:cont, {:ok, [id | list]}} {:cont, [id | list]}
_ -> _ ->
{:halt, :error} {:cont, list}
end end
end) end)
|> Enum.sort()
|> Enum.uniq()
{:ok, data}
end end
def cast(_) do def cast(data) do
:error {:error, data}
end end
def dump(data) do def dump(data) do

View file

@ -12,4 +12,10 @@ def put_if_present(map, key, value, value_function \\ &{:ok, &1}) when is_map(ma
_ -> map _ -> map
end end
end end
def safe_put_in(data, keys, value) when is_map(data) and is_list(keys) do
Kernel.put_in(data, keys, value)
rescue
_ -> data
end
end end

View file

@ -4,6 +4,7 @@
defmodule Pleroma.Object.Fetcher do defmodule Pleroma.Object.Fetcher do
alias Pleroma.HTTP alias Pleroma.HTTP
alias Pleroma.Maps
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Object.Containment alias Pleroma.Object.Containment
alias Pleroma.Repo alias Pleroma.Repo
@ -101,6 +102,9 @@ def fetch_object_from_id(id, options \\ []) do
{:transmogrifier, {:error, {:reject, e}}} -> {:transmogrifier, {:error, {:reject, e}}} ->
{:reject, e} {:reject, e}
{:transmogrifier, {:reject, e}} ->
{:reject, e}
{:transmogrifier, _} = e -> {:transmogrifier, _} = e ->
{:error, e} {:error, e}
@ -124,12 +128,14 @@ def fetch_object_from_id(id, options \\ []) do
defp prepare_activity_params(data) do defp prepare_activity_params(data) do
%{ %{
"type" => "Create", "type" => "Create",
"to" => data["to"] || [],
"cc" => data["cc"] || [],
# Should we seriously keep this attributedTo thing? # Should we seriously keep this attributedTo thing?
"actor" => data["actor"] || data["attributedTo"], "actor" => data["actor"] || data["attributedTo"],
"object" => data "object" => data
} }
|> Maps.put_if_present("to", data["to"])
|> Maps.put_if_present("cc", data["cc"])
|> Maps.put_if_present("bto", data["bto"])
|> Maps.put_if_present("bcc", data["bcc"])
end end
def fetch_object_from_id!(id, options \\ []) do def fetch_object_from_id!(id, options \\ []) do

View file

@ -88,7 +88,7 @@ defp increase_replies_count_if_reply(%{
defp increase_replies_count_if_reply(_create_data), do: :noop defp increase_replies_count_if_reply(_create_data), do: :noop
@object_types ~w[ChatMessage Question Answer Audio Video Event Article] @object_types ~w[ChatMessage Question Answer Audio Video Event Article Note]
@impl true @impl true
def persist(%{"type" => type} = object, meta) when type in @object_types do def persist(%{"type" => type} = object, meta) when type in @object_types do
with {:ok, object} <- Object.create(object) do with {:ok, object} <- Object.create(object) do

View file

@ -102,7 +102,7 @@ def validate(
%{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity, %{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
meta meta
) )
when objtype in ~w[Question Answer Audio Video Event Article] do when objtype in ~w[Question Answer Audio Video Event Article Note] do
with {:ok, object_data} <- cast_and_apply(object), with {:ok, object_data} <- cast_and_apply(object),
meta = Keyword.put(meta, :object_data, object_data |> stringify_keys), meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
{:ok, create_activity} <- {:ok, create_activity} <-
@ -115,7 +115,7 @@ def validate(
end end
def validate(%{"type" => type} = object, meta) def validate(%{"type" => type} = object, meta)
when type in ~w[Event Question Audio Video Article] do when type in ~w[Event Question Audio Video Article Note] do
validator = validator =
case type do case type do
"Event" -> EventValidator "Event" -> EventValidator
@ -123,6 +123,7 @@ def validate(%{"type" => type} = object, meta)
"Audio" -> AudioVideoValidator "Audio" -> AudioVideoValidator
"Video" -> AudioVideoValidator "Video" -> AudioVideoValidator
"Article" -> ArticleNoteValidator "Article" -> ArticleNoteValidator
"Note" -> ArticleNoteValidator
end end
with {:ok, object} <- with {:ok, object} <-
@ -194,7 +195,7 @@ def cast_and_apply(%{"type" => "Event"} = object) do
EventValidator.cast_and_apply(object) EventValidator.cast_and_apply(object)
end end
def cast_and_apply(%{"type" => "Article"} = object) do def cast_and_apply(%{"type" => type} = object) when type in ~w[Article Note] do
ArticleNoteValidator.cast_and_apply(object) ArticleNoteValidator.cast_and_apply(object)
end end

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator do
use Ecto.Schema use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
import Ecto.Changeset import Ecto.Changeset
@ -23,6 +24,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator do
field(:name, :string) field(:name, :string)
field(:inReplyTo, ObjectValidators.ObjectID) field(:inReplyTo, ObjectValidators.ObjectID)
field(:attributedTo, ObjectValidators.ObjectID) field(:attributedTo, ObjectValidators.ObjectID)
field(:context, :string)
# TODO: Remove actor on objects # TODO: Remove actor on objects
field(:actor, ObjectValidators.ObjectID) field(:actor, ObjectValidators.ObjectID)
@ -46,6 +48,11 @@ def cast_data(data) do
end end
def changeset(struct, data) do def changeset(struct, data) do
data =
data
|> CommonFixes.fix_actor()
|> CommonFixes.fix_object_defaults()
struct struct
|> cast(data, __schema__(:fields)) |> cast(data, __schema__(:fields))
end end

View file

@ -50,6 +50,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNoteValidator do
field(:likes, {:array, ObjectValidators.ObjectID}, default: []) field(:likes, {:array, ObjectValidators.ObjectID}, default: [])
field(:announcements, {:array, ObjectValidators.ObjectID}, default: []) field(:announcements, {:array, ObjectValidators.ObjectID}, default: [])
field(:replies, {:array, ObjectValidators.ObjectID}, default: [])
end end
def cast_and_apply(data) do def cast_and_apply(data) do
@ -65,25 +67,39 @@ def cast_and_validate(data) do
end end
def cast_data(data) do def cast_data(data) do
data = fix(data)
%__MODULE__{} %__MODULE__{}
|> changeset(data) |> changeset(data)
end end
defp fix_url(%{"url" => url} = data) when is_map(url) do defp fix_url(%{"url" => url} = data) when is_bitstring(url), do: data
Map.put(data, "url", url["href"]) defp fix_url(%{"url" => url} = data) when is_map(url), do: Map.put(data, "url", url["href"])
end
defp fix_url(data), do: data defp fix_url(data), do: data
defp fix_tag(%{"tag" => tag} = data) when is_list(tag), do: data
defp fix_tag(%{"tag" => tag} = data) when is_map(tag), do: Map.put(data, "tag", [tag])
defp fix_tag(data), do: Map.drop(data, ["tag"])
defp fix_replies(%{"replies" => %{"first" => %{"items" => replies}}} = data)
when is_list(replies),
do: Map.put(data, "replies", replies)
defp fix_replies(%{"replies" => %{"items" => replies}} = data) when is_list(replies),
do: Map.put(data, "replies", replies)
defp fix_replies(%{"replies" => replies} = data) when is_bitstring(replies),
do: Map.drop(data, ["replies"])
defp fix_replies(data), do: data
defp fix(data) do defp fix(data) do
data data
|> CommonFixes.fix_defaults()
|> CommonFixes.fix_attribution()
|> CommonFixes.fix_actor() |> CommonFixes.fix_actor()
|> CommonFixes.fix_object_defaults()
|> fix_url() |> fix_url()
|> fix_tag()
|> fix_replies()
|> Transmogrifier.fix_emoji() |> Transmogrifier.fix_emoji()
|> Transmogrifier.fix_content_map()
end end
def changeset(struct, data) do def changeset(struct, data) do

View file

@ -119,9 +119,8 @@ defp fix_content(data), do: data
defp fix(data) do defp fix(data) do
data data
|> CommonFixes.fix_defaults()
|> CommonFixes.fix_attribution()
|> CommonFixes.fix_actor() |> CommonFixes.fix_actor()
|> CommonFixes.fix_object_defaults()
|> Transmogrifier.fix_emoji() |> Transmogrifier.fix_emoji()
|> fix_url() |> fix_url()
|> fix_content() |> fix_content()

View file

@ -3,26 +3,55 @@
# SPDX-License-Identifier: AGPL-3.0-only # SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object.Containment alias Pleroma.Object.Containment
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
# based on Pleroma.Web.ActivityPub.Utils.lazy_put_objects_defaults def cast_and_filter_recipients(message, field, follower_collection, field_fallback \\ []) do
def fix_defaults(data) do {:ok, data} = ObjectValidators.Recipients.cast(message[field] || field_fallback)
data =
Enum.reject(data, fn x ->
String.ends_with?(x, "/followers") and x != follower_collection
end)
Map.put(message, field, data)
end
def fix_object_defaults(data) do
%{data: %{"id" => context}, id: context_id} = %{data: %{"id" => context}, id: context_id} =
Utils.create_context(data["context"] || data["conversation"]) Utils.create_context(data["context"] || data["conversation"])
%User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["attributedTo"])
data data
|> Map.put("context", context) |> Map.put("context", context)
|> Map.put("context_id", context_id) |> Map.put("context_id", context_id)
|> cast_and_filter_recipients("to", follower_collection)
|> cast_and_filter_recipients("cc", follower_collection)
|> cast_and_filter_recipients("bto", follower_collection)
|> cast_and_filter_recipients("bcc", follower_collection)
|> Transmogrifier.fix_implicit_addressing(follower_collection)
end end
def fix_attribution(data) do def fix_activity_addressing(activity, _meta) do
data %User{follower_address: follower_collection} = User.get_cached_by_ap_id(activity["actor"])
|> Map.put_new("actor", data["attributedTo"])
activity
|> cast_and_filter_recipients("to", follower_collection)
|> cast_and_filter_recipients("cc", follower_collection)
|> cast_and_filter_recipients("bto", follower_collection)
|> cast_and_filter_recipients("bcc", follower_collection)
|> Transmogrifier.fix_implicit_addressing(follower_collection)
end end
def fix_actor(data) do def fix_actor(data) do
actor = Containment.get_actor(data) actor =
data
|> Map.put_new("actor", data["attributedTo"])
|> Containment.get_actor()
data data
|> Map.put("actor", actor) |> Map.put("actor", actor)

View file

@ -15,6 +15,7 @@ def validate_any_presence(cng, fields) do
fields fields
|> Enum.map(fn field -> get_field(cng, field) end) |> Enum.map(fn field -> get_field(cng, field) end)
|> Enum.any?(fn |> Enum.any?(fn
nil -> false
[] -> false [] -> false
_ -> true _ -> true
end) end)

View file

@ -10,8 +10,10 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
alias Pleroma.Web.ActivityPub.Transmogrifier
import Ecto.Changeset import Ecto.Changeset
@ -23,6 +25,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
field(:type, :string) field(:type, :string)
field(:to, ObjectValidators.Recipients, default: []) field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: []) field(:cc, ObjectValidators.Recipients, default: [])
field(:bto, ObjectValidators.Recipients, default: [])
field(:bcc, ObjectValidators.Recipients, default: [])
field(:object, ObjectValidators.ObjectID) field(:object, ObjectValidators.ObjectID)
field(:expires_at, ObjectValidators.DateTime) field(:expires_at, ObjectValidators.DateTime)
@ -54,39 +58,37 @@ def changeset(struct, data) do
|> cast(data, __schema__(:fields)) |> cast(data, __schema__(:fields))
end end
defp fix_context(data, meta) do # CommonFixes.fix_activity_addressing adapted for Create specific behavior
if object = meta[:object_data] do defp fix_addressing(data, object) do
Map.put_new(data, "context", object["context"]) %User{follower_address: follower_collection} = User.get_cached_by_ap_id(data["actor"])
else
data data
end |> CommonFixes.cast_and_filter_recipients("to", follower_collection, object["to"])
|> CommonFixes.cast_and_filter_recipients("cc", follower_collection, object["cc"])
|> CommonFixes.cast_and_filter_recipients("bto", follower_collection, object["bto"])
|> CommonFixes.cast_and_filter_recipients("bcc", follower_collection, object["bcc"])
|> Transmogrifier.fix_implicit_addressing(follower_collection)
end end
defp fix_addressing(data, meta) do def fix(data, meta) do
if object = meta[:object_data] do object = meta[:object_data]
data
|> Map.put_new("to", object["to"] || [])
|> Map.put_new("cc", object["cc"] || [])
else
data
end
end
defp fix(data, meta) do
data data
|> fix_context(meta)
|> fix_addressing(meta)
|> CommonFixes.fix_actor() |> CommonFixes.fix_actor()
|> Map.put_new("context", object["context"])
|> fix_addressing(object)
end end
defp validate_data(cng, meta) do defp validate_data(cng, meta) do
object = meta[:object_data]
cng cng
|> validate_required([:actor, :type, :object]) |> validate_required([:actor, :type, :object, :to, :cc])
|> validate_inclusion(:type, ["Create"]) |> validate_inclusion(:type, ["Create"])
|> CommonValidations.validate_actor_presence() |> CommonValidations.validate_actor_presence()
|> CommonValidations.validate_any_presence([:to, :cc]) |> validate_actors_match(object)
|> validate_actors_match(meta) |> validate_context_match(object)
|> validate_context_match(meta) |> validate_addressing_match(object)
|> validate_object_nonexistence() |> validate_object_nonexistence()
|> validate_object_containment() |> validate_object_containment()
end end
@ -118,8 +120,8 @@ def validate_object_nonexistence(cng) do
end) end)
end end
def validate_actors_match(cng, meta) do def validate_actors_match(cng, object) do
attributed_to = meta[:object_data]["attributedTo"] || meta[:object_data]["actor"] attributed_to = object["attributedTo"] || object["actor"]
cng cng
|> validate_change(:actor, fn :actor, actor -> |> validate_change(:actor, fn :actor, actor ->
@ -131,7 +133,7 @@ def validate_actors_match(cng, meta) do
end) end)
end end
def validate_context_match(cng, %{object_data: %{"context" => object_context}}) do def validate_context_match(cng, %{"context" => object_context}) do
cng cng
|> validate_change(:context, fn :context, context -> |> validate_change(:context, fn :context, context ->
if context == object_context do if context == object_context do
@ -142,5 +144,18 @@ def validate_context_match(cng, %{object_data: %{"context" => object_context}})
end) end)
end end
def validate_context_match(cng, _), do: cng def validate_addressing_match(cng, object) do
[:to, :cc, :bcc, :bto]
|> Enum.reduce(cng, fn field, cng ->
object_data = object[to_string(field)]
validate_change(cng, field, fn field, data ->
if data == object_data do
[]
else
[{field, "field doesn't match with object (#{inspect(object_data)})"}]
end
end)
end)
end
end end

View file

@ -1,29 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateNoteValidator do
use Ecto.Schema
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator
import Ecto.Changeset
@primary_key false
embedded_schema do
field(:id, ObjectValidators.ObjectID, primary_key: true)
field(:actor, ObjectValidators.ObjectID)
field(:type, :string)
field(:to, ObjectValidators.Recipients, default: [])
field(:cc, ObjectValidators.Recipients, default: [])
field(:bto, ObjectValidators.Recipients, default: [])
field(:bcc, ObjectValidators.Recipients, default: [])
embeds_one(:object, NoteValidator)
end
def cast_data(data) do
cast(%__MODULE__{}, data, __schema__(:fields))
end
end

View file

@ -72,8 +72,8 @@ def cast_data(data) do
defp fix(data) do defp fix(data) do
data data
|> CommonFixes.fix_defaults() |> CommonFixes.fix_actor()
|> CommonFixes.fix_attribution() |> CommonFixes.fix_object_defaults()
|> Transmogrifier.fix_emoji() |> Transmogrifier.fix_emoji()
end end

View file

@ -83,8 +83,8 @@ defp fix_closed(data) do
defp fix(data) do defp fix(data) do
data data
|> CommonFixes.fix_defaults() |> CommonFixes.fix_actor()
|> CommonFixes.fix_attribution() |> CommonFixes.fix_object_defaults()
|> Transmogrifier.fix_emoji() |> Transmogrifier.fix_emoji()
|> fix_closed() |> fix_closed()
end end

View file

@ -203,6 +203,19 @@ def handle(%{data: %{"type" => "Create"}} = activity, meta) do
Object.increase_replies_count(in_reply_to) Object.increase_replies_count(in_reply_to)
end end
reply_depth = (meta[:depth] || 0) + 1
# FIXME: Force inReplyTo to replies
if Pleroma.Web.Federator.allowed_thread_distance?(reply_depth) and
object.data["replies"] != nil do
for reply_id <- object.data["replies"] do
Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{
"id" => reply_id,
"depth" => reply_depth
})
end
end
ConcurrentLimiter.limit(Pleroma.Web.RichMedia.Helpers, fn -> ConcurrentLimiter.limit(Pleroma.Web.RichMedia.Helpers, fn ->
Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end) Task.start(fn -> Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) end)
end) end)
@ -423,7 +436,7 @@ def handle_object_creation(%{"type" => "Answer"} = object_map, meta) do
end end
def handle_object_creation(%{"type" => objtype} = object, meta) def handle_object_creation(%{"type" => objtype} = object, meta)
when objtype in ~w[Audio Video Question Event Article] do when objtype in ~w[Audio Video Question Event Article Note] do
with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do with {:ok, object, meta} <- Pipeline.common_pipeline(object, meta) do
{:ok, object, meta} {:ok, object, meta}
end end

View file

@ -43,7 +43,6 @@ def fix_object(object, options \\ []) do
|> fix_content_map() |> fix_content_map()
|> fix_addressing() |> fix_addressing()
|> fix_summary() |> fix_summary()
|> fix_type(options)
end end
def fix_summary(%{"summary" => nil} = object) do def fix_summary(%{"summary" => nil} = object) do
@ -72,17 +71,21 @@ def fix_addressing_list(map, field) do
end end
end end
def fix_explicit_addressing( # if directMessage flag is set to true, leave the addressing alone
%{"to" => to, "cc" => cc} = object, def fix_explicit_addressing(%{"directMessage" => true} = object, _follower_collection),
explicit_mentions, do: object
follower_collection
) do
explicit_to = Enum.filter(to, fn x -> x in explicit_mentions end)
def fix_explicit_addressing(%{"to" => to, "cc" => cc} = object, follower_collection) do
explicit_mentions =
Utils.determine_explicit_mentions(object) ++
[Pleroma.Constants.as_public(), follower_collection]
explicit_to = Enum.filter(to, fn x -> x in explicit_mentions end)
explicit_cc = Enum.filter(to, fn x -> x not in explicit_mentions end) explicit_cc = Enum.filter(to, fn x -> x not in explicit_mentions end)
final_cc = final_cc =
(cc ++ explicit_cc) (cc ++ explicit_cc)
|> Enum.filter(& &1)
|> Enum.reject(fn x -> String.ends_with?(x, "/followers") and x != follower_collection end) |> Enum.reject(fn x -> String.ends_with?(x, "/followers") and x != follower_collection end)
|> Enum.uniq() |> Enum.uniq()
@ -91,29 +94,6 @@ def fix_explicit_addressing(
|> Map.put("cc", final_cc) |> Map.put("cc", final_cc)
end end
def fix_explicit_addressing(object, _explicit_mentions, _followers_collection), do: object
# if directMessage flag is set to true, leave the addressing alone
def fix_explicit_addressing(%{"directMessage" => true} = object), do: object
def fix_explicit_addressing(object) do
explicit_mentions = Utils.determine_explicit_mentions(object)
%User{follower_address: follower_collection} =
object
|> Containment.get_actor()
|> User.get_cached_by_ap_id()
explicit_mentions =
explicit_mentions ++
[
Pleroma.Constants.as_public(),
follower_collection
]
fix_explicit_addressing(object, explicit_mentions, follower_collection)
end
# if as:Public is addressed, then make sure the followers collection is also addressed # if as:Public is addressed, then make sure the followers collection is also addressed
# so that the activities will be delivered to local users. # so that the activities will be delivered to local users.
def fix_implicit_addressing(%{"to" => to, "cc" => cc} = object, followers_collection) do def fix_implicit_addressing(%{"to" => to, "cc" => cc} = object, followers_collection) do
@ -137,19 +117,19 @@ def fix_implicit_addressing(%{"to" => to, "cc" => cc} = object, followers_collec
end end
end end
def fix_implicit_addressing(object, _), do: object
def fix_addressing(object) do def fix_addressing(object) do
{:ok, %User{} = user} = User.get_or_fetch_by_ap_id(object["actor"]) {:ok, %User{follower_address: follower_collection}} =
followers_collection = User.ap_followers(user) object
|> Containment.get_actor()
|> User.get_or_fetch_by_ap_id()
object object
|> fix_addressing_list("to") |> fix_addressing_list("to")
|> fix_addressing_list("cc") |> fix_addressing_list("cc")
|> fix_addressing_list("bto") |> fix_addressing_list("bto")
|> fix_addressing_list("bcc") |> fix_addressing_list("bcc")
|> fix_explicit_addressing() |> fix_explicit_addressing(follower_collection)
|> fix_implicit_addressing(followers_collection) |> fix_implicit_addressing(follower_collection)
end end
def fix_actor(%{"attributedTo" => actor} = object) do def fix_actor(%{"attributedTo" => actor} = object) do
@ -342,19 +322,18 @@ def fix_content_map(%{"contentMap" => content_map} = object) do
def fix_content_map(object), do: object def fix_content_map(object), do: object
def fix_type(object, options \\ []) defp fix_type(%{"type" => "Note", "inReplyTo" => reply_id, "name" => _} = object, options)
def fix_type(%{"inReplyTo" => reply_id, "name" => _} = object, options)
when is_binary(reply_id) do when is_binary(reply_id) do
with true <- Federator.allowed_thread_distance?(options[:depth]), options = Keyword.put(options, :fetch, true)
{:ok, %{data: %{"type" => "Question"} = _} = _} <- get_obj_helper(reply_id, options) do
with %Object{data: %{"type" => "Question"}} <- Object.normalize(reply_id, options) do
Map.put(object, "type", "Answer") Map.put(object, "type", "Answer")
else else
_ -> object _ -> object
end end
end end
def fix_type(object, _), do: object defp fix_type(object, _options), do: object
# Reduce the object list to find the reported user. # Reduce the object list to find the reported user.
defp get_reported(objects) do defp get_reported(objects) do
@ -425,10 +404,9 @@ def handle_incoming(%{"id" => id}, _options) when is_binary(id) and byte_size(id
# - tags # - tags
# - emoji # - emoji
def handle_incoming( def handle_incoming(
%{"type" => "Create", "object" => %{"type" => objtype} = object} = data, %{"type" => "Create", "object" => %{"type" => "Page"} = object} = data,
options options
) ) do
when objtype in ~w{Note Page} do
actor = Containment.get_actor(data) actor = Containment.get_actor(data)
with nil <- Activity.get_create_by_object_ap_id(object["id"]), with nil <- Activity.get_create_by_object_ap_id(object["id"]),
@ -520,14 +498,23 @@ def handle_incoming(
def handle_incoming( def handle_incoming(
%{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data, %{"type" => "Create", "object" => %{"type" => objtype, "id" => obj_id}} = data,
_options options
) )
when objtype in ~w{Question Answer ChatMessage Audio Video Event Article} do when objtype in ~w{Question Answer ChatMessage Audio Video Event Article Note} do
data = Map.put(data, "object", strip_internal_fields(data["object"])) fetch_options = Keyword.put(options, :depth, (options[:depth] || 0) + 1)
object =
data["object"]
|> strip_internal_fields()
|> fix_type(fetch_options)
|> fix_in_reply_to(fetch_options)
data = Map.put(data, "object", object)
options = Keyword.put(options, :local, false)
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data), with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
nil <- Activity.get_create_by_object_ap_id(obj_id), nil <- Activity.get_create_by_object_ap_id(obj_id),
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do {:ok, activity, _} <- Pipeline.common_pipeline(data, options) do
{:ok, activity} {:ok, activity}
else else
%Activity{} = activity -> {:ok, activity} %Activity{} = activity -> {:ok, activity}

View file

@ -96,8 +96,11 @@ def maybe_splice_recipient(ap_id, params) do
!label_in_collection?(ap_id, params["cc"]) !label_in_collection?(ap_id, params["cc"])
if need_splice? do if need_splice? do
cc_list = extract_list(params["cc"]) cc = [ap_id | extract_list(params["cc"])]
Map.put(params, "cc", [ap_id | cc_list])
params
|> Map.put("cc", cc)
|> Maps.safe_put_in(["object", "cc"], cc)
else else
params params
end end

View file

@ -96,6 +96,11 @@ def perform(:incoming_ap_doc, params) do
Logger.debug("Unhandled actor #{actor}, #{inspect(e)}") Logger.debug("Unhandled actor #{actor}, #{inspect(e)}")
{:error, e} {:error, e}
{:error, {:validate_object, _}} = e ->
Logger.error("Incoming AP doc validation error: #{inspect(e)}")
Logger.debug(Jason.encode!(params, pretty: true))
e
e -> e ->
# Just drop those for now # Just drop those for now
Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end) Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end)

View file

@ -3,6 +3,7 @@
"type": "Create", "type": "Create",
"object": { "object": {
"type": "Note", "type": "Note",
"to": ["https://www.w3.org/ns/activitystreams#Public"],
"content": "It's a note" "content": "It's a note"
}, },
"to": ["https://www.w3.org/ns/activitystreams#Public"] "to": ["https://www.w3.org/ns/activitystreams#Public"]

View file

@ -123,7 +123,8 @@ test "when association is not loaded" do
"type" => "Note", "type" => "Note",
"content" => "find me!", "content" => "find me!",
"id" => "http://mastodon.example.org/users/admin/objects/1", "id" => "http://mastodon.example.org/users/admin/objects/1",
"attributedTo" => "http://mastodon.example.org/users/admin" "attributedTo" => "http://mastodon.example.org/users/admin",
"to" => ["https://www.w3.org/ns/activitystreams#Public"]
}, },
"to" => ["https://www.w3.org/ns/activitystreams#Public"] "to" => ["https://www.w3.org/ns/activitystreams#Public"]
} }
@ -132,6 +133,7 @@ test "when association is not loaded" do
{:ok, japanese_activity} = Pleroma.Web.CommonAPI.post(user, %{status: "更新情報"}) {:ok, japanese_activity} = Pleroma.Web.CommonAPI.post(user, %{status: "更新情報"})
{:ok, job} = Pleroma.Web.Federator.incoming_ap_doc(params) {:ok, job} = Pleroma.Web.Federator.incoming_ap_doc(params)
{:ok, remote_activity} = ObanHelpers.perform(job) {:ok, remote_activity} = ObanHelpers.perform(job)
remote_activity = Activity.get_by_id_with_object(remote_activity.id)
%{ %{
japanese_activity: japanese_activity, japanese_activity: japanese_activity,

View file

@ -6,10 +6,10 @@ defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.RecipientsTest do
alias Pleroma.EctoType.ActivityPub.ObjectValidators.Recipients alias Pleroma.EctoType.ActivityPub.ObjectValidators.Recipients
use Pleroma.DataCase, async: true use Pleroma.DataCase, async: true
test "it asserts that all elements of the list are object ids" do test "it only keeps elements that are valid object ids" do
list = ["https://lain.com/users/lain", "invalid"] list = ["https://lain.com/users/lain", "invalid"]
assert :error == Recipients.cast(list) assert {:ok, ["https://lain.com/users/lain"]} == Recipients.cast(list)
end end
test "it works with a list" do test "it works with a list" do

View file

@ -624,6 +624,8 @@ test "it sends notifications to mentioned users in new messages" do
"actor" => user.ap_id, "actor" => user.ap_id,
"object" => %{ "object" => %{
"type" => "Note", "type" => "Note",
"id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(),
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"content" => "message with a Mention tag, but no explicit tagging", "content" => "message with a Mention tag, but no explicit tagging",
"tag" => [ "tag" => [
%{ %{
@ -655,6 +657,9 @@ test "it does not send notifications to users who are only cc in new messages" d
"actor" => user.ap_id, "actor" => user.ap_id,
"object" => %{ "object" => %{
"type" => "Note", "type" => "Note",
"id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(),
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [other_user.ap_id],
"content" => "hi everyone", "content" => "hi everyone",
"attributedTo" => user.ap_id "attributedTo" => user.ap_id
} }
@ -951,6 +956,7 @@ test "notifications are deleted if a remote user is deleted" do
"cc" => [], "cc" => [],
"object" => %{ "object" => %{
"type" => "Note", "type" => "Note",
"id" => remote_user.ap_id <> "/objects/test",
"content" => "Hello!", "content" => "Hello!",
"tag" => [ "tag" => [
%{ %{

View file

@ -66,6 +66,14 @@ defmodule Pleroma.Object.FetcherTest do
%Tesla.Env{ %Tesla.Env{
status: 500 status: 500
} }
%{
method: :get,
url: "https://stereophonic.space/objects/02997b83-3ea7-4b63-94af-ef3aa2d4ed17"
} ->
%Tesla.Env{
status: 500
}
end) end)
:ok :ok
@ -124,8 +132,7 @@ test "it fetches an object" do
{:ok, object} = {:ok, object} =
Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367") Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")
assert activity = Activity.get_create_by_object_ap_id(object.data["id"]) assert _activity = Activity.get_create_by_object_ap_id(object.data["id"])
assert activity.data["id"]
{:ok, object_again} = {:ok, object_again} =
Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367") Fetcher.fetch_object_from_id("http://mastodon.example.org/@admin/99541947525187367")

View file

@ -539,7 +539,7 @@ test "it inserts an incoming activity into the database" <>
File.read!("test/fixtures/mastodon-post-activity.json") File.read!("test/fixtures/mastodon-post-activity.json")
|> Jason.decode!() |> Jason.decode!()
|> Map.put("actor", user.ap_id) |> Map.put("actor", user.ap_id)
|> put_in(["object", "attridbutedTo"], user.ap_id) |> put_in(["object", "attributedTo"], user.ap_id)
conn = conn =
conn conn
@ -829,7 +829,11 @@ test "mastodon pin/unpin", %{conn: conn} do
test "it inserts an incoming activity into the database", %{conn: conn, data: data} do test "it inserts an incoming activity into the database", %{conn: conn, data: data} do
user = insert(:user) user = insert(:user)
data = Map.put(data, "bcc", [user.ap_id])
data =
data
|> Map.put("bcc", [user.ap_id])
|> Kernel.put_in(["object", "bcc"], [user.ap_id])
conn = conn =
conn conn
@ -846,8 +850,11 @@ test "it accepts messages with to as string instead of array", %{conn: conn, dat
user = insert(:user) user = insert(:user)
data = data =
Map.put(data, "to", user.ap_id) data
|> Map.delete("cc") |> Map.put("to", user.ap_id)
|> Map.put("cc", [])
|> Kernel.put_in(["object", "to"], user.ap_id)
|> Kernel.put_in(["object", "cc"], [])
conn = conn =
conn conn
@ -864,8 +871,11 @@ test "it accepts messages with cc as string instead of array", %{conn: conn, dat
user = insert(:user) user = insert(:user)
data = data =
Map.put(data, "cc", user.ap_id) data
|> Map.delete("to") |> Map.put("to", [])
|> Map.put("cc", user.ap_id)
|> Kernel.put_in(["object", "to"], [])
|> Kernel.put_in(["object", "cc"], user.ap_id)
conn = conn =
conn conn
@ -883,9 +893,13 @@ test "it accepts messages with bcc as string instead of array", %{conn: conn, da
user = insert(:user) user = insert(:user)
data = data =
Map.put(data, "bcc", user.ap_id) data
|> Map.delete("to") |> Map.put("to", [])
|> Map.delete("cc") |> Map.put("cc", [])
|> Map.put("bcc", user.ap_id)
|> Kernel.put_in(["object", "to"], [])
|> Kernel.put_in(["object", "cc"], [])
|> Kernel.put_in(["object", "bcc"], user.ap_id)
conn = conn =
conn conn
@ -1000,29 +1014,34 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn, da
assert Instances.reachable?(sender_host) assert Instances.reachable?(sender_host)
end end
@tag capture_log: true
test "it removes all follower collections but actor's", %{conn: conn} do test "it removes all follower collections but actor's", %{conn: conn} do
[actor, recipient] = insert_pair(:user) [actor, recipient] = insert_pair(:user)
data = to = [
File.read!("test/fixtures/activitypub-client-post-activity.json")
|> Jason.decode!()
object = Map.put(data["object"], "attributedTo", actor.ap_id)
data =
data
|> Map.put("id", Utils.generate_object_id())
|> Map.put("actor", actor.ap_id)
|> Map.put("object", object)
|> Map.put("cc", [
recipient.follower_address,
actor.follower_address
])
|> Map.put("to", [
recipient.ap_id, recipient.ap_id,
recipient.follower_address, recipient.follower_address,
"https://www.w3.org/ns/activitystreams#Public" "https://www.w3.org/ns/activitystreams#Public"
]) ]
cc = [recipient.follower_address, actor.follower_address]
data = %{
"@context" => ["https://www.w3.org/ns/activitystreams"],
"type" => "Create",
"id" => Utils.generate_activity_id(),
"to" => to,
"cc" => cc,
"actor" => actor.ap_id,
"object" => %{
"type" => "Note",
"to" => to,
"cc" => cc,
"content" => "It's a note",
"attributedTo" => actor.ap_id,
"id" => Utils.generate_object_id()
}
}
conn conn
|> assign(:valid_signature, true) |> assign(:valid_signature, true)
@ -1032,7 +1051,7 @@ test "it removes all follower collections but actor's", %{conn: conn} do
ObanHelpers.perform(all_enqueued(worker: ReceiverWorker)) ObanHelpers.perform(all_enqueued(worker: ReceiverWorker))
activity = Activity.get_by_ap_id(data["id"]) assert activity = Activity.get_by_ap_id(data["id"])
assert activity.id assert activity.id
assert actor.follower_address in activity.recipients assert actor.follower_address in activity.recipients
@ -1164,7 +1183,6 @@ test "forwarded report from mastodon", %{conn: conn} do
"actor" => remote_actor, "actor" => remote_actor,
"content" => "test report", "content" => "test report",
"id" => "https://#{remote_domain}/e3b12fd1-948c-446e-b93b-a5e67edbe1d8", "id" => "https://#{remote_domain}/e3b12fd1-948c-446e-b93b-a5e67edbe1d8",
"nickname" => reported_user.nickname,
"object" => [ "object" => [
reported_user.ap_id, reported_user.ap_id,
note.data["object"] note.data["object"]

View file

@ -24,6 +24,8 @@ test "it works for incoming listens" do
"actor" => "http://mastodon.example.org/users/admin", "actor" => "http://mastodon.example.org/users/admin",
"object" => %{ "object" => %{
"type" => "Audio", "type" => "Audio",
"to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [],
"id" => "http://mastodon.example.org/users/admin/listens/1234", "id" => "http://mastodon.example.org/users/admin/listens/1234",
"attributedTo" => "http://mastodon.example.org/users/admin", "attributedTo" => "http://mastodon.example.org/users/admin",
"title" => "lain radio episode 1", "title" => "lain radio episode 1",
@ -61,7 +63,9 @@ test "Funkwhale Audio object" do
assert object.data["to"] == ["https://www.w3.org/ns/activitystreams#Public"] assert object.data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
assert object.data["cc"] == [] assert object.data["cc"] == [
"https://channels.tests.funkwhale.audio/federation/actors/compositions/followers"
]
assert object.data["url"] == "https://channels.tests.funkwhale.audio/library/tracks/74" assert object.data["url"] == "https://channels.tests.funkwhale.audio/library/tracks/74"

View file

@ -31,7 +31,7 @@ test "Mobilizon Event object" do
) )
assert object.data["to"] == ["https://www.w3.org/ns/activitystreams#Public"] assert object.data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
assert object.data["cc"] == [] assert object.data["cc"] == ["https://mobilizon.org/@tcit/followers"]
assert object.data["url"] == assert object.data["url"] ==
"https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39" "https://mobilizon.org/events/252d5816-00a3-4a89-a66f-15bf65c33e39"

View file

@ -10,11 +10,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.NoteHandlingTest do
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
import Mock import Mock
import Pleroma.Factory import Pleroma.Factory
import ExUnit.CaptureLog
setup_all do setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
@ -43,36 +43,6 @@ test "it works for incoming notices with tag not being an array (kroeg)" do
assert Object.hashtags(object) == ["test"] assert Object.hashtags(object) == ["test"]
end end
test "it cleans up incoming notices which are not really DMs" do
user = insert(:user)
other_user = insert(:user)
to = [user.ap_id, other_user.ap_id]
data =
File.read!("test/fixtures/mastodon-post-activity.json")
|> Jason.decode!()
|> Map.put("to", to)
|> Map.put("cc", [])
object =
data["object"]
|> Map.put("to", to)
|> Map.put("cc", [])
data = Map.put(data, "object", object)
{:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
assert data["to"] == []
assert data["cc"] == to
object_data = Object.normalize(activity, fetch: false).data
assert object_data["to"] == []
assert object_data["cc"] == to
end
test "it ignores an incoming notice if we already have it" do test "it ignores an incoming notice if we already have it" do
activity = insert(:note_activity) activity = insert(:note_activity)
@ -147,9 +117,7 @@ test "it does not crash if the object in inReplyTo can't be fetched" do
data data
|> Map.put("object", object) |> Map.put("object", object)
assert capture_log(fn -> assert {:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
{:ok, _returned_activity} = Transmogrifier.handle_incoming(data)
end) =~ "[warn] Couldn't fetch \"https://404.site/whatever\", error: nil"
end end
test "it does not work for deactivated users" do test "it does not work for deactivated users" do
@ -174,8 +142,8 @@ test "it works for incoming notices" do
assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"] assert data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
assert data["cc"] == [ assert data["cc"] == [
"http://mastodon.example.org/users/admin/followers", "http://localtesting.pleroma.lol/users/lain",
"http://localtesting.pleroma.lol/users/lain" "http://mastodon.example.org/users/admin/followers"
] ]
assert data["actor"] == "http://mastodon.example.org/users/admin" assert data["actor"] == "http://mastodon.example.org/users/admin"
@ -188,8 +156,8 @@ test "it works for incoming notices" do
assert object_data["to"] == ["https://www.w3.org/ns/activitystreams#Public"] assert object_data["to"] == ["https://www.w3.org/ns/activitystreams#Public"]
assert object_data["cc"] == [ assert object_data["cc"] == [
"http://mastodon.example.org/users/admin/followers", "http://localtesting.pleroma.lol/users/lain",
"http://localtesting.pleroma.lol/users/lain" "http://mastodon.example.org/users/admin/followers"
] ]
assert object_data["actor"] == "http://mastodon.example.org/users/admin" assert object_data["actor"] == "http://mastodon.example.org/users/admin"
@ -221,8 +189,25 @@ test "it works for incoming notices with hashtags" do
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
object = Object.normalize(data["object"], fetch: false) object = Object.normalize(data["object"], fetch: false)
assert Enum.at(Object.tags(object), 2) == "moo" assert match?(
assert Object.hashtags(object) == ["moo"] %{
"href" => "http://localtesting.pleroma.lol/users/lain",
"name" => "@lain@localtesting.pleroma.lol",
"type" => "Mention"
},
Enum.at(object.data["tag"], 0)
)
assert match?(
%{
"href" => "http://mastodon.example.org/tags/moo",
"name" => "#moo",
"type" => "Hashtag"
},
Enum.at(object.data["tag"], 1)
)
assert "moo" == Enum.at(object.data["tag"], 2)
end end
test "it works for incoming notices with contentMap" do test "it works for incoming notices with contentMap" do
@ -276,13 +261,11 @@ test "it ensures that address fields become lists" do
File.read!("test/fixtures/mastodon-post-activity.json") File.read!("test/fixtures/mastodon-post-activity.json")
|> Jason.decode!() |> Jason.decode!()
|> Map.put("actor", user.ap_id) |> Map.put("actor", user.ap_id)
|> Map.put("to", nil)
|> Map.put("cc", nil) |> Map.put("cc", nil)
object = object =
data["object"] data["object"]
|> Map.put("attributedTo", user.ap_id) |> Map.put("attributedTo", user.ap_id)
|> Map.put("to", nil)
|> Map.put("cc", nil) |> Map.put("cc", nil)
|> Map.put("id", user.ap_id <> "/activities/12345678") |> Map.put("id", user.ap_id <> "/activities/12345678")
@ -290,8 +273,7 @@ test "it ensures that address fields become lists" do
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data) {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
assert !is_nil(data["to"]) refute is_nil(data["cc"])
assert !is_nil(data["cc"])
end end
test "it strips internal likes" do test "it strips internal likes" do
@ -310,9 +292,11 @@ test "it strips internal likes" do
object = Map.put(data["object"], "likes", likes) object = Map.put(data["object"], "likes", likes)
data = Map.put(data, "object", object) data = Map.put(data, "object", object)
{:ok, %Activity{object: object}} = Transmogrifier.handle_incoming(data) {:ok, %Activity{} = activity} = Transmogrifier.handle_incoming(data)
refute Map.has_key?(object.data, "likes") object = Object.normalize(activity)
assert object.data["likes"] == []
end end
test "it strips internal reactions" do test "it strips internal reactions" do
@ -330,70 +314,46 @@ test "it strips internal reactions" do
end end
test "it correctly processes messages with non-array to field" do test "it correctly processes messages with non-array to field" do
user = insert(:user) data =
File.read!("test/fixtures/mastodon-post-activity.json")
|> Poison.decode!()
|> Map.put("to", "https://www.w3.org/ns/activitystreams#Public")
|> put_in(["object", "to"], "https://www.w3.org/ns/activitystreams#Public")
message = %{ assert {:ok, activity} = Transmogrifier.handle_incoming(data)
"@context" => "https://www.w3.org/ns/activitystreams",
"to" => "https://www.w3.org/ns/activitystreams#Public",
"type" => "Create",
"object" => %{
"content" => "blah blah blah",
"type" => "Note",
"attributedTo" => user.ap_id,
"inReplyTo" => nil
},
"actor" => user.ap_id
}
assert {:ok, activity} = Transmogrifier.handle_incoming(message) assert [
"http://localtesting.pleroma.lol/users/lain",
"http://mastodon.example.org/users/admin/followers"
] == activity.data["cc"]
assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"] assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
end end
test "it correctly processes messages with non-array cc field" do test "it correctly processes messages with non-array cc field" do
user = insert(:user) data =
File.read!("test/fixtures/mastodon-post-activity.json")
|> Poison.decode!()
|> Map.put("cc", "http://mastodon.example.org/users/admin/followers")
|> put_in(["object", "cc"], "http://mastodon.example.org/users/admin/followers")
message = %{ assert {:ok, activity} = Transmogrifier.handle_incoming(data)
"@context" => "https://www.w3.org/ns/activitystreams",
"to" => user.follower_address,
"cc" => "https://www.w3.org/ns/activitystreams#Public",
"type" => "Create",
"object" => %{
"content" => "blah blah blah",
"type" => "Note",
"attributedTo" => user.ap_id,
"inReplyTo" => nil
},
"actor" => user.ap_id
}
assert {:ok, activity} = Transmogrifier.handle_incoming(message) assert ["http://mastodon.example.org/users/admin/followers"] == activity.data["cc"]
assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
assert [user.follower_address] == activity.data["to"]
end end
test "it correctly processes messages with weirdness in address fields" do test "it correctly processes messages with weirdness in address fields" do
user = insert(:user) data =
File.read!("test/fixtures/mastodon-post-activity.json")
|> Poison.decode!()
|> Map.put("cc", ["http://mastodon.example.org/users/admin/followers", ["¿"]])
|> put_in(["object", "cc"], ["http://mastodon.example.org/users/admin/followers", ["¿"]])
message = %{ assert {:ok, activity} = Transmogrifier.handle_incoming(data)
"@context" => "https://www.w3.org/ns/activitystreams",
"to" => [nil, user.follower_address],
"cc" => ["https://www.w3.org/ns/activitystreams#Public", ["¿"]],
"type" => "Create",
"object" => %{
"content" => "",
"type" => "Note",
"attributedTo" => user.ap_id,
"inReplyTo" => nil
},
"actor" => user.ap_id
}
assert {:ok, activity} = Transmogrifier.handle_incoming(message) assert ["http://mastodon.example.org/users/admin/followers"] == activity.data["cc"]
assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["to"]
assert ["https://www.w3.org/ns/activitystreams#Public"] == activity.data["cc"]
assert [user.follower_address] == activity.data["to"]
end end
end end
@ -419,7 +379,11 @@ test "schedules background fetching of `replies` items if max thread depth limit
} do } do
clear_config([:instance, :federation_incoming_replies_max_depth], 10) clear_config([:instance, :federation_incoming_replies_max_depth], 10)
{:ok, _activity} = Transmogrifier.handle_incoming(data) {:ok, activity} = Transmogrifier.handle_incoming(data)
object = Object.normalize(activity.data["object"])
assert object.data["replies"] == items
for id <- items do for id <- items do
job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1} job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
@ -442,45 +406,38 @@ test "does NOT schedule background fetching of `replies` beyond max thread depth
setup do: clear_config([:instance, :federation_incoming_replies_max_depth]) setup do: clear_config([:instance, :federation_incoming_replies_max_depth])
setup do setup do
user = insert(:user) replies = %{
"type" => "Collection",
"items" => [Utils.generate_object_id(), Utils.generate_object_id()]
}
{:ok, activity} = CommonAPI.post(user, %{status: "post1"}) activity =
File.read!("test/fixtures/mastodon-post-activity.json")
|> Poison.decode!()
|> Kernel.put_in(["object", "replies"], replies)
{:ok, reply1} = %{activity: activity}
CommonAPI.post(user, %{status: "reply1", in_reply_to_status_id: activity.id})
{:ok, reply2} =
CommonAPI.post(user, %{status: "reply2", in_reply_to_status_id: activity.id})
replies_uris = Enum.map([reply1, reply2], fn a -> a.object.data["id"] end)
{:ok, federation_output} = Transmogrifier.prepare_outgoing(activity.data)
Repo.delete(activity.object)
Repo.delete(activity)
%{federation_output: federation_output, replies_uris: replies_uris}
end end
test "schedules background fetching of `replies` items if max thread depth limit allows", %{ test "schedules background fetching of `replies` items if max thread depth limit allows", %{
federation_output: federation_output, activity: activity
replies_uris: replies_uris
} do } do
clear_config([:instance, :federation_incoming_replies_max_depth], 1) clear_config([:instance, :federation_incoming_replies_max_depth], 1)
{:ok, _activity} = Transmogrifier.handle_incoming(federation_output) assert {:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(activity)
object = Object.normalize(data["object"])
for id <- replies_uris do for id <- object.data["replies"] do
job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1} job_args = %{"op" => "fetch_remote", "id" => id, "depth" => 1}
assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args) assert_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker, args: job_args)
end end
end end
test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows", test "does NOT schedule background fetching of `replies` beyond max thread depth limit allows",
%{federation_output: federation_output} do %{activity: activity} do
clear_config([:instance, :federation_incoming_replies_max_depth], 0) clear_config([:instance, :federation_incoming_replies_max_depth], 0)
{:ok, _activity} = Transmogrifier.handle_incoming(federation_output) {:ok, _activity} = Transmogrifier.handle_incoming(activity)
assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == [] assert all_enqueued(worker: Pleroma.Workers.RemoteFetcherWorker) == []
end end
@ -498,6 +455,7 @@ test "successfully reserializes a message with inReplyTo == nil" do
"object" => %{ "object" => %{
"to" => ["https://www.w3.org/ns/activitystreams#Public"], "to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [], "cc" => [],
"id" => Utils.generate_object_id(),
"type" => "Note", "type" => "Note",
"content" => "Hi", "content" => "Hi",
"inReplyTo" => nil, "inReplyTo" => nil,
@ -522,6 +480,7 @@ test "successfully reserializes a message with AS2 objects in IR" do
"object" => %{ "object" => %{
"to" => ["https://www.w3.org/ns/activitystreams#Public"], "to" => ["https://www.w3.org/ns/activitystreams#Public"],
"cc" => [], "cc" => [],
"id" => Utils.generate_object_id(),
"type" => "Note", "type" => "Note",
"content" => "Hi", "content" => "Hi",
"inReplyTo" => nil, "inReplyTo" => nil,

View file

@ -11,6 +11,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do
alias Pleroma.Tests.ObanHelpers alias Pleroma.Tests.ObanHelpers
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.AdminAPI.AccountView alias Pleroma.Web.AdminAPI.AccountView
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
@ -159,8 +160,7 @@ test "it adds the json-ld context and the conversation property" do
{:ok, activity} = CommonAPI.post(user, %{status: "hey"}) {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
{:ok, modified} = Transmogrifier.prepare_outgoing(activity.data) {:ok, modified} = Transmogrifier.prepare_outgoing(activity.data)
assert modified["@context"] == assert modified["@context"] == Utils.make_json_ld_header()["@context"]
Pleroma.Web.ActivityPub.Utils.make_json_ld_header()["@context"]
assert modified["object"]["conversation"] == modified["context"] assert modified["object"]["conversation"] == modified["context"]
end end
@ -446,7 +446,7 @@ test "moves non-explicitly mentioned actors to cc", %{user: user} do
end) end)
} }
fixed_object = Transmogrifier.fix_explicit_addressing(object) fixed_object = Transmogrifier.fix_explicit_addressing(object, user.follower_address)
assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"])) assert Enum.all?(explicitly_mentioned_actors, &(&1 in fixed_object["to"]))
refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"] refute "https://social.beepboop.ga/users/dirb" in fixed_object["to"]
assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"] assert "https://social.beepboop.ga/users/dirb" in fixed_object["cc"]
@ -459,7 +459,7 @@ test "does not move actor's follower collection to cc", %{user: user} do
"cc" => [] "cc" => []
} }
fixed_object = Transmogrifier.fix_explicit_addressing(object) fixed_object = Transmogrifier.fix_explicit_addressing(object, user.follower_address)
assert user.follower_address in fixed_object["to"] assert user.follower_address in fixed_object["to"]
refute user.follower_address in fixed_object["cc"] refute user.follower_address in fixed_object["cc"]
end end
@ -473,7 +473,7 @@ test "removes recipient's follower collection from cc", %{user: user} do
"cc" => [user.follower_address, recipient.follower_address] "cc" => [user.follower_address, recipient.follower_address]
} }
fixed_object = Transmogrifier.fix_explicit_addressing(object) fixed_object = Transmogrifier.fix_explicit_addressing(object, user.follower_address)
assert user.follower_address in fixed_object["cc"] assert user.follower_address in fixed_object["cc"]
refute recipient.follower_address in fixed_object["cc"] refute recipient.follower_address in fixed_object["cc"]

View file

@ -539,8 +539,8 @@ test "it copies emoji from the subject of the parent post" do
spoiler_text: ":joker_smile:" spoiler_text: ":joker_smile:"
}) })
assert Object.normalize(reply_activity).data["emoji"][":joker_smile:"] assert Object.normalize(reply_activity).data["emoji"]["joker_smile"]
refute Object.normalize(reply_activity).data["emoji"][":joker_disapprove:"] refute Object.normalize(reply_activity).data["emoji"]["joker_disapprove"]
end end
test "deactivated users can't post" do test "deactivated users can't post" do

View file

@ -123,7 +123,8 @@ test "successfully processes incoming AP docs with correct origin" do
"type" => "Note", "type" => "Note",
"content" => "hi world!", "content" => "hi world!",
"id" => "http://mastodon.example.org/users/admin/objects/1", "id" => "http://mastodon.example.org/users/admin/objects/1",
"attributedTo" => "http://mastodon.example.org/users/admin" "attributedTo" => "http://mastodon.example.org/users/admin",
"to" => ["https://www.w3.org/ns/activitystreams#Public"]
}, },
"to" => ["https://www.w3.org/ns/activitystreams#Public"] "to" => ["https://www.w3.org/ns/activitystreams#Public"]
} }
@ -145,7 +146,8 @@ test "rejects incoming AP docs with incorrect origin" do
"type" => "Note", "type" => "Note",
"content" => "hi world!", "content" => "hi world!",
"id" => "http://mastodon.example.org/users/admin/objects/1", "id" => "http://mastodon.example.org/users/admin/objects/1",
"attributedTo" => "http://mastodon.example.org/users/admin" "attributedTo" => "http://mastodon.example.org/users/admin",
"to" => ["https://www.w3.org/ns/activitystreams#Public"]
}, },
"to" => ["https://www.w3.org/ns/activitystreams#Public"] "to" => ["https://www.w3.org/ns/activitystreams#Public"]
} }

View file

@ -7,6 +7,7 @@ defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do
alias Pleroma.Activity alias Pleroma.Activity
alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.CommonAPI alias Pleroma.Web.CommonAPI
import Pleroma.Factory import Pleroma.Factory
@ -185,16 +186,16 @@ test "404 for private status", %{conn: conn, user: user} do
test "302 for remote cached status", %{conn: conn, user: user} do test "302 for remote cached status", %{conn: conn, user: user} do
message = %{ message = %{
"@context" => "https://www.w3.org/ns/activitystreams", "@context" => "https://www.w3.org/ns/activitystreams",
"type" => "Create",
"actor" => user.ap_id,
"object" => %{
"to" => user.follower_address, "to" => user.follower_address,
"cc" => "https://www.w3.org/ns/activitystreams#Public", "cc" => "https://www.w3.org/ns/activitystreams#Public",
"type" => "Create", "id" => Utils.generate_object_id(),
"object" => %{
"content" => "blah blah blah", "content" => "blah blah blah",
"type" => "Note", "type" => "Note",
"attributedTo" => user.ap_id, "attributedTo" => user.ap_id
"inReplyTo" => nil }
},
"actor" => user.ap_id
} }
assert {:ok, activity} = Transmogrifier.handle_incoming(message) assert {:ok, activity} = Transmogrifier.handle_incoming(message)