First
[anni] / lib / pleroma / object / fetcher.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.Object.Fetcher do
6   alias Pleroma.HTTP
7   alias Pleroma.Instances
8   alias Pleroma.Maps
9   alias Pleroma.Object
10   alias Pleroma.Object.Containment
11   alias Pleroma.Signature
12   alias Pleroma.Web.ActivityPub.InternalFetchActor
13   alias Pleroma.Web.ActivityPub.MRF
14   alias Pleroma.Web.ActivityPub.ObjectValidator
15   alias Pleroma.Web.ActivityPub.Pipeline
16   alias Pleroma.Web.ActivityPub.Transmogrifier
17   alias Pleroma.Web.Federator
18
19   require Logger
20   require Pleroma.Constants
21
22   @spec reinject_object(struct(), map()) :: {:ok, Object.t()} | {:error, any()}
23   defp reinject_object(%Object{data: %{}} = object, new_data) do
24     Logger.debug("Reinjecting object #{new_data["id"]}")
25
26     with {:ok, new_data, _} <- ObjectValidator.validate(new_data, %{}),
27          {:ok, new_data} <- MRF.filter(new_data),
28          {:ok, new_object, _} <-
29            Object.Updater.do_update_and_invalidate_cache(
30              object,
31              new_data,
32              _touch_changeset? = true
33            ) do
34       {:ok, new_object}
35     else
36       e ->
37         Logger.error("Error while processing object: #{inspect(e)}")
38         {:error, e}
39     end
40   end
41
42   defp reinject_object(_, new_data) do
43     with {:ok, object, _} <- Pipeline.common_pipeline(new_data, local: false) do
44       {:ok, object}
45     else
46       e -> e
47     end
48   end
49
50   def refetch_object(%Object{data: %{"id" => id}} = object) do
51     with {:local, false} <- {:local, Object.local?(object)},
52          {:ok, new_data} <- fetch_and_contain_remote_object_from_id(id),
53          {:ok, object} <- reinject_object(object, new_data) do
54       {:ok, object}
55     else
56       {:local, true} -> {:ok, object}
57       e -> {:error, e}
58     end
59   end
60
61   # Note: will create a Create activity, which we need internally at the moment.
62   def fetch_object_from_id(id, options \\ []) do
63     with {_, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)},
64          {_, true} <- {:allowed_depth, Federator.allowed_thread_distance?(options[:depth])},
65          {_, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)},
66          {_, nil} <- {:normalize, Object.normalize(data, fetch: false)},
67          params <- prepare_activity_params(data),
68          {_, :ok} <- {:containment, Containment.contain_origin(id, params)},
69          {_, {:ok, activity}} <-
70            {:transmogrifier, Transmogrifier.handle_incoming(params, options)},
71          {_, _data, %Object{} = object} <-
72            {:object, data, Object.normalize(activity, fetch: false)} do
73       {:ok, object}
74     else
75       {:allowed_depth, false} ->
76         {:error, "Max thread distance exceeded."}
77
78       {:containment, _} ->
79         {:error, "Object containment failed."}
80
81       {:transmogrifier, {:error, {:reject, e}}} ->
82         {:reject, e}
83
84       {:transmogrifier, {:reject, e}} ->
85         {:reject, e}
86
87       {:transmogrifier, _} = e ->
88         {:error, e}
89
90       {:object, data, nil} ->
91         reinject_object(%Object{}, data)
92
93       {:normalize, object = %Object{}} ->
94         {:ok, object}
95
96       {:fetch_object, %Object{} = object} ->
97         {:ok, object}
98
99       {:fetch, {:error, error}} ->
100         {:error, error}
101
102       e ->
103         e
104     end
105   end
106
107   defp prepare_activity_params(data) do
108     %{
109       "type" => "Create",
110       # Should we seriously keep this attributedTo thing?
111       "actor" => data["actor"] || data["attributedTo"],
112       "object" => data
113     }
114     |> Maps.put_if_present("to", data["to"])
115     |> Maps.put_if_present("cc", data["cc"])
116     |> Maps.put_if_present("bto", data["bto"])
117     |> Maps.put_if_present("bcc", data["bcc"])
118   end
119
120   def fetch_object_from_id!(id, options \\ []) do
121     with {:ok, object} <- fetch_object_from_id(id, options) do
122       object
123     else
124       {:error, %Tesla.Mock.Error{}} ->
125         nil
126
127       {:error, "Object has been deleted"} ->
128         nil
129
130       {:reject, reason} ->
131         Logger.info("Rejected #{id} while fetching: #{inspect(reason)}")
132         nil
133
134       e ->
135         Logger.error("Error while fetching #{id}: #{inspect(e)}")
136         nil
137     end
138   end
139
140   defp make_signature(id, date) do
141     uri = URI.parse(id)
142
143     signature =
144       InternalFetchActor.get_actor()
145       |> Signature.sign(%{
146         "(request-target)": "get #{uri.path}",
147         host: uri.host,
148         date: date
149       })
150
151     {"signature", signature}
152   end
153
154   defp sign_fetch(headers, id, date) do
155     if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do
156       [make_signature(id, date) | headers]
157     else
158       headers
159     end
160   end
161
162   defp maybe_date_fetch(headers, date) do
163     if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do
164       [{"date", date} | headers]
165     else
166       headers
167     end
168   end
169
170   def fetch_and_contain_remote_object_from_id(id)
171
172   def fetch_and_contain_remote_object_from_id(%{"id" => id}),
173     do: fetch_and_contain_remote_object_from_id(id)
174
175   def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do
176     Logger.debug("Fetching object #{id} via AP")
177
178     with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")},
179          {:ok, body} <- get_object(id),
180          {:ok, data} <- safe_json_decode(body),
181          :ok <- Containment.contain_origin_from_id(id, data) do
182       if not Instances.reachable?(id) do
183         Instances.set_reachable(id)
184       end
185
186       {:ok, data}
187     else
188       {:scheme, _} ->
189         {:error, "Unsupported URI scheme"}
190
191       {:error, e} ->
192         {:error, e}
193
194       e ->
195         {:error, e}
196     end
197   end
198
199   def fetch_and_contain_remote_object_from_id(_id),
200     do: {:error, "id must be a string"}
201
202   defp get_object(id) do
203     date = Pleroma.Signature.signed_date()
204
205     headers =
206       [{"accept", "application/activity+json"}]
207       |> maybe_date_fetch(date)
208       |> sign_fetch(id, date)
209
210     case HTTP.get(id, headers) do
211       {:ok, %{body: body, status: code, headers: headers}} when code in 200..299 ->
212         case List.keyfind(headers, "content-type", 0) do
213           {_, content_type} ->
214             case Plug.Conn.Utils.media_type(content_type) do
215               {:ok, "application", "activity+json", _} ->
216                 {:ok, body}
217
218               {:ok, "application", "ld+json",
219                %{"profile" => "https://www.w3.org/ns/activitystreams"}} ->
220                 {:ok, body}
221
222               _ ->
223                 {:error, {:content_type, content_type}}
224             end
225
226           _ ->
227             {:error, {:content_type, nil}}
228         end
229
230       {:ok, %{status: code}} when code in [404, 410] ->
231         {:error, "Object has been deleted"}
232
233       {:error, e} ->
234         {:error, e}
235
236       e ->
237         {:error, e}
238     end
239   end
240
241   defp safe_json_decode(nil), do: {:ok, nil}
242   defp safe_json_decode(json), do: Jason.decode(json)
243 end