diff options
Diffstat (limited to 'lib')
32 files changed, 532 insertions, 215 deletions
diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index 3a5e35301..5268ebe7a 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -85,6 +85,36 @@ defmodule Pleroma.Constants do ] ) + const(activity_types, + do: [ + "Create", + "Update", + "Delete", + "Follow", + "Accept", + "Reject", + "Add", + "Remove", + "Like", + "Announce", + "Undo", + "Flag", + "EmojiReact" + ] + ) + + const(allowed_activity_types_from_strangers, + do: [ + "Block", + "Create", + "Flag", + "Follow", + "Like", + "EmojiReact", + "Announce" + ] + ) + # basic regex, just there to weed out potential mistakes # https://datatracker.ietf.org/doc/html/rfc2045#section-5.1 const(mime_regex, diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex index dcb27a29d..32c1080f7 100644 --- a/lib/pleroma/http/adapter_helper.ex +++ b/lib/pleroma/http/adapter_helper.ex @@ -52,6 +52,7 @@ defmodule Pleroma.HTTP.AdapterHelper do case adapter() do Tesla.Adapter.Gun -> AdapterHelper.Gun Tesla.Adapter.Hackney -> AdapterHelper.Hackney + {Tesla.Adapter.Finch, _} -> AdapterHelper.Finch _ -> AdapterHelper.Default end end @@ -118,4 +119,13 @@ defmodule Pleroma.HTTP.AdapterHelper do host_charlist end end + + @spec can_stream? :: bool() + def can_stream? do + case Application.get_env(:tesla, :adapter) do + Tesla.Adapter.Gun -> true + {Tesla.Adapter.Finch, _} -> true + _ -> false + end + end end diff --git a/lib/pleroma/http/adapter_helper/finch.ex b/lib/pleroma/http/adapter_helper/finch.ex new file mode 100644 index 000000000..181caed7e --- /dev/null +++ b/lib/pleroma/http/adapter_helper/finch.ex @@ -0,0 +1,33 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.AdapterHelper.Finch do + @behaviour Pleroma.HTTP.AdapterHelper + + alias Pleroma.Config + alias Pleroma.HTTP.AdapterHelper + + @spec options(keyword(), URI.t()) :: keyword() + def options(incoming_opts \\ [], %URI{} = _uri) do + proxy = + [:http, :proxy_url] + |> Config.get() + |> AdapterHelper.format_proxy() + + config_opts = Config.get([:http, :adapter], []) + + config_opts + |> Keyword.merge(incoming_opts) + |> AdapterHelper.maybe_add_proxy(proxy) + |> maybe_stream() + end + + # Finch uses [response: :stream] + defp maybe_stream(opts) do + case Keyword.pop(opts, :stream, nil) do + {true, opts} -> Keyword.put(opts, :response, :stream) + {_, opts} -> opts + end + end +end diff --git a/lib/pleroma/http/adapter_helper/gun.ex b/lib/pleroma/http/adapter_helper/gun.ex index 1fe8dd4b2..30ba26765 100644 --- a/lib/pleroma/http/adapter_helper/gun.ex +++ b/lib/pleroma/http/adapter_helper/gun.ex @@ -32,6 +32,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do |> AdapterHelper.maybe_add_proxy(proxy) |> Keyword.merge(incoming_opts) |> put_timeout() + |> maybe_stream() end defp add_scheme_opts(opts, %{scheme: "http"}), do: opts @@ -47,6 +48,14 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do Keyword.put(opts, :timeout, recv_timeout) end + # Gun uses [body_as: :stream] + defp maybe_stream(opts) do + case Keyword.pop(opts, :stream, nil) do + {true, opts} -> Keyword.put(opts, :body_as, :stream) + {_, opts} -> opts + end + end + @spec pool_timeout(pool()) :: non_neg_integer() def pool_timeout(pool) do default = Config.get([:pools, :default, :recv_timeout], 5_000) diff --git a/lib/pleroma/maps.ex b/lib/pleroma/maps.ex index 5020a8ff8..1afbde484 100644 --- a/lib/pleroma/maps.ex +++ b/lib/pleroma/maps.ex @@ -20,15 +20,13 @@ defmodule Pleroma.Maps do end def filter_empty_values(data) do - # TODO: Change to Map.filter in Elixir 1.13+ data - |> Enum.filter(fn + |> Map.filter(fn {_k, nil} -> false {_k, ""} -> false {_k, []} -> false {_k, %{} = v} -> Map.keys(v) != [] {_k, _v} -> true end) - |> Map.new() end end diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index c0f671dd4..69a5f3268 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -58,8 +58,12 @@ defmodule Pleroma.Object.Fetcher do end end + @typep fetcher_errors :: + :error | :reject | :allowed_depth | :fetch | :containment | :transmogrifier + # Note: will create a Create activity, which we need internally at the moment. - @spec fetch_object_from_id(String.t(), list()) :: {:ok, Object.t()} | {:error | :reject, any()} + @spec fetch_object_from_id(String.t(), list()) :: + {:ok, Object.t()} | {fetcher_errors(), any()} | Pipeline.errors() 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])}, @@ -73,50 +77,22 @@ defmodule Pleroma.Object.Fetcher do {: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} + {:object, data, nil} -> + reinject_object(%Object{}, data) e -> - log_fetch_error(id, e) - {:error, e} + Logger.metadata(object: id) + Logger.error("Object rejected while fetching #{id} #{inspect(e)}") + 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", diff --git a/lib/pleroma/user/backup.ex b/lib/pleroma/user/backup.ex index 7feaa22bf..d77d49890 100644 --- a/lib/pleroma/user/backup.ex +++ b/lib/pleroma/user/backup.ex @@ -92,9 +92,6 @@ defmodule Pleroma.User.Backup do else true -> {:error, "Backup is missing id. Please insert it into the Repo first."} - - e -> - {:error, e} end end @@ -121,14 +118,13 @@ defmodule Pleroma.User.Backup do end defp permitted?(user) do - with {_, %__MODULE__{inserted_at: inserted_at}} <- {:last, get_last(user)}, - days = Config.get([__MODULE__, :limit_days]), - diff = Timex.diff(NaiveDateTime.utc_now(), inserted_at, :days), - {_, true} <- {:diff, diff > days} do - true + with {_, %__MODULE__{inserted_at: inserted_at}} <- {:last, get_last(user)} do + days = Config.get([__MODULE__, :limit_days]) + diff = Timex.diff(NaiveDateTime.utc_now(), inserted_at, :days) + + diff > days else {:last, nil} -> true - {:diff, false} -> false end end @@ -297,9 +293,6 @@ defmodule Pleroma.User.Backup do ) acc - - _ -> - acc end end) diff --git a/lib/pleroma/user/import.ex b/lib/pleroma/user/import.ex index 11905237c..ab6bdb8d4 100644 --- a/lib/pleroma/user/import.ex +++ b/lib/pleroma/user/import.ex @@ -5,87 +5,107 @@ defmodule Pleroma.User.Import do use Ecto.Schema + alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.CommonAPI alias Pleroma.Workers.BackgroundWorker require Logger - @spec perform(atom(), User.t(), list()) :: :ok | list() | {:error, any()} - def perform(:mutes_import, %User{} = user, [_ | _] = identifiers) do - Enum.map( - identifiers, - fn identifier -> - with {:ok, %User{} = muted_user} <- User.get_or_fetch(identifier), - {:ok, _} <- User.mute(user, muted_user) do - muted_user - else - error -> handle_error(:mutes_import, identifier, error) - end - end - ) + @spec perform(atom(), User.t(), String.t()) :: :ok | {:error, any()} + def perform(:mute_import, %User{} = user, actor) do + with {:ok, %User{} = muted_user} <- User.get_or_fetch(actor), + {_, false} <- {:existing_mute, User.mutes_user?(user, muted_user)}, + {:ok, _} <- User.mute(user, muted_user) do + {:ok, muted_user} + else + {:existing_mute, true} -> :ok + error -> handle_error(:mutes_import, actor, error) + end end - def perform(:blocks_import, %User{} = blocker, [_ | _] = identifiers) do - Enum.map( - identifiers, - fn identifier -> - with {:ok, %User{} = blocked} <- User.get_or_fetch(identifier), - {:ok, _block} <- CommonAPI.block(blocked, blocker) do - blocked - else - error -> handle_error(:blocks_import, identifier, error) - end - end - ) + def perform(:block_import, %User{} = user, actor) do + with {:ok, %User{} = blocked} <- User.get_or_fetch(actor), + {_, false} <- {:existing_block, User.blocks_user?(user, blocked)}, + {:ok, _block} <- CommonAPI.block(blocked, user) do + {:ok, blocked} + else + {:existing_block, true} -> :ok + error -> handle_error(:blocks_import, actor, error) + end end - def perform(:follow_import, %User{} = follower, [_ | _] = identifiers) do - Enum.map( - identifiers, - fn identifier -> - with {:ok, %User{} = followed} <- User.get_or_fetch(identifier), - {:ok, follower, followed} <- User.maybe_direct_follow(follower, followed), - {:ok, _, _, _} <- CommonAPI.follow(followed, follower) do - followed - else - error -> handle_error(:follow_import, identifier, error) - end - end - ) + def perform(:follow_import, %User{} = user, actor) do + with {:ok, %User{} = followed} <- User.get_or_fetch(actor), + {_, false} <- {:existing_follow, User.following?(user, followed)}, + {:ok, user, followed} <- User.maybe_direct_follow(user, followed), + {:ok, _, _, _} <- CommonAPI.follow(followed, user) do + {:ok, followed} + else + {:existing_follow, true} -> :ok + error -> handle_error(:follow_import, actor, error) + end end - def perform(_, _, _), do: :ok - defp handle_error(op, user_id, error) do Logger.debug("#{op} failed for #{user_id} with: #{inspect(error)}") - error + {:error, error} end - def blocks_import(%User{} = blocker, [_ | _] = identifiers) do - BackgroundWorker.new(%{ - "op" => "blocks_import", - "user_id" => blocker.id, - "identifiers" => identifiers - }) - |> Oban.insert() + def blocks_import(%User{} = user, [_ | _] = actors) do + jobs = + Repo.checkout(fn -> + Enum.reduce(actors, [], fn actor, acc -> + {:ok, job} = + BackgroundWorker.new(%{ + "op" => "block_import", + "user_id" => user.id, + "actor" => actor + }) + |> Oban.insert() + + acc ++ [job] + end) + end) + + {:ok, jobs} end - def follow_import(%User{} = follower, [_ | _] = identifiers) do - BackgroundWorker.new(%{ - "op" => "follow_import", - "user_id" => follower.id, - "identifiers" => identifiers - }) - |> Oban.insert() + def follows_import(%User{} = user, [_ | _] = actors) do + jobs = + Repo.checkout(fn -> + Enum.reduce(actors, [], fn actor, acc -> + {:ok, job} = + BackgroundWorker.new(%{ + "op" => "follow_import", + "user_id" => user.id, + "actor" => actor + }) + |> Oban.insert() + + acc ++ [job] + end) + end) + + {:ok, jobs} end - def mutes_import(%User{} = user, [_ | _] = identifiers) do - BackgroundWorker.new(%{ - "op" => "mutes_import", - "user_id" => user.id, - "identifiers" => identifiers - }) - |> Oban.insert() + def mutes_import(%User{} = user, [_ | _] = actors) do + jobs = + Repo.checkout(fn -> + Enum.reduce(actors, [], fn actor, acc -> + {:ok, job} = + BackgroundWorker.new(%{ + "op" => "mute_import", + "user_id" => user.id, + "actor" => actor + }) + |> Oban.insert() + + acc ++ [job] + end) + end) + + {:ok, jobs} end end diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index cdd054e1a..a08eda5f4 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -311,7 +311,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do post_inbox_relayed_create(conn, params) else conn - |> put_status(:bad_request) + |> put_status(403) |> json("Not federating") end end diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex index 7f11a4d67..fc36935d5 100644 --- a/lib/pleroma/web/activity_pub/pipeline.ex +++ b/lib/pleroma/web/activity_pub/pipeline.ex @@ -22,22 +22,27 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do defp activity_pub, do: Config.get([:pipeline, :activity_pub], ActivityPub) defp config, do: Config.get([:pipeline, :config], Config) - @spec common_pipeline(map(), keyword()) :: - {:ok, Activity.t() | Object.t(), keyword()} | {:error | :reject, any()} + @type results :: {:ok, Activity.t() | Object.t(), keyword()} + @type errors :: {:error | :reject, any()} + + # The Repo.transaction will wrap the result in an {:ok, _} + # and only returns an {:error, _} if the error encountered was related + # to the SQL transaction + @spec common_pipeline(map(), keyword()) :: results() | errors() def common_pipeline(object, meta) do case Repo.transaction(fn -> do_common_pipeline(object, meta) end, Utils.query_timeout()) do {:ok, {:ok, activity, meta}} -> side_effects().handle_after_transaction(meta) {:ok, activity, meta} - {:ok, value} -> - value + {:ok, {:error, _} = error} -> + error + + {:ok, {:reject, _} = error} -> + error {:error, e} -> {:error, e} - - {:reject, e} -> - {:reject, e} end end diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 5cd982c6a..0de3a0d43 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -148,12 +148,17 @@ defmodule Pleroma.Web.ActivityPub.Publisher do {"digest", p.digest} ] ) do - maybe_set_reachable(p.unreachable_since, p.inbox) + if not is_nil(p.unreachable_since) do + Instances.set_reachable(p.inbox) + end result else {_post_result, %{status: code} = response} = e -> - maybe_set_unreachable(p.unreachable_since, p.inbox) + if is_nil(p.unreachable_since) do + Instances.set_unreachable(p.inbox) + end + Logger.metadata(activity: p.activity_id, inbox: p.inbox, status: code) Logger.error("Publisher failed to inbox #{p.inbox} with status #{code}") @@ -174,7 +179,10 @@ defmodule Pleroma.Web.ActivityPub.Publisher do connection_pool_snooze() e -> - maybe_set_unreachable(p.unreachable_since, p.inbox) + if is_nil(p.unreachable_since) do + Instances.set_unreachable(p.inbox) + end + Logger.metadata(activity: p.activity_id, inbox: p.inbox) Logger.error("Publisher failed to inbox #{p.inbox} #{inspect(e)}") {:error, e} @@ -183,12 +191,6 @@ defmodule Pleroma.Web.ActivityPub.Publisher do defp connection_pool_snooze, do: {:snooze, 3} - defp maybe_set_reachable(%NaiveDateTime{}, inbox), do: Instances.set_reachable(inbox) - defp maybe_set_reachable(_, _), do: :ok - - defp maybe_set_unreachable(nil, inbox), do: Instances.set_unreachable(inbox) - defp maybe_set_unreachable(%NaiveDateTime{}, _), do: :ok - defp signature_host(%URI{port: port, scheme: scheme, host: host}) do if port == URI.default_port(scheme) do host diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index 6e537b5da..25548d75b 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -249,6 +249,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do nullable: true, description: "A datetime (ISO 8601) that states when the post was pinned or `null` if the post is not pinned" + }, + list_id: %Schema{ + type: :integer, + nullable: true, + description: + "The ID of the list the post is addressed to (if any, only returned to author)" } } }, diff --git a/lib/pleroma/web/auth/ldap_authenticator.ex b/lib/pleroma/web/auth/ldap_authenticator.ex index ea5620cf6..d31f34747 100644 --- a/lib/pleroma/web/auth/ldap_authenticator.ex +++ b/lib/pleroma/web/auth/ldap_authenticator.ex @@ -41,6 +41,7 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do port = Keyword.get(ldap, :port, 389) ssl = Keyword.get(ldap, :ssl, false) sslopts = Keyword.get(ldap, :sslopts, []) + tlsopts = Keyword.get(ldap, :tlsopts, []) options = [{:port, port}, {:ssl, ssl}, {:timeout, @connection_timeout}] ++ @@ -54,7 +55,16 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do case :eldap.start_tls( connection, - Keyword.get(ldap, :tlsopts, []), + Keyword.merge( + [ + verify: :verify_peer, + cacerts: :certifi.cacerts(), + customize_hostname_check: [ + fqdn_fun: fn _ -> to_charlist(host) end + ] + ], + tlsopts + ), @connection_timeout ) do :ok -> diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index 921e414c3..412424dae 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -26,7 +26,7 @@ defmodule Pleroma.Web.CommonAPI do require Pleroma.Constants require Logger - @spec block(User.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()} + @spec block(User.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors() def block(blocked, blocker) do with {:ok, block_data, _} <- Builder.block(blocker, blocked), {:ok, block, _} <- Pipeline.common_pipeline(block_data, local: true) do @@ -35,7 +35,7 @@ defmodule Pleroma.Web.CommonAPI do end @spec post_chat_message(User.t(), User.t(), String.t(), list()) :: - {:ok, Activity.t()} | {:error, any()} + {:ok, Activity.t()} | Pipeline.errors() def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]), :ok <- validate_chat_attachment_attribution(maybe_attachment, user), @@ -58,7 +58,7 @@ defmodule Pleroma.Web.CommonAPI do )} do {:ok, activity} else - {:common_pipeline, {:reject, _} = e} -> e + {:common_pipeline, e} -> e e -> e end end @@ -99,7 +99,8 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec unblock(User.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()} + @spec unblock(User.t(), User.t()) :: + {:ok, Activity.t()} | {:ok, :no_activity} | Pipeline.errors() | {:error, :not_blocking} def unblock(blocked, blocker) do with {_, %Activity{} = block} <- {:fetch_block, Utils.fetch_latest_block(blocker, blocked)}, {:ok, unblock_data, _} <- Builder.undo(blocker, block), @@ -120,7 +121,9 @@ defmodule Pleroma.Web.CommonAPI do end @spec follow(User.t(), User.t()) :: - {:ok, User.t(), User.t(), Activity.t() | Object.t()} | {:error, :rejected} + {:ok, User.t(), User.t(), Activity.t() | Object.t()} + | {:error, :rejected} + | Pipeline.errors() def follow(followed, follower) do timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout]) @@ -145,7 +148,7 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec accept_follow_request(User.t(), User.t()) :: {:ok, User.t()} | {:error, any()} + @spec accept_follow_request(User.t(), User.t()) :: {:ok, User.t()} | Pipeline.errors() def accept_follow_request(follower, followed) do with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), {:ok, accept_data, _} <- Builder.accept(followed, follow_activity), @@ -154,7 +157,7 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec reject_follow_request(User.t(), User.t()) :: {:ok, User.t()} | {:error, any()} | nil + @spec reject_follow_request(User.t(), User.t()) :: {:ok, User.t()} | Pipeline.errors() | nil def reject_follow_request(follower, followed) do with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), {:ok, reject_data, _} <- Builder.reject(followed, follow_activity), @@ -163,7 +166,8 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec delete(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()} + @spec delete(String.t(), User.t()) :: + {:ok, Activity.t()} | Pipeline.errors() | {:error, :not_found | String.t()} def delete(activity_id, user) do with {_, %Activity{data: %{"object" => _, "type" => "Create"}} = activity} <- {:find_activity, Activity.get_by_id(activity_id, filter: [])}, @@ -213,7 +217,7 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec repeat(String.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, any()} + @spec repeat(String.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, :not_found} def repeat(id, user, params \\ %{}) do with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id), object = %Object{} <- Object.normalize(activity, fetch: false), @@ -231,7 +235,7 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec unrepeat(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()} + @spec unrepeat(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, :not_found | String.t()} def unrepeat(id, user) do with {_, %Activity{data: %{"type" => "Create"}} = activity} <- {:find_activity, Activity.get_by_id(id)}, @@ -247,7 +251,8 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec favorite(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()} + @spec favorite(String.t(), User.t()) :: + {:ok, Activity.t()} | {:ok, :already_liked} | {:error, :not_found | String.t()} def favorite(id, %User{} = user) do case favorite_helper(user, id) do {:ok, _} = res -> @@ -285,7 +290,8 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec unfavorite(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()} + @spec unfavorite(String.t(), User.t()) :: + {:ok, Activity.t()} | {:error, :not_found | String.t()} def unfavorite(id, user) do with {_, %Activity{data: %{"type" => "Create"}} = activity} <- {:find_activity, Activity.get_by_id(id)}, @@ -302,7 +308,7 @@ defmodule Pleroma.Web.CommonAPI do end @spec react_with_emoji(String.t(), User.t(), String.t()) :: - {:ok, Activity.t()} | {:error, any()} + {:ok, Activity.t()} | {:error, String.t()} def react_with_emoji(id, user, emoji) do with %Activity{} = activity <- Activity.get_by_id(id), object <- Object.normalize(activity, fetch: false), @@ -316,7 +322,7 @@ defmodule Pleroma.Web.CommonAPI do end @spec unreact_with_emoji(String.t(), User.t(), String.t()) :: - {:ok, Activity.t()} | {:error, any()} + {:ok, Activity.t()} | {:error, String.t()} def unreact_with_emoji(id, user, emoji) do with %Activity{} = reaction_activity <- Utils.get_latest_reaction(id, user, emoji), {_, {:ok, _}} <- {:cancel_jobs, maybe_cancel_jobs(reaction_activity)}, @@ -329,7 +335,7 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec vote(Object.t(), User.t(), list()) :: {:ok, list(), Object.t()} | {:error, any()} + @spec vote(Object.t(), User.t(), list()) :: {:ok, list(), Object.t()} | Pipeline.errors() def vote(%Object{data: %{"type" => "Question"}} = object, %User{} = user, choices) do with :ok <- validate_not_author(object, user), :ok <- validate_existing_votes(user, object), @@ -461,7 +467,7 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec update(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, any()} + @spec update(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, nil} def update(orig_activity, %User{} = user, changes) do with orig_object <- Object.normalize(orig_activity), {:ok, new_object} <- make_update_data(user, orig_object, changes), @@ -497,7 +503,7 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, term()} + @spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors() def pin(id, %User{} = user) do with %Activity{} = activity <- create_activity_by_id(id), true <- activity_belongs_to_actor(activity, user.ap_id), @@ -537,7 +543,7 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec unpin(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, term()} + @spec unpin(String.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors() def unpin(id, user) do with %Activity{} = activity <- create_activity_by_id(id), {:ok, unpin_data, _} <- Builder.unpin(user, activity.object), @@ -552,7 +558,7 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec add_mute(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, any()} + @spec add_mute(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, String.t()} def add_mute(activity, user, params \\ %{}) do expires_in = Map.get(params, :expires_in, 0) diff --git a/lib/pleroma/web/federator.ex b/lib/pleroma/web/federator.ex index 2df716556..58260afa8 100644 --- a/lib/pleroma/web/federator.ex +++ b/lib/pleroma/web/federator.ex @@ -102,7 +102,8 @@ defmodule Pleroma.Web.Federator do # NOTE: we use the actor ID to do the containment, this is fine because an # actor shouldn't be acting on objects outside their own AP server. - with {_, {:ok, _user}} <- {:actor, User.get_or_fetch_by_ap_id(actor)}, + with {_, {:ok, user}} <- {:actor, User.get_or_fetch_by_ap_id(actor)}, + {:user_active, true} <- {:user_active, match?(true, user.is_active)}, nil <- Activity.normalize(params["id"]), {_, :ok} <- {:correct_origin?, Containment.contain_origin_from_id(actor, params)}, @@ -121,11 +122,6 @@ defmodule Pleroma.Web.Federator do Logger.debug("Unhandled actor #{actor}, #{inspect(e)}") {:error, e} - {:error, {:validate_object, _}} = e -> - Logger.error("Incoming AP doc validation error: #{inspect(e)}") - Logger.debug(Jason.encode!(params, pretty: true)) - e - e -> # Just drop those for now Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end) diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex index 844673ae0..6cfeb712e 100644 --- a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex @@ -19,6 +19,8 @@ defmodule Pleroma.Web.MastodonAPI.AppController do action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + plug(Pleroma.Web.Plugs.RateLimiter, [name: :oauth_app_creation] when action == :create) + plug(:skip_auth when action in [:create, :verify_credentials]) plug(Pleroma.Web.ApiSpec.CastAndValidate) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 0643b8f14..7de6745d4 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -92,14 +92,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do User.get_follow_state(reading_user, target) end - followed_by = - if following_relationships do - case FollowingRelationship.find(following_relationships, target, reading_user) do - %{state: :follow_accept} -> true - _ -> false - end - else - User.following?(target, reading_user) + followed_by = FollowingRelationship.following?(target, reading_user) + following = FollowingRelationship.following?(reading_user, target) + + requested = + cond do + following -> false + true -> match?(:follow_pending, follow_state) end subscribing = @@ -114,7 +113,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do # NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags %{ id: to_string(target.id), - following: follow_state == :follow_accept, + following: following, followed_by: followed_by, blocking: UserRelationship.exists?( @@ -150,7 +149,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do ), subscribing: subscribing, notifying: subscribing, - requested: follow_state == :follow_pending, + requested: requested, domain_blocking: User.blocks_domain?(reading_user, target), showing_reblogs: not UserRelationship.exists?( diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 747638c53..3bf870c24 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -465,7 +465,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do parent_visible: visible_for_user?(reply_to, opts[:for]), pinned_at: pinned_at, quotes_count: object.data["quotesCount"] || 0, - bookmark_folder: bookmark_folder + bookmark_folder: bookmark_folder, + list_id: get_list_id(object, client_posted_this_activity) } } end @@ -803,19 +804,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do defp build_application(_), do: nil - # Workaround for Elixir issue #10771 - # Avoid applying URI.merge unless necessary - # TODO: revert to always attempting URI.merge(image_url_data, page_url_data) - # when Elixir 1.12 is the minimum supported version - @spec build_image_url(struct() | nil, struct()) :: String.t() | nil - defp build_image_url( - %URI{scheme: image_scheme, host: image_host} = image_url_data, - %URI{} = _page_url_data - ) - when not is_nil(image_scheme) and not is_nil(image_host) do - image_url_data |> to_string - end - + @spec build_image_url(URI.t(), URI.t()) :: String.t() defp build_image_url(%URI{} = image_url_data, %URI{} = page_url_data) do URI.merge(page_url_data, image_url_data) |> to_string end @@ -847,4 +836,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do nil end end + + defp get_list_id(object, client_posted_this_activity) do + with true <- client_posted_this_activity, + %{data: %{"listMessage" => list_ap_id}} when is_binary(list_ap_id) <- object, + %{id: list_id} <- Pleroma.List.get_by_ap_id(list_ap_id) do + list_id + else + _ -> nil + end + end end diff --git a/lib/pleroma/web/o_auth/app.ex b/lib/pleroma/web/o_auth/app.ex index d1bf6dd18..7661c2566 100644 --- a/lib/pleroma/web/o_auth/app.ex +++ b/lib/pleroma/web/o_auth/app.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.OAuth.App do import Ecto.Query alias Pleroma.Repo alias Pleroma.User + alias Pleroma.Web.OAuth.Token @type t :: %__MODULE__{} @@ -155,4 +156,29 @@ defmodule Pleroma.Web.OAuth.App do Map.put(acc, key, error) end) end + + @spec maybe_update_owner(Token.t()) :: :ok + def maybe_update_owner(%Token{app_id: app_id, user_id: user_id}) when not is_nil(user_id) do + __MODULE__.update(app_id, %{user_id: user_id}) + + :ok + end + + def maybe_update_owner(_), do: :ok + + @spec remove_orphans(pos_integer()) :: :ok + def remove_orphans(limit \\ 100) do + fifteen_mins_ago = DateTime.add(DateTime.utc_now(), -900, :second) + + Repo.transaction(fn -> + from(a in __MODULE__, + where: is_nil(a.user_id) and a.inserted_at < ^fifteen_mins_ago, + limit: ^limit + ) + |> Repo.all() + |> Enum.each(&Repo.delete(&1)) + end) + + :ok + end end diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex index 47b03215f..0b3de5481 100644 --- a/lib/pleroma/web/o_auth/o_auth_controller.ex +++ b/lib/pleroma/web/o_auth/o_auth_controller.ex @@ -318,6 +318,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params) def after_token_exchange(%Plug.Conn{} = conn, %{token: token} = view_params) do + App.maybe_update_owner(token) + conn |> AuthHelper.put_session_token(token.token) |> json(OAuthView.render("token.json", view_params)) diff --git a/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex index 96466f192..d65c30dab 100644 --- a/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex @@ -38,8 +38,8 @@ defmodule Pleroma.Web.PleromaAPI.UserImportController do |> Enum.map(&(&1 |> String.trim() |> String.trim_leading("@"))) |> Enum.reject(&(&1 == "")) - User.Import.follow_import(follower, identifiers) - json(conn, "job started") + User.Import.follows_import(follower, identifiers) + json(conn, "jobs started") end def blocks( @@ -55,7 +55,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportController do defp do_block(%{assigns: %{user: blocker}} = conn, list) do User.Import.blocks_import(blocker, prepare_user_identifiers(list)) - json(conn, "job started") + json(conn, "jobs started") end def mutes( @@ -71,7 +71,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportController do defp do_mute(%{assigns: %{user: user}} = conn, list) do User.Import.mutes_import(user, prepare_user_identifiers(list)) - json(conn, "job started") + json(conn, "jobs started") end defp prepare_user_identifiers(list) do diff --git a/lib/pleroma/web/plugs/authentication_plug.ex b/lib/pleroma/web/plugs/authentication_plug.ex index f912a1542..af7d7f45a 100644 --- a/lib/pleroma/web/plugs/authentication_plug.ex +++ b/lib/pleroma/web/plugs/authentication_plug.ex @@ -47,6 +47,11 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do Pleroma.Password.Pbkdf2.verify_pass(password, password_hash) end + def checkpw(password, "$argon2" <> _ = password_hash) do + # Handle argon2 passwords for Akkoma migration + Argon2.verify_pass(password, password_hash) + end + def checkpw(_password, _password_hash) do Logger.error("Password hash not recognized") false @@ -56,6 +61,10 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do do_update_password(user, password) end + def maybe_update_password(%User{password_hash: "$argon2" <> _} = user, password) do + do_update_password(user, password) + end + def maybe_update_password(user, _), do: {:ok, user} defp do_update_password(user, password) do diff --git a/lib/pleroma/web/plugs/inbox_guard_plug.ex b/lib/pleroma/web/plugs/inbox_guard_plug.ex new file mode 100644 index 000000000..0064cce76 --- /dev/null +++ b/lib/pleroma/web/plugs/inbox_guard_plug.ex @@ -0,0 +1,89 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Plugs.InboxGuardPlug do + import Plug.Conn + import Pleroma.Constants, only: [activity_types: 0, allowed_activity_types_from_strangers: 0] + + alias Pleroma.Config + alias Pleroma.User + + def init(options) do + options + end + + def call(%{assigns: %{valid_signature: true}} = conn, _opts) do + with {_, true} <- {:federating, Config.get!([:instance, :federating])} do + conn + |> filter_activity_types() + else + {:federating, false} -> + conn + |> json(403, "Not federating") + |> halt() + end + end + + def call(conn, _opts) do + with {_, true} <- {:federating, Config.get!([:instance, :federating])}, + conn = filter_activity_types(conn), + {:known, true} <- {:known, known_actor?(conn)} do + conn + else + {:federating, false} -> + conn + |> json(403, "Not federating") + |> halt() + + {:known, false} -> + conn + |> filter_from_strangers() + end + end + + # Early rejection of unrecognized types + defp filter_activity_types(%{body_params: %{"type" => type}} = conn) do + with true <- type in activity_types() do + conn + else + _ -> + conn + |> json(400, "Invalid activity type") + |> halt() + end + end + + # If signature failed but we know this actor we should + # accept it as we may only need to refetch their public key + # during processing + defp known_actor?(%{body_params: data}) do + case Pleroma.Object.Containment.get_actor(data) |> User.get_cached_by_ap_id() do + %User{} -> true + _ -> false + end + end + + # Only permit a subset of activity types from strangers + # or else it will add actors you've never interacted with + # to the database + defp filter_from_strangers(%{body_params: %{"type" => type}} = conn) do + with true <- type in allowed_activity_types_from_strangers() do + conn + else + _ -> + conn + |> json(400, "Invalid activity type for an unknown actor") + |> halt() + end + end + + defp json(conn, status, resp) do + json_resp = Jason.encode!(resp) + + conn + |> put_resp_content_type("application/json") + |> resp(status, json_resp) + |> halt() + end +end diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index e2889b351..d4be97957 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -11,16 +11,39 @@ defmodule Pleroma.Web.RichMedia.Helpers do @spec rich_media_get(String.t()) :: {:ok, String.t()} | get_errors() def rich_media_get(url) do - headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}] + case Pleroma.HTTP.AdapterHelper.can_stream?() do + true -> stream(url) + false -> head_first(url) + end + |> handle_result(url) + end + + defp stream(url) do + with {_, {:ok, %Tesla.Env{status: 200, body: stream_body, headers: headers}}} <- + {:get, Pleroma.HTTP.get(url, req_headers(), http_options())}, + {_, :ok} <- {:content_type, check_content_type(headers)}, + {_, :ok} <- {:content_length, check_content_length(headers)}, + {:read_stream, {:ok, body}} <- {:read_stream, read_stream(stream_body)} do + {:ok, body} + end + end + defp head_first(url) do with {_, {:ok, %Tesla.Env{status: 200, headers: headers}}} <- - {:head, Pleroma.HTTP.head(url, headers, http_options())}, + {:head, Pleroma.HTTP.head(url, req_headers(), http_options())}, {_, :ok} <- {:content_type, check_content_type(headers)}, {_, :ok} <- {:content_length, check_content_length(headers)}, {_, {:ok, %Tesla.Env{status: 200, body: body}}} <- - {:get, Pleroma.HTTP.get(url, headers, http_options())} do + {:get, Pleroma.HTTP.get(url, req_headers(), http_options())} do {:ok, body} - else + end + end + + defp handle_result(result, url) do + case result do + {:ok, body} -> + {:ok, body} + {:head, _} -> Logger.debug("Rich media error for #{url}: HTTP HEAD failed") {:error, :head} @@ -29,8 +52,12 @@ defmodule Pleroma.Web.RichMedia.Helpers do Logger.debug("Rich media error for #{url}: content-type is #{type}") {:error, :content_type} - {:content_length, {_, length}} -> - Logger.debug("Rich media error for #{url}: content-length is #{length}") + {:content_length, :error} -> + Logger.debug("Rich media error for #{url}: content-length exceeded") + {:error, :body_too_large} + + {:read_stream, :error} -> + Logger.debug("Rich media error for #{url}: content-length exceeded") {:error, :body_too_large} {:get, _} -> @@ -59,7 +86,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do {_, maybe_content_length} -> case Integer.parse(maybe_content_length) do {content_length, ""} when content_length <= max_body -> :ok - {_, ""} -> {:error, maybe_content_length} + {_, ""} -> :error _ -> :ok end @@ -68,13 +95,37 @@ defmodule Pleroma.Web.RichMedia.Helpers do end end - defp http_options do - timeout = Config.get!([:rich_media, :timeout]) + defp read_stream(stream) do + max_body = Keyword.get(http_options(), :max_body) + + try do + result = + Stream.transform(stream, 0, fn chunk, total_bytes -> + new_total = total_bytes + byte_size(chunk) + + if new_total > max_body do + raise("Exceeds max body limit of #{max_body}") + else + {[chunk], new_total} + end + end) + |> Enum.into(<<>>) + {:ok, result} + rescue + _ -> :error + end + end + + defp http_options do [ pool: :rich_media, max_body: Config.get([:rich_media, :max_body], 5_000_000), - tesla_middleware: [{Tesla.Middleware.Timeout, timeout: timeout}] + stream: true ] end + + defp req_headers do + [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}] + end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 6492e3861..0423ca9e2 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -189,7 +189,7 @@ defmodule Pleroma.Web.Router do end pipeline :well_known do - plug(:accepts, ["json", "jrd", "jrd+json", "xml", "xrd+xml"]) + plug(:accepts, ["activity+json", "json", "jrd", "jrd+json", "xml", "xrd+xml"]) end pipeline :config do @@ -217,6 +217,10 @@ defmodule Pleroma.Web.Router do plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug) end + pipeline :inbox_guard do + plug(Pleroma.Web.Plugs.InboxGuardPlug) + end + pipeline :static_fe do plug(Pleroma.Web.Plugs.StaticFEPlug) end @@ -920,7 +924,7 @@ defmodule Pleroma.Web.Router do end scope "/", Pleroma.Web.ActivityPub do - pipe_through(:activitypub) + pipe_through([:activitypub, :inbox_guard]) post("/inbox", ActivityPubController, :inbox) post("/users/:nickname/inbox", ActivityPubController, :inbox) end diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index 60da2d5ca..4737c6ea2 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -19,10 +19,10 @@ defmodule Pleroma.Workers.BackgroundWorker do User.perform(:force_password_reset, user) end - def perform(%Job{args: %{"op" => op, "user_id" => user_id, "identifiers" => identifiers}}) - when op in ["blocks_import", "follow_import", "mutes_import"] do + def perform(%Job{args: %{"op" => op, "user_id" => user_id, "actor" => actor}}) + when op in ["block_import", "follow_import", "mute_import"] do user = User.get_cached_by_id(user_id) - {:ok, User.Import.perform(String.to_existing_atom(op), user, identifiers)} + User.Import.perform(String.to_existing_atom(op), user, actor) end def perform(%Job{ diff --git a/lib/pleroma/workers/cron/app_cleanup_worker.ex b/lib/pleroma/workers/cron/app_cleanup_worker.ex new file mode 100644 index 000000000..ee71cd7b6 --- /dev/null +++ b/lib/pleroma/workers/cron/app_cleanup_worker.ex @@ -0,0 +1,21 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.Cron.AppCleanupWorker do + @moduledoc """ + Cleans up registered apps that were never associated with a user. + """ + + use Oban.Worker, queue: "background" + + alias Pleroma.Web.OAuth.App + + @impl true + def perform(_job) do + App.remove_orphans() + end + + @impl true + def timeout(_job), do: :timer.seconds(30) +end diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex index d4db97b63..11b672bef 100644 --- a/lib/pleroma/workers/receiver_worker.ex +++ b/lib/pleroma/workers/receiver_worker.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Workers.ReceiverWorker do alias Pleroma.User alias Pleroma.Web.Federator - use Oban.Worker, queue: :federator_incoming, max_attempts: 5 + use Oban.Worker, queue: :federator_incoming, max_attempts: 5, unique: [period: :infinity] @impl true @@ -33,7 +33,7 @@ defmodule Pleroma.Workers.ReceiverWorker do query_string: query_string } - with {:ok, %User{} = _actor} <- User.get_or_fetch_by_ap_id(conn_data.params["actor"]), + with {:ok, %User{}} <- User.get_or_fetch_by_ap_id(conn_data.params["actor"]), {:ok, _public_key} <- Signature.refetch_public_key(conn_data), {:signature, true} <- {:signature, Signature.validate_signature(conn_data)}, {:ok, res} <- Federator.perform(:incoming_ap_doc, params) do @@ -56,17 +56,29 @@ defmodule Pleroma.Workers.ReceiverWorker do def timeout(_job), do: :timer.seconds(5) + defp process_errors({:error, {:error, _} = error}), do: process_errors(error) + defp process_errors(errors) do case errors do - {:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed} + # User fetch failures + {:error, :not_found} = reason -> {:cancel, reason} + {:error, :forbidden} = reason -> {:cancel, reason} + # Inactive user + {:error, {:user_active, false} = reason} -> {:cancel, reason} + # Validator will error and return a changeset error + # e.g., duplicate activities or if the object was deleted + {:error, {:validate, {:error, _changeset} = reason}} -> {:cancel, reason} + # Duplicate detection during Normalization {:error, :already_present} -> {:cancel, :already_present} - {:error, {:validate_object, _} = reason} -> {:cancel, reason} - {:error, {:error, {:validate, {:error, _changeset} = reason}}} -> {:cancel, reason} + # MRFs will return a reject {:error, {:reject, _} = reason} -> {:cancel, reason} + # HTTP Sigs {:signature, false} -> {:cancel, :invalid_signature} - {:error, "Object has been deleted"} = reason -> {:cancel, reason} + # Origin / URL validation failed somewhere possibly due to spoofing + {:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed} + # Unclear if this can be reached {:error, {:side_effects, {:error, :no_object_actor}} = reason} -> {:cancel, reason} - {:error, :not_found} = reason -> {:cancel, reason} + # Catchall {:error, _} = e -> e e -> {:error, e} end diff --git a/lib/pleroma/workers/remote_fetcher_worker.ex b/lib/pleroma/workers/remote_fetcher_worker.ex index e43765733..aa09362f5 100644 --- a/lib/pleroma/workers/remote_fetcher_worker.ex +++ b/lib/pleroma/workers/remote_fetcher_worker.ex @@ -5,7 +5,7 @@ defmodule Pleroma.Workers.RemoteFetcherWorker do alias Pleroma.Object.Fetcher - use Oban.Worker, queue: :background + use Oban.Worker, queue: :background, unique: [period: :infinity] @impl true def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do @@ -13,17 +13,26 @@ defmodule Pleroma.Workers.RemoteFetcherWorker do {:ok, _object} -> :ok - {:reject, reason} -> + {:allowed_depth, false} -> + {:cancel, :allowed_depth} + + {:containment, reason} -> {:cancel, reason} - {:error, :forbidden} -> - {:cancel, :forbidden} + {:transmogrifier, reason} -> + {:cancel, reason} - {:error, :not_found} -> - {:cancel, :not_found} + {:fetch, {:error, :forbidden = reason}} -> + {:cancel, reason} - {:error, :allowed_depth} -> - {:cancel, :allowed_depth} + {:fetch, {:error, :not_found = reason}} -> + {:cancel, reason} + + {:fetch, {:error, {:content_type, _}} = reason} -> + {:cancel, reason} + + {:fetch, {:error, reason}} -> + {:error, reason} {:error, _} = e -> e diff --git a/lib/pleroma/workers/rich_media_worker.ex b/lib/pleroma/workers/rich_media_worker.ex index d5ba7b63e..e351ecd6e 100644 --- a/lib/pleroma/workers/rich_media_worker.ex +++ b/lib/pleroma/workers/rich_media_worker.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Workers.RichMediaWorker do alias Pleroma.Web.RichMedia.Backfill alias Pleroma.Web.RichMedia.Card - use Oban.Worker, queue: :background, max_attempts: 3, unique: [period: 300] + use Oban.Worker, queue: :background, max_attempts: 3, unique: [period: :infinity] @impl true def perform(%Job{args: %{"op" => "expire", "url" => url} = _args}) do diff --git a/lib/pleroma/workers/user_refresh_worker.ex b/lib/pleroma/workers/user_refresh_worker.ex index 222a4a8f7..ee276774b 100644 --- a/lib/pleroma/workers/user_refresh_worker.ex +++ b/lib/pleroma/workers/user_refresh_worker.ex @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Workers.UserRefreshWorker do - use Oban.Worker, queue: :background, max_attempts: 1, unique: [period: 300] + use Oban.Worker, queue: :background, max_attempts: 1, unique: [period: :infinity] alias Pleroma.User diff --git a/lib/pleroma/workers/web_pusher_worker.ex b/lib/pleroma/workers/web_pusher_worker.ex index f4232d02a..879b26cc3 100644 --- a/lib/pleroma/workers/web_pusher_worker.ex +++ b/lib/pleroma/workers/web_pusher_worker.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Workers.WebPusherWorker do alias Pleroma.Repo alias Pleroma.Web.Push.Impl - use Oban.Worker, queue: :web_push + use Oban.Worker, queue: :web_push, unique: [period: :infinity] @impl true def perform(%Job{args: %{"op" => "web_push", "notification_id" => notification_id}}) do |