logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://hacktivis.me/git/pleroma.git

fetcher.ex (6999B)


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