X-Git-Url: https://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fweb%2Factivity_pub%2Fobject_validator.ex;h=cb0cc9ed792f5287ce0f74389569fae8d20cff06;hb=18bf82d7479b0bb767a657e1b7447529f9c2884f;hp=e1114a44d952f30d23d46d9273d4a57194e03012;hpb=9cebdf30a4ece49fb3b00185887b0801fd1b5452;p=akkoma diff --git a/lib/pleroma/web/activity_pub/object_validator.ex b/lib/pleroma/web/activity_pub/object_validator.ex index e1114a44d..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,37 +9,33 @@ 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.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 + alias Pleroma.Web.ActivityPub.ObjectValidators.EventValidator alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator 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" => "Follow"} = object, meta) do - with {:ok, object} <- - object - |> FollowValidator.cast_and_validate() - |> Ecto.Changeset.apply_action(:insert) do - object = stringify_keys(object) - {:ok, object, meta} - end - end - def validate(%{"type" => "Block"} = block_activity, meta) do with {:ok, block_activity} <- block_activity @@ -59,16 +55,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do end end - def validate(%{"type" => "Update"} = update_activity, meta) do - with {: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} - end - end - def validate(%{"type" => "Undo"} = object, meta) do with {:ok, object} <- object @@ -95,112 +81,151 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do end end - def validate(%{"type" => "Like"} = object, meta) do - with {:ok, object} <- - object - |> LikeValidator.cast_and_validate() + 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 - object = stringify_keys(object) - {:ok, object, meta} + create_activity = stringify_keys(create_activity) + {:ok, create_activity, meta} end end - def validate(%{"type" => "ChatMessage"} = object, meta) do + 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} <- - object - |> ChatMessageValidator.cast_and_validate() - |> Ecto.Changeset.apply_action(:insert) do - object = stringify_keys(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" => "Question"} = object, meta) do - with {:ok, object} <- - object - |> QuestionValidator.cast_and_validate() + 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 - object = stringify_keys(object) - {:ok, object, meta} + 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" => "Answer"} = object, meta) do + 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 - |> AnswerValidator.cast_and_validate() + |> validator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do object = stringify_keys(object) {:ok, object, meta} end end - def validate(%{"type" => "EmojiReact"} = object, meta) do + def validate(%{"type" => type} = object, meta) when type in ~w(Add Remove) do with {:ok, object} <- object - |> EmojiReactValidator.cast_and_validate() + |> AddRemoveValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do object = stringify_keys(object) {:ok, object, meta} end end - def validate( - %{"type" => "Create", "object" => %{"type" => "ChatMessage"} = object} = create_activity, - meta - ) do - 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) - |> Ecto.Changeset.apply_action(:insert) do - create_activity = stringify_keys(create_activity) - {:ok, create_activity, meta} - 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 validate( - %{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity, - meta - ) - when objtype in ["Question", "Answer"] do - with {:ok, object_data} <- cast_and_apply(object), - meta = Keyword.put(meta, :object_data, object_data |> stringify_keys), - {: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 + def cast_and_apply(%{"type" => "Question"} = object) do + QuestionValidator.cast_and_apply(object) end - def validate(%{"type" => "Announce"} = object, meta) do - with {:ok, object} <- - object - |> AnnounceValidator.cast_and_validate() - |> Ecto.Changeset.apply_action(:insert) do - object = stringify_keys(object) - {:ok, object, meta} - end + def cast_and_apply(%{"type" => "Answer"} = object) do + AnswerValidator.cast_and_apply(object) end - def cast_and_apply(%{"type" => "ChatMessage"} = object) do - ChatMessageValidator.cast_and_apply(object) + 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" => "Question"} = object) do - QuestionValidator.cast_and_apply(object) + def cast_and_apply(%{"type" => "Event"} = object) do + EventValidator.cast_and_apply(object) end - def cast_and_apply(%{"type" => "Answer"} = object) do - AnswerValidator.cast_and_apply(object) + 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 isn't present in Elixir 1.8.x + # is_struct/1 appears in Elixir 1.11 def stringify_keys(%{__struct__: _} = object) do object |> Map.from_struct() @@ -209,6 +234,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do def stringify_keys(object) when is_map(object) do object + |> Enum.filter(fn {_, v} -> v != nil end) |> Map.new(fn {key, val} -> {to_string(key), stringify_keys(val)} end) end @@ -220,14 +246,65 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do def stringify_keys(object), do: object def fetch_actor(object) do - with {:ok, actor} <- ObjectValidators.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"], true) + 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