diff options
Diffstat (limited to 'lib/pleroma/object')
| -rw-r--r--[-rwxr-xr-x] | lib/pleroma/object/containment.ex | 0 | ||||
| -rw-r--r--[-rwxr-xr-x] | lib/pleroma/object/fetcher.ex | 85 | ||||
| -rw-r--r-- | lib/pleroma/object/fetcher.ex.orig | 238 | ||||
| -rw-r--r-- | lib/pleroma/object/fetcher.ex.rej | 13 | ||||
| -rw-r--r--[-rwxr-xr-x] | lib/pleroma/object/updater.ex | 0 |
5 files changed, 301 insertions, 35 deletions
diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex index f6106cb..f6106cb 100755..100644 --- a/lib/pleroma/object/containment.ex +++ b/lib/pleroma/object/containment.ex diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index cc37725..2350664 100755..100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -3,9 +3,11 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Object.Fetcher do + @behaviour HTTPSignatures.Adapter alias Pleroma.HTTP alias Pleroma.Instances alias Pleroma.Maps + alias Pleroma.Keys alias Pleroma.Object alias Pleroma.Object.Containment alias Pleroma.Signature @@ -72,20 +74,25 @@ defmodule Pleroma.Object.Fetcher do {:object, data, Object.normalize(activity, fetch: false)} do {:ok, object} else - {:allowed_depth, false} -> - {:error, "Max thread distance exceeded."} + {:allowed_depth, false} = e -> + log_fetch_error(id, e) + {:error, :allowed_depth} - {:containment, _} -> - {:error, "Object containment failed."} + {:containment, reason} = e -> + log_fetch_error(id, e) + {:error, reason} - {:transmogrifier, {:error, {:reject, e}}} -> - {:reject, e} + {:transmogrifier, {:error, {:reject, reason}}} = e -> + log_fetch_error(id, e) + {:reject, reason} - {:transmogrifier, {:reject, e}} -> - {:reject, e} + {:transmogrifier, {:reject, reason}} = e -> + log_fetch_error(id, e) + {:reject, reason} - {:transmogrifier, _} = e -> - {:error, e} + {:transmogrifier, reason} = e -> + log_fetch_error(id, e) + {:error, reason} {:object, data, nil} -> reinject_object(%Object{}, data) @@ -96,14 +103,21 @@ defmodule Pleroma.Object.Fetcher do {:fetch_object, %Object{} = object} -> {:ok, object} - {:fetch, {:error, error}} -> - {:error, error} + {:fetch, {:error, reason}} = e -> + log_fetch_error(id, e) + {:error, reason} e -> - e + log_fetch_error(id, e) + {:error, e} end end + defp log_fetch_error(id, error) do + Logger.metadata(object: id) + Logger.error("Object rejected while fetching #{id} #{inspect(error)}") + end + defp prepare_activity_params(data) do %{ "type" => "Create", @@ -117,36 +131,34 @@ defmodule Pleroma.Object.Fetcher do |> Maps.put_if_present("bcc", data["bcc"]) end - def fetch_object_from_id!(id, options \\ []) do - with {:ok, object} <- fetch_object_from_id(id, options) do - object - else - {:error, %Tesla.Mock.Error{}} -> - nil - - {:error, "Object has been deleted"} -> - nil - - {:reject, reason} -> - Logger.info("Rejected #{id} while fetching: #{inspect(reason)}") - nil - - e -> - Logger.error("Error while fetching #{id}: #{inspect(e)}") - nil - end - end - defp make_signature(id, date) do uri = URI.parse(id) - signature = + spoofed_pem = Pleroma.Config.get([:activitypub, :spoofed_key]) + # workaround for syntax shite disallowing me from defining signature in "if" block + spoofed_key = if Pleroma.Config.get([:activitypub, :spoof_object_fetch_signatures]) do + with {:ok, private_key, _} <- Keys.keys_from_pem(spoofed_pem) do + private_key + end + else + "" + end + spoofed_instance = Pleroma.Config.get([:activitypub, :spoofed_instance]) + + signature = if Pleroma.Config.get([:activitypub, :spoof_object_fetch_signatures]) do + HTTPSignatures.sign(spoofed_key, spoofed_instance <> "/internal/fetch#main-key", %{ + "(request-target)": "get #{uri.path}", + host: uri.host, + date: date + }) + else InternalFetchActor.get_actor() |> Signature.sign(%{ "(request-target)": "get #{uri.path}", host: uri.host, date: date }) + end {"signature", signature} end @@ -227,8 +239,11 @@ defmodule Pleroma.Object.Fetcher do {:error, {:content_type, nil}} end + {:ok, %{status: code}} when code in [401, 403] -> + {:error, :forbidden} + {:ok, %{status: code}} when code in [404, 410] -> - {:error, "Object has been deleted"} + {:error, :not_found} {:error, e} -> {:error, e} diff --git a/lib/pleroma/object/fetcher.ex.orig b/lib/pleroma/object/fetcher.ex.orig new file mode 100644 index 0000000..af5642a --- /dev/null +++ b/lib/pleroma/object/fetcher.ex.orig @@ -0,0 +1,238 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# 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.Signature + alias Pleroma.Web.ActivityPub.InternalFetchActor + alias Pleroma.Web.ActivityPub.MRF + alias Pleroma.Web.ActivityPub.ObjectValidator + alias Pleroma.Web.ActivityPub.Pipeline + alias Pleroma.Web.ActivityPub.Transmogrifier + alias Pleroma.Web.Federator + + require Logger + require Pleroma.Constants + + @spec reinject_object(struct(), map()) :: {:ok, Object.t()} | {:error, any()} + defp reinject_object(%Object{data: %{}} = object, new_data) do + Logger.debug("Reinjecting object #{new_data["id"]}") + + with {:ok, new_data, _} <- ObjectValidator.validate(new_data, %{}), + {:ok, new_data} <- MRF.filter(new_data), + {:ok, new_object, _} <- + Object.Updater.do_update_and_invalidate_cache( + object, + new_data, + _touch_changeset? = true + ) do + {:ok, new_object} + else + e -> + Logger.error("Error while processing object: #{inspect(e)}") + {:error, e} + end + end + + defp reinject_object(_, new_data) do + with {:ok, object, _} <- Pipeline.common_pipeline(new_data, local: false) do + {:ok, object} + else + e -> e + end + end + + def refetch_object(%Object{data: %{"id" => id}} = object) do + with {:local, false} <- {:local, Object.local?(object)}, + {: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} + 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, 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, fetch: false)} do + {:ok, object} + else + {:allowed_depth, false} = e -> + log_fetch_error(id, e) + {:error, :allowed_depth} + + {:containment, reason} = e -> + log_fetch_error(id, e) + {:error, reason} + + {:transmogrifier, {:error, {:reject, reason}}} = e -> + log_fetch_error(id, e) + {:reject, reason} + + {:transmogrifier, {:reject, reason}} = e -> + log_fetch_error(id, e) + {:reject, reason} + + {:transmogrifier, reason} = e -> + log_fetch_error(id, e) + {:error, reason} + + {:object, data, nil} -> + reinject_object(%Object{}, data) + + {:normalize, object = %Object{}} -> + {:ok, object} + + {:fetch_object, %Object{} = object} -> + {:ok, object} + + {:fetch, {:error, reason}} = e -> + log_fetch_error(id, e) + {:error, reason} + + e -> + log_fetch_error(id, e) + {:error, e} + end + end + + defp log_fetch_error(id, error) do + Logger.metadata(object: id) + Logger.error("Object rejected while fetching #{id} #{inspect(error)}") + end + + defp prepare_activity_params(data) do + %{ + "type" => "Create", + # 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 + + 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 + [make_signature(id, date) | headers] + else + headers + end + end + + defp maybe_date_fetch(headers, date) do + if Pleroma.Config.get([:activitypub, :sign_object_fetches]) do + [{"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 + if not 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 = + [{"accept", "application/activity+json"}] + |> maybe_date_fetch(date) + |> sign_fetch(id, date) + + 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} + + {:ok, "application", "ld+json", + %{"profile" => "https://www.w3.org/ns/activitystreams"}} -> + {:ok, body} + + _ -> + {:error, {:content_type, content_type}} + end + + _ -> + {:error, {:content_type, nil}} + end + + {:ok, %{status: code}} when code in [401, 403] -> + {:error, :forbidden} + + {:ok, %{status: code}} when code in [404, 410] -> + {:error, :not_found} + + {:error, e} -> + {:error, e} + + e -> + {:error, e} + end + end + + defp safe_json_decode(nil), do: {:ok, nil} + defp safe_json_decode(json), do: Jason.decode(json) +end diff --git a/lib/pleroma/object/fetcher.ex.rej b/lib/pleroma/object/fetcher.ex.rej new file mode 100644 index 0000000..25b13f5 --- /dev/null +++ b/lib/pleroma/object/fetcher.ex.rej @@ -0,0 +1,13 @@ +--- lib/pleroma/object/fetcher.ex ++++ lib/pleroma/object/fetcher.ex +@@ -3,7 +3,10 @@ + # SPDX-License-Identifier: AGPL-3.0-only + + defmodule Pleroma.Object.Fetcher do ++ @behaviour HTTPSignatures.Adapter ++ + alias Pleroma.HTTP ++ alias Pleroma.Keys + alias Pleroma.Maps + alias Pleroma.Object + alias Pleroma.Object.Containment diff --git a/lib/pleroma/object/updater.ex b/lib/pleroma/object/updater.ex index b1e4870..b1e4870 100755..100644 --- a/lib/pleroma/object/updater.ex +++ b/lib/pleroma/object/updater.ex |
