X-Git-Url: http://git.squeep.com/?a=blobdiff_plain;f=lib%2Fpleroma%2Fobject%2Ffetcher.ex;h=7758cb90b1f6637d5fdb406a5fb0fe474713c903;hb=b2eb1124d115beda0907121c3c6f10783b34f352;hp=c422490ac4ae922399589d7ee842e5eb21a4a434;hpb=a0c65bbd6c708b555f457bf24ec07d2d41c3fe4a;p=akkoma diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index c422490ac..263ded5dd 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -1,17 +1,46 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + defmodule Pleroma.Object.Fetcher do alias Pleroma.HTTP alias Pleroma.Object alias Pleroma.Object.Containment + alias Pleroma.Repo + alias Pleroma.Signature + alias Pleroma.Web.ActivityPub.InternalFetchActor alias Pleroma.Web.ActivityPub.Transmogrifier - alias Pleroma.Web.OStatus + alias Pleroma.Web.Federator require Logger + require Pleroma.Constants + + defp touch_changeset(changeset) do + updated_at = + NaiveDateTime.utc_now() + |> NaiveDateTime.truncate(:second) + + Ecto.Changeset.put_change(changeset, :updated_at, updated_at) + end + + defp maybe_reinject_internal_fields(data, %{data: %{} = old_data}) do + internal_fields = Map.take(old_data, Pleroma.Constants.object_internal_fields()) + + Map.merge(data, internal_fields) + end + + defp maybe_reinject_internal_fields(data, _), do: data - defp reinject_object(data) do + @spec reinject_object(struct(), map()) :: {:ok, Object.t()} | {:error, any()} + defp reinject_object(struct, data) do Logger.debug("Reinjecting object #{data["id"]}") with data <- Transmogrifier.fix_object(data), - {:ok, object} <- Object.create(data) do + data <- maybe_reinject_internal_fields(data, struct), + changeset <- Object.change(struct, %{data: data}), + changeset <- touch_changeset(changeset), + {:ok, object} <- Repo.insert_or_update(changeset), + {:ok, object} <- Object.set_cache(object) do {:ok, object} else e -> @@ -20,67 +49,131 @@ defmodule Pleroma.Object.Fetcher do end end - # TODO: - # This will create a Create activity, which we need internally at the moment. - def fetch_object_from_id(id) do - if object = Object.get_cached_by_ap_id(id) 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, object} else - Logger.info("Fetching #{id} via AP") - - with {:ok, data} <- fetch_and_contain_remote_object_from_id(id), - nil <- Object.normalize(data, false), - params <- %{ - "type" => "Create", - "to" => data["to"], - "cc" => data["cc"], - "actor" => data["actor"] || data["attributedTo"], - "object" => data - }, - :ok <- Containment.contain_origin(id, params), - {:ok, activity} <- Transmogrifier.handle_incoming(params), - {:object, _data, %Object{} = object} <- - {:object, data, Object.normalize(activity, false)} do - {:ok, object} - else - {:error, {:reject, nil}} -> - {:reject, nil} + {:local, true} -> {:ok, object} + e -> {:error, e} + end + end + + # Note: will create a Create activity, which we need internally at the moment. + def fetch_object_from_id(id, options \\ []) 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)}, + 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 + {:ok, object} + else + {:allowed_depth, false} -> + {:error, "Max thread distance exceeded."} + + {:containment, _} -> + {:error, "Object containment failed."} + + {:transmogrifier, {:error, {:reject, nil}}} -> + {:reject, nil} + + {:transmogrifier, _} -> + {:error, "Transmogrifier failure."} + + {:object, data, nil} -> + reinject_object(%Object{}, data) - {:object, data, nil} -> - reinject_object(data) + {:normalize, object = %Object{}} -> + {:ok, object} - object = %Object{} -> - {:ok, object} + {:fetch_object, %Object{} = object} -> + {:ok, object} - _e -> - Logger.info("Couldn't get object via AP, trying out OStatus fetching...") + {:fetch, {:error, error}} -> + {:error, error} - case OStatus.fetch_activity_from_url(id) do - {:ok, [activity | _]} -> {:ok, Object.normalize(activity, false)} - e -> e - end - end + e -> + e end end - def fetch_object_from_id!(id) do - with {:ok, object} <- fetch_object_from_id(id) 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 + } + end + + def fetch_object_from_id!(id, options \\ []) do + with {:ok, object} <- fetch_object_from_id(id, options) do object else - _e -> + {:error, %Tesla.Mock.Error{}} -> + nil + + {:error, "Object has been deleted"} -> + nil + + e -> + Logger.error("Error while fetching #{id}: #{inspect(e)}") nil end end - def fetch_and_contain_remote_object_from_id(id) do - Logger.info("Fetching object #{id} via AP") + defp make_signature(id, date) do + uri = URI.parse(id) + + signature = + InternalFetchActor.get_actor() + |> Signature.sign(%{ + "(request-target)": "get #{uri.path}", + host: uri.host, + date: date + }) + + [{"signature", signature}] + end + + defp sign_fetch(headers, id, date) do + if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do + headers ++ make_signature(id, date) + else + headers + end + end + + defp maybe_date_fetch(headers, date) do + if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do + headers ++ [{"date", date}] + else + headers + end + end + + def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do + Logger.debug("Fetching object #{id} via AP") + + date = Pleroma.Signature.signed_date() + + headers = + [{"accept", "application/activity+json"}] + |> maybe_date_fetch(date) + |> sign_fetch(id, date) - with true <- String.starts_with?(id, "http"), - {:ok, %{body: body, status: code}} when code in 200..299 <- - HTTP.get( - id, - [{:Accept, "application/activity+json"}] - ), + Logger.debug("Fetch headers: #{inspect(headers)}") + + 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} @@ -88,8 +181,19 @@ defmodule Pleroma.Object.Fetcher do {:ok, %{status: code}} when code in [404, 410] -> {:error, "Object has been deleted"} + {:scheme, _} -> + {:error, "Unsupported URI scheme"} + + {:error, e} -> + {:error, e} + e -> {:error, e} 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"} end