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