First
[anni] / lib / pleroma / web / activity_pub / object_validator.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.ActivityPub.ObjectValidator do
6   @moduledoc """
7   This module is responsible for validating an object (which can be an activity)
8   and checking if it is both well formed and also compatible with our view of
9   the system.
10   """
11
12   @behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating
13
14   alias Pleroma.Activity
15   alias Pleroma.EctoType.ActivityPub.ObjectValidators
16   alias Pleroma.Object
17   alias Pleroma.Object.Containment
18   alias Pleroma.User
19   alias Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator
20   alias Pleroma.Web.ActivityPub.ObjectValidators.AddRemoveValidator
21   alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
22   alias Pleroma.Web.ActivityPub.ObjectValidators.AnswerValidator
23   alias Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator
24   alias Pleroma.Web.ActivityPub.ObjectValidators.AudioVideoValidator
25   alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
26   alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
27   alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
28   alias Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator
29   alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
30   alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
31   alias Pleroma.Web.ActivityPub.ObjectValidators.EventValidator
32   alias Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator
33   alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
34   alias Pleroma.Web.ActivityPub.ObjectValidators.QuestionValidator
35   alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
36   alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator
37
38   @impl true
39   def validate(object, meta)
40
41   def validate(%{"type" => "Block"} = block_activity, meta) do
42     with {:ok, block_activity} <-
43            block_activity
44            |> BlockValidator.cast_and_validate()
45            |> Ecto.Changeset.apply_action(:insert) do
46       block_activity = stringify_keys(block_activity)
47       outgoing_blocks = Pleroma.Config.get([:activitypub, :outgoing_blocks])
48
49       meta =
50         if !outgoing_blocks do
51           Keyword.put(meta, :do_not_federate, true)
52         else
53           meta
54         end
55
56       {:ok, block_activity, meta}
57     end
58   end
59
60   def validate(%{"type" => "Undo"} = object, meta) do
61     with {:ok, object} <-
62            object
63            |> UndoValidator.cast_and_validate()
64            |> Ecto.Changeset.apply_action(:insert) do
65       object = stringify_keys(object)
66       undone_object = Activity.get_by_ap_id(object["object"])
67
68       meta =
69         meta
70         |> Keyword.put(:object_data, undone_object.data)
71
72       {:ok, object, meta}
73     end
74   end
75
76   def validate(%{"type" => "Delete"} = object, meta) do
77     with cng <- DeleteValidator.cast_and_validate(object),
78          do_not_federate <- DeleteValidator.do_not_federate?(cng),
79          {:ok, object} <- Ecto.Changeset.apply_action(cng, :insert) do
80       object = stringify_keys(object)
81       meta = Keyword.put(meta, :do_not_federate, do_not_federate)
82       {:ok, object, meta}
83     end
84   end
85
86   def validate(
87         %{"type" => "Create", "object" => %{"type" => "ChatMessage"} = object} = create_activity,
88         meta
89       ) do
90     with {:ok, object_data} <- cast_and_apply(object),
91          meta = Keyword.put(meta, :object_data, object_data |> stringify_keys),
92          {:ok, create_activity} <-
93            create_activity
94            |> CreateChatMessageValidator.cast_and_validate(meta)
95            |> Ecto.Changeset.apply_action(:insert) do
96       create_activity = stringify_keys(create_activity)
97       {:ok, create_activity, meta}
98     end
99   end
100
101   def validate(
102         %{"type" => "Create", "object" => %{"type" => objtype} = object} = create_activity,
103         meta
104       )
105       when objtype in ~w[Question Answer Audio Video Event Article Note Page] do
106     with {:ok, object_data} <- cast_and_apply_and_stringify_with_history(object),
107          meta = Keyword.put(meta, :object_data, object_data),
108          {:ok, create_activity} <-
109            create_activity
110            |> CreateGenericValidator.cast_and_validate(meta)
111            |> Ecto.Changeset.apply_action(:insert) do
112       create_activity = stringify_keys(create_activity)
113       {:ok, create_activity, meta}
114     end
115   end
116
117   def validate(%{"type" => type} = object, meta)
118       when type in ~w[Event Question Audio Video Article Note Page] do
119     validator =
120       case type do
121         "Event" -> EventValidator
122         "Question" -> QuestionValidator
123         "Audio" -> AudioVideoValidator
124         "Video" -> AudioVideoValidator
125         "Article" -> ArticleNotePageValidator
126         "Note" -> ArticleNotePageValidator
127         "Page" -> ArticleNotePageValidator
128       end
129
130     with {:ok, object} <-
131            do_separate_with_history(object, fn object ->
132              with {:ok, object} <-
133                     object
134                     |> validator.cast_and_validate()
135                     |> Ecto.Changeset.apply_action(:insert) do
136                object = stringify_keys(object)
137
138                # Insert copy of hashtags as strings for the non-hashtag table indexing
139                tag = (object["tag"] || []) ++ Object.hashtags(%Object{data: object})
140                object = Map.put(object, "tag", tag)
141
142                {:ok, object}
143              end
144            end) do
145       {:ok, object, meta}
146     end
147   end
148
149   def validate(
150         %{"type" => "Update", "object" => %{"type" => objtype} = object} = update_activity,
151         meta
152       )
153       when objtype in ~w[Question Answer Audio Video Event Article Note Page] do
154     with {_, false} <- {:local, Access.get(meta, :local, false)},
155          {_, {:ok, object_data, _}} <- {:object_validation, validate(object, meta)},
156          meta = Keyword.put(meta, :object_data, object_data),
157          {:ok, update_activity} <-
158            update_activity
159            |> UpdateValidator.cast_and_validate()
160            |> Ecto.Changeset.apply_action(:insert) do
161       update_activity = stringify_keys(update_activity)
162       {:ok, update_activity, meta}
163     else
164       {:local, _} ->
165         with {:ok, object} <-
166                update_activity
167                |> UpdateValidator.cast_and_validate()
168                |> Ecto.Changeset.apply_action(:insert) do
169           object = stringify_keys(object)
170           {:ok, object, meta}
171         end
172
173       {:object_validation, e} ->
174         e
175     end
176   end
177
178   def validate(%{"type" => type} = object, meta)
179       when type in ~w[Accept Reject Follow Update Like EmojiReact Announce
180       ChatMessage Answer] do
181     validator =
182       case type do
183         "Accept" -> AcceptRejectValidator
184         "Reject" -> AcceptRejectValidator
185         "Follow" -> FollowValidator
186         "Update" -> UpdateValidator
187         "Like" -> LikeValidator
188         "EmojiReact" -> EmojiReactValidator
189         "Announce" -> AnnounceValidator
190         "ChatMessage" -> ChatMessageValidator
191         "Answer" -> AnswerValidator
192       end
193
194     with {:ok, object} <-
195            object
196            |> validator.cast_and_validate()
197            |> Ecto.Changeset.apply_action(:insert) do
198       object = stringify_keys(object)
199       {:ok, object, meta}
200     end
201   end
202
203   def validate(%{"type" => type} = object, meta) when type in ~w(Add Remove) do
204     with {:ok, object} <-
205            object
206            |> AddRemoveValidator.cast_and_validate()
207            |> Ecto.Changeset.apply_action(:insert) do
208       object = stringify_keys(object)
209       {:ok, object, meta}
210     end
211   end
212
213   def validate(o, m), do: {:error, {:validator_not_set, {o, m}}}
214
215   def cast_and_apply_and_stringify_with_history(object) do
216     do_separate_with_history(object, fn object ->
217       with {:ok, object_data} <- cast_and_apply(object),
218            object_data <- object_data |> stringify_keys() do
219         {:ok, object_data}
220       end
221     end)
222   end
223
224   def cast_and_apply(%{"type" => "ChatMessage"} = object) do
225     ChatMessageValidator.cast_and_apply(object)
226   end
227
228   def cast_and_apply(%{"type" => "Question"} = object) do
229     QuestionValidator.cast_and_apply(object)
230   end
231
232   def cast_and_apply(%{"type" => "Answer"} = object) do
233     AnswerValidator.cast_and_apply(object)
234   end
235
236   def cast_and_apply(%{"type" => type} = object) when type in ~w[Audio Video] do
237     AudioVideoValidator.cast_and_apply(object)
238   end
239
240   def cast_and_apply(%{"type" => "Event"} = object) do
241     EventValidator.cast_and_apply(object)
242   end
243
244   def cast_and_apply(%{"type" => type} = object) when type in ~w[Article Note Page] do
245     ArticleNotePageValidator.cast_and_apply(object)
246   end
247
248   def cast_and_apply(o), do: {:error, {:validator_not_set, o}}
249
250   def stringify_keys(object) when is_struct(object) do
251     object
252     |> Map.from_struct()
253     |> stringify_keys
254   end
255
256   def stringify_keys(object) when is_map(object) do
257     object
258     |> Enum.filter(fn {_, v} -> v != nil end)
259     |> Map.new(fn {key, val} -> {to_string(key), stringify_keys(val)} end)
260   end
261
262   def stringify_keys(object) when is_list(object) do
263     object
264     |> Enum.map(&stringify_keys/1)
265   end
266
267   def stringify_keys(object), do: object
268
269   def fetch_actor(object) do
270     with actor <- Containment.get_actor(object),
271          {:ok, actor} <- ObjectValidators.ObjectID.cast(actor) do
272       User.get_or_fetch_by_ap_id(actor)
273     end
274   end
275
276   def fetch_actor_and_object(object) do
277     fetch_actor(object)
278     Object.normalize(object["object"], fetch: true)
279     :ok
280   end
281
282   defp for_each_history_item(
283          %{"type" => "OrderedCollection", "orderedItems" => items} = history,
284          object,
285          fun
286        ) do
287     processed_items =
288       Enum.map(items, fn item ->
289         with item <- Map.put(item, "id", object["id"]),
290              {:ok, item} <- fun.(item) do
291           item
292         else
293           _ -> nil
294         end
295       end)
296
297     if Enum.all?(processed_items, &(not is_nil(&1))) do
298       {:ok, Map.put(history, "orderedItems", processed_items)}
299     else
300       {:error, :invalid_history}
301     end
302   end
303
304   defp for_each_history_item(nil, _object, _fun) do
305     {:ok, nil}
306   end
307
308   defp for_each_history_item(_, _object, _fun) do
309     {:error, :invalid_history}
310   end
311
312   # fun is (object -> {:ok, validated_object_with_string_keys})
313   defp do_separate_with_history(object, fun) do
314     with history <- object["formerRepresentations"],
315          object <- Map.drop(object, ["formerRepresentations"]),
316          {_, {:ok, object}} <- {:main_body, fun.(object)},
317          {_, {:ok, history}} <- {:history_items, for_each_history_item(history, object, fun)} do
318       object =
319         if history do
320           Map.put(object, "formerRepresentations", history)
321         else
322           object
323         end
324
325       {:ok, object}
326     else
327       {:main_body, e} -> e
328       {:history_items, e} -> e
329     end
330   end
331 end