Fork of Pleroma with site-specific changes and feature branches https://git.pleroma.social/pleroma/pleroma
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

255 行
7.3KB

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