X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fweb%2Factivity_pub%2Fobject_validator.ex;h=cb0cc9ed792f5287ce0f74389569fae8d20cff06;hb=a079ec3a3cdfd42d2cbd51c7698c2c87828e5778;hp=4782cd8f3e3c37b196219c00e948f501e38698a8;hpb=bef08b85e5e1e25b65851938d6152bf0c170af03;p=akkoma diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index 4782cd8f3..cb0cc9ed7 100644 --- a/lib/pleroma/web/activity_pub/object_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validator.ex @@ -1,5 +1,5 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors +# Copyright © 2017-2021 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.ObjectValidator do @@ -9,22 +9,64 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do the system. """ + @behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating + + alias Pleroma.Activity + alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.Object + alias Pleroma.Object.Containment alias Pleroma.User + alias Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.EventValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator - alias Pleroma.Web.ActivityPub.ObjectValidators.Types + alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator + alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator - @spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()} + @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 + def validate(%{"type" => "Undo"} = object, meta) do with {:ok, object} <- 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) + {:ok, object, meta} end end @@ -39,34 +81,230 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do end end - def validate(%{"type" => "Like"} = object, meta) do + def validate( + %{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity, + meta + ) + when objtype in ~w[Question Answer Audio Video 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), + {:ok, create_activity} <- + create_activity + |> CreateGenericValidator.cast_and_validate(meta) + |> Ecto.Changeset.apply_action(:insert) do + create_activity = stringify_keys(create_activity) + {:ok, create_activity, meta} + end + end + + def validate(%{"type" => type} = object, meta) + when type in ~w[Event Question Audio Video Article Note Page] do + validator = + case type do + "Event" -> EventValidator + "Question" -> QuestionValidator + "Audio" -> AudioVideoValidator + "Video" -> AudioVideoValidator + "Article" -> ArticleNotePageValidator + "Note" -> ArticleNotePageValidator + "Page" -> ArticleNotePageValidator + 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 + {: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 + Answer] do + validator = + case type do + "Accept" -> AcceptRejectValidator + "Reject" -> AcceptRejectValidator + "Follow" -> FollowValidator + "Update" -> UpdateValidator + "Like" -> LikeValidator + "EmojiReact" -> EmojiReactValidator + "Announce" -> AnnounceValidator + "Answer" -> AnswerValidator + end + + with {:ok, object} <- + object + |> validator.cast_and_validate() + |> Ecto.Changeset.apply_action(:insert) do + 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 |> LikeValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do - object = stringify_keys(object |> Map.from_struct()) + object + |> 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 + + def cast_and_apply(%{"type" => "Question"} = object) do + QuestionValidator.cast_and_apply(object) + end + + def cast_and_apply(%{"type" => "Answer"} = object) do + AnswerValidator.cast_and_apply(object) + end + + def cast_and_apply(%{"type" => type} = object) when type in ~w[Audio Video] do + AudioVideoValidator.cast_and_apply(object) + end + + def cast_and_apply(%{"type" => "Event"} = object) do + EventValidator.cast_and_apply(object) + end + + def cast_and_apply(%{"type" => type} = object) when type in ~w[Article Note Page] do + ArticleNotePageValidator.cast_and_apply(object) + end + + def cast_and_apply(o), do: {:error, {:validator_not_set, o}} + + # is_struct/1 appears in Elixir 1.11 def stringify_keys(%{__struct__: _} = object) do object |> Map.from_struct() |> stringify_keys end - def stringify_keys(object) do + def stringify_keys(object) when is_map(object) do object - |> Map.new(fn {key, val} -> {to_string(key), val} end) + |> Enum.filter(fn {_, v} -> v != nil end) + |> Map.new(fn {key, val} -> {to_string(key), stringify_keys(val)} end) end + 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 - with {:ok, actor} <- Types.ObjectID.cast(object["actor"]) do + 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"]) + 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