X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fobject%2Ffetcher.ex;h=cde4e503969e4a07f86e2a048afc4c08c3a449cd;hb=f5a315f04cc4e07ff951a2c084525d899291df92;hp=3e2949ee2354f3009ae68142a1a01a5a10b86c6b;hpb=d884b6f704b3d6e5f4b09349224a0c498b75a09b;p=akkoma diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 3e2949ee2..cde4e5039 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -1,14 +1,17 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2020 Pleroma Authors +# Copyright © 2017-2021 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Object.Fetcher do alias Pleroma.HTTP + alias Pleroma.Instances + alias Pleroma.Maps alias Pleroma.Object alias Pleroma.Object.Containment alias Pleroma.Repo alias Pleroma.Signature alias Pleroma.Web.ActivityPub.InternalFetchActor + alias Pleroma.Web.ActivityPub.ObjectValidator alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.Federator @@ -23,21 +26,72 @@ defmodule Pleroma.Object.Fetcher do Ecto.Changeset.put_change(changeset, :updated_at, updated_at) end - defp maybe_reinject_internal_fields(data, %{data: %{} = old_data}) do + defp maybe_reinject_internal_fields(%{data: %{} = old_data}, new_data) do + has_history? = fn + %{"formerRepresentations" => %{"orderedItems" => list}} when is_list(list) -> true + _ -> false + end + internal_fields = Map.take(old_data, Pleroma.Constants.object_internal_fields()) - Map.merge(data, internal_fields) + remote_history_exists? = has_history?.(new_data) + + # If the remote history exists, we treat that as the only source of truth. + new_data = + if has_history?.(old_data) and not remote_history_exists? do + Map.put(new_data, "formerRepresentations", old_data["formerRepresentations"]) + else + new_data + end + + # If the remote does not have history information, we need to manage it ourselves + new_data = + if not remote_history_exists? do + changed? = + Pleroma.Constants.status_updatable_fields() + |> Enum.any?(fn field -> Map.get(old_data, field) != Map.get(new_data, field) end) + + %{updated_object: updated_object} = + new_data + |> Object.Updater.maybe_update_history(old_data, + updated: changed?, + use_history_in_new_object?: false + ) + + updated_object + else + new_data + end + + Map.merge(new_data, internal_fields) end - defp maybe_reinject_internal_fields(data, _), do: data + defp maybe_reinject_internal_fields(_, new_data), do: new_data @spec reinject_object(struct(), map()) :: {:ok, Object.t()} | {:error, any()} - defp reinject_object(struct, data) do - Logger.debug("Reinjecting object #{data["id"]}") + defp reinject_object(%Object{data: %{"type" => "Question"}} = object, new_data) do + Logger.debug("Reinjecting object #{new_data["id"]}") + + with data <- maybe_reinject_internal_fields(object, new_data), + {:ok, data, _} <- ObjectValidator.validate(data, %{}), + changeset <- Object.change(object, %{data: data}), + changeset <- touch_changeset(changeset), + {:ok, object} <- Repo.insert_or_update(changeset), + {:ok, object} <- Object.set_cache(object) do + {:ok, object} + else + e -> + Logger.error("Error while processing object: #{inspect(e)}") + {:error, e} + end + end - with data <- Transmogrifier.fix_object(data), - data <- maybe_reinject_internal_fields(data, struct), - changeset <- Object.change(struct, %{data: data}), + defp reinject_object(%Object{} = object, new_data) do + Logger.debug("Reinjecting object #{new_data["id"]}") + + with new_data <- Transmogrifier.fix_object(new_data), + data <- maybe_reinject_internal_fields(object, new_data), + changeset <- Object.change(object, %{data: data}), changeset <- touch_changeset(changeset), {:ok, object} <- Repo.insert_or_update(changeset), {:ok, object} <- Object.set_cache(object) do @@ -51,8 +105,8 @@ defmodule Pleroma.Object.Fetcher do def refetch_object(%Object{data: %{"id" => id}} = object) do with {:local, false} <- {:local, Object.local?(object)}, - {:ok, data} <- fetch_and_contain_remote_object_from_id(id), - {:ok, object} <- reinject_object(object, data) do + {:ok, new_data} <- fetch_and_contain_remote_object_from_id(id), + {:ok, object} <- reinject_object(object, new_data) do {:ok, object} else {:local, true} -> {:ok, object} @@ -65,13 +119,13 @@ defmodule Pleroma.Object.Fetcher do with {_, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)}, {_, true} <- {:allowed_depth, Federator.allowed_thread_distance?(options[:depth])}, {_, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)}, - {_, nil} <- {:normalize, Object.normalize(data, false)}, + {_, nil} <- {:normalize, Object.normalize(data, fetch: false)}, params <- prepare_activity_params(data), {_, :ok} <- {:containment, Containment.contain_origin(id, params)}, {_, {:ok, activity}} <- {:transmogrifier, Transmogrifier.handle_incoming(params, options)}, {_, _data, %Object{} = object} <- - {:object, data, Object.normalize(activity, false)} do + {:object, data, Object.normalize(activity, fetch: false)} do {:ok, object} else {:allowed_depth, false} -> @@ -80,8 +134,11 @@ defmodule Pleroma.Object.Fetcher do {:containment, _} -> {:error, "Object containment failed."} - {:transmogrifier, {:error, {:reject, nil}}} -> - {:reject, nil} + {:transmogrifier, {:error, {:reject, e}}} -> + {:reject, e} + + {:transmogrifier, {:reject, e}} -> + {:reject, e} {:transmogrifier, _} = e -> {:error, e} @@ -106,12 +163,14 @@ defmodule Pleroma.Object.Fetcher do defp prepare_activity_params(data) do %{ "type" => "Create", - "to" => data["to"], - "cc" => data["cc"], # Should we seriously keep this attributedTo thing? "actor" => data["actor"] || data["attributedTo"], "object" => data } + |> Maps.put_if_present("to", data["to"]) + |> Maps.put_if_present("cc", data["cc"]) + |> Maps.put_if_present("bto", data["bto"]) + |> Maps.put_if_present("bcc", data["bcc"]) end def fetch_object_from_id!(id, options \\ []) do @@ -121,7 +180,11 @@ defmodule Pleroma.Object.Fetcher do {:error, %Tesla.Mock.Error{}} -> nil - {:error, "Object has been deleted"} -> + {:error, {"Object has been deleted", _id, _code}} -> + nil + + {:reject, reason} -> + Logger.debug("Rejected #{id} while fetching: #{inspect(reason)}") nil e -> @@ -141,12 +204,12 @@ defmodule Pleroma.Object.Fetcher do date: date }) - [{"signature", signature}] + {"signature", signature} end defp sign_fetch(headers, id, date) do if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do - headers ++ make_signature(id, date) + [make_signature(id, date) | headers] else headers end @@ -154,15 +217,45 @@ defmodule Pleroma.Object.Fetcher do defp maybe_date_fetch(headers, date) do if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do - headers ++ [{"date", date}] + [{"date", date} | headers] else headers end end + def fetch_and_contain_remote_object_from_id(id) + + def fetch_and_contain_remote_object_from_id(%{"id" => id}), + do: fetch_and_contain_remote_object_from_id(id) + def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do Logger.debug("Fetching object #{id} via AP") + with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")}, + {:ok, body} <- get_object(id), + {:ok, data} <- safe_json_decode(body), + :ok <- Containment.contain_origin_from_id(id, data) do + unless Instances.reachable?(id) do + Instances.set_reachable(id) + end + + {:ok, data} + else + {:scheme, _} -> + {:error, "Unsupported URI scheme"} + + {:error, e} -> + {:error, e} + + e -> + {:error, e} + end + end + + def fetch_and_contain_remote_object_from_id(_id), + do: {:error, "id must be a string"} + + defp get_object(id) do date = Pleroma.Signature.signed_date() headers = @@ -170,19 +263,28 @@ defmodule Pleroma.Object.Fetcher do |> maybe_date_fetch(date) |> sign_fetch(id, date) - Logger.debug("Fetch headers: #{inspect(headers)}") + case HTTP.get(id, headers) do + {:ok, %{body: body, status: code, headers: headers}} when code in 200..299 -> + case List.keyfind(headers, "content-type", 0) do + {_, content_type} -> + case Plug.Conn.Utils.media_type(content_type) do + {:ok, "application", "activity+json", _} -> + {:ok, body} - with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")}, - {:ok, %{body: body, status: code}} when code in 200..299 <- HTTP.get(id, headers), - {:ok, data} <- Jason.decode(body), - :ok <- Containment.contain_origin_from_id(id, data) do - {:ok, data} - else - {:ok, %{status: code}} when code in [404, 410] -> - {:error, "Object has been deleted"} + {:ok, "application", "ld+json", + %{"profile" => "https://www.w3.org/ns/activitystreams"}} -> + {:ok, body} - {:scheme, _} -> - {:error, "Unsupported URI scheme"} + _ -> + {:error, {:content_type, content_type}} + end + + _ -> + {:error, {:content_type, nil}} + end + + {:ok, %{status: code}} when code in [404, 410] -> + {:error, {"Object has been deleted", id, code}} {:error, e} -> {:error, e} @@ -192,8 +294,6 @@ defmodule Pleroma.Object.Fetcher do end end - def fetch_and_contain_remote_object_from_id(%{"id" => id}), - do: fetch_and_contain_remote_object_from_id(id) - - def fetch_and_contain_remote_object_from_id(_id), do: {:error, "id must be a string"} + defp safe_json_decode(nil), do: {:ok, nil} + defp safe_json_decode(json), do: Jason.decode(json) end