spc-pleroma/lib/pleroma/web/activity_pub/object_validator.ex

333 lines
11 KiB
Elixir
Raw Normal View History

# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.ObjectValidator do
@moduledoc """
This module is responsible for validating an object (which can be an activity)
and checking if it is both well formed and also compatible with our view of
the system.
"""
@behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating
alias Pleroma.Activity
alias Pleroma.EctoType.ActivityPub.ObjectValidators
2020-06-17 07:31:06 +00:00
alias Pleroma.Object
2020-08-20 00:00:04 +00:00
alias Pleroma.Object.Containment
2019-10-23 10:18:05 +00:00
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
2020-06-11 18:43:01 +00:00
alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator
2020-10-21 08:23:10 +00:00
alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator
2020-11-16 16:13:42 +00:00
alias Pleroma.Web.ActivityPub.ObjectValidators.AudioImageVideoValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
2020-04-08 13:55:43 +00:00
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
2020-08-20 16:41:42 +00:00
alias Pleroma.Web.ActivityPub.ObjectValidators.EventValidator
2020-07-06 13:57:19 +00:00
alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator
2020-04-20 10:29:19 +00:00
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
2020-06-11 18:23:10 +00:00
alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator
2020-05-07 13:09:37 +00:00
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator
@impl true
def validate(object, meta)
def validate(%{"type" => "Block"} = block_activity, meta) do
with {:ok, block_activity} <-
block_activity
|> BlockValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
block_activity = stringify_keys(block_activity)
outgoing_blocks = Pleroma.Config.get([:activitypub, :outgoing_blocks])
meta =
if !outgoing_blocks do
Keyword.put(meta, :do_not_federate, true)
else
meta
end
{:ok, block_activity, meta}
end
end
2020-05-05 12:17:47 +00:00
def validate(%{"type" => "Undo"} = object, meta) do
with {:ok, object} <-
2020-05-07 12:52:37 +00:00
object
|> UndoValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
object = stringify_keys(object)
undone_object = Activity.get_by_ap_id(object["object"])
meta =
meta
|> Keyword.put(:object_data, undone_object.data)
2020-05-05 12:17:47 +00:00
{:ok, object, meta}
end
end
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
2020-06-11 18:23:10 +00:00
def validate(
%{"type" => "Create", "object" => %{"type" => "ChatMessage"} = object} = create_activity,
meta
) do
2020-04-28 15:29:54 +00:00
with {:ok, object_data} <- cast_and_apply(object),
meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
{:ok, create_activity} <-
create_activity
|> CreateChatMessageValidator.cast_and_validate(meta)
2020-04-16 13:21:47 +00:00
|> Ecto.Changeset.apply_action(:insert) do
2020-04-28 15:29:54 +00:00
create_activity = stringify_keys(create_activity)
{:ok, create_activity, meta}
2020-04-08 13:55:43 +00:00
end
end
2020-06-11 18:23:10 +00:00
def validate(
2020-06-11 18:43:01 +00:00
%{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
2020-06-11 18:23:10 +00:00
meta
2020-06-11 18:43:01 +00:00
)
2020-11-16 16:13:42 +00:00
when objtype in ~w[Question Answer Audio Video Image Event Article Note Page] do
with {:ok, object_data} <- cast_and_apply_and_stringify_with_history(object),
meta = Keyword.put(meta, :object_data, object_data),
2020-06-11 18:23:10 +00:00
{:ok, create_activity} <-
create_activity
|> CreateGenericValidator.cast_and_validate(meta)
2020-06-11 18:23:10 +00:00
|> Ecto.Changeset.apply_action(:insert) do
create_activity = stringify_keys(create_activity)
{:ok, create_activity, meta}
end
end
2021-04-01 11:49:04 +00:00
def validate(%{"type" => type} = object, meta)
2020-11-16 16:13:42 +00:00
when type in ~w[Event Question Audio Video Image Article Note Page] do
2021-04-01 11:49:04 +00:00
validator =
case type do
"Event" -> EventValidator
"Question" -> QuestionValidator
2020-11-16 16:13:42 +00:00
"Audio" -> AudioImageVideoValidator
"Video" -> AudioImageVideoValidator
"Image" -> AudioImageVideoValidator
2020-10-21 08:23:10 +00:00
"Article" -> ArticleNotePageValidator
"Note" -> ArticleNotePageValidator
"Page" -> ArticleNotePageValidator
2021-04-01 11:49:04 +00:00
end
with {:ok, object} <-
do_separate_with_history(object, fn object ->
with {:ok, object} <-
object
|> validator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
object = stringify_keys(object)
# Insert copy of hashtags as strings for the non-hashtag table indexing
tag = (object["tag"] || []) ++ Object.hashtags(%Object{data: object})
object = Map.put(object, "tag", tag)
{:ok, object}
end
end) do
2021-04-01 11:49:04 +00:00
{:ok, object, meta}
end
end
def validate(
%{"type" => "Update", "object" => %{"type" => objtype} = object} = update_activity,
meta
)
when objtype in ~w[Question Answer Audio Video Event Article Note Page] do
with {_, false} <- {:local, Access.get(meta, :local, false)},
{_, {:ok, object_data, _}} <- {:object_validation, validate(object, meta)},
meta = Keyword.put(meta, :object_data, object_data),
{:ok, update_activity} <-
update_activity
|> UpdateValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
update_activity = stringify_keys(update_activity)
{:ok, update_activity, meta}
else
{:local, _} ->
with {:ok, object} <-
update_activity
|> UpdateValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
object = stringify_keys(object)
{:ok, object, meta}
end
{:object_validation, e} ->
e
end
end
def validate(%{"type" => type} = object, meta)
when type in ~w[Accept Reject Follow Update Like EmojiReact Announce
2021-04-01 11:49:04 +00:00
ChatMessage Answer] do
validator =
case type do
"Accept" -> AcceptRejectValidator
"Reject" -> AcceptRejectValidator
"Follow" -> FollowValidator
"Update" -> UpdateValidator
"Like" -> LikeValidator
"EmojiReact" -> EmojiReactValidator
"Announce" -> AnnounceValidator
"ChatMessage" -> ChatMessageValidator
"Answer" -> AnswerValidator
end
with {:ok, object} <-
object
|> validator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
2020-06-11 18:23:10 +00:00
object = stringify_keys(object)
{:ok, object, meta}
end
end
def validate(%{"type" => type} = object, meta) when type in ~w(Add Remove) do
with {:ok, object} <-
object
2021-03-19 14:25:12 +00:00
|> AddRemoveValidator.cast_and_validate()
|> Ecto.Changeset.apply_action(:insert) do
object = stringify_keys(object)
{:ok, object, meta}
end
end
def validate(o, m), do: {:error, {:validator_not_set, {o, m}}}
def cast_and_apply_and_stringify_with_history(object) do
do_separate_with_history(object, fn object ->
with {:ok, object_data} <- cast_and_apply(object),
object_data <- object_data |> stringify_keys() do
{:ok, object_data}
end
end)
end
2020-04-28 15:29:54 +00:00
def cast_and_apply(%{"type" => "ChatMessage"} = object) do
ChatMessageValidator.cast_and_apply(object)
end
2020-06-11 18:23:10 +00:00
def cast_and_apply(%{"type" => "Question"} = object) do
QuestionValidator.cast_and_apply(object)
end
2020-06-11 18:43:01 +00:00
def cast_and_apply(%{"type" => "Answer"} = object) do
AnswerValidator.cast_and_apply(object)
2020-06-11 18:43:01 +00:00
end
2020-11-16 16:13:42 +00:00
def cast_and_apply(%{"type" => type} = object) when type in ~w[Audio Image Video] do
AudioImageVideoValidator.cast_and_apply(object)
2020-08-17 23:48:42 +00:00
end
2020-08-20 16:41:42 +00:00
def cast_and_apply(%{"type" => "Event"} = object) do
EventValidator.cast_and_apply(object)
end
2020-10-21 08:23:10 +00:00
def cast_and_apply(%{"type" => type} = object) when type in ~w[Article Note Page] do
ArticleNotePageValidator.cast_and_apply(object)
2020-08-20 18:03:07 +00:00
end
2020-04-28 15:29:54 +00:00
def cast_and_apply(o), do: {:error, {:validator_not_set, o}}
def stringify_keys(object) when is_struct(object) do
2020-04-08 13:55:43 +00:00
object
|> Map.from_struct()
|> stringify_keys
end
2020-05-06 14:12:36 +00:00
def stringify_keys(object) when is_map(object) do
object
|> Enum.filter(fn {_, v} -> v != nil end)
2020-05-06 14:12:36 +00:00
|> Map.new(fn {key, val} -> {to_string(key), stringify_keys(val)} end)
end
2020-05-06 14:12:36 +00:00
def stringify_keys(object) when is_list(object) do
object
|> Enum.map(&stringify_keys/1)
end
def stringify_keys(object), do: object
def fetch_actor(object) do
2020-08-20 00:00:04 +00:00
with actor <- Containment.get_actor(object),
{:ok, actor} <- ObjectValidators.ObjectID.cast(actor) do
User.get_or_fetch_by_ap_id(actor)
end
end
def fetch_actor_and_object(object) do
fetch_actor(object)
Object.normalize(object["object"], fetch: true)
:ok
end
defp for_each_history_item(
%{"type" => "OrderedCollection", "orderedItems" => items} = history,
object,
fun
) do
processed_items =
Enum.map(items, fn item ->
with item <- Map.put(item, "id", object["id"]),
{:ok, item} <- fun.(item) do
item
else
_ -> nil
end
end)
if Enum.all?(processed_items, &(not is_nil(&1))) do
{:ok, Map.put(history, "orderedItems", processed_items)}
else
{:error, :invalid_history}
end
end
defp for_each_history_item(nil, _object, _fun) do
{:ok, nil}
end
defp for_each_history_item(_, _object, _fun) do
{:error, :invalid_history}
end
# fun is (object -> {:ok, validated_object_with_string_keys})
defp do_separate_with_history(object, fun) do
with history <- object["formerRepresentations"],
object <- Map.drop(object, ["formerRepresentations"]),
{_, {:ok, object}} <- {:main_body, fun.(object)},
{_, {:ok, history}} <- {:history_items, for_each_history_item(history, object, fun)} do
object =
if history do
Map.put(object, "formerRepresentations", history)
else
object
end
{:ok, object}
else
{:main_body, e} -> e
{:history_items, e} -> e
end
end
end