diff --git a/.gitignore b/.gitignore index c0d0179514..355cea0696 100644 --- a/.gitignore +++ b/.gitignore @@ -63,3 +63,6 @@ pleroma.iml archive-* .gitlab-ci-local + +# Test files should be named *.exs +test/pleroma/**/*.ex diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index eaa9d3b25c..1e04dae76f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,6 +9,7 @@ variables: &global_variables DB_HOST: postgres DB_PORT: "5432" MIX_ENV: test + GIT_STRATEGY: fetch workflow: rules: @@ -133,7 +134,7 @@ unit-testing-1.13.4-otp-25: script: &testing_script - mix ecto.create - mix ecto.migrate - - mix pleroma.test_runner --cover --preload-modules + - mix test --cover --preload-modules coverage: '/^Line total: ([^ ]*%)$/' artifacts: reports: diff --git a/changelog.d/bugfix-truncate-remote-user-fields.fix b/changelog.d/bugfix-truncate-remote-user-fields.fix new file mode 100644 index 0000000000..239a3c2247 --- /dev/null +++ b/changelog.d/bugfix-truncate-remote-user-fields.fix @@ -0,0 +1 @@ +Truncate remote user fields, avoids them getting rejected diff --git a/changelog.d/ci-git-fetch.skip b/changelog.d/ci-git-fetch.skip new file mode 100644 index 0000000000..e69de29bb2 diff --git a/changelog.d/commonapi.skip b/changelog.d/commonapi.skip new file mode 100644 index 0000000000..e69de29bb2 diff --git a/changelog.d/deprecate-subscribe.change b/changelog.d/deprecate-subscribe.change new file mode 100644 index 0000000000..bd7e8aec75 --- /dev/null +++ b/changelog.d/deprecate-subscribe.change @@ -0,0 +1 @@ +Deprecate `/api/v1/pleroma/accounts/:id/subscribe`/`unsubscribe` \ No newline at end of file diff --git a/changelog.d/docs-fix.skip b/changelog.d/docs-fix.skip new file mode 100644 index 0000000000..e69de29bb2 diff --git a/changelog.d/drop-unwanted.change b/changelog.d/drop-unwanted.change new file mode 100644 index 0000000000..459d4bfe64 --- /dev/null +++ b/changelog.d/drop-unwanted.change @@ -0,0 +1 @@ +Restrict incoming activities from unknown actors to a subset that does not imply a previous relationship and early rejection of unrecognized activity types. diff --git a/changelog.d/fix-test-failures.skip b/changelog.d/fix-test-failures.skip new file mode 100644 index 0000000000..e69de29bb2 diff --git a/changelog.d/follow-request.fix b/changelog.d/follow-request.fix new file mode 100644 index 0000000000..59d34e9bfd --- /dev/null +++ b/changelog.d/follow-request.fix @@ -0,0 +1 @@ +Fixed malformed follow requests that cause them to appear stuck pending due to the recipient being unable to process them. diff --git a/changelog.d/follow-validator.fix b/changelog.d/follow-validator.fix new file mode 100644 index 0000000000..d49932b7b3 --- /dev/null +++ b/changelog.d/follow-validator.fix @@ -0,0 +1 @@ +Improve the FollowValidator to successfully incoming activities with an errant cc field. diff --git a/changelog.d/get-statuses-param.change b/changelog.d/get-statuses-param.change new file mode 100644 index 0000000000..3edcad2684 --- /dev/null +++ b/changelog.d/get-statuses-param.change @@ -0,0 +1 @@ +Support `id` param in `GET /api/v1/statuses` \ No newline at end of file diff --git a/changelog.d/identity-proofs.remove b/changelog.d/identity-proofs.remove new file mode 100644 index 0000000000..efe1c34f57 --- /dev/null +++ b/changelog.d/identity-proofs.remove @@ -0,0 +1 @@ +Remove stub for /api/v1/accounts/:id/identity_proofs (deprecated by Mastodon 3.5.0) \ No newline at end of file diff --git a/changelog.d/mailgun.fix b/changelog.d/mailgun.fix new file mode 100644 index 0000000000..8555887526 --- /dev/null +++ b/changelog.d/mailgun.fix @@ -0,0 +1 @@ +The Swoosh email adapter for Mailgun was missing a new dependency on :multipart diff --git a/changelog.d/mogrify.skip b/changelog.d/mogrify.skip new file mode 100644 index 0000000000..e69de29bb2 diff --git a/changelog.d/mrf-cleanup.skip b/changelog.d/mrf-cleanup.skip new file mode 100644 index 0000000000..e69de29bb2 diff --git a/changelog.d/mrf-fodirectreply.add b/changelog.d/mrf-fodirectreply.add new file mode 100644 index 0000000000..10fd5d16ac --- /dev/null +++ b/changelog.d/mrf-fodirectreply.add @@ -0,0 +1 @@ +Added MRF.FODirectReply which changes replies to followers-only posts to be direct. diff --git a/changelog.d/mrf-quietreply.add b/changelog.d/mrf-quietreply.add new file mode 100644 index 0000000000..4ed20bce60 --- /dev/null +++ b/changelog.d/mrf-quietreply.add @@ -0,0 +1 @@ +Added MRF.QuietReply which prevents replies to public posts from being published to the timelines diff --git a/changelog.d/notifications-marker.change b/changelog.d/notifications-marker.change new file mode 100644 index 0000000000..9e350a95cf --- /dev/null +++ b/changelog.d/notifications-marker.change @@ -0,0 +1 @@ +Fix 'Setting a marker should mark notifications as read' \ No newline at end of file diff --git a/changelog.d/oban-recevier-improvements.fix b/changelog.d/oban-recevier-improvements.fix new file mode 100644 index 0000000000..f91502ed25 --- /dev/null +++ b/changelog.d/oban-recevier-improvements.fix @@ -0,0 +1 @@ +ReceiverWorker will cancel processing jobs instead of retrying if the user cannot be fetched due to 403, 404, or 410 errors or if the account is disabled locally. diff --git a/changelog.d/publisher-reachability.fix b/changelog.d/publisher-reachability.fix new file mode 100644 index 0000000000..3f50be581b --- /dev/null +++ b/changelog.d/publisher-reachability.fix @@ -0,0 +1 @@ +Address case where instance reachability status couldn't be updated diff --git a/changelog.d/remote-object-fetcher.fix b/changelog.d/remote-object-fetcher.fix new file mode 100644 index 0000000000..dcf2b1b315 --- /dev/null +++ b/changelog.d/remote-object-fetcher.fix @@ -0,0 +1 @@ +Remote Fetcher Worker recognizes more permanent failure errors diff --git a/changelog.d/rich-media-no-heads.change b/changelog.d/rich-media-no-heads.change new file mode 100644 index 0000000000..0bab323aa5 --- /dev/null +++ b/changelog.d/rich-media-no-heads.change @@ -0,0 +1 @@ +Rich Media preview fetching will skip making an HTTP HEAD request to check a URL for allowed content type and length if the Tesla adapter is Gun or Finch diff --git a/changelog.d/scrubbers-allow-mention-hashtag.add b/changelog.d/scrubbers-allow-mention-hashtag.add new file mode 100644 index 0000000000..c12ab1ffb2 --- /dev/null +++ b/changelog.d/scrubbers-allow-mention-hashtag.add @@ -0,0 +1 @@ +scrubbers/default: Allow "mention hashtag" classes used by Mastodon \ No newline at end of file diff --git a/changelog.d/stream-follow-relationships-count.fix b/changelog.d/stream-follow-relationships-count.fix new file mode 100644 index 0000000000..68452a88b8 --- /dev/null +++ b/changelog.d/stream-follow-relationships-count.fix @@ -0,0 +1 @@ +StreamerView: Do not leak follows count if hidden \ No newline at end of file diff --git a/changelog.d/text-extensions.skip b/changelog.d/text-extensions.skip new file mode 100644 index 0000000000..e69de29bb2 diff --git a/changelog.d/todo-cleanup.skip b/changelog.d/todo-cleanup.skip new file mode 100644 index 0000000000..e69de29bb2 diff --git a/changelog.d/update-oban.change b/changelog.d/update-oban.change new file mode 100644 index 0000000000..a67b3e3cf4 --- /dev/null +++ b/changelog.d/update-oban.change @@ -0,0 +1 @@ +Update Oban to 2.18 diff --git a/changelog.d/user-factory.skip b/changelog.d/user-factory.skip new file mode 100644 index 0000000000..e69de29bb2 diff --git a/changelog.d/user-imports.fix b/changelog.d/user-imports.fix new file mode 100644 index 0000000000..0076c73d77 --- /dev/null +++ b/changelog.d/user-imports.fix @@ -0,0 +1 @@ +Imports of blocks, mutes, and follows would retry repeatedly due to incorrect error handling and all work executed in a single job diff --git a/changelog.d/workerhelper.change b/changelog.d/workerhelper.change new file mode 100644 index 0000000000..539c9b54f7 --- /dev/null +++ b/changelog.d/workerhelper.change @@ -0,0 +1 @@ +Worker configuration is no longer available. This only affects custom max_retries values for a couple Oban queues. diff --git a/config/config.exs b/config/config.exs index 2543f40698..9b9a8ba6ef 100644 --- a/config/config.exs +++ b/config/config.exs @@ -600,13 +600,6 @@ config :pleroma, Oban, {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker} ] -config :pleroma, :workers, - retries: [ - federator_incoming: 5, - federator_outgoing: 5, - search_indexing: 2 - ] - config :pleroma, Pleroma.Formatter, class: false, rel: "ugc", diff --git a/config/description.exs b/config/description.exs index 2809e91307..15faecb38c 100644 --- a/config/description.exs +++ b/config/description.exs @@ -2013,23 +2013,6 @@ config :pleroma, :config_description, [ } ] }, - %{ - group: :pleroma, - key: :workers, - type: :group, - description: "Includes custom worker options not interpretable directly by `Oban`", - children: [ - %{ - key: :retries, - type: {:keyword, :integer}, - description: "Max retry attempts for failed jobs, per `Oban` queue", - suggestions: [ - federator_incoming: 5, - federator_outgoing: 5 - ] - } - ] - }, %{ group: :pleroma, key: Pleroma.Web.Metadata, diff --git a/docs/development/API/admin_api.md b/docs/development/API/admin_api.md index 5b373b8e1f..409e78a1e8 100644 --- a/docs/development/API/admin_api.md +++ b/docs/development/API/admin_api.md @@ -433,7 +433,7 @@ Response: * On success: URL of the unfollowed relay ```json -{"https://example.com/relay"} +"https://example.com/relay" ``` ## `POST /api/v1/pleroma/admin/users/invite_token` @@ -1193,20 +1193,23 @@ Loads json generated from `config/descriptions.exs`. - Response: ```json -[ - { - "id": 1234, - "data": { - "actor": { - "id": 1, - "nickname": "lain" +{ + "items": [ + { + "id": 1234, + "data": { + "actor": { + "id": 1, + "nickname": "lain" + }, + "action": "relay_follow" }, - "action": "relay_follow" - }, - "time": 1502812026, // timestamp - "message": "[2017-08-15 15:47:06] @nick0 followed relay: https://example.org/relay" // log message - } -] + "time": 1502812026, // timestamp + "message": "[2017-08-15 15:47:06] @nick0 followed relay: https://example.org/relay" // log message + } + ], + "total": 1 +} ``` ## `POST /api/v1/pleroma/admin/reload_emoji` diff --git a/docs/development/API/differences_in_mastoapi_responses.md b/docs/development/API/differences_in_mastoapi_responses.md index e3b6a3c779..41464e8021 100644 --- a/docs/development/API/differences_in_mastoapi_responses.md +++ b/docs/development/API/differences_in_mastoapi_responses.md @@ -510,12 +510,6 @@ Pleroma is generally compatible with the Mastodon 2.7.2 API, but some newer feat - `GET /api/v1/trends`: Returns an empty array, `[]` -### Identity proofs - -*Added in Mastodon 2.8.0* - -- `GET /api/v1/identity_proofs`: Returns an empty array, `[]` - ### Featured tags *Added in Mastodon 3.0.0* diff --git a/docs/development/API/pleroma_api.md b/docs/development/API/pleroma_api.md index 57d333ffe1..000d7d27d9 100644 --- a/docs/development/API/pleroma_api.md +++ b/docs/development/API/pleroma_api.md @@ -145,6 +145,9 @@ See [Admin-API](admin_api.md) ## `/api/v1/pleroma/accounts/:id/subscribe` ### Subscribe to receive notifications for all statuses posted by a user + +Deprecated. `notify` parameter in `POST /api/v1/accounts/:id/follow` should be used instead. + * Method `POST` * Authentication: required * Params: @@ -171,6 +174,9 @@ See [Admin-API](admin_api.md) ## `/api/v1/pleroma/accounts/:id/unsubscribe` ### Unsubscribe to stop receiving notifications from user statuses + +Deprecated. `notify` parameter in `POST /api/v1/accounts/:id/follow` should be used instead. + * Method `POST` * Authentication: required * Params: diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index b82d1f079d..e52b5e0a72 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -295,10 +295,12 @@ defmodule Mix.Tasks.Pleroma.Database do |> DateTime.from_naive!("Etc/UTC") |> Timex.shift(days: days) - Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ - activity_id: activity.id, - expires_at: expires_at - }) + Pleroma.Workers.PurgeExpiredActivity.enqueue( + %{ + activity_id: activity.id + }, + scheduled_at: expires_at + ) end) end) |> Stream.run() diff --git a/lib/mix/tasks/pleroma/test_runner.ex b/lib/mix/tasks/pleroma/test_runner.ex deleted file mode 100644 index 69fefb0014..0000000000 --- a/lib/mix/tasks/pleroma/test_runner.ex +++ /dev/null @@ -1,25 +0,0 @@ -defmodule Mix.Tasks.Pleroma.TestRunner do - @shortdoc "Retries tests once if they fail" - - use Mix.Task - - def run(args \\ []) do - case System.cmd("mix", ["test"] ++ args, into: IO.stream(:stdio, :line)) do - {_, 0} -> - :ok - - _ -> - retry(args) - end - end - - def retry(args) do - case System.cmd("mix", ["test", "--failed"] ++ args, into: IO.stream(:stdio, :line)) do - {_, 0} -> - :ok - - _ -> - exit(1) - end - end -end diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index 3a5e353012..5268ebe7a2 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/emails/mailer.ex b/lib/pleroma/emails/mailer.ex index 1014421308..2a80f8547c 100644 --- a/lib/pleroma/emails/mailer.ex +++ b/lib/pleroma/emails/mailer.ex @@ -25,7 +25,8 @@ defmodule Pleroma.Emails.Mailer do |> :erlang.term_to_binary() |> Base.encode64() - MailerWorker.enqueue("email", %{"encoded_email" => encoded_email, "config" => config}) + MailerWorker.new(%{"op" => "email", "encoded_email" => encoded_email, "config" => config}) + |> Oban.insert() end @doc "callback to perform send email from queue" diff --git a/lib/pleroma/filter.ex b/lib/pleroma/filter.ex index 8c62d761f6..85bd370d7e 100644 --- a/lib/pleroma/filter.ex +++ b/lib/pleroma/filter.ex @@ -141,10 +141,13 @@ defmodule Pleroma.Filter do defp maybe_add_expires_at(changeset, _), do: changeset defp maybe_add_expiration_job(%{expires_at: %NaiveDateTime{} = expires_at} = filter) do - Pleroma.Workers.PurgeExpiredFilter.enqueue(%{ - filter_id: filter.id, - expires_at: DateTime.from_naive!(expires_at, "Etc/UTC") - }) + Pleroma.Workers.PurgeExpiredFilter.new( + %{ + filter_id: filter.id + }, + scheduled_at: DateTime.from_naive!(expires_at, "Etc/UTC") + ) + |> Oban.insert() end defp maybe_add_expiration_job(_), do: {:ok, nil} diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex index dcb27a29d0..32c1080f7e 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 0000000000..181caed7e1 --- /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 +# 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 1fe8dd4b26..30ba26765b 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/instances/instance.ex b/lib/pleroma/instances/instance.ex index 288555146e..33f1229d02 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -297,7 +297,8 @@ defmodule Pleroma.Instances.Instance do all of those users' activities and notifications. """ def delete_users_and_activities(host) when is_binary(host) do - DeleteWorker.enqueue("delete_instance", %{"host" => host}) + DeleteWorker.new(%{"op" => "delete_instance", "host" => host}) + |> Oban.insert() end def perform(:delete_instance, host) when is_binary(host) do diff --git a/lib/pleroma/maps.ex b/lib/pleroma/maps.ex index 5020a8ff8d..1afbde4840 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/mfa/token.ex b/lib/pleroma/mfa/token.ex index 57bc11ed52..b53e1c7d0e 100644 --- a/lib/pleroma/mfa/token.ex +++ b/lib/pleroma/mfa/token.ex @@ -52,11 +52,14 @@ defmodule Pleroma.MFA.Token do @spec create(User.t(), Authorization.t() | nil) :: {:ok, t()} | {:error, Ecto.Changeset.t()} def create(user, authorization \\ nil) do with {:ok, token} <- do_create(user, authorization) do - Pleroma.Workers.PurgeExpiredToken.enqueue(%{ - token_id: token.id, - valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), - mod: __MODULE__ - }) + Pleroma.Workers.PurgeExpiredToken.new( + %{ + token_id: token.id, + mod: __MODULE__ + }, + scheduled_at: DateTime.from_naive!(token.valid_until, "Etc/UTC") + ) + |> Oban.insert() {:ok, token} end diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index eb44b3855b..748f18e6cd 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -255,7 +255,8 @@ defmodule Pleroma.Object do @spec cleanup_attachments(boolean(), Object.t()) :: {:ok, Oban.Job.t() | nil} def cleanup_attachments(true, %Object{} = object) do - AttachmentsCleanupWorker.enqueue("cleanup_attachments", %{"object" => object}) + AttachmentsCleanupWorker.new(%{"op" => "cleanup_attachments", "object" => object}) + |> Oban.insert() end def cleanup_attachments(_, _), do: {:ok, nil} diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index c0f671dd43..9d9a201caf 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -73,50 +73,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/search.ex b/lib/pleroma/search.ex index b9d2a0188e..30b3ba9586 100644 --- a/lib/pleroma/search.ex +++ b/lib/pleroma/search.ex @@ -2,11 +2,13 @@ defmodule Pleroma.Search do alias Pleroma.Workers.SearchIndexingWorker def add_to_index(%Pleroma.Activity{id: activity_id}) do - SearchIndexingWorker.enqueue("add_to_index", %{"activity" => activity_id}) + SearchIndexingWorker.new(%{"op" => "add_to_index", "activity" => activity_id}) + |> Oban.insert() end def remove_from_index(%Pleroma.Object{id: object_id}) do - SearchIndexingWorker.enqueue("remove_from_index", %{"object" => object_id}) + SearchIndexingWorker.new(%{"op" => "remove_from_index", "object" => object_id}) + |> Oban.insert() end def search(query, options) do diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index e28d76a7c8..c6c5369431 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -463,6 +463,7 @@ defmodule Pleroma.User do def remote_user_changeset(struct \\ %User{local: false}, params) do bio_limit = Config.get([:instance, :user_bio_length], 5000) name_limit = Config.get([:instance, :user_name_length], 100) + fields_limit = Config.get([:instance, :max_remote_account_fields], 0) name = case params[:name] do @@ -476,6 +477,7 @@ defmodule Pleroma.User do |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now()) |> truncate_if_exists(:name, name_limit) |> truncate_if_exists(:bio, bio_limit) + |> Map.update(:fields, [], &Enum.take(&1, fields_limit)) |> truncate_fields_param() |> fix_follower_address() @@ -736,7 +738,8 @@ defmodule Pleroma.User do end def force_password_reset_async(user) do - BackgroundWorker.enqueue("force_password_reset", %{"user_id" => user.id}) + BackgroundWorker.new(%{"op" => "force_password_reset", "user_id" => user.id}) + |> Oban.insert() end @spec force_password_reset(User.t()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()} @@ -1218,7 +1221,8 @@ defmodule Pleroma.User do def update_and_set_cache(changeset) do with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do if get_change(changeset, :raw_fields) do - BackgroundWorker.enqueue("verify_fields_links", %{"user_id" => user.id}) + BackgroundWorker.new(%{"op" => "verify_fields_links", "user_id" => user.id}) + |> Oban.insert() end set_cache(user) @@ -1589,11 +1593,11 @@ defmodule Pleroma.User do )) || {:ok, nil} do if duration > 0 do - Pleroma.Workers.MuteExpireWorker.enqueue( - "unmute_user", - %{"muter_id" => muter.id, "mutee_id" => mutee.id}, + Pleroma.Workers.MuteExpireWorker.new( + %{"op" => "unmute_user", "muter_id" => muter.id, "mutee_id" => mutee.id}, scheduled_at: expires_at ) + |> Oban.insert() end @cachex.del(:user_cache, "muted_users_ap_ids:#{muter.ap_id}") @@ -1836,7 +1840,8 @@ defmodule Pleroma.User do defp maybe_filter_on_ap_id(query, _ap_ids), do: query def set_activation_async(user, status \\ true) do - BackgroundWorker.enqueue("user_activation", %{"user_id" => user.id, "status" => status}) + BackgroundWorker.new(%{"op" => "user_activation", "user_id" => user.id, "status" => status}) + |> Oban.insert() end @spec set_activation([User.t()], boolean()) :: {:ok, User.t()} | {:error, Ecto.Changeset.t()} @@ -1983,7 +1988,9 @@ defmodule Pleroma.User do def delete(%User{} = user) do # Purge the user immediately purge(user) - DeleteWorker.enqueue("delete_user", %{"user_id" => user.id}) + + DeleteWorker.new(%{"op" => "delete_user", "user_id" => user.id}) + |> Oban.insert() end # *Actually* delete the user from the DB diff --git a/lib/pleroma/user/import.ex b/lib/pleroma/user/import.ex index 53ffd1ab3d..b79fa88eb2 100644 --- a/lib/pleroma/user/import.ex +++ b/lib/pleroma/user/import.ex @@ -5,6 +5,7 @@ defmodule Pleroma.User.Import do use Ecto.Schema + alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.CommonAPI alias Pleroma.Workers.BackgroundWorker @@ -12,74 +13,99 @@ defmodule Pleroma.User.Import do 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 - ) + 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 end - def blocks_import(%User{} = blocker, [_ | _] = identifiers) do - BackgroundWorker.enqueue( - "blocks_import", - %{"user_id" => blocker.id, "identifiers" => identifiers} - ) + 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.enqueue( - "follow_import", - %{"user_id" => follower.id, "identifiers" => identifiers} - ) + 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.enqueue( - "mutes_import", - %{"user_id" => user.id, "identifiers" => identifiers} - ) + 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.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index d5f25bf692..b6be3ae17f 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -222,10 +222,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do %{data: %{"expires_at" => %DateTime{} = expires_at}} = activity ) do with {:ok, _job} <- - Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ - activity_id: activity.id, - expires_at: expires_at - }) do + Pleroma.Workers.PurgeExpiredActivity.enqueue( + %{ + activity_id: activity.id + }, + scheduled_at: expires_at + ) do {:ok, activity} end end @@ -446,10 +448,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do _ <- notify_and_stream(activity) do maybe_federate(activity) - BackgroundWorker.enqueue("move_following", %{ + BackgroundWorker.new(%{ + "op" => "move_following", "origin_id" => origin.id, "target_id" => target.id }) + |> Oban.insert() {:ok, activity} else @@ -1793,10 +1797,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do # enqueue a task to fetch all pinned objects Enum.each(pins, fn {ap_id, _} -> if is_nil(Object.get_cached_by_ap_id(ap_id)) do - Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{ + Pleroma.Workers.RemoteFetcherWorker.new(%{ + "op" => "fetch_remote", "id" => ap_id, "depth" => 1 }) + |> Oban.insert() end 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 cdd054e1a3..a08eda5f47 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/mrf/anti_followbot_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex index df4ba819c8..8ea61aec26 100644 --- a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex @@ -63,20 +63,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do end @impl true - def filter(%{"type" => "Follow", "actor" => actor_id} = message) do + def filter(%{"type" => "Follow", "actor" => actor_id} = activity) do %User{} = actor = normalize_by_ap_id(actor_id) score = determine_if_followbot(actor) - if score < 0.8 || bot_allowed?(message, actor) do - {:ok, message} + if score < 0.8 || bot_allowed?(activity, actor) do + {:ok, activity} else {:reject, "[AntiFollowbotPolicy] Scored #{actor_id} as #{score}"} end end @impl true - def filter(message), do: {:ok, message} + def filter(activity), do: {:ok, activity} @impl true def describe, do: {:ok, %{}} diff --git a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex index 3ec9c52ee7..2be6d8df46 100644 --- a/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/anti_link_spam_policy.ex @@ -29,17 +29,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do defp contains_links?(_), do: false @impl true - def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do + def filter(%{"type" => "Create", "actor" => actor, "object" => object} = activity) do with {:ok, %User{local: false} = u} <- User.get_or_fetch_by_ap_id(actor), {:contains_links, true} <- {:contains_links, contains_links?(object)}, {:old_user, true} <- {:old_user, old_user?(u)} do - {:ok, message} + {:ok, activity} else {:ok, %User{local: true}} -> - {:ok, message} + {:ok, activity} {:contains_links, false} -> - {:ok, message} + {:ok, activity} {:old_user, false} -> {:reject, "[AntiLinkSpamPolicy] User has no posts nor followers"} @@ -53,7 +53,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy do end # in all other cases, pass through - def filter(message), do: {:ok, message} + def filter(activity), do: {:ok, activity} @impl true def describe, do: {:ok, %{}} diff --git a/lib/pleroma/web/activity_pub/mrf/anti_mention_spam_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_mention_spam_policy.ex index 531e75ce80..1d76a307bf 100644 --- a/lib/pleroma/web/activity_pub/mrf/anti_mention_spam_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/anti_mention_spam_policy.ex @@ -22,11 +22,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiMentionSpamPolicy do end # copied from HellthreadPolicy - defp get_recipient_count(message) do - recipients = (message["to"] || []) ++ (message["cc"] || []) + defp get_recipient_count(activity) do + recipients = (activity["to"] || []) ++ (activity["cc"] || []) follower_collection = - User.get_cached_by_ap_id(message["actor"] || message["attributedTo"]).follower_address + User.get_cached_by_ap_id(activity["actor"] || activity["attributedTo"]).follower_address if Enum.member?(recipients, Pleroma.Constants.as_public()) do recipients = @@ -80,7 +80,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiMentionSpamPolicy do end # in all other cases, pass through - def filter(message), do: {:ok, message} + def filter(activity), do: {:ok, activity} @impl true def describe, do: {:ok, %{}} diff --git a/lib/pleroma/web/activity_pub/mrf/dnsrbl_policy.ex b/lib/pleroma/web/activity_pub/mrf/dnsrbl_policy.ex index 7c6bb888f1..ca41c464c4 100644 --- a/lib/pleroma/web/activity_pub/mrf/dnsrbl_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/dnsrbl_policy.ex @@ -38,18 +38,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.DNSRBLPolicy do @query_timeout 500 @impl true - def filter(%{"actor" => actor} = object) do + def filter(%{"actor" => actor} = activity) do actor_info = URI.parse(actor) - with {:ok, object} <- check_rbl(actor_info, object) do - {:ok, object} + with {:ok, activity} <- check_rbl(actor_info, activity) do + {:ok, activity} else _ -> {:reject, "[DNSRBLPolicy]"} end end @impl true - def filter(object), do: {:ok, object} + def filter(activity), do: {:ok, activity} @impl true def describe do @@ -90,7 +90,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.DNSRBLPolicy do } end - defp check_rbl(%{host: actor_host}, object) do + defp check_rbl(%{host: actor_host}, activity) do with false <- match?(^actor_host, Pleroma.Web.Endpoint.host()), zone when not is_nil(zone) <- Keyword.get(Config.get([:mrf_dnsrbl]), :zone) do query = @@ -100,7 +100,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.DNSRBLPolicy do rbl_response = rblquery(query) if Enum.empty?(rbl_response) do - {:ok, object} + {:ok, activity} else Task.start(fn -> reason = @@ -117,7 +117,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.DNSRBLPolicy do :error end else - _ -> {:ok, object} + _ -> {:ok, activity} end end diff --git a/lib/pleroma/web/activity_pub/mrf/drop_policy.ex b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex index ad09368397..e4fcc9935b 100644 --- a/lib/pleroma/web/activity_pub/mrf/drop_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/drop_policy.ex @@ -8,9 +8,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.DropPolicy do @behaviour Pleroma.Web.ActivityPub.MRF.Policy @impl true - def filter(object) do - Logger.debug("REJECTING #{inspect(object)}") - {:reject, object} + def filter(activity) do + Logger.debug("REJECTING #{inspect(activity)}") + {:reject, activity} end @impl true diff --git a/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex index f884962b92..1de5280d9c 100644 --- a/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/emoji_policy.ex @@ -28,11 +28,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do Pleroma.Config.get([:mrf_emoji, :federated_timeline_removal_shortcode], []) end - @impl Pleroma.Web.ActivityPub.MRF.Policy + @impl true def history_awareness, do: :manual - @impl Pleroma.Web.ActivityPub.MRF.Policy - def filter(%{"type" => type, "object" => %{"type" => objtype} = object} = message) + @impl true + def filter(%{"type" => type, "object" => %{"type" => objtype} = object} = activity) when type in ["Create", "Update"] and objtype in Pleroma.Constants.status_object_types() do with {:ok, object} <- Updater.do_with_history(object, fn object -> @@ -42,13 +42,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do Updater.do_with_history(object, fn object -> {:ok, process_remove(object, :shortcode, config_remove_shortcode())} end), - activity <- Map.put(message, "object", object), + activity <- Map.put(activity, "object", object), activity <- maybe_delist(activity) do {:ok, activity} end end - @impl Pleroma.Web.ActivityPub.MRF.Policy + @impl true def filter(%{"type" => type} = object) when type in Pleroma.Constants.actor_types() do with object <- process_remove(object, :url, config_remove_url()), object <- process_remove(object, :shortcode, config_remove_shortcode()) do @@ -56,7 +56,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do end end - @impl Pleroma.Web.ActivityPub.MRF.Policy + @impl true def filter(%{"type" => "EmojiReact"} = object) do with {:ok, _} <- matched_emoji_checker(config_remove_url(), config_remove_shortcode()).(object) do @@ -67,9 +67,9 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do end end - @impl Pleroma.Web.ActivityPub.MRF.Policy - def filter(message) do - {:ok, message} + @impl true + def filter(activity) do + {:ok, activity} end defp match_string?(string, pattern) when is_binary(pattern) do @@ -214,7 +214,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do ) end - @impl Pleroma.Web.ActivityPub.MRF.Policy + @impl true def describe do mrf_emoji = Pleroma.Config.get(:mrf_emoji, []) @@ -226,7 +226,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do {:ok, %{mrf_emoji: mrf_emoji}} end - @impl Pleroma.Web.ActivityPub.MRF.Policy + @impl true def config_description do %{ key: :mrf_emoji, @@ -239,7 +239,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do key: :remove_url, type: {:list, :string}, description: """ - A list of patterns which result in emoji whose URL matches being removed from the message. This will apply to statuses, emoji reactions, and user profiles. + A list of patterns which result in emoji whose URL matches being removed from the activity. This will apply to statuses, emoji reactions, and user profiles. Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. """, @@ -249,7 +249,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do key: :remove_shortcode, type: {:list, :string}, description: """ - A list of patterns which result in emoji whose shortcode matches being removed from the message. This will apply to statuses, emoji reactions, and user profiles. + A list of patterns which result in emoji whose shortcode matches being removed from the activity. This will apply to statuses, emoji reactions, and user profiles. Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. """, @@ -259,7 +259,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do key: :federated_timeline_removal_url, type: {:list, :string}, description: """ - A list of patterns which result in message with emojis whose URLs match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses. + A list of patterns which result in activity with emojis whose URLs match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses. Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. """, @@ -269,7 +269,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.EmojiPolicy do key: :federated_timeline_removal_shortcode, type: {:list, :string}, description: """ - A list of patterns which result in message with emojis whose shortcodes match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses. + A list of patterns which result in activities with emojis whose shortcodes match being removed from federated timelines (a.k.a unlisted). This will apply only to statuses. Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. """, diff --git a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex index a148cc1e7f..f5983c8a70 100644 --- a/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex +++ b/lib/pleroma/web/activity_pub/mrf/ensure_re_prepended.ex @@ -29,19 +29,19 @@ defmodule Pleroma.Web.ActivityPub.MRF.EnsureRePrepended do def filter_by_summary(_in_reply_to, child), do: child - def filter(%{"type" => type, "object" => child_object} = object) - when type in ["Create", "Update"] and is_map(child_object) do + def filter(%{"type" => type, "object" => object} = activity) + when type in ["Create", "Update"] and is_map(object) do child = - child_object["inReplyTo"] + object["inReplyTo"] |> Object.normalize(fetch: false) - |> filter_by_summary(child_object) + |> filter_by_summary(object) - object = Map.put(object, "object", child) + activity = Map.put(activity, "object", child) - {:ok, object} + {:ok, activity} end - def filter(object), do: {:ok, object} + def filter(activity), do: {:ok, activity} def describe, do: {:ok, %{}} end diff --git a/lib/pleroma/web/activity_pub/mrf/fo_direct_reply.ex b/lib/pleroma/web/activity_pub/mrf/fo_direct_reply.ex new file mode 100644 index 0000000000..2cf22745a2 --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/fo_direct_reply.ex @@ -0,0 +1,53 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2024 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.FODirectReply do + @moduledoc """ + FODirectReply alters the scope of replies to activities which are Followers Only to be Direct. The purpose of this policy is to prevent broken threads for followers of the reply author because their response was to a user that they are not also following. + """ + + alias Pleroma.Object + alias Pleroma.User + alias Pleroma.Web.ActivityPub.Visibility + + @behaviour Pleroma.Web.ActivityPub.MRF.Policy + + @impl true + def filter( + %{ + "type" => "Create", + "to" => to, + "object" => %{ + "actor" => actor, + "type" => "Note", + "inReplyTo" => in_reply_to + } + } = activity + ) do + with true <- is_binary(in_reply_to), + %User{follower_address: followers_collection, local: true} <- User.get_by_ap_id(actor), + %Object{} = in_reply_to_object <- Object.get_by_ap_id(in_reply_to), + "private" <- Visibility.get_visibility(in_reply_to_object) do + direct_to = to -- [followers_collection] + + updated_activity = + activity + |> Map.put("cc", []) + |> Map.put("to", direct_to) + |> Map.put("directMessage", true) + |> put_in(["object", "cc"], []) + |> put_in(["object", "to"], direct_to) + + {:ok, updated_activity} + else + _ -> {:ok, activity} + end + end + + @impl true + def filter(activity), do: {:ok, activity} + + @impl true + def describe, do: {:ok, %{}} +end diff --git a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex index 55ea2683c3..480a03ef6e 100644 --- a/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/follow_bot_policy.ex @@ -11,12 +11,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do require Logger @impl true - def filter(message) do + def filter(activity) do with follower_nickname <- Config.get([:mrf_follow_bot, :follower_nickname]), %User{actor_type: "Service"} = follower <- User.get_cached_by_nickname(follower_nickname), - %{"type" => "Create", "object" => %{"type" => "Note"}} <- message do - try_follow(follower, message) + %{"type" => "Create", "object" => %{"type" => "Note"}} <- activity do + try_follow(follower, activity) else nil -> Logger.warning( @@ -24,17 +24,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do account does not exist, or the account is not correctly configured as a bot." ) - {:ok, message} + {:ok, activity} _ -> - {:ok, message} + {:ok, activity} end end - defp try_follow(follower, message) do - to = Map.get(message, "to", []) - cc = Map.get(message, "cc", []) - actor = [message["actor"]] + defp try_follow(follower, activity) do + to = Map.get(activity, "to", []) + cc = Map.get(activity, "cc", []) + actor = [activity["actor"]] Enum.concat([to, cc, actor]) |> List.flatten() @@ -53,7 +53,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.FollowBotPolicy do end end) - {:ok, message} + {:ok, activity} end @impl true diff --git a/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex index 8cec8eabe9..3b3251dc3f 100644 --- a/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/force_bot_unlisted_policy.ex @@ -22,7 +22,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy do "cc" => cc, "actor" => actor, "object" => object - } = message + } = activity ) do user = User.get_cached_by_ap_id(actor) isbot = check_if_bot(user) @@ -36,20 +36,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy do |> Map.put("to", to) |> Map.put("cc", cc) - message = - message + activity = + activity |> Map.put("to", to) |> Map.put("cc", cc) |> Map.put("object", object) - {:ok, message} + {:ok, activity} else - {:ok, message} + {:ok, activity} end end @impl true - def filter(message), do: {:ok, message} + def filter(activity), do: {:ok, activity} @impl true def describe, do: {:ok, %{}} diff --git a/lib/pleroma/web/activity_pub/mrf/force_mention.ex b/lib/pleroma/web/activity_pub/mrf/force_mention.ex index 3853489fcf..4ea23540da 100644 --- a/lib/pleroma/web/activity_pub/mrf/force_mention.ex +++ b/lib/pleroma/web/activity_pub/mrf/force_mention.ex @@ -52,7 +52,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMention do end @impl true - def filter(object), do: {:ok, object} + def filter(activity), do: {:ok, activity} @impl true def describe, do: {:ok, %{}} diff --git a/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex index 5532093cb7..caae365e5f 100644 --- a/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex +++ b/lib/pleroma/web/activity_pub/mrf/force_mentions_in_content.ex @@ -79,18 +79,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do %{ "type" => type, "object" => %{"type" => "Note", "to" => to, "inReplyTo" => in_reply_to} - } = object + } = activity ) when type in ["Create", "Update"] and is_list(to) and is_binary(in_reply_to) do # image-only posts from pleroma apparently reach this MRF without the content field - content = object["object"]["content"] || "" + content = activity["object"]["content"] || "" # Get the replied-to user for sorting - replied_to_user = get_replied_to_user(object["object"]) + replied_to_user = get_replied_to_user(activity["object"]) mention_users = to - |> clean_recipients(object) + |> clean_recipients(activity) |> Enum.map(&User.get_cached_by_ap_id/1) |> Enum.reject(&is_nil/1) |> sort_replied_user(replied_to_user) @@ -126,11 +126,11 @@ defmodule Pleroma.Web.ActivityPub.MRF.ForceMentionsInContent do content end - {:ok, put_in(object["object"]["content"], content)} + {:ok, put_in(activity["object"]["content"], content)} end @impl true - def filter(object), do: {:ok, object} + def filter(activity), do: {:ok, activity} @impl true def describe, do: {:ok, %{}} diff --git a/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex b/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex index fdb9a9dba8..72f2274ed2 100644 --- a/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/hashtag_policy.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do alias Pleroma.Object @moduledoc """ - Reject, TWKN-remove or Set-Sensitive messages with specific hashtags (without the leading #) + Reject, TWKN-remove or Set-Sensitive activities with specific hashtags (without the leading #) Note: This MRF Policy is always enabled, if you want to disable it you have to set empty lists. """ @@ -19,40 +19,40 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do @impl true def history_awareness, do: :manual - defp check_reject(message, hashtags) do + defp check_reject(activity, hashtags) do if Enum.any?(Config.get([:mrf_hashtag, :reject]), fn match -> match in hashtags end) do {:reject, "[HashtagPolicy] Matches with rejected keyword"} else - {:ok, message} + {:ok, activity} end end - defp check_ftl_removal(%{"to" => to} = message, hashtags) do + defp check_ftl_removal(%{"to" => to} = activity, hashtags) do if Pleroma.Constants.as_public() in to and Enum.any?(Config.get([:mrf_hashtag, :federated_timeline_removal]), fn match -> match in hashtags end) do to = List.delete(to, Pleroma.Constants.as_public()) - cc = [Pleroma.Constants.as_public() | message["cc"] || []] + cc = [Pleroma.Constants.as_public() | activity["cc"] || []] - message = - message + activity = + activity |> Map.put("to", to) |> Map.put("cc", cc) |> Kernel.put_in(["object", "to"], to) |> Kernel.put_in(["object", "cc"], cc) - {:ok, message} + {:ok, activity} else - {:ok, message} + {:ok, activity} end end - defp check_ftl_removal(message, _hashtags), do: {:ok, message} + defp check_ftl_removal(activity, _hashtags), do: {:ok, activity} - defp check_sensitive(message) do + defp check_sensitive(activity) do {:ok, new_object} = - Object.Updater.do_with_history(message["object"], fn object -> + Object.Updater.do_with_history(activity["object"], fn object -> hashtags = Object.hashtags(%Object{data: object}) if Enum.any?(Config.get([:mrf_hashtag, :sensitive]), fn match -> match in hashtags end) do @@ -62,11 +62,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do end end) - {:ok, Map.put(message, "object", new_object)} + {:ok, Map.put(activity, "object", new_object)} end @impl true - def filter(%{"type" => type, "object" => object} = message) when type in ["Create", "Update"] do + def filter(%{"type" => type, "object" => object} = activity) + when type in ["Create", "Update"] do history_items = with %{"formerRepresentations" => %{"orderedItems" => items}} <- object do items @@ -82,23 +83,23 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do hashtags = Object.hashtags(%Object{data: object}) ++ historical_hashtags if hashtags != [] do - with {:ok, message} <- check_reject(message, hashtags), - {:ok, message} <- + with {:ok, activity} <- check_reject(activity, hashtags), + {:ok, activity} <- (if type == "Create" do - check_ftl_removal(message, hashtags) + check_ftl_removal(activity, hashtags) else - {:ok, message} + {:ok, activity} end), - {:ok, message} <- check_sensitive(message) do - {:ok, message} + {:ok, activity} <- check_sensitive(activity) do + {:ok, activity} end else - {:ok, message} + {:ok, activity} end end @impl true - def filter(message), do: {:ok, message} + def filter(activity), do: {:ok, activity} @impl true def describe do @@ -120,21 +121,21 @@ defmodule Pleroma.Web.ActivityPub.MRF.HashtagPolicy do %{ key: :reject, type: {:list, :string}, - description: "A list of hashtags which result in message being rejected.", + description: "A list of hashtags which result in the activity being rejected.", suggestions: ["foo"] }, %{ key: :federated_timeline_removal, type: {:list, :string}, description: - "A list of hashtags which result in message being removed from federated timelines (a.k.a unlisted).", + "A list of hashtags which result in the activity being removed from federated timelines (a.k.a unlisted).", suggestions: ["foo"] }, %{ key: :sensitive, type: {:list, :string}, description: - "A list of hashtags which result in message being set as sensitive (a.k.a NSFW/R-18)", + "A list of hashtags which result in the activity being set as sensitive (a.k.a NSFW/R-18)", suggestions: ["nsfw", "r18"] } ] diff --git a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex index 80e235d6ec..3a80d0a699 100644 --- a/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/hellthread_policy.ex @@ -7,54 +7,54 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do require Pleroma.Constants - @moduledoc "Block messages with too much mentions (configurable)" + @moduledoc "Block activities with too much mentions (configurable)" @behaviour Pleroma.Web.ActivityPub.MRF.Policy - defp delist_message(message, threshold) when threshold > 0 do - follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address - to = message["to"] || [] - cc = message["cc"] || [] + defp delist_activity(activity, threshold) when threshold > 0 do + follower_collection = User.get_cached_by_ap_id(activity["actor"]).follower_address + to = activity["to"] || [] + cc = activity["cc"] || [] follower_collection? = Enum.member?(to ++ cc, follower_collection) - message = - case get_recipient_count(message) do + activity = + case get_recipient_count(activity) do {:public, recipients} when follower_collection? and recipients > threshold -> - message + activity |> Map.put("to", [follower_collection]) |> Map.put("cc", [Pleroma.Constants.as_public()]) {:public, recipients} when recipients > threshold -> - message + activity |> Map.put("to", []) |> Map.put("cc", [Pleroma.Constants.as_public()]) _ -> - message + activity end - {:ok, message} + {:ok, activity} end - defp delist_message(message, _threshold), do: {:ok, message} + defp delist_activity(activity, _threshold), do: {:ok, activity} - defp reject_message(message, threshold) when threshold > 0 do - with {_, recipients} <- get_recipient_count(message) do + defp reject_activity(activity, threshold) when threshold > 0 do + with {_, recipients} <- get_recipient_count(activity) do if recipients > threshold do {:reject, "[HellthreadPolicy] #{recipients} recipients is over the limit of #{threshold}"} else - {:ok, message} + {:ok, activity} end end end - defp reject_message(message, _threshold), do: {:ok, message} + defp reject_activity(activity, _threshold), do: {:ok, activity} - defp get_recipient_count(message) do - recipients = (message["to"] || []) ++ (message["cc"] || []) - follower_collection = User.get_cached_by_ap_id(message["actor"]).follower_address + defp get_recipient_count(activity) do + recipients = (activity["to"] || []) ++ (activity["cc"] || []) + follower_collection = User.get_cached_by_ap_id(activity["actor"]).follower_address if Enum.member?(recipients, Pleroma.Constants.as_public()) do recipients = @@ -73,7 +73,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do end @impl true - def filter(%{"type" => "Create", "object" => %{"type" => object_type}} = message) + def filter(%{"type" => "Create", "object" => %{"type" => object_type}} = activity) when object_type in ~w{Note Article} do reject_threshold = Pleroma.Config.get( @@ -83,16 +83,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do delist_threshold = Pleroma.Config.get([:mrf_hellthread, :delist_threshold]) - with {:ok, message} <- reject_message(message, reject_threshold), - {:ok, message} <- delist_message(message, delist_threshold) do - {:ok, message} + with {:ok, activity} <- reject_activity(activity, reject_threshold), + {:ok, activity} <- delist_activity(activity, delist_threshold) do + {:ok, activity} else e -> e end end @impl true - def filter(message), do: {:ok, message} + def filter(activity), do: {:ok, activity} @impl true def describe, @@ -104,13 +104,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do key: :mrf_hellthread, related_policy: "Pleroma.Web.ActivityPub.MRF.HellthreadPolicy", label: "MRF Hellthread", - description: "Block messages with excessive user mentions", + description: "Block activities with excessive user mentions", children: [ %{ key: :delist_threshold, type: :integer, description: - "Number of mentioned users after which the message gets removed from timelines and" <> + "Number of mentioned users after which the activity gets removed from timelines and" <> "disables notifications. Set to 0 to disable.", suggestions: [10] }, @@ -118,7 +118,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.HellthreadPolicy do key: :reject_threshold, type: :integer, description: - "Number of mentioned users after which the messaged gets rejected. Set to 0 to disable.", + "Number of mentioned users after which the activity gets rejected. Set to 0 to disable.", suggestions: [20] } ] diff --git a/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex index b7a01c27c2..469d06ef60 100644 --- a/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/inline_quote_policy.ex @@ -48,12 +48,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy do end @impl true - def filter(object), do: {:ok, object} + def filter(activity), do: {:ok, activity} @impl true def describe, do: {:ok, %{}} - @impl Pleroma.Web.ActivityPub.MRF.Policy + @impl true def history_awareness, do: :auto @impl true diff --git a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex index 729da4e9c9..6ba6fd5096 100644 --- a/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/keyword_policy.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do alias Pleroma.Web.ActivityPub.MRF.Utils - @moduledoc "Reject or Word-Replace messages with a keyword or regex" + @moduledoc "Reject or Word-Replace activities with a keyword or regex" @behaviour Pleroma.Web.ActivityPub.MRF.Policy @@ -25,7 +25,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do |> Enum.join("\n") end - defp check_reject(%{"object" => %{} = object} = message) do + defp check_reject(%{"object" => %{} = object} = activity) do with {:ok, _new_object} <- Pleroma.Object.Updater.do_with_history(object, fn object -> payload = object_payload(object) @@ -35,16 +35,16 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do end) do {:reject, "[KeywordPolicy] Matches with rejected keyword"} else - {:ok, message} + {:ok, activity} end end) do - {:ok, message} + {:ok, activity} else e -> e end end - defp check_ftl_removal(%{"type" => "Create", "to" => to, "object" => %{} = object} = message) do + defp check_ftl_removal(%{"type" => "Create", "to" => to, "object" => %{} = object} = activity) do check_keyword = fn object -> payload = object_payload(object) @@ -67,24 +67,24 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do if Pleroma.Constants.as_public() in to and should_delist?.(object) do to = List.delete(to, Pleroma.Constants.as_public()) - cc = [Pleroma.Constants.as_public() | message["cc"] || []] + cc = [Pleroma.Constants.as_public() | activity["cc"] || []] - message = - message + activity = + activity |> Map.put("to", to) |> Map.put("cc", cc) - {:ok, message} + {:ok, activity} else - {:ok, message} + {:ok, activity} end end - defp check_ftl_removal(message) do - {:ok, message} + defp check_ftl_removal(activity) do + {:ok, activity} end - defp check_replace(%{"object" => %{} = object} = message) do + defp check_replace(%{"object" => %{} = object} = activity) do replace_kw = fn object -> ["content", "name", "summary"] |> Enum.filter(fn field -> Map.has_key?(object, field) && object[field] end) @@ -103,18 +103,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do {:ok, object} = Pleroma.Object.Updater.do_with_history(object, replace_kw) - message = Map.put(message, "object", object) + activity = Map.put(activity, "object", object) - {:ok, message} + {:ok, activity} end @impl true - def filter(%{"type" => type, "object" => %{"content" => _content}} = message) + def filter(%{"type" => type, "object" => %{"content" => _content}} = activity) when type in ["Create", "Update"] do - with {:ok, message} <- check_reject(message), - {:ok, message} <- check_ftl_removal(message), - {:ok, message} <- check_replace(message) do - {:ok, message} + with {:ok, activity} <- check_reject(activity), + {:ok, activity} <- check_ftl_removal(activity), + {:ok, activity} <- check_replace(activity) do + {:ok, activity} else {:reject, nil} -> {:reject, "[KeywordPolicy] "} {:reject, _} = e -> e @@ -123,7 +123,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do end @impl true - def filter(message), do: {:ok, message} + def filter(activity), do: {:ok, activity} @impl true def describe do @@ -154,13 +154,13 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do related_policy: "Pleroma.Web.ActivityPub.MRF.KeywordPolicy", label: "MRF Keyword", description: - "Reject or Word-Replace messages matching a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html).", + "Reject or Word-Replace activities matching a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html).", children: [ %{ key: :reject, type: {:list, :string}, description: """ - A list of patterns which result in message being rejected. + A list of patterns which result in the activity being rejected. Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. """, @@ -170,7 +170,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.KeywordPolicy do key: :federated_timeline_removal, type: {:list, :string}, description: """ - A list of patterns which result in message being removed from federated timelines (a.k.a unlisted). + A list of patterns which result in the activity being removed from federated timelines (a.k.a unlisted). Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`. """, diff --git a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex index 0c5b53def0..b0d07a6f86 100644 --- a/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/media_proxy_warming_policy.ex @@ -31,7 +31,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do HTTP.get(url, [], http_client_opts) end - defp preload(%{"object" => %{"attachment" => attachments}} = _message) do + defp preload(%{"object" => %{"attachment" => attachments}} = _activity) do Enum.each(attachments, fn %{"url" => url} when is_list(url) -> url @@ -49,15 +49,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy do end @impl true - def filter(%{"type" => type, "object" => %{"attachment" => attachments} = _object} = message) + def filter(%{"type" => type, "object" => %{"attachment" => attachments} = _object} = activity) when type in ["Create", "Update"] and is_list(attachments) and length(attachments) > 0 do - preload(message) + preload(activity) - {:ok, message} + {:ok, activity} end @impl true - def filter(message), do: {:ok, message} + def filter(activity), do: {:ok, activity} @impl true def describe, do: {:ok, %{}} diff --git a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex index 8aa4f347f1..f7bff121fd 100644 --- a/lib/pleroma/web/activity_pub/mrf/mention_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/mention_policy.ex @@ -3,25 +3,25 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do - @moduledoc "Block messages which mention a user" + @moduledoc "Block activities which mention a user" @behaviour Pleroma.Web.ActivityPub.MRF.Policy @impl true - def filter(%{"type" => "Create"} = message) do + def filter(%{"type" => "Create"} = activity) do reject_actors = Pleroma.Config.get([:mrf_mention, :actors], []) - recipients = (message["to"] || []) ++ (message["cc"] || []) + recipients = (activity["to"] || []) ++ (activity["cc"] || []) if rejected_mention = Enum.find(recipients, fn recipient -> Enum.member?(reject_actors, recipient) end) do {:reject, "[MentionPolicy] Rejected for mention of #{rejected_mention}"} else - {:ok, message} + {:ok, activity} end end @impl true - def filter(message), do: {:ok, message} + def filter(activity), do: {:ok, activity} @impl true def describe, do: {:ok, %{}} @@ -32,7 +32,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.MentionPolicy do key: :mrf_mention, related_policy: "Pleroma.Web.ActivityPub.MRF.MentionPolicy", label: "MRF Mention", - description: "Block messages which mention a specific user", + description: "Block activities which mention a specific user", children: [ %{ key: :actors, diff --git a/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex index 12bf4ddd27..08dd39878d 100644 --- a/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/no_empty_policy.ex @@ -9,20 +9,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy do alias Pleroma.Web.Endpoint @impl true - def filter(%{"actor" => actor} = object) do + def filter(%{"actor" => actor} = activity) do with true <- local?(actor), - true <- eligible_type?(object), - true <- note?(object), - false <- has_attachment?(object), - true <- only_mentions?(object) do + true <- eligible_type?(activity), + true <- note?(activity), + false <- has_attachment?(activity), + true <- only_mentions?(activity) do {:reject, "[NoEmptyPolicy]"} else _ -> - {:ok, object} + {:ok, activity} end end - def filter(object), do: {:ok, object} + def filter(activity), do: {:ok, activity} defp local?(actor) do if actor |> String.starts_with?("#{Endpoint.url()}") do diff --git a/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex index 8840c4fac4..64a5872bc1 100644 --- a/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/no_op_policy.ex @@ -7,8 +7,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoOpPolicy do @behaviour Pleroma.Web.ActivityPub.MRF.Policy @impl true - def filter(object) do - {:ok, object} + def filter(activity) do + {:ok, activity} end @impl true diff --git a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex index f81e9e52a0..c6f239a5ea 100644 --- a/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/no_placeholder_text_policy.ex @@ -13,15 +13,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy do def filter( %{ "type" => type, - "object" => %{"content" => content, "attachment" => _} = _child_object - } = object + "object" => %{"content" => content, "attachment" => _} = _object + } = activity ) when type in ["Create", "Update"] and content in [".", "

.

"] do - {:ok, put_in(object, ["object", "content"], "")} + {:ok, put_in(activity, ["object", "content"], "")} end @impl true - def filter(object), do: {:ok, object} + def filter(activity), do: {:ok, activity} @impl true def describe, do: {:ok, %{}} diff --git a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex index 2dfc9a901c..91855ef845 100644 --- a/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex +++ b/lib/pleroma/web/activity_pub/mrf/normalize_markup.ex @@ -12,20 +12,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do def history_awareness, do: :auto @impl true - def filter(%{"type" => type, "object" => child_object} = object) + def filter(%{"type" => type, "object" => object} = activity) when type in ["Create", "Update"] do scrub_policy = Pleroma.Config.get([:mrf_normalize_markup, :scrub_policy]) content = - child_object["content"] + object["content"] |> HTML.filter_tags(scrub_policy) - object = put_in(object, ["object", "content"], content) + activity = put_in(activity, ["object", "content"], content) - {:ok, object} + {:ok, activity} end - def filter(object), do: {:ok, object} + def filter(activity), do: {:ok, activity} @impl true def describe, do: {:ok, %{}} diff --git a/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex b/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex index 451a212d44..52aaf05aaa 100644 --- a/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/nsfw_api_policy.ex @@ -122,52 +122,52 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do end end - def check_object_nsfw(%{"object" => %{} = child_object} = object) do - case check_object_nsfw(child_object) do - {:sfw, _} -> {:sfw, object} - {:nsfw, _} -> {:nsfw, object} + def check_object_nsfw(%{"object" => %{} = object} = activity) do + case check_object_nsfw(object) do + {:sfw, _} -> {:sfw, activity} + {:nsfw, _} -> {:nsfw, activity} end end def check_object_nsfw(object), do: {:sfw, object} @impl true - def filter(object) do - with {:sfw, object} <- check_object_nsfw(object) do - {:ok, object} + def filter(activity) do + with {:sfw, activity} <- check_object_nsfw(activity) do + {:ok, activity} else - {:nsfw, _data} -> handle_nsfw(object) + {:nsfw, _data} -> handle_nsfw(activity) end end - defp handle_nsfw(object) do + defp handle_nsfw(activity) do if Config.get([@policy, :reject]) do - {:reject, object} + {:reject, activity} else {:ok, - object + activity |> maybe_unlist() |> maybe_mark_sensitive()} end end - defp maybe_unlist(object) do + defp maybe_unlist(activity) do if Config.get([@policy, :unlist]) do - unlist(object) + unlist(activity) else - object + activity end end - defp maybe_mark_sensitive(object) do + defp maybe_mark_sensitive(activity) do if Config.get([@policy, :mark_sensitive]) do - mark_sensitive(object) + mark_sensitive(activity) else - object + activity end end - def unlist(%{"to" => to, "cc" => cc, "actor" => actor} = object) do + def unlist(%{"to" => to, "cc" => cc, "actor" => actor} = activity) do with %User{} = user <- User.get_cached_by_ap_id(actor) do to = [user.follower_address | to] @@ -179,7 +179,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do |> List.delete(user.follower_address) |> Enum.uniq() - object + activity |> Map.put("to", to) |> Map.put("cc", cc) else @@ -187,14 +187,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.NsfwApiPolicy do end end - def mark_sensitive(%{"object" => child_object} = object) when is_map(child_object) do - Map.put(object, "object", mark_sensitive(child_object)) + def mark_sensitive(%{"object" => object} = activity) when is_map(object) do + Map.put(activity, "object", mark_sensitive(object)) end - def mark_sensitive(object) when is_map(object) do - tags = (object["tag"] || []) ++ ["nsfw"] + def mark_sensitive(activity) when is_map(activity) do + tags = (activity["tag"] || []) ++ ["nsfw"] - object + activity |> Map.put("tag", tags) |> Map.put("sensitive", true) end diff --git a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex index df1a6dcbbc..34905fc216 100644 --- a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex @@ -11,12 +11,12 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do @moduledoc "Filter activities depending on their age" @behaviour Pleroma.Web.ActivityPub.MRF.Policy - defp check_date(%{"object" => %{"published" => published}} = message) do + defp check_date(%{"object" => %{"published" => published}} = activity) do with %DateTime{} = now <- DateTime.utc_now(), {:ok, %DateTime{} = then, _} <- DateTime.from_iso8601(published), max_ttl <- Config.get([:mrf_object_age, :threshold]), {:ttl, false} <- {:ttl, DateTime.diff(now, then) > max_ttl} do - {:ok, message} + {:ok, activity} else {:ttl, true} -> {:reject, nil} @@ -26,73 +26,73 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do end end - defp check_reject(message, actions) do + defp check_reject(activity, actions) do if :reject in actions do {:reject, "[ObjectAgePolicy]"} else - {:ok, message} + {:ok, activity} end end - defp check_delist(message, actions) do + defp check_delist(activity, actions) do if :delist in actions do - with %User{} = user <- User.get_cached_by_ap_id(message["actor"]) do + with %User{} = user <- User.get_cached_by_ap_id(activity["actor"]) do to = - List.delete(message["to"] || [], Pleroma.Constants.as_public()) ++ + List.delete(activity["to"] || [], Pleroma.Constants.as_public()) ++ [user.follower_address] cc = - List.delete(message["cc"] || [], user.follower_address) ++ + List.delete(activity["cc"] || [], user.follower_address) ++ [Pleroma.Constants.as_public()] - message = - message + activity = + activity |> Map.put("to", to) |> Map.put("cc", cc) |> Kernel.put_in(["object", "to"], to) |> Kernel.put_in(["object", "cc"], cc) - {:ok, message} + {:ok, activity} else _e -> {:reject, "[ObjectAgePolicy] Unhandled error"} end else - {:ok, message} + {:ok, activity} end end - defp check_strip_followers(message, actions) do + defp check_strip_followers(activity, actions) do if :strip_followers in actions do - with %User{} = user <- User.get_cached_by_ap_id(message["actor"]) do - to = List.delete(message["to"] || [], user.follower_address) - cc = List.delete(message["cc"] || [], user.follower_address) + with %User{} = user <- User.get_cached_by_ap_id(activity["actor"]) do + to = List.delete(activity["to"] || [], user.follower_address) + cc = List.delete(activity["cc"] || [], user.follower_address) - message = - message + activity = + activity |> Map.put("to", to) |> Map.put("cc", cc) |> Kernel.put_in(["object", "to"], to) |> Kernel.put_in(["object", "cc"], cc) - {:ok, message} + {:ok, activity} else _e -> {:reject, "[ObjectAgePolicy] Unhandled error"} end else - {:ok, message} + {:ok, activity} end end @impl true - def filter(%{"type" => "Create", "object" => %{"published" => _}} = message) do + def filter(%{"type" => "Create", "object" => %{"published" => _}} = activity) do with actions <- Config.get([:mrf_object_age, :actions]), - {:reject, _} <- check_date(message), - {:ok, message} <- check_reject(message, actions), - {:ok, message} <- check_delist(message, actions), - {:ok, message} <- check_strip_followers(message, actions) do - {:ok, message} + {:reject, _} <- check_date(activity), + {:ok, activity} <- check_reject(activity, actions), + {:ok, activity} <- check_delist(activity, actions), + {:ok, activity} <- check_strip_followers(activity, actions) do + {:ok, activity} else # check_date() is allowed to short-circuit the pipeline e -> e @@ -100,7 +100,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do end @impl true - def filter(message), do: {:ok, message} + def filter(activity), do: {:ok, activity} @impl true def describe do @@ -131,8 +131,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy do type: {:list, :atom}, description: "A list of actions to apply to the post. `:delist` removes the post from public timelines; " <> - "`:strip_followers` removes followers from the ActivityPub recipient list ensuring they won't be delivered to home timelines, additionally for followers-only it degrades to a direct message; " <> - "`:reject` rejects the message entirely", + "`:strip_followers` removes followers from the ActivityPub recipient list ensuring they won't be delivered to home timelines, additionally for followers-only it degrades to a direct activity; " <> + "`:reject` rejects the activity entirely", suggestions: [:delist, :strip_followers, :reject] } ] diff --git a/lib/pleroma/web/activity_pub/mrf/policy.ex b/lib/pleroma/web/activity_pub/mrf/policy.ex index 1f34883e70..54ca4b7357 100644 --- a/lib/pleroma/web/activity_pub/mrf/policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/policy.ex @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.Policy do - @callback filter(map()) :: {:ok | :reject, map()} + @callback filter(Pleroma.Activity.t()) :: {:ok | :reject, Pleroma.Activity.t()} @callback describe() :: {:ok | :error, map()} @callback config_description() :: %{ optional(:children) => [map()], diff --git a/lib/pleroma/web/activity_pub/mrf/quiet_reply.ex b/lib/pleroma/web/activity_pub/mrf/quiet_reply.ex new file mode 100644 index 0000000000..b07dc3b562 --- /dev/null +++ b/lib/pleroma/web/activity_pub/mrf/quiet_reply.ex @@ -0,0 +1,60 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2023 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.QuietReply do + @moduledoc """ + QuietReply alters the scope of activities from local users when replying by enforcing them to be "Unlisted" or "Quiet Public". This delivers the activity to all the expected recipients and instances, but it will not be published in the Federated / The Whole Known Network timelines. It will still be published to the Home timelines of the user's followers and visible to anyone who opens the thread. + """ + require Pleroma.Constants + + alias Pleroma.User + + @behaviour Pleroma.Web.ActivityPub.MRF.Policy + + @impl true + def history_awareness, do: :auto + + @impl true + def filter( + %{ + "type" => "Create", + "to" => to, + "cc" => cc, + "object" => %{ + "actor" => actor, + "type" => "Note", + "inReplyTo" => in_reply_to + } + } = activity + ) do + with true <- is_binary(in_reply_to), + false <- match?([], cc), + %User{follower_address: followers_collection, local: true} <- + User.get_by_ap_id(actor) do + updated_to = + to + |> Kernel.++([followers_collection]) + |> Kernel.--([Pleroma.Constants.as_public()]) + + updated_cc = [Pleroma.Constants.as_public()] + + updated_activity = + activity + |> Map.put("to", updated_to) + |> Map.put("cc", updated_cc) + |> put_in(["object", "to"], updated_to) + |> put_in(["object", "cc"], updated_cc) + + {:ok, updated_activity} + else + _ -> {:ok, activity} + end + end + + @impl true + def filter(activity), do: {:ok, activity} + + @impl true + def describe, do: {:ok, %{}} +end diff --git a/lib/pleroma/web/activity_pub/mrf/quote_to_link_tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/quote_to_link_tag_policy.ex index ac353f03f9..2a17b67618 100644 --- a/lib/pleroma/web/activity_pub/mrf/quote_to_link_tag_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/quote_to_link_tag_policy.ex @@ -10,18 +10,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.QuoteToLinkTagPolicy do require Pleroma.Constants - @impl Pleroma.Web.ActivityPub.MRF.Policy + @impl true def filter(%{"object" => %{"quoteUrl" => _} = object} = activity) do {:ok, Map.put(activity, "object", filter_object(object))} end - @impl Pleroma.Web.ActivityPub.MRF.Policy - def filter(object), do: {:ok, object} + @impl true + def filter(activity), do: {:ok, activity} - @impl Pleroma.Web.ActivityPub.MRF.Policy + @impl true def describe, do: {:ok, %{}} - @impl Pleroma.Web.ActivityPub.MRF.Policy + @impl true def history_awareness, do: :auto defp filter_object(%{"quoteUrl" => quote_url} = object) do diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index d708c99ebd..ae7f18bfe5 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -13,20 +13,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do require Pleroma.Constants - defp check_accept(%{host: actor_host} = _actor_info, object) do + defp check_accept(%{host: actor_host} = _actor_info, activity) do accepts = instance_list(:accept) |> MRF.subdomains_regex() cond do - accepts == [] -> {:ok, object} - actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object} - MRF.subdomain_match?(accepts, actor_host) -> {:ok, object} + accepts == [] -> {:ok, activity} + actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, activity} + MRF.subdomain_match?(accepts, actor_host) -> {:ok, activity} true -> {:reject, "[SimplePolicy] host not in accept list"} end end - defp check_reject(%{host: actor_host} = _actor_info, object) do + defp check_reject(%{host: actor_host} = _actor_info, activity) do rejects = instance_list(:reject) |> MRF.subdomains_regex() @@ -34,109 +34,109 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do if MRF.subdomain_match?(rejects, actor_host) do {:reject, "[SimplePolicy] host in reject list"} else - {:ok, object} + {:ok, activity} end end defp check_media_removal( %{host: actor_host} = _actor_info, - %{"type" => type, "object" => %{"attachment" => child_attachment}} = object + %{"type" => type, "object" => %{"attachment" => object_attachment}} = activity ) - when length(child_attachment) > 0 and type in ["Create", "Update"] do + when length(object_attachment) > 0 and type in ["Create", "Update"] do media_removal = instance_list(:media_removal) |> MRF.subdomains_regex() - object = + activity = if MRF.subdomain_match?(media_removal, actor_host) do - child_object = Map.delete(object["object"], "attachment") - Map.put(object, "object", child_object) + object = Map.delete(activity["object"], "attachment") + Map.put(activity, "object", object) else - object + activity end - {:ok, object} + {:ok, activity} end - defp check_media_removal(_actor_info, object), do: {:ok, object} + defp check_media_removal(_actor_info, activity), do: {:ok, activity} defp check_media_nsfw( %{host: actor_host} = _actor_info, %{ "type" => type, - "object" => %{} = _child_object - } = object + "object" => %{} = _object + } = activity ) when type in ["Create", "Update"] do media_nsfw = instance_list(:media_nsfw) |> MRF.subdomains_regex() - object = + activity = if MRF.subdomain_match?(media_nsfw, actor_host) do - Kernel.put_in(object, ["object", "sensitive"], true) + Kernel.put_in(activity, ["object", "sensitive"], true) else - object + activity end - {:ok, object} + {:ok, activity} end - defp check_media_nsfw(_actor_info, object), do: {:ok, object} + defp check_media_nsfw(_actor_info, activity), do: {:ok, activity} - defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do + defp check_ftl_removal(%{host: actor_host} = _actor_info, activity) do timeline_removal = instance_list(:federated_timeline_removal) |> MRF.subdomains_regex() - object = + activity = with true <- MRF.subdomain_match?(timeline_removal, actor_host), - user <- User.get_cached_by_ap_id(object["actor"]), - true <- Pleroma.Constants.as_public() in object["to"] do - to = List.delete(object["to"], Pleroma.Constants.as_public()) ++ [user.follower_address] + user <- User.get_cached_by_ap_id(activity["actor"]), + true <- Pleroma.Constants.as_public() in activity["to"] do + to = List.delete(activity["to"], Pleroma.Constants.as_public()) ++ [user.follower_address] - cc = List.delete(object["cc"], user.follower_address) ++ [Pleroma.Constants.as_public()] + cc = List.delete(activity["cc"], user.follower_address) ++ [Pleroma.Constants.as_public()] - object + activity |> Map.put("to", to) |> Map.put("cc", cc) else - _ -> object + _ -> activity end - {:ok, object} + {:ok, activity} end defp intersection(list1, list2) do list1 -- list1 -- list2 end - defp check_followers_only(%{host: actor_host} = _actor_info, object) do + defp check_followers_only(%{host: actor_host} = _actor_info, activity) do followers_only = instance_list(:followers_only) |> MRF.subdomains_regex() - object = + activity = with true <- MRF.subdomain_match?(followers_only, actor_host), - user <- User.get_cached_by_ap_id(object["actor"]) do + user <- User.get_cached_by_ap_id(activity["actor"]) do # Don't use Map.get/3 intentionally, these must not be nil - fixed_to = object["to"] || [] - fixed_cc = object["cc"] || [] + fixed_to = activity["to"] || [] + fixed_cc = activity["cc"] || [] to = FollowingRelationship.followers_ap_ids(user, fixed_to) cc = FollowingRelationship.followers_ap_ids(user, fixed_cc) - object + activity |> Map.put("to", intersection([user.follower_address | to], fixed_to)) |> Map.put("cc", intersection([user.follower_address | cc], fixed_cc)) else - _ -> object + _ -> activity end - {:ok, object} + {:ok, activity} end - defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do + defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = activity) do report_removal = instance_list(:report_removal) |> MRF.subdomains_regex() @@ -144,39 +144,39 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do if MRF.subdomain_match?(report_removal, actor_host) do {:reject, "[SimplePolicy] host in report_removal list"} else - {:ok, object} + {:ok, activity} end end - defp check_report_removal(_actor_info, object), do: {:ok, object} + defp check_report_removal(_actor_info, activity), do: {:ok, activity} - defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do + defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = activity) do avatar_removal = instance_list(:avatar_removal) |> MRF.subdomains_regex() if MRF.subdomain_match?(avatar_removal, actor_host) do - {:ok, Map.delete(object, "icon")} + {:ok, Map.delete(activity, "icon")} else - {:ok, object} + {:ok, activity} end end - defp check_avatar_removal(_actor_info, object), do: {:ok, object} + defp check_avatar_removal(_actor_info, activity), do: {:ok, activity} - defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do + defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = activity) do banner_removal = instance_list(:banner_removal) |> MRF.subdomains_regex() if MRF.subdomain_match?(banner_removal, actor_host) do - {:ok, Map.delete(object, "image")} + {:ok, Map.delete(activity, "image")} else - {:ok, object} + {:ok, activity} end end - defp check_banner_removal(_actor_info, object), do: {:ok, object} + defp check_banner_removal(_actor_info, activity), do: {:ok, activity} defp check_object(%{"object" => object} = activity) do with {:ok, _object} <- filter(object) do @@ -184,7 +184,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do end end - defp check_object(object), do: {:ok, object} + defp check_object(activity), do: {:ok, activity} defp instance_list(config_key) do Config.get([:mrf_simple, config_key]) @@ -192,7 +192,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do end @impl true - def filter(%{"type" => "Delete", "actor" => actor} = object) do + def filter(%{"type" => "Delete", "actor" => actor} = activity) do %{host: actor_host} = URI.parse(actor) reject_deletes = @@ -202,54 +202,54 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do if MRF.subdomain_match?(reject_deletes, actor_host) do {:reject, "[SimplePolicy] host in reject_deletes list"} else - {:ok, object} + {:ok, activity} end end @impl true - def filter(%{"actor" => actor} = object) do + def filter(%{"actor" => actor} = activity) do actor_info = URI.parse(actor) - with {:ok, object} <- check_accept(actor_info, object), - {:ok, object} <- check_reject(actor_info, object), - {:ok, object} <- check_media_removal(actor_info, object), - {:ok, object} <- check_media_nsfw(actor_info, object), - {:ok, object} <- check_ftl_removal(actor_info, object), - {:ok, object} <- check_followers_only(actor_info, object), - {:ok, object} <- check_report_removal(actor_info, object), - {:ok, object} <- check_object(object) do - {:ok, object} + with {:ok, activity} <- check_accept(actor_info, activity), + {:ok, activity} <- check_reject(actor_info, activity), + {:ok, activity} <- check_media_removal(actor_info, activity), + {:ok, activity} <- check_media_nsfw(actor_info, activity), + {:ok, activity} <- check_ftl_removal(actor_info, activity), + {:ok, activity} <- check_followers_only(actor_info, activity), + {:ok, activity} <- check_report_removal(actor_info, activity), + {:ok, activity} <- check_object(activity) do + {:ok, activity} else {:reject, _} = e -> e end end - def filter(%{"id" => actor, "type" => obj_type} = object) - when obj_type in ["Application", "Group", "Organization", "Person", "Service"] do + def filter(%{"id" => actor, "type" => actor_type} = activity) + when actor_type in ["Application", "Group", "Organization", "Person", "Service"] do actor_info = URI.parse(actor) - with {:ok, object} <- check_accept(actor_info, object), - {:ok, object} <- check_reject(actor_info, object), - {:ok, object} <- check_avatar_removal(actor_info, object), - {:ok, object} <- check_banner_removal(actor_info, object) do - {:ok, object} + with {:ok, activity} <- check_accept(actor_info, activity), + {:ok, activity} <- check_reject(actor_info, activity), + {:ok, activity} <- check_avatar_removal(actor_info, activity), + {:ok, activity} <- check_banner_removal(actor_info, activity) do + {:ok, activity} else {:reject, _} = e -> e end end - def filter(object) when is_binary(object) do - uri = URI.parse(object) + def filter(activity) when is_binary(activity) do + uri = URI.parse(activity) - with {:ok, object} <- check_accept(uri, object), - {:ok, object} <- check_reject(uri, object) do - {:ok, object} + with {:ok, activity} <- check_accept(uri, activity), + {:ok, activity} <- check_reject(uri, activity) do + {:ok, activity} else {:reject, _} = e -> e end end - def filter(object), do: {:ok, object} + def filter(activity), do: {:ok, activity} @impl true def describe do diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex index fa6b595ea9..6edfb124e4 100644 --- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex @@ -62,7 +62,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do end @impl true - def filter(%{"object" => %{"emoji" => foreign_emojis, "actor" => actor}} = message) do + def filter(%{"object" => %{"emoji" => foreign_emojis, "actor" => actor}} = activity) do host = URI.parse(actor).host if host != Pleroma.Web.Endpoint.host() and accept_host?(host) do @@ -97,10 +97,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do end end - {:ok, message} + {:ok, activity} end - def filter(message), do: {:ok, message} + def filter(activity), do: {:ok, activity} @impl true @spec config_description :: %{ diff --git a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex index fdb9e51765..97acca7e87 100644 --- a/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/subchain_policy.ex @@ -20,20 +20,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do end @impl true - def filter(%{"actor" => actor} = message) do + def filter(%{"actor" => actor} = activity) do with {:ok, match, subchain} <- lookup_subchain(actor) do Logger.debug( "[SubchainPolicy] Matched #{actor} against #{inspect(match)} with subchain #{inspect(subchain)}" ) - MRF.filter(subchain, message) + MRF.filter(subchain, activity) else - _e -> {:ok, message} + _e -> {:ok, activity} end end @impl true - def filter(message), do: {:ok, message} + def filter(activity), do: {:ok, activity} @impl true def describe, do: {:ok, %{}} @@ -45,7 +45,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SubchainPolicy do related_policy: "Pleroma.Web.ActivityPub.MRF.SubchainPolicy", label: "MRF Subchain", description: - "This policy processes messages through an alternate pipeline when a given message matches certain criteria." <> + "This policy processes activities through an alternate pipeline when a given activity matches certain criteria." <> " All criteria are configured as a map of regular expressions to lists of policy modules.", children: [ %{ diff --git a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex index 73760ca8f2..c236a5a99e 100644 --- a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex @@ -28,25 +28,25 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do "mrf_tag:media-force-nsfw", %{ "type" => type, - "object" => %{"attachment" => child_attachment} - } = message + "object" => %{"attachment" => object_attachment} + } = activity ) - when length(child_attachment) > 0 and type in ["Create", "Update"] do - {:ok, Kernel.put_in(message, ["object", "sensitive"], true)} + when length(object_attachment) > 0 and type in ["Create", "Update"] do + {:ok, Kernel.put_in(activity, ["object", "sensitive"], true)} end defp process_tag( "mrf_tag:media-strip", %{ "type" => type, - "object" => %{"attachment" => child_attachment} = object - } = message + "object" => %{"attachment" => object_attachment} = object + } = activity ) - when length(child_attachment) > 0 and type in ["Create", "Update"] do + when length(object_attachment) > 0 and type in ["Create", "Update"] do object = Map.delete(object, "attachment") - message = Map.put(message, "object", object) + activity = Map.put(activity, "object", object) - {:ok, message} + {:ok, activity} end defp process_tag( @@ -57,7 +57,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do "cc" => cc, "actor" => actor, "object" => object - } = message + } = activity ) do user = User.get_cached_by_ap_id(actor) @@ -70,15 +70,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do |> Map.put("to", to) |> Map.put("cc", cc) - message = - message + activity = + activity |> Map.put("to", to) |> Map.put("cc", cc) |> Map.put("object", object) - {:ok, message} + {:ok, activity} else - {:ok, message} + {:ok, activity} end end @@ -90,7 +90,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do "cc" => cc, "actor" => actor, "object" => object - } = message + } = activity ) do user = User.get_cached_by_ap_id(actor) @@ -104,26 +104,26 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do |> Map.put("to", to) |> Map.put("cc", cc) - message = - message + activity = + activity |> Map.put("to", to) |> Map.put("cc", cc) |> Map.put("object", object) - {:ok, message} + {:ok, activity} else - {:ok, message} + {:ok, activity} end end defp process_tag( "mrf_tag:disable-remote-subscription", - %{"type" => "Follow", "actor" => actor} = message + %{"type" => "Follow", "actor" => actor} = activity ) do user = User.get_cached_by_ap_id(actor) if user.local == true do - {:ok, message} + {:ok, activity} else {:reject, "[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-remote-subscription"} @@ -133,14 +133,14 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do defp process_tag("mrf_tag:disable-any-subscription", %{"type" => "Follow", "actor" => actor}), do: {:reject, "[TagPolicy] Follow from #{actor} tagged with mrf_tag:disable-any-subscription"} - defp process_tag(_, message), do: {:ok, message} + defp process_tag(_, activity), do: {:ok, activity} - def filter_message(actor, message) do + def filter_activity(actor, activity) do User.get_cached_by_ap_id(actor) |> get_tags() - |> Enum.reduce({:ok, message}, fn - tag, {:ok, message} -> - process_tag(tag, message) + |> Enum.reduce({:ok, activity}, fn + tag, {:ok, activity} -> + process_tag(tag, activity) _, error -> error @@ -148,15 +148,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do end @impl true - def filter(%{"object" => target_actor, "type" => "Follow"} = message), - do: filter_message(target_actor, message) + def filter(%{"object" => target_actor, "type" => "Follow"} = activity), + do: filter_activity(target_actor, activity) @impl true - def filter(%{"actor" => actor, "type" => type} = message) when type in ["Create", "Update"], - do: filter_message(actor, message) + def filter(%{"actor" => actor, "type" => type} = activity) when type in ["Create", "Update"], + do: filter_activity(actor, activity) @impl true - def filter(message), do: {:ok, message} + def filter(activity), do: {:ok, activity} @impl true def describe, do: {:ok, %{}} diff --git a/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex index e14047d4e3..10cc0e09d0 100644 --- a/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex @@ -8,18 +8,18 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do @moduledoc "Accept-list of users from specified instances" @behaviour Pleroma.Web.ActivityPub.MRF.Policy - defp filter_by_list(object, []), do: {:ok, object} + defp filter_by_list(activity, []), do: {:ok, activity} - defp filter_by_list(%{"actor" => actor} = object, allow_list) do + defp filter_by_list(%{"actor" => actor} = activity, allow_list) do if actor in allow_list do - {:ok, object} + {:ok, activity} else {:reject, "[UserAllowListPolicy] #{actor} not in the list"} end end @impl true - def filter(%{"actor" => actor} = object) do + def filter(%{"actor" => actor} = activity) do actor_info = URI.parse(actor) allow_list = @@ -28,10 +28,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy do [] ) - filter_by_list(object, allow_list) + filter_by_list(activity, allow_list) end - def filter(object), do: {:ok, object} + def filter(activity), do: {:ok, activity} @impl true def describe do diff --git a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex index 1c114558e6..5671e4cf32 100644 --- a/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/vocabulary_policy.ex @@ -3,38 +3,38 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do - @moduledoc "Filter messages which belong to certain activity vocabularies" + @moduledoc "Filter activities which belong to certain activity vocabularies" @behaviour Pleroma.Web.ActivityPub.MRF.Policy @impl true - def filter(%{"type" => "Undo", "object" => child_message} = message) do - with {:ok, _} <- filter(child_message) do - {:ok, message} + def filter(%{"type" => "Undo", "object" => object} = activity) do + with {:ok, _} <- filter(object) do + {:ok, activity} else {:reject, _} = e -> e end end - def filter(%{"type" => message_type} = message) do + def filter(%{"type" => activity_type} = activity) do with accepted_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :accept]), rejected_vocabulary <- Pleroma.Config.get([:mrf_vocabulary, :reject]), {_, true} <- {:accepted, - Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, message_type)}, + Enum.empty?(accepted_vocabulary) || Enum.member?(accepted_vocabulary, activity_type)}, {_, false} <- {:rejected, - length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, message_type)}, - {:ok, _} <- filter(message["object"]) do - {:ok, message} + length(rejected_vocabulary) > 0 && Enum.member?(rejected_vocabulary, activity_type)}, + {:ok, _} <- filter(activity["object"]) do + {:ok, activity} else {:reject, _} = e -> e - {:accepted, _} -> {:reject, "[VocabularyPolicy] #{message_type} not in accept list"} - {:rejected, _} -> {:reject, "[VocabularyPolicy] #{message_type} in reject list"} + {:accepted, _} -> {:reject, "[VocabularyPolicy] #{activity_type} not in accept list"} + {:rejected, _} -> {:reject, "[VocabularyPolicy] #{activity_type} in reject list"} end end - def filter(message), do: {:ok, message} + def filter(activity), do: {:ok, activity} @impl true def describe, @@ -46,20 +46,20 @@ defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do key: :mrf_vocabulary, related_policy: "Pleroma.Web.ActivityPub.MRF.VocabularyPolicy", label: "MRF Vocabulary", - description: "Filter messages which belong to certain activity vocabularies", + description: "Filter activities which belong to certain activity vocabularies", children: [ %{ key: :accept, type: {:list, :string}, description: - "A list of ActivityStreams terms to accept. If empty, all supported messages are accepted.", + "A list of ActivityStreams terms to accept. If empty, all supported activities are accepted.", suggestions: ["Create", "Follow", "Mention", "Announce", "Like"] }, %{ key: :reject, type: {:list, :string}, description: - "A list of ActivityStreams terms to reject. If empty, no messages are rejected.", + "A list of ActivityStreams terms to reject. If empty, no activities are rejected.", suggestions: ["Create", "Follow", "Mention", "Announce", "Like"] } ] diff --git a/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex b/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex index d611da0516..03ab83347f 100644 --- a/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/accept_reject_validator.ex @@ -29,7 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AcceptRejectValidator do defp validate_data(cng) do cng - |> validate_required([:id, :type, :actor, :to, :cc, :object]) + |> validate_required([:id, :type, :actor, :to, :object]) |> validate_inclusion(:type, ["Accept", "Reject"]) |> validate_actor_presence() |> validate_object_presence(allowed_types: ["Follow"]) diff --git a/lib/pleroma/web/activity_pub/object_validators/block_validator.ex b/lib/pleroma/web/activity_pub/object_validators/block_validator.ex index 0de87a27ef..98340545cc 100644 --- a/lib/pleroma/web/activity_pub/object_validators/block_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/block_validator.ex @@ -29,7 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator do defp validate_data(cng) do cng - |> validate_required([:id, :type, :actor, :to, :cc, :object]) + |> validate_required([:id, :type, :actor, :to, :object]) |> validate_inclusion(:type, ["Block"]) |> CommonValidations.validate_actor_presence() |> CommonValidations.validate_actor_presence(field_name: :object) diff --git a/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex b/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex index b3ca5b6910..e4e97bf72a 100644 --- a/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/follow_validator.ex @@ -29,7 +29,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.FollowValidator do defp validate_data(cng) do cng - |> validate_required([:id, :type, :actor, :to, :cc, :object]) + |> validate_required([:id, :type, :actor, :to, :object]) |> validate_inclusion(:type, ["Follow"]) |> validate_inclusion(:state, ~w{pending reject accept}) |> validate_actor_presence() diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index e63b8ff1f6..0de3a0d434 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -11,6 +11,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User + alias Pleroma.Web.ActivityPub.Publisher.Prepared alias Pleroma.Web.ActivityPub.Relay alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Workers.PublisherWorker @@ -30,11 +31,11 @@ defmodule Pleroma.Web.ActivityPub.Publisher do """ @spec enqueue_one(map(), Keyword.t()) :: {:ok, %Oban.Job{}} def enqueue_one(%{} = params, worker_args \\ []) do - PublisherWorker.enqueue( - "publish_one", - %{"params" => params}, + PublisherWorker.new( + %{"op" => "publish_one", "params" => params}, worker_args ) + |> Oban.insert() end @doc """ @@ -76,14 +77,13 @@ defmodule Pleroma.Web.ActivityPub.Publisher do end @doc """ - Publish a single message to a peer. Takes a struct with the following - parameters set: - + Prepare an activity for publishing from an Oban job * `inbox`: the inbox to publish to * `activity_id`: the internal activity id * `cc`: the cc recipients relevant to this inbox (optional) """ - def publish_one(%{inbox: inbox, activity_id: activity_id} = params) do + @spec prepare_one(map()) :: Prepared.t() + def prepare_one(%{inbox: inbox, activity_id: activity_id} = params) do activity = Activity.get_by_id_with_user_actor(activity_id) actor = activity.user_actor @@ -93,7 +93,7 @@ defmodule Pleroma.Web.ActivityPub.Publisher do {:ok, data} = Transmogrifier.prepare_outgoing(activity.data) - cc = Map.get(params, :cc) + cc = Map.get(params, :cc, []) json = data @@ -113,27 +113,54 @@ defmodule Pleroma.Web.ActivityPub.Publisher do date: date }) + %Prepared{ + activity_id: activity_id, + json: json, + date: date, + signature: signature, + digest: digest, + inbox: inbox, + unreachable_since: params[:unreachable_since] + } + end + + @doc """ + Publish a single message to a peer. Takes a struct with the following + parameters set: + * `activity_id`: the activity id + * `json`: the json payload + * `date`: the signed date from Pleroma.Signature.signed_date() + * `signature`: the signature from Pleroma.Signature.sign/2 + * `digest`: base64 encoded the hash of the json payload prefixed with "SHA-256=" + * `inbox`: the inbox URI of this delivery + * `unreachable_since`: timestamp the instance was marked unreachable + + """ + def publish_one(%Prepared{} = p) do with {:ok, %{status: code}} = result when code in 200..299 <- HTTP.post( - inbox, - json, + p.inbox, + p.json, [ {"Content-Type", "application/activity+json"}, - {"Date", date}, - {"signature", signature}, - {"digest", digest} + {"Date", p.date}, + {"signature", p.signature}, + {"digest", p.digest} ] ) do - if not Map.has_key?(params, :unreachable_since) || params[:unreachable_since] do - Instances.set_reachable(inbox) + if not is_nil(p.unreachable_since) do + Instances.set_reachable(p.inbox) end result else {_post_result, %{status: code} = response} = e -> - unless params[:unreachable_since], do: Instances.set_unreachable(inbox) - Logger.metadata(activity: activity_id, inbox: inbox, status: code) - Logger.error("Publisher failed to inbox #{inbox} with status #{code}") + 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}") case response do %{status: 400} -> {:cancel, :bad_request} @@ -152,9 +179,12 @@ defmodule Pleroma.Web.ActivityPub.Publisher do connection_pool_snooze() e -> - unless params[:unreachable_since], do: Instances.set_unreachable(inbox) - Logger.metadata(activity: activity_id, inbox: inbox) - Logger.error("Publisher failed to inbox #{inbox} #{inspect(e)}") + 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} end end diff --git a/lib/pleroma/web/activity_pub/publisher/prepared.ex b/lib/pleroma/web/activity_pub/publisher/prepared.ex new file mode 100644 index 0000000000..ddd8167e1c --- /dev/null +++ b/lib/pleroma/web/activity_pub/publisher/prepared.ex @@ -0,0 +1,8 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.Publisher.Prepared do + @type t :: %__MODULE__{} + defstruct [:activity_id, :json, :date, :signature, :digest, :inbox, :unreachable_since] +end diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index cc1c7a0afb..d6d4036719 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -223,10 +223,12 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do if Pleroma.Web.Federator.allowed_thread_distance?(reply_depth) and object.data["replies"] != nil do for reply_id <- object.data["replies"] do - Pleroma.Workers.RemoteFetcherWorker.enqueue("fetch_remote", %{ + Pleroma.Workers.RemoteFetcherWorker.new(%{ + "op" => "fetch_remote", "id" => reply_id, "depth" => reply_depth }) + |> Oban.insert() end end @@ -410,10 +412,12 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do {:ok, expires_at} = Pleroma.EctoType.ActivityPub.ObjectValidators.DateTime.cast(meta[:expires_at]) - Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ - activity_id: meta[:activity_id], - expires_at: expires_at - }) + Pleroma.Workers.PurgeExpiredActivity.enqueue( + %{ + activity_id: meta[:activity_id] + }, + scheduled_at: expires_at + ) end {:ok, object, meta} diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 85f02166f0..d9614bc481 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -498,22 +498,6 @@ defmodule Pleroma.Web.ApiSpec.AccountOperation do } end - def identity_proofs_operation do - %Operation{ - tags: ["Retrieve account information"], - summary: "Identity proofs", - operationId: "AccountController.identity_proofs", - # Validators complains about unused path params otherwise - parameters: [ - %Reference{"$ref": "#/components/parameters/accountIdOrNickname"} - ], - description: "Not implemented", - responses: %{ - 200 => empty_array_response() - } - } - end - def familiar_followers_operation do %Operation{ tags: ["Retrieve account information"], diff --git a/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex index 7340653fba..b8b37d7cf3 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_account_operation.ex @@ -85,9 +85,11 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do def subscribe_operation do %Operation{ + deprecated: true, tags: ["Account actions"], summary: "Subscribe", - description: "Receive notifications for all statuses posted by the account.", + description: + "Receive notifications for all statuses posted by the account. Deprecated, use `notify: true` in follow operation instead.", operationId: "PleromaAPI.AccountController.subscribe", parameters: [id_param()], security: [%{"oAuth" => ["follow", "write:follows"]}], @@ -100,9 +102,11 @@ defmodule Pleroma.Web.ApiSpec.PleromaAccountOperation do def unsubscribe_operation do %Operation{ + deprecated: true, tags: ["Account actions"], summary: "Unsubscribe", - description: "Stop receiving notifications for all statuses posted by the account.", + description: + "Stop receiving notifications for all statuses posted by the account. Deprecated, use `notify: false` in follow operation instead.", operationId: "PleromaAPI.AccountController.unsubscribe", parameters: [id_param()], security: [%{"oAuth" => ["follow", "write:follows"]}], diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex index 1717c68c8d..ef828feeef 100644 --- a/lib/pleroma/web/api_spec/operations/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -31,11 +31,17 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do security: [%{"oAuth" => ["read:statuses"]}], parameters: [ Operation.parameter( - :ids, + :id, :query, %Schema{type: :array, items: FlakeID}, "Array of status IDs" ), + Operation.parameter( + :ids, + :query, + %Schema{type: :array, items: FlakeID}, + "Deprecated, use `id` instead" + ), Operation.parameter( :with_muted, :query, diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index b90b6a6d90..921e414c34 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -130,7 +130,7 @@ defmodule Pleroma.Web.CommonAPI do if activity.data["state"] == "reject" do {:error, :rejected} else - {:ok, follower, followed, activity} + {:ok, followed, follower, activity} end end end @@ -559,11 +559,11 @@ defmodule Pleroma.Web.CommonAPI do with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]), _ <- Pleroma.Notification.mark_context_as_read(user, activity.data["context"]) do if expires_in > 0 do - Pleroma.Workers.MuteExpireWorker.enqueue( - "unmute_conversation", - %{"user_id" => user.id, "activity_id" => activity.id}, + Pleroma.Workers.MuteExpireWorker.new( + %{"op" => "unmute_conversation", "user_id" => user.id, "activity_id" => activity.id}, schedule_in: expires_in ) + |> Oban.insert() end {:ok, activity} diff --git a/lib/pleroma/web/federator.ex b/lib/pleroma/web/federator.ex index 3d3101d61e..58260afa88 100644 --- a/lib/pleroma/web/federator.ex +++ b/lib/pleroma/web/federator.ex @@ -35,22 +35,30 @@ defmodule Pleroma.Web.Federator do end # Client API - def incoming_ap_doc(%{params: _params, req_headers: _req_headers} = args) do - job_args = Enum.into(args, %{}, fn {k, v} -> {Atom.to_string(k), v} end) - - ReceiverWorker.enqueue( - "incoming_ap_doc", - Map.put(job_args, "timeout", :timer.seconds(20)), + def incoming_ap_doc(%{params: params, req_headers: req_headers}) do + ReceiverWorker.new( + %{ + "op" => "incoming_ap_doc", + "req_headers" => req_headers, + "params" => params, + "timeout" => :timer.seconds(20) + }, priority: 2 ) + |> Oban.insert() end def incoming_ap_doc(%{"type" => "Delete"} = params) do - ReceiverWorker.enqueue("incoming_ap_doc", %{"params" => params}, priority: 3, queue: :slow) + ReceiverWorker.new(%{"op" => "incoming_ap_doc", "params" => params}, + priority: 3, + queue: :slow + ) + |> Oban.insert() end def incoming_ap_doc(params) do - ReceiverWorker.enqueue("incoming_ap_doc", %{"params" => params}) + ReceiverWorker.new(%{"op" => "incoming_ap_doc", "params" => params}) + |> Oban.insert() end @impl true @@ -60,9 +68,10 @@ defmodule Pleroma.Web.Federator do @impl true def publish(%Pleroma.Activity{data: %{"type" => type}} = activity) do - PublisherWorker.enqueue("publish", %{"activity_id" => activity.id}, + PublisherWorker.new(%{"op" => "publish", "activity_id" => activity.id}, priority: publish_priority(type) ) + |> Oban.insert() end defp publish_priority("Delete"), do: 3 @@ -71,7 +80,10 @@ defmodule Pleroma.Web.Federator do # Job Worker Callbacks @spec perform(atom(), any()) :: {:ok, any()} | {:error, any()} - def perform(:publish_one, params), do: Publisher.publish_one(params) + def perform(:publish_one, params) do + Publisher.prepare_one(params) + |> Publisher.publish_one() + end def perform(:publish, activity) do Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end) @@ -90,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)}, @@ -109,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/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 80ab95a572..54d46c86b8 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -22,7 +22,6 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do alias Pleroma.Web.CommonAPI alias Pleroma.Web.MastodonAPI.ListView alias Pleroma.Web.MastodonAPI.MastodonAPI - alias Pleroma.Web.MastodonAPI.MastodonAPIController alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.OAuth.OAuthController alias Pleroma.Web.Plugs.OAuthScopesPlug @@ -51,7 +50,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do plug( OAuthScopesPlug, %{scopes: ["read:accounts"]} - when action in [:verify_credentials, :endorsements, :identity_proofs] + when action in [:verify_credentials, :endorsements] ) plug( @@ -660,7 +659,4 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do defp get_familiar_followers(user, current_user) do User.get_familiar_followers(user, current_user) end - - @doc "GET /api/v1/identity_proofs" - def identity_proofs(conn, params), do: MastodonAPIController.empty_array(conn, params) end diff --git a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex index 4ad30f330f..42b2a201d8 100644 --- a/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/marker_controller.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.MastodonAPI.MarkerController do use Pleroma.Web, :controller + alias Pleroma.Web.Plugs.OAuthScopesPlug plug(Pleroma.Web.ApiSpec.CastAndValidate) @@ -30,9 +31,16 @@ defmodule Pleroma.Web.MastodonAPI.MarkerController do def upsert(%{assigns: %{user: user}, body_params: params} = conn, _) do params = Map.new(params, fn {key, value} -> {to_string(key), value} end) - with {:ok, result} <- Pleroma.Marker.upsert(user, params), + with {:ok, _} <- mark_notifications_read(user, params), + {:ok, result} <- Pleroma.Marker.upsert(user, params), markers <- Map.values(result) do render(conn, "markers.json", %{markers: markers}) end end + + defp mark_notifications_read(user, %{"notifications" => %{last_read_id: last_read_id}}) do + Pleroma.Notification.set_read_up_to(user, last_read_id) + end + + defp mark_notifications_read(_, _), do: {:ok, :noop} end diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index b9b2369207..d5aef5ad29 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -111,10 +111,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do `ids` query param is required """ def index( - %{assigns: %{user: user}, private: %{open_api_spex: %{params: %{ids: ids} = params}}} = + %{assigns: %{user: user}, private: %{open_api_spex: %{params: params}}} = conn, _ ) do + ids = Map.get(params, :id, Map.get(params, :ids)) limit = 100 activities = diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex index 6dcbfb097e..c9e045d238 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex @@ -18,10 +18,10 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do if not User.following?(follower, followed) do CommonAPI.follow(followed, follower) else - {:ok, follower, followed, nil} + {:ok, followed, follower, nil} end - with {:ok, follower, _followed, _} <- result do + with {:ok, _followed, follower, _} <- result do options = cast_params(params) set_reblogs_visibility(options[:reblogs], result) set_subscription(options[:notify], result) @@ -29,19 +29,19 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do end end - defp set_reblogs_visibility(false, {:ok, follower, followed, _}) do + defp set_reblogs_visibility(false, {:ok, followed, follower, _}) do CommonAPI.hide_reblogs(followed, follower) end - defp set_reblogs_visibility(_, {:ok, follower, followed, _}) do + defp set_reblogs_visibility(_, {:ok, followed, follower, _}) do CommonAPI.show_reblogs(followed, follower) end - defp set_subscription(true, {:ok, follower, followed, _}) do + defp set_subscription(true, {:ok, followed, follower, _}) do User.subscribe(follower, followed) end - defp set_subscription(false, {:ok, follower, followed, _}) do + defp set_subscription(false, {:ok, followed, follower, _}) do User.unsubscribe(follower, followed) end diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 747638c53a..1b78477d0c 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -803,19 +803,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 diff --git a/lib/pleroma/web/o_auth/token.ex b/lib/pleroma/web/o_auth/token.ex index 9b1198b428..d964250944 100644 --- a/lib/pleroma/web/o_auth/token.ex +++ b/lib/pleroma/web/o_auth/token.ex @@ -100,11 +100,10 @@ defmodule Pleroma.Web.OAuth.Token do def create(%App{} = app, %User{} = user, attrs \\ %{}) do with {:ok, token} <- do_create(app, user, attrs) do if Pleroma.Config.get([:oauth2, :clean_expired_tokens]) do - Pleroma.Workers.PurgeExpiredToken.enqueue(%{ - token_id: token.id, - valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), - mod: __MODULE__ - }) + Pleroma.Workers.PurgeExpiredToken.new(%{token_id: token.id, mod: __MODULE__}, + scheduled_at: DateTime.from_naive!(token.valid_until, "Etc/UTC") + ) + |> Oban.insert() end {:ok, token} 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 96466f1921..d65c30dab5 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/inbox_guard_plug.ex b/lib/pleroma/web/plugs/inbox_guard_plug.ex new file mode 100644 index 0000000000..0064cce766 --- /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 +# 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/push.ex b/lib/pleroma/web/push.ex index d4693f63e4..6d777142e9 100644 --- a/lib/pleroma/web/push.ex +++ b/lib/pleroma/web/push.ex @@ -28,6 +28,7 @@ defmodule Pleroma.Web.Push do @spec send(Pleroma.Notification.t()) :: {:ok, Oban.Job.t()} | {:error, Oban.Job.changeset() | term()} def send(notification) do - WebPusherWorker.enqueue("web_push", %{"notification_id" => notification.id}) + WebPusherWorker.new(%{"op" => "web_push", "notification_id" => notification.id}) + |> Oban.insert() end end diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index e2889b3516..d4be979578 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 fc40a11432..9b9ee421cf 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -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 @@ -648,7 +652,6 @@ defmodule Pleroma.Web.Router do get("/accounts/relationships", AccountController, :relationships) get("/accounts/familiar_followers", AccountController, :familiar_followers) get("/accounts/:id/lists", AccountController, :lists) - get("/accounts/:id/identity_proofs", AccountController, :identity_proofs) get("/endorsements", AccountController, :endorsements) get("/blocks", AccountController, :blocks) get("/mutes", AccountController, :mutes) @@ -921,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/web/views/streamer_view.ex b/lib/pleroma/web/views/streamer_view.ex index f97570b0a2..079a373516 100644 --- a/lib/pleroma/web/views/streamer_view.ex +++ b/lib/pleroma/web/views/streamer_view.ex @@ -109,7 +109,25 @@ defmodule Pleroma.Web.StreamerView do |> Jason.encode!() end - def render("follow_relationships_update.json", item, topic) do + def render( + "follow_relationships_update.json", + %{follower: follower, following: following} = item, + topic + ) do + following_follower_count = + if Enum.any?([following.hide_followers_count, following.hide_followers]) do + 0 + else + following.follower_count + end + + following_following_count = + if Enum.any?([following.hide_follows_count, following.hide_follows]) do + 0 + else + following.following_count + end + %{ stream: render("stream.json", %{topic: topic}), event: "pleroma:follow_relationships_update", @@ -117,14 +135,14 @@ defmodule Pleroma.Web.StreamerView do %{ state: item.state, follower: %{ - id: item.follower.id, - follower_count: item.follower.follower_count, - following_count: item.follower.following_count + id: follower.id, + follower_count: follower.follower_count, + following_count: follower.following_count }, following: %{ - id: item.following.id, - follower_count: item.following.follower_count, - following_count: item.following.following_count + id: following.id, + follower_count: following_follower_count, + following_count: following_following_count } } |> Jason.encode!() diff --git a/lib/pleroma/workers/attachments_cleanup_worker.ex b/lib/pleroma/workers/attachments_cleanup_worker.ex index 0b570b70b0..e2f92b1fd0 100644 --- a/lib/pleroma/workers/attachments_cleanup_worker.ex +++ b/lib/pleroma/workers/attachments_cleanup_worker.ex @@ -8,9 +8,9 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do alias Pleroma.Object alias Pleroma.Repo - use Pleroma.Workers.WorkerHelper, queue: "slow" + use Oban.Worker, queue: :slow - @impl Oban.Worker + @impl true def perform(%Job{ args: %{ "op" => "cleanup_attachments", @@ -31,7 +31,7 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do def perform(%Job{args: %{"op" => "cleanup_attachments", "object" => _object}}), do: {:ok, :skip} - @impl Oban.Worker + @impl true def timeout(_job), do: :timer.seconds(900) defp do_clean({object_ids, attachment_urls}) do diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index 870aef3c6a..4737c6ea24 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -5,9 +5,9 @@ defmodule Pleroma.Workers.BackgroundWorker do alias Pleroma.User - use Pleroma.Workers.WorkerHelper, queue: "background" + use Oban.Worker, queue: :background - @impl Oban.Worker + @impl true def perform(%Job{args: %{"op" => "user_activation", "user_id" => user_id, "status" => status}}) do user = User.get_cached_by_id(user_id) @@ -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{ @@ -39,6 +39,6 @@ defmodule Pleroma.Workers.BackgroundWorker do User.perform(:verify_fields_links, user) end - @impl Oban.Worker - def timeout(_job), do: :timer.seconds(15) + @impl true + def timeout(_job), do: :timer.seconds(900) end diff --git a/lib/pleroma/workers/backup_worker.ex b/lib/pleroma/workers/backup_worker.ex index d1b6fcdadf..6466d8d73e 100644 --- a/lib/pleroma/workers/backup_worker.ex +++ b/lib/pleroma/workers/backup_worker.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Workers.BackupWorker do alias Pleroma.Config.Getting, as: Config alias Pleroma.User.Backup - @impl Oban.Worker + @impl true def perform(%Job{ args: %{"op" => "process", "backup_id" => backup_id} }) do @@ -32,7 +32,7 @@ defmodule Pleroma.Workers.BackupWorker do end end - @impl Oban.Worker + @impl true def timeout(_job), do: Config.get([Backup, :timeout], :timer.minutes(30)) defp has_email?(user) do diff --git a/lib/pleroma/workers/cron/digest_emails_worker.ex b/lib/pleroma/workers/cron/digest_emails_worker.ex index 17e92d10b0..b50b52a7b0 100644 --- a/lib/pleroma/workers/cron/digest_emails_worker.ex +++ b/lib/pleroma/workers/cron/digest_emails_worker.ex @@ -18,7 +18,7 @@ defmodule Pleroma.Workers.Cron.DigestEmailsWorker do require Logger - @impl Oban.Worker + @impl true def perform(_job) do config = Config.get([:email_notifications, :digest]) @@ -59,6 +59,6 @@ defmodule Pleroma.Workers.Cron.DigestEmailsWorker do User.touch_last_digest_emailed_at(user) end - @impl Oban.Worker + @impl true def timeout(_job), do: :timer.seconds(5) end diff --git a/lib/pleroma/workers/cron/new_users_digest_worker.ex b/lib/pleroma/workers/cron/new_users_digest_worker.ex index 1f57aad4a2..7876499831 100644 --- a/lib/pleroma/workers/cron/new_users_digest_worker.ex +++ b/lib/pleroma/workers/cron/new_users_digest_worker.ex @@ -9,9 +9,9 @@ defmodule Pleroma.Workers.Cron.NewUsersDigestWorker do import Ecto.Query - use Pleroma.Workers.WorkerHelper, queue: "background" + use Oban.Worker, queue: :background - @impl Oban.Worker + @impl true def perform(_job) do if Pleroma.Config.get([Pleroma.Emails.NewUsersDigestEmail, :enabled]) do today = NaiveDateTime.utc_now() |> Timex.beginning_of_day() @@ -61,6 +61,6 @@ defmodule Pleroma.Workers.Cron.NewUsersDigestWorker do :ok end - @impl Oban.Worker + @impl true def timeout(_job), do: :timer.seconds(5) end diff --git a/lib/pleroma/workers/delete_worker.ex b/lib/pleroma/workers/delete_worker.ex index 97003fb69e..6a1c7bb383 100644 --- a/lib/pleroma/workers/delete_worker.ex +++ b/lib/pleroma/workers/delete_worker.ex @@ -6,10 +6,9 @@ defmodule Pleroma.Workers.DeleteWorker do alias Pleroma.Instances.Instance alias Pleroma.User - use Pleroma.Workers.WorkerHelper, queue: "slow" - - @impl Oban.Worker + use Oban.Worker, queue: :slow + @impl true def perform(%Job{args: %{"op" => "delete_user", "user_id" => user_id}}) do user = User.get_cached_by_id(user_id) User.perform(:delete, user) @@ -19,6 +18,6 @@ defmodule Pleroma.Workers.DeleteWorker do Instance.perform(:delete_instance, host) end - @impl Oban.Worker + @impl true def timeout(_job), do: :timer.seconds(900) end diff --git a/lib/pleroma/workers/mailer_worker.ex b/lib/pleroma/workers/mailer_worker.ex index 652bf77e01..b0259b1917 100644 --- a/lib/pleroma/workers/mailer_worker.ex +++ b/lib/pleroma/workers/mailer_worker.ex @@ -3,9 +3,9 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Workers.MailerWorker do - use Pleroma.Workers.WorkerHelper, queue: "background" + use Oban.Worker, queue: :background - @impl Oban.Worker + @impl true def perform(%Job{args: %{"op" => "email", "encoded_email" => encoded_email, "config" => config}}) do encoded_email |> Base.decode64!() @@ -13,6 +13,6 @@ defmodule Pleroma.Workers.MailerWorker do |> Pleroma.Emails.Mailer.deliver(config) end - @impl Oban.Worker + @impl true def timeout(_job), do: :timer.seconds(5) end diff --git a/lib/pleroma/workers/mute_expire_worker.ex b/lib/pleroma/workers/mute_expire_worker.ex index a7ab5883af..8356a775d7 100644 --- a/lib/pleroma/workers/mute_expire_worker.ex +++ b/lib/pleroma/workers/mute_expire_worker.ex @@ -3,9 +3,9 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Workers.MuteExpireWorker do - use Pleroma.Workers.WorkerHelper, queue: "background" + use Oban.Worker, queue: :background - @impl Oban.Worker + @impl true def perform(%Job{args: %{"op" => "unmute_user", "muter_id" => muter_id, "mutee_id" => mutee_id}}) do Pleroma.User.unmute(muter_id, mutee_id) :ok @@ -18,6 +18,6 @@ defmodule Pleroma.Workers.MuteExpireWorker do :ok end - @impl Oban.Worker + @impl true def timeout(_job), do: :timer.seconds(5) end diff --git a/lib/pleroma/workers/poll_worker.ex b/lib/pleroma/workers/poll_worker.ex index af8997e702..d263aa1b9e 100644 --- a/lib/pleroma/workers/poll_worker.ex +++ b/lib/pleroma/workers/poll_worker.ex @@ -6,13 +6,13 @@ defmodule Pleroma.Workers.PollWorker do @moduledoc """ Generates notifications when a poll ends. """ - use Pleroma.Workers.WorkerHelper, queue: "background" + use Oban.Worker, queue: :background alias Pleroma.Activity alias Pleroma.Notification alias Pleroma.Object - @impl Oban.Worker + @impl true def perform(%Job{args: %{"op" => "poll_end", "activity_id" => activity_id}}) do with %Activity{} = activity <- find_poll_activity(activity_id), {:ok, notifications} <- Notification.create_poll_notifications(activity) do @@ -23,7 +23,7 @@ defmodule Pleroma.Workers.PollWorker do end end - @impl Oban.Worker + @impl true def timeout(_job), do: :timer.seconds(5) defp find_poll_activity(activity_id) do diff --git a/lib/pleroma/workers/publisher_worker.ex b/lib/pleroma/workers/publisher_worker.ex index 63fcf4ac22..7d9b022de3 100644 --- a/lib/pleroma/workers/publisher_worker.ex +++ b/lib/pleroma/workers/publisher_worker.ex @@ -6,13 +6,9 @@ defmodule Pleroma.Workers.PublisherWorker do alias Pleroma.Activity alias Pleroma.Web.Federator - use Pleroma.Workers.WorkerHelper, queue: "federator_outgoing" + use Oban.Worker, queue: :federator_outgoing, max_attempts: 5 - def backoff(%Job{attempt: attempt}) when is_integer(attempt) do - Pleroma.Workers.WorkerHelper.sidekiq_backoff(attempt, 5) - end - - @impl Oban.Worker + @impl true def perform(%Job{args: %{"op" => "publish", "activity_id" => activity_id}}) do activity = Activity.get_by_id(activity_id) Federator.perform(:publish, activity) @@ -23,6 +19,18 @@ defmodule Pleroma.Workers.PublisherWorker do Federator.perform(:publish_one, params) end - @impl Oban.Worker + @impl true def timeout(_job), do: :timer.seconds(10) + + @base_backoff 15 + @pow 5 + @impl true + def backoff(%Job{attempt: attempt}) when is_integer(attempt) do + backoff = + :math.pow(attempt, @pow) + + @base_backoff + + :rand.uniform(2 * @base_backoff) * attempt + + trunc(backoff) + end end diff --git a/lib/pleroma/workers/purge_expired_activity.ex b/lib/pleroma/workers/purge_expired_activity.ex index f48e340420..f05e75f46a 100644 --- a/lib/pleroma/workers/purge_expired_activity.ex +++ b/lib/pleroma/workers/purge_expired_activity.ex @@ -13,16 +13,13 @@ defmodule Pleroma.Workers.PurgeExpiredActivity do alias Pleroma.Activity - @spec enqueue(map()) :: + @spec enqueue(map(), list()) :: {:ok, Oban.Job.t()} | {:error, :expired_activities_disabled} | {:error, :expiration_too_close} - def enqueue(args) do + def enqueue(params, worker_args) do with true <- enabled?() do - {scheduled_at, args} = Map.pop(args, :expires_at) - - args - |> new(scheduled_at: scheduled_at) + new(params, worker_args) |> Oban.insert() end end @@ -35,7 +32,7 @@ defmodule Pleroma.Workers.PurgeExpiredActivity do end end - @impl Oban.Worker + @impl true def timeout(_job), do: :timer.seconds(5) defp enabled? do diff --git a/lib/pleroma/workers/purge_expired_filter.ex b/lib/pleroma/workers/purge_expired_filter.ex index 1f6931e4c2..0405f66847 100644 --- a/lib/pleroma/workers/purge_expired_filter.ex +++ b/lib/pleroma/workers/purge_expired_filter.ex @@ -31,7 +31,7 @@ defmodule Pleroma.Workers.PurgeExpiredFilter do |> Repo.delete() end - @impl Oban.Worker + @impl true def timeout(_job), do: :timer.seconds(5) @spec get_expiration(pos_integer()) :: Job.t() | nil diff --git a/lib/pleroma/workers/purge_expired_token.ex b/lib/pleroma/workers/purge_expired_token.ex index 1854bf5619..ff962f21b6 100644 --- a/lib/pleroma/workers/purge_expired_token.ex +++ b/lib/pleroma/workers/purge_expired_token.ex @@ -9,16 +9,6 @@ defmodule Pleroma.Workers.PurgeExpiredToken do use Oban.Worker, queue: :background, max_attempts: 1 - @spec enqueue(%{token_id: integer(), valid_until: DateTime.t(), mod: module()}) :: - {:ok, Oban.Job.t()} | {:error, Ecto.Changeset.t()} - def enqueue(args) do - {scheduled_at, args} = Map.pop(args, :valid_until) - - args - |> __MODULE__.new(scheduled_at: scheduled_at) - |> Oban.insert() - end - @impl true def perform(%Oban.Job{args: %{"token_id" => id, "mod" => module}}) do module @@ -27,6 +17,6 @@ defmodule Pleroma.Workers.PurgeExpiredToken do |> Pleroma.Repo.delete() end - @impl Oban.Worker + @impl true def timeout(_job), do: :timer.seconds(5) end diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex index fd5c13fca2..0373ec15f8 100644 --- a/lib/pleroma/workers/receiver_worker.ex +++ b/lib/pleroma/workers/receiver_worker.ex @@ -7,9 +7,9 @@ defmodule Pleroma.Workers.ReceiverWorker do alias Pleroma.User alias Pleroma.Web.Federator - use Pleroma.Workers.WorkerHelper, queue: "federator_incoming" + use Oban.Worker, queue: :federator_incoming, max_attempts: 5 - @impl Oban.Worker + @impl true def perform(%Job{ args: %{ @@ -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 @@ -51,22 +51,34 @@ defmodule Pleroma.Workers.ReceiverWorker do end end - @impl Oban.Worker + @impl true def timeout(%_{args: %{"timeout" => timeout}}), do: timeout 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 60096e14b5..9d3f1ec539 100644 --- a/lib/pleroma/workers/remote_fetcher_worker.ex +++ b/lib/pleroma/workers/remote_fetcher_worker.ex @@ -5,31 +5,40 @@ defmodule Pleroma.Workers.RemoteFetcherWorker do alias Pleroma.Object.Fetcher - use Pleroma.Workers.WorkerHelper, queue: "background" + use Oban.Worker, queue: :background - @impl Oban.Worker + @impl true def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do case Fetcher.fetch_object_from_id(id, depth: args["depth"]) 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 end end - @impl Oban.Worker + @impl true def timeout(_job), do: :timer.seconds(15) end diff --git a/lib/pleroma/workers/rich_media_worker.ex b/lib/pleroma/workers/rich_media_worker.ex index 2ebf42d4f2..d5ba7b63ec 100644 --- a/lib/pleroma/workers/rich_media_worker.ex +++ b/lib/pleroma/workers/rich_media_worker.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Workers.RichMediaWorker do use Oban.Worker, queue: :background, max_attempts: 3, unique: [period: 300] - @impl Oban.Worker + @impl true def perform(%Job{args: %{"op" => "expire", "url" => url} = _args}) do Card.delete(url) end @@ -33,7 +33,7 @@ defmodule Pleroma.Workers.RichMediaWorker do # a slow/infinite data stream and insert a negative cache entry for the URL # We pad it by 2 seconds to be certain a slow connection is detected and we # can inject a negative cache entry for the URL - @impl Oban.Worker + @impl true def timeout(_job) do Config.get!([:rich_media, :timeout]) + :timer.seconds(2) end diff --git a/lib/pleroma/workers/scheduled_activity_worker.ex b/lib/pleroma/workers/scheduled_activity_worker.ex index ab62686f42..da386e0c32 100644 --- a/lib/pleroma/workers/scheduled_activity_worker.ex +++ b/lib/pleroma/workers/scheduled_activity_worker.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Workers.ScheduledActivityWorker do The worker to post scheduled activity. """ - use Pleroma.Workers.WorkerHelper, queue: "federator_outgoing" + use Oban.Worker, queue: :federator_outgoing, max_attempts: 5 alias Pleroma.Repo alias Pleroma.ScheduledActivity @@ -15,7 +15,7 @@ defmodule Pleroma.Workers.ScheduledActivityWorker do require Logger - @impl Oban.Worker + @impl true def perform(%Job{args: %{"activity_id" => activity_id}}) do with %ScheduledActivity{} = scheduled_activity <- find_scheduled_activity(activity_id), %User{} = user <- find_user(scheduled_activity.user_id) do @@ -37,7 +37,7 @@ defmodule Pleroma.Workers.ScheduledActivityWorker do end end - @impl Oban.Worker + @impl true def timeout(_job), do: :timer.seconds(5) defp find_scheduled_activity(id) do diff --git a/lib/pleroma/workers/search_indexing_worker.ex b/lib/pleroma/workers/search_indexing_worker.ex index 8969ae3788..001f5254d4 100644 --- a/lib/pleroma/workers/search_indexing_worker.ex +++ b/lib/pleroma/workers/search_indexing_worker.ex @@ -1,7 +1,7 @@ defmodule Pleroma.Workers.SearchIndexingWorker do - use Pleroma.Workers.WorkerHelper, queue: "search_indexing" + use Oban.Worker, queue: :search_indexing, max_attempts: 2 - @impl Oban.Worker + @impl true alias Pleroma.Config.Getting, as: Config @@ -21,6 +21,6 @@ defmodule Pleroma.Workers.SearchIndexingWorker do search_module.remove_from_index(object) end - @impl Oban.Worker + @impl true def timeout(_job), do: :timer.seconds(5) end diff --git a/lib/pleroma/workers/user_refresh_worker.ex b/lib/pleroma/workers/user_refresh_worker.ex index fb90e9c9c0..222a4a8f7e 100644 --- a/lib/pleroma/workers/user_refresh_worker.ex +++ b/lib/pleroma/workers/user_refresh_worker.ex @@ -12,6 +12,6 @@ defmodule Pleroma.Workers.UserRefreshWorker do User.fetch_by_ap_id(ap_id) end - @impl Oban.Worker + @impl true def timeout(_job), do: :timer.seconds(15) end diff --git a/lib/pleroma/workers/web_pusher_worker.ex b/lib/pleroma/workers/web_pusher_worker.ex index c549d3cd65..f4232d02af 100644 --- a/lib/pleroma/workers/web_pusher_worker.ex +++ b/lib/pleroma/workers/web_pusher_worker.ex @@ -7,9 +7,9 @@ defmodule Pleroma.Workers.WebPusherWorker do alias Pleroma.Repo alias Pleroma.Web.Push.Impl - use Pleroma.Workers.WorkerHelper, queue: "web_push" + use Oban.Worker, queue: :web_push - @impl Oban.Worker + @impl true def perform(%Job{args: %{"op" => "web_push", "notification_id" => notification_id}}) do notification = Notification @@ -20,6 +20,6 @@ defmodule Pleroma.Workers.WebPusherWorker do |> Enum.each(&Impl.deliver(&1)) end - @impl Oban.Worker + @impl true def timeout(_job), do: :timer.seconds(5) end diff --git a/lib/pleroma/workers/worker_helper.ex b/lib/pleroma/workers/worker_helper.ex deleted file mode 100644 index 1d20cbd893..0000000000 --- a/lib/pleroma/workers/worker_helper.ex +++ /dev/null @@ -1,48 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Workers.WorkerHelper do - alias Pleroma.Config - alias Pleroma.Workers.WorkerHelper - - def worker_args(queue) do - case Config.get([:workers, :retries, queue]) do - nil -> [] - max_attempts -> [max_attempts: max_attempts] - end - end - - def sidekiq_backoff(attempt, pow \\ 4, base_backoff \\ 15) do - backoff = - :math.pow(attempt, pow) + - base_backoff + - :rand.uniform(2 * base_backoff) * attempt - - trunc(backoff) - end - - defmacro __using__(opts) do - caller_module = __CALLER__.module - queue = Keyword.fetch!(opts, :queue) - - quote do - # Note: `max_attempts` is intended to be overridden in `new/2` call - use Oban.Worker, - queue: unquote(queue), - max_attempts: 1 - - alias Oban.Job - - def enqueue(op, params, worker_args \\ []) do - params = Map.merge(%{"op" => op}, params) - queue_atom = String.to_atom(unquote(queue)) - worker_args = worker_args ++ WorkerHelper.worker_args(queue_atom) - - unquote(caller_module) - |> apply(:new, [params, worker_args]) - |> Oban.insert() - end - end - end -end diff --git a/mix.exs b/mix.exs index ac7b21afe6..e989219cbc 100644 --- a/mix.exs +++ b/mix.exs @@ -144,7 +144,7 @@ defmodule Pleroma.Mixfile do {:telemetry_poller, "~> 1.0"}, {:tzdata, "~> 1.0.3"}, {:plug_cowboy, "~> 2.5"}, - {:oban, "~> 2.17.9"}, + {:oban, "~> 2.18.0"}, {:gettext, "~> 0.20"}, {:bcrypt_elixir, "~> 2.2"}, {:trailing_format_plug, "~> 0.0.7"}, @@ -158,7 +158,7 @@ defmodule Pleroma.Mixfile do {:gun, "~> 2.0.0-rc.1", override: true}, {:finch, "~> 0.15"}, {:jason, "~> 1.2"}, - {:mogrify, "~> 0.8.0"}, + {:mogrify, "~> 0.9.0", override: "true"}, {:ex_aws, "~> 2.1.6"}, {:ex_aws_s3, "~> 2.0"}, {:sweet_xml, "~> 0.7.2"}, @@ -204,6 +204,7 @@ defmodule Pleroma.Mixfile do {:bandit, "~> 1.5.2"}, {:websock_adapter, "~> 0.5.6"}, {:oban_live_dashboard, "~> 0.1.1"}, + {:multipart, "~> 0.4.0", optional: true}, ## dev & test {:phoenix_live_reload, "~> 1.3.3", only: :dev}, diff --git a/mix.lock b/mix.lock index 8e88157661..fe0c9772a1 100644 --- a/mix.lock +++ b/mix.lock @@ -65,7 +65,7 @@ "httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "inet_cidr": {:hex, :inet_cidr, "1.0.8", "d26bb7bdbdf21ae401ead2092bf2bb4bf57fe44a62f5eaa5025280720ace8a40", [:mix], [], "hexpm", "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e"}, - "jason": {:hex, :jason, "1.4.3", "d3f984eeb96fe53b85d20e0b049f03e57d075b5acda3ac8d465c969a2536c17b", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "9a90e868927f7c777689baa16d86f4d0e086d968db5c05d917ccff6d443e58a3"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "joken": {:hex, :joken, "2.6.0", "b9dd9b6d52e3e6fcb6c65e151ad38bf4bc286382b5b6f97079c47ade6b1bcc6a", [:mix], [{:jose, "~> 1.11.5", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5a95b05a71cd0b54abd35378aeb1d487a23a52c324fa7efdffc512b655b5aaa7"}, "jose": {:hex, :jose, "1.11.6", "613fda82552128aa6fb804682e3a616f4bc15565a048dabd05b1ebd5827ed965", [:mix, :rebar3], [], "hexpm", "6275cb75504f9c1e60eeacb771adfeee4905a9e182103aa59b53fed651ff9738"}, "jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"}, @@ -82,13 +82,14 @@ "mint": {:hex, :mint, "1.6.1", "065e8a5bc9bbd46a41099dfea3e0656436c5cbcb6e741c80bd2bad5cd872446f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1 or ~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4fc518dcc191d02f433393a72a7ba3f6f94b101d094cb6bf532ea54c89423780"}, "mochiweb": {:hex, :mochiweb, "2.18.0", "eb55f1db3e6e960fac4e6db4e2db9ec3602cc9f30b86cd1481d56545c3145d2e", [:rebar3], [], "hexpm"}, "mock": {:hex, :mock, "0.3.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"}, - "mogrify": {:hex, :mogrify, "0.8.0", "3506f3ca3f7b95a155f3b4ef803b5db176f5a0633723e3fe85e0d6399e3b11c8", [:mix], [], "hexpm", "2278d245f07056ea3b586e98801e933695147066fa4cf563f552c1b4f0ff8ad9"}, + "mogrify": {:hex, :mogrify, "0.9.3", "238c782f00271dace01369ad35ae2e9dd020feee3443b9299ea5ea6bed559841", [:mix], [], "hexpm", "0189b1e1de27455f2b9ae8cf88239cefd23d38de9276eb5add7159aea51731e6"}, "mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"}, + "multipart": {:hex, :multipart, "0.4.0", "634880a2148d4555d050963373d0e3bbb44a55b2badd87fa8623166172e9cda0", [:mix], [{:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}], "hexpm", "3c5604bc2fb17b3137e5d2abdf5dacc2647e60c5cc6634b102cf1aef75a06f0a"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "0.6.0", "32111b3bf39137144abd7ba1cce0914533b2d16ef35e8abc5ec8be6122944263", [:mix], [], "hexpm", "27eac315a94909d4dc68bc07a4a83e06c8379237c5ea528a9acff4ca1c873c52"}, "nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"}, "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]}, - "oban": {:hex, :oban, "2.17.12", "33fb0cbfb92b910d48dd91a908590fe3698bb85eacec8cd0d9bc6aa13dddd6d6", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7a647d6cd6bb300073db17faabce22d80ae135da3baf3180a064fa7c4fa046e3"}, + "oban": {:hex, :oban, "2.18.2", "583e78965ee15263ac968e38c983bad169ae55eadaa8e1e39912562badff93ba", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9dd25fd35883a91ed995e9fe516e479344d3a8623dfe2b8c3fc8e5be0228ec3a"}, "oban_live_dashboard": {:hex, :oban_live_dashboard, "0.1.1", "8aa4ceaf381c818f7d5c8185cc59942b8ac82ef0cf559881aacf8d3f8ac7bdd3", [:mix], [{:oban, "~> 2.15", [hex: :oban, repo: "hexpm", optional: false]}, {:phoenix_live_dashboard, "~> 0.7", [hex: :phoenix_live_dashboard, repo: "hexpm", optional: false]}], "hexpm", "16dc4ce9c9a95aa2e655e35ed4e675652994a8def61731a18af85e230e1caa63"}, "octo_fetch": {:hex, :octo_fetch, "0.4.0", "074b5ecbc08be10b05b27e9db08bc20a3060142769436242702931c418695b19", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "cf8be6f40cd519d7000bb4e84adcf661c32e59369ca2827c4e20042eda7a7fc6"}, "open_api_spex": {:hex, :open_api_spex, "3.18.2", "8c855e83bfe8bf81603d919d6e892541eafece3720f34d1700b58024dadde247", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "aa3e6dcfc0ad6a02596b2172662da21c9dd848dac145ea9e603f54e3d81b8d2b"}, diff --git a/priv/gettext/zh_Hans/LC_MESSAGES/config_descriptions.po b/priv/gettext/zh_Hans/LC_MESSAGES/config_descriptions.po index ff9ad52452..a56c907247 100644 --- a/priv/gettext/zh_Hans/LC_MESSAGES/config_descriptions.po +++ b/priv/gettext/zh_Hans/LC_MESSAGES/config_descriptions.po @@ -3,9 +3,9 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2022-07-21 04:21+0300\n" -"PO-Revision-Date: 2022-07-24 10:04+0000\n" -"Last-Translator: Yating Zhan \n" -"Language-Team: Chinese (Simplified) \n" +"Language-Team: Chinese (Simplified) \n" "Language: zh_Hans\n" "MIME-Version: 1.0\n" @@ -49,6 +49,8 @@ msgstr "Mime 类型设置" msgctxt "config description at :pleroma" msgid "Allows setting a token that can be used to authenticate requests with admin privileges without a normal user account token. Append the `admin_token` parameter to requests to utilize it. (Please reconsider using HTTP Basic Auth or OAuth-based authentication if possible)" msgstr "" +"允许设置令牌以不使用普通用户令牌来授权管理员权限。在参数后加上 `admin_token` " +"来启用该功能。(可用时可以考虑使用 HTTP Basic Auth 或 基于 OAuth 的鉴定方式)" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -126,7 +128,7 @@ msgstr "ActivityPub 相关设置" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:assets" msgid "This section configures assets to be used with various frontends. Currently the only option relates to mascots on the mastodon frontend" -msgstr "" +msgstr "该部分配置不同前端使用的资源。目前该选项只对 Mastodon 前端的吉祥物有效" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -138,7 +140,7 @@ msgstr "鉴权/授权设置" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:connections_pool" msgid "Advanced settings for `Gun` connections pool" -msgstr "「Gun」连接池的高级设置" +msgstr "`Gun` 连接池的高级设置" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -183,7 +185,7 @@ msgstr "Gopher 设置" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:hackney_pools" msgid "Advanced settings for `Hackney` connections pools" -msgstr "「Hackney」连接池的高级设置" +msgstr "`Hackney` 连接池的高级设置" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -226,7 +228,7 @@ msgid "Majic/libmagic configuration" msgstr "Majic/libmagic 配置" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:manifest" msgid "This section describe PWA manifest instance-specific values. Currently this option relate only for MastoFE." msgstr "此处提供针对特定实例的 PWA manifest 数值。目前相关设定尚只支持 MastoFE。" @@ -244,10 +246,10 @@ msgid "Media proxy" msgstr "媒体代理" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:modules" msgid "Custom Runtime Modules" -msgstr "自定义 Runtime 模块" +msgstr "自定义运行库模块" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -299,7 +301,7 @@ msgstr "拒绝提及特定用户的讯息" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_normalize_markup" msgid "MRF NormalizeMarkup settings. Scrub configured hypertext markup." -msgstr "" +msgstr "MRF NomalizeMarkup 设置。清楚超文本标记。" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -317,7 +319,7 @@ msgstr "RejectNonPublic 丢弃有非公开的可见性设置的文章。" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_simple" msgid "Simple ingress policies" -msgstr "" +msgstr "简单入口流量控制" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -326,10 +328,11 @@ msgid "Steals emojis from selected instances when it sees them." msgstr "从选择的实例偷取看到的 emoji。" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format +#, elixir-autogen, elixir-format, fuzzy msgctxt "config description at :pleroma-:mrf_subchain" msgid "This policy processes messages through an alternate pipeline when a given message matches certain criteria. All criteria are configured as a map of regular expressions to lists of policy modules." -msgstr "" +msgstr "此策略将会把满足特定条件的信息通过另一管线处理。所有条件都以正则表达式来对应" +"列出的策略模块配置。" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -347,13 +350,13 @@ msgstr "配置 OAuth 2 提供者的能力" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:pools" msgid "Advanced settings for `Gun` workers pools" -msgstr "「Gun」工人池的高级设置" +msgstr "`Gun` worker 池的高级设置" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:populate_hashtags_table" msgid "`populate_hashtags_table` background migration settings" -msgstr "「populate_hashtags_table」后台迁移设置" +msgstr "`populate_hashtags_table` 后台迁移设置" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -395,13 +398,13 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:uri_schemes" msgid "URI schemes related settings" -msgstr "" +msgstr "URI scheme 相关设置" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:web_cache_ttl" msgid "The expiration time for the web responses cache. Values should be in milliseconds or `nil` to disable expiration." -msgstr "web 回应缓存的过期时间。值应该以毫秒为单位,或者用「nil」来禁用过期。" +msgstr "网页回应缓存的过期时间。以毫秒为单位,或者用 `nil` 来禁用过期。" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -413,7 +416,7 @@ msgstr "欢迎讯息设置" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:workers" msgid "Includes custom worker options not interpretable directly by `Oban`" -msgstr "包含不能直接被「Oban」解读的自定工人选项" +msgstr "包含不能直接被 `Oban` 解读的自定 worker 选项" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -488,7 +491,7 @@ msgstr "过滤器将会匿名化上传文件的文件名" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Upload.Filter.Mogrify" msgid "Uploads mogrify filter settings" -msgstr "" +msgstr "morgify 上传过滤器设置" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -531,6 +534,9 @@ msgstr "元数据相关设定" msgctxt "config description at :pleroma-Pleroma.Web.Plugs.RemoteIp" msgid "`Pleroma.Web.Plugs.RemoteIp` is a shim to call [`RemoteIp`](https://git.pleroma.social/pleroma/remote_ip) but with runtime configuration.\n**If your instance is not behind at least one reverse proxy, you should not enable this plug.**\n" msgstr "" +"`Pleroma.Web.Plugs.RemoteIp` 是一个呼叫 [`RemoteIp`](https://git.pleroma." +"social/pleroma/remote_ip) 的 shim 但是包含运行库配置。\n" +"**如果您的实例不在至少一个反向代理后面,您不应该启用这个插件。**\n" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -548,13 +554,13 @@ msgstr "失效活动设定" #, elixir-autogen, elixir-format msgctxt "config description at :prometheus-Pleroma.Web.Endpoint.MetricsExporter" msgid "Prometheus app metrics endpoint configuration" -msgstr "" +msgstr "Prometheus 服务监控端点配置" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :web_push_encryption-:vapid_details" msgid "Web Push Notifications configuration. You can use the mix task mix web_push.gen.keypair to generate it." -msgstr "" +msgstr "网页推送通知配置。您可以使用 mix task mix web_push.gen.keypair 来生成它。" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -584,7 +590,7 @@ msgstr "ActivityPub" #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:assets" msgid "Assets" -msgstr "" +msgstr "资源" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -608,13 +614,13 @@ msgstr "邮件通知" #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:emoji" msgid "Emoji" -msgstr "Emoji" +msgstr "表情符号" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:features" msgid "Features" -msgstr "特性" +msgstr "功能" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -644,7 +650,7 @@ msgstr "Gopher" #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:hackney_pools" msgid "Hackney pools" -msgstr "" +msgstr "Hackney 池" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -674,25 +680,25 @@ msgstr "实例图标" #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:ldap" msgid "LDAP" -msgstr "" +msgstr "LDAP" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:majic_pool" msgid "Majic pool" -msgstr "" +msgstr "Majic 池" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:manifest" msgid "Manifest" -msgstr "" +msgstr "Manifest" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:markup" msgid "Markup Settings" -msgstr "" +msgstr "标记设置" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -710,25 +716,25 @@ msgstr "媒体文件代理" #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:modules" msgid "Modules" -msgstr "" +msgstr "模块" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf" msgid "MRF" -msgstr "" +msgstr "MRF" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_activity_expiration" msgid "MRF Activity Expiration Policy" -msgstr "" +msgstr "MRF 活动过期策略" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_follow_bot" msgid "MRF FollowBot Policy" -msgstr "" +msgstr "MRF FollowBot 策略" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -740,49 +746,49 @@ msgstr "MRF 标签" #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_hellthread" msgid "MRF Hellthread" -msgstr "" +msgstr "MRF Hellthread" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_keyword" msgid "MRF Keyword" -msgstr "" +msgstr "MRF 关键词" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_mention" msgid "MRF Mention" -msgstr "" +msgstr "MRF 提及" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_normalize_markup" msgid "MRF Normalize Markup" -msgstr "" +msgstr "MRF 标记标准化" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_object_age" msgid "MRF Object Age" -msgstr "" +msgstr "MRF 对象年龄" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_rejectnonpublic" msgid "MRF Reject Non Public" -msgstr "" +msgstr "MRF 拒绝非公开帖子" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_simple" msgid "MRF Simple" -msgstr "" +msgstr "MRF 简单" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_steal_emoji" msgid "MRF Emojis" -msgstr "" +msgstr "MRF 表情符号" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -800,13 +806,13 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:oauth2" msgid "OAuth2" -msgstr "" +msgstr "OAuth2" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:pools" msgid "Pools" -msgstr "" +msgstr "池" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -818,13 +824,13 @@ msgstr "本站话题标签列表" #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:rate_limit" msgid "Rate limit" -msgstr "" +msgstr "限流" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:restrict_unauthenticated" msgid "Restrict Unauthenticated" -msgstr "" +msgstr "限制未授权用户" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -842,7 +848,7 @@ msgstr "留言板" #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:static_fe" msgid "Static FE" -msgstr "" +msgstr "静态 FE" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -854,7 +860,7 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:uri_schemes" msgid "URI Schemes" -msgstr "" +msgstr "URI Schemes" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -866,7 +872,7 @@ msgstr "用户" #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:web_cache_ttl" msgid "Web cache TTL" -msgstr "" +msgstr "网页缓存 TTL" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -878,13 +884,13 @@ msgstr "欢迎" #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:workers" msgid "Workers" -msgstr "工人" +msgstr "Workers" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-ConcurrentLimiter" msgid "ConcurrentLimiter" -msgstr "" +msgstr "ConcurrentLimiter" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -896,133 +902,133 @@ msgstr "Oban" #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Captcha" msgid "Pleroma.Captcha" -msgstr "" +msgstr "Pleroma.Captcha" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Captcha.Kocaptcha" msgid "Pleroma.Captcha.Kocaptcha" -msgstr "" +msgstr "Pleroma.Captcha.Kocaptcha" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer" msgid "Pleroma.Emails.Mailer" -msgstr "" +msgstr "Pleroma.Emails.Mailer" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.NewUsersDigestEmail" msgid "Pleroma.Emails.NewUsersDigestEmail" -msgstr "" +msgstr "Pleroma.Emails.NewUsersDigestEmail" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail" msgid "Pleroma.Emails.UserEmail" -msgstr "" +msgstr "Pleroma.Emails.UserEmail" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Formatter" msgid "Linkify" -msgstr "" +msgstr "Linkify" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.ScheduledActivity" msgid "Pleroma.ScheduledActivity" -msgstr "" +msgstr "Pleroma.ScheduledActivity" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Upload" msgid "Pleroma.Upload" -msgstr "" +msgstr "Pleroma.Upload" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Upload.Filter.AnonymizeFilename" msgid "Pleroma.Upload.Filter.AnonymizeFilename" -msgstr "" +msgstr "Pleroma.Upload.Filter.AnonymizeFilename" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Upload.Filter.Mogrify" msgid "Pleroma.Upload.Filter.Mogrify" -msgstr "" +msgstr "Pleroma.Upload.Filter.Mogrify" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Uploaders.Local" msgid "Pleroma.Uploaders.Local" -msgstr "" +msgstr "Pleroma.Uploaders.Local" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Uploaders.S3" msgid "Pleroma.Uploaders.S3" -msgstr "" +msgstr "Pleroma.Uploaders.S3" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.User" msgid "Pleroma.User" -msgstr "" +msgstr "Pleroma.User" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.User.Backup" msgid "Pleroma.User.Backup" -msgstr "" +msgstr "Pleroma.User.Backup" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.ApiSpec.CastAndValidate" msgid "Pleroma.Web.ApiSpec.CastAndValidate" -msgstr "" +msgstr "Pleroma.Web.ApiSpec.CastAndValidate" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http" msgid "Pleroma.Web.MediaProxy.Invalidation.Http" -msgstr "" +msgstr "Pleroma.Web.MediaProxy.Invalidation.Http" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Script" msgid "Pleroma.Web.MediaProxy.Invalidation.Script" -msgstr "" +msgstr "Pleroma.Web.MediaProxy.Invalidation.Script" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.Metadata" msgid "Pleroma.Web.Metadata" -msgstr "" +msgstr "Pleroma.Web.Metadata" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.Plugs.RemoteIp" msgid "Pleroma.Web.Plugs.RemoteIp" -msgstr "" +msgstr "Pleroma.Web.Plugs.RemoteIp" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.Preload" msgid "Pleroma.Web.Preload" -msgstr "" +msgstr "Pleroma.Web.Preload" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Workers.PurgeExpiredActivity" msgid "Pleroma.Workers.PurgeExpiredActivity" -msgstr "" +msgstr "Pleroma.Workers.PurgeExpiredActivity" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config label at :prometheus-Pleroma.Web.Endpoint.MetricsExporter" msgid "Pleroma.Web.Endpoint.MetricsExporter" -msgstr "" +msgstr "Pleroma.Web.Endpoint.MetricsExporter" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1064,37 +1070,39 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config description at :ex_aws-:s3 > :access_key_id" msgid "S3 access key ID" -msgstr "" +msgstr "S3 访问密钥 ID" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :ex_aws-:s3 > :host" msgid "S3 host" -msgstr "" +msgstr "S3 主机" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :ex_aws-:s3 > :region" msgid "S3 region (for AWS)" -msgstr "" +msgstr "S3 区域(AWS)" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :ex_aws-:s3 > :secret_access_key" msgid "Secret access key" -msgstr "" +msgstr "访问密钥" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :logger > :backends" msgid "Where logs will be sent, :console - send logs to stdout, { ExSyslogger, :ex_syslogger } - to syslog, Quack.Logger - to Slack." msgstr "" +"日志发送的地点,:console - 将日志发送到 stdout, { ExSyslogger, :ex_syslogger " +"} - 发送到 syslog, Quack.Logger - 发送到 Slack." #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :logger-:console > :format" msgid "Default: \"$date $time [$level] $levelpad$node $metadata $message\"" -msgstr "" +msgstr "默认:\"$date $time [$level] $levelpad$node $metadata $message\"" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1106,13 +1114,13 @@ msgstr "日志等级" #, elixir-autogen, elixir-format msgctxt "config description at :logger-:ex_syslogger > :format" msgid "Default: \"$date $time [$level] $levelpad$node $metadata $message\"" -msgstr "" +msgstr "默认:\"$date $time [$level] $levelpad$node $metadata $message\"" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :logger-:ex_syslogger > :ident" msgid "A string that's prepended to every message, and is typically set to the app name" -msgstr "" +msgstr "注入在每一个消息前面的字符串,通常设为应用程序的名称" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1148,13 +1156,13 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:activitypub > :outgoing_blocks" msgid "Whether to federate blocks to other instances" -msgstr "" +msgstr "是否与其他实例同步屏蔽列表" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:activitypub > :sign_object_fetches" msgid "Sign object fetches with HTTP signatures" -msgstr "" +msgstr "为对象获取进行 HTTP 签名" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1166,7 +1174,7 @@ msgstr "屏蔽对象时是否同时取消对其的关注" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:assets > :default_mascot" msgid "This will be used as the default mascot on MastoFE. Default: `:pleroma_fox_tan`" -msgstr "" +msgstr "这将是 MastoFE 的默认吉祥物。默认:`:pleroma_fox_tan`" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1178,13 +1186,15 @@ msgstr "默认用户头像的网址" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:assets > :mascots" msgid "Keyword of mascots, each element must contain both an URL and a mime_type key" -msgstr "" +msgstr "吉祥物关键词,每一个元素必须包含一个 URL 和 mine_type 值" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:auth > :auth_template" msgid "Authentication form template. By default it's `show.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/show.html.ee`." msgstr "" +"授权表达模板。默认是 `show.html`,对应于 `lib/pleroma/web/templates/o_auth/" +"o_auth/show.html.ee`。" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1208,7 +1218,7 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:connections_pool > :connect_timeout" msgid "Timeout while `gun` will wait until connection is up. Default: 5000ms." -msgstr "「Gun」等待连接时触发超时的上限。默认为5000ms。" +msgstr "`gun` 等待连接时触发超时的上限。默认:5000ms。" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1226,7 +1236,7 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:connections_pool > :max_connections" msgid "Maximum number of connections in the pool. Default: 250 connections." -msgstr "" +msgstr "池的最大连接数量。默认:250 连接。" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1262,13 +1272,15 @@ msgstr "单个用户每次收到摘要邮件的间隔" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:email_notifications > :digest > :schedule" msgid "When to send digest email, in crontab format. \"0 0 0\" is the default, meaning \"once a week at midnight on Sunday morning\"." -msgstr "" +msgstr "发送摘要邮件的时间,以 crontab 格式。默认为“0 0 " +"0”,意味着“每周在周日的午夜时分”。" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:emoji > :default_manifest" msgid "Location of the JSON-manifest. This manifest contains information about the emoji-packs you can download. Currently only one manifest can be added (no arrays)." -msgstr "" +msgstr "JSON-manifest 的位置。manifest 包含您可以下载的表情包信息。目前只能添加一个 " +"manifest(无数列)。" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1286,7 +1298,7 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:emoji > :shortcode_globs" msgid "Location of custom emoji files. * can be used as a wildcard." -msgstr "" +msgstr "自定义表情符号位置。* 可以当作通配符使用。" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1298,7 +1310,7 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:feed > :post_title" msgid "Configure title rendering" -msgstr "" +msgstr "配置标题渲染" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1468,7 +1480,7 @@ msgstr "管理员前端" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontends > :admin > name" msgid "Name of the installed frontend. Valid config must include both `Name` and `Reference` values." -msgstr "已安装的前端名称。只有包含了「名称」与「引用」数值才能被算作有效配置。" +msgstr "已安装的前端名称。有效配置必须包含 `Name` 和 `Reference` 数值。" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1498,13 +1510,13 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontends > :available > custom-http-headers" msgid "The custom HTTP headers for the frontend" -msgstr "" +msgstr "前端的自定义 HTTP 响应头" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontends > :available > git" msgid "URL of the git repository of the frontend" -msgstr "" +msgstr "前端 git 仓库的 URL" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1522,13 +1534,13 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontends > :primary" msgid "Primary frontend, the one that is served for all pages by default" -msgstr "" +msgstr "主要前端,这是默认服务所有页面的前端" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontends > :primary > name" msgid "Name of the installed frontend. Valid config must include both `Name` and `Reference` values." -msgstr "已安装的前端名称。只有包含了「名称」与「引用」数值才能被算作有效配置。" +msgstr "已安装的前端名称。有效配置必须包含 `Name` 和 `Reference` 数值。" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1540,7 +1552,7 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:gopher > :dstport" msgid "Port advertised in URLs (optional, defaults to port)" -msgstr "" +msgstr "URL 中宣传的端口(可选,默认为端口)" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1570,7 +1582,7 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:hackney_pools > :federation > :max_connections" msgid "Number workers in the pool." -msgstr "池内的工人数量。" +msgstr "池内的 worker 数量。" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1588,7 +1600,7 @@ msgstr "媒体池设定。" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:hackney_pools > :media > :max_connections" msgid "Number workers in the pool." -msgstr "池内的工人数量。" +msgstr "池内的 worker 数量。" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1600,7 +1612,7 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:hackney_pools > :upload" msgid "Settings for upload pool." -msgstr "" +msgstr "上传池设置。" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1780,7 +1792,7 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :email" msgid "Email used to reach an Administrator/Moderator of the instance" -msgstr "" +msgstr "用于联系实例管理员/监管员的电子邮箱" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1813,10 +1825,10 @@ msgid "Timeout (in days) of each external federation target being unreachable pr msgstr "" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :healthcheck" msgid "If enabled, system data will be shown on `/api/pleroma/healthcheck`" -msgstr "若启用,「/api/pleroma/healthcheck」下将显示系统数据" +msgstr "若启用,`/api/pleroma/healthcheck` 下将显示系统数据" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1828,7 +1840,7 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :invites_enabled" msgid "Enable user invitations for admins (depends on `registrations_open` being disabled)" -msgstr "只有管理员邀请的用户方能注册(需要关闭「registrations_open」选项)" +msgstr "只有管理员邀请的用户才能注册(需要关闭 `registrations_open` 选项)" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1900,13 +1912,13 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :multi_factor_authentication > :backup_codes > :number" msgid "Number of backup codes to generate." -msgstr "" +msgstr "生成的备份密钥数目。" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :multi_factor_authentication > :totp" msgid "TOTP settings" -msgstr "" +msgstr "TOTP 设置" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -1973,7 +1985,7 @@ msgstr "允许管理员访问敏感信息(例,更新用户凭据、取得密 #, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :profile_directory" msgid "Enable profile directory." -msgstr "" +msgstr "启用用户主页配置。" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -2020,10 +2032,10 @@ msgstr "" "用户(例,“@admin 请留意 @bad_actor”)。默认下为关闭状态" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance > :show_reactions" msgid "Let favourites and emoji reactions be viewed through the API." -msgstr "允许通过此API来看见喜欢数量与表情反应。" +msgstr "允许通过此 API 来看见喜欢数量与表情回应。" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format @@ -3607,7 +3619,7 @@ msgstr "" #, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:assets > :mascots" msgid "Mascots" -msgstr "" +msgstr "吉祥物" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format diff --git a/priv/gettext/zh_Hans/LC_MESSAGES/default.po b/priv/gettext/zh_Hans/LC_MESSAGES/default.po index ed0d1576b6..7d5828fae4 100644 --- a/priv/gettext/zh_Hans/LC_MESSAGES/default.po +++ b/priv/gettext/zh_Hans/LC_MESSAGES/default.po @@ -8,9 +8,9 @@ ## to merge POT files into PO files. msgid "" msgstr "" -"PO-Revision-Date: 2022-07-22 19:00+0000\n" -"Last-Translator: Yating Zhan \n" -"Language-Team: Chinese (Simplified) \n" +"Language-Team: Chinese (Simplified) \n" "Language: zh_Hans\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -106,7 +106,7 @@ msgstr "转换到 %{polymorphic_type} 中的任一 schema 失败" #: lib/pleroma/web/api_spec/render_error.ex:71 #, elixir-format msgid "Failed to cast value as %{invalid_schema}. Value must be castable using `allOf` schemas listed." -msgstr "把值转换成 %{invalid_schema} 失败。值必须可以被转换成在列的「所有」schema。" +msgstr "把值转换成 %{invalid_schema} 失败。值必须可以被转换成在列的 `allOf` schema。" #: lib/pleroma/web/api_spec/render_error.ex:84 #, elixir-format @@ -136,17 +136,17 @@ msgstr "缺少头:%{name}。" #: lib/pleroma/web/api_spec/render_error.ex:196 #, elixir-format msgid "No value provided for required discriminator `%{field}`." -msgstr "" +msgstr "没有提供给鉴别器 `%{field}` 提供所需要的值。" #: lib/pleroma/web/api_spec/render_error.ex:216 #, elixir-format msgid "Object property count %{property_count} is greater than maxProperties: %{max_properties}." -msgstr "" +msgstr "对象属性数 %{property_count} 大于 maxProperties: %{max_properties}。" #: lib/pleroma/web/api_spec/render_error.ex:224 #, elixir-format msgid "Object property count %{property_count} is less than minProperties: %{min_properties}" -msgstr "" +msgstr "对象属性数 %{property_count} 小于 minProperties: %{min_properties}" #: lib/pleroma/web/templates/static_fe/static_fe/error.html.eex:2 #, elixir-format @@ -166,7 +166,7 @@ msgstr "未知的 schema:%{name}。" #: lib/pleroma/web/api_spec/render_error.ex:192 #, elixir-format msgid "Value used as discriminator for `%{field}` matches no schemas." -msgstr "" +msgstr "用于 `%{field}` 鉴别器的值无法匹配到任何 schema。" #: lib/pleroma/web/templates/embed/show.html.eex:43 #: lib/pleroma/web/templates/static_fe/static_fe/_notice.html.eex:37 diff --git a/priv/gettext/zh_Hans/LC_MESSAGES/errors.po b/priv/gettext/zh_Hans/LC_MESSAGES/errors.po index 4431445e34..6684729392 100644 --- a/priv/gettext/zh_Hans/LC_MESSAGES/errors.po +++ b/priv/gettext/zh_Hans/LC_MESSAGES/errors.po @@ -3,9 +3,9 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2020-09-20 13:18+0000\n" -"PO-Revision-Date: 2022-07-22 19:00+0000\n" -"Last-Translator: Yating Zhan \n" -"Language-Team: Chinese (Simplified) \n" +"Language-Team: Chinese (Simplified) \n" "Language: zh_Hans\n" "MIME-Version: 1.0\n" @@ -392,7 +392,7 @@ msgid "Invalid answer data" msgstr "无效的回答数据" #: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:33 -#, elixir-format, fuzzy +#, elixir-format msgid "Nodeinfo schema version not handled" msgstr "Nodeinfo schema 版本没被处理" diff --git a/priv/gettext/zh_Hans/LC_MESSAGES/oauth_scopes.po b/priv/gettext/zh_Hans/LC_MESSAGES/oauth_scopes.po new file mode 100644 index 0000000000..2044148367 --- /dev/null +++ b/priv/gettext/zh_Hans/LC_MESSAGES/oauth_scopes.po @@ -0,0 +1,274 @@ +msgid "" +msgstr "" +"Project-Id-Version: PACKAGE VERSION\n" +"Report-Msgid-Bugs-To: \n" +"POT-Creation-Date: 2024-08-01 10:12+0300\n" +"PO-Revision-Date: 2024-08-01 08:19+0000\n" +"Last-Translator: Eric Zhang \n" +"Language-Team: Chinese (Simplified) \n" +"Language: zh_Hans\n" +"MIME-Version: 1.0\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" +"Plural-Forms: nplurals=1; plural=0;\n" +"X-Generator: Weblate 4.13.1\n" + +## This file is a PO Template file. +## +## "msgid"s here are often extracted from source code. +## Add new translations manually only if they're dynamic +## translations that can't be statically extracted. +## +## Run "mix gettext.extract" to bring this file up to +## date. Leave "msgstr"s empty as changing them here has no +## effect: edit them in PO (.po) files instead. +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "admin" +msgstr "全部管理员权限" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "admin:read" +msgstr "使用管理员 API 读取" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "admin:write" +msgstr "使用管理员 API 写入" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "follow" +msgstr "读取并写入用户关系" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "read" +msgstr "读取任何信息" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "read:accounts" +msgstr "读取所有账号的信息" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "read:blocks" +msgstr "读取块关系" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "read:bookmarks" +msgstr "读取您的书签" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "read:favourites" +msgstr "读取您喜欢的帖子" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "read:filters" +msgstr "读取您的过滤器设置" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "read:follows" +msgstr "读取关注关系" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "read:lists" +msgstr "读取您的列表" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "read:notifications" +msgstr "读取您的通知" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "read:search" +msgstr "执行搜索" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "read:statuses" +msgstr "读取您可以看到的动态" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "write" +msgstr "写入任何信息" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "write:accounts" +msgstr "更改您的账号信息" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "write:blocks" +msgstr "屏蔽或取消屏蔽任何人" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "write:bookmarks" +msgstr "从您的书签中添加或移除" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "write:conversations" +msgstr "更改收件人,标记为已阅,或删除聊天" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "write:favourites" +msgstr "喜欢或取消喜欢动态" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "write:filters" +msgstr "更改您的过滤器设置" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "write:follows" +msgstr "关注或取消关注任何人" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "write:lists" +msgstr "创建,更改或删除您的列表" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "write:media" +msgstr "上传媒体文件或更改您上传的媒体文件" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "write:mutes" +msgstr "隐藏或取消隐藏任何人" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "write:notifications" +msgstr "标记通知为已读" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "write:statuses" +msgstr "发表,编辑,转发帖子或对帖子做出回应" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "admin:read:accounts" +msgstr "使用管理员 API 读取所有账号" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "admin:read:chats" +msgstr "使用管理员 API 读取所有聊天" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "admin:read:invites" +msgstr "使用管理员 API 读取所有邀请码" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "admin:read:media_proxy_caches" +msgstr "使用管理员 API 读取媒体代理缓存" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "admin:read:reports" +msgstr "使用管理员 API 读取所有举报" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "admin:read:statuses" +msgstr "使用管理员 API 读取所有动态" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "admin:write:accounts" +msgstr "使用管理员 API 更改所有账号" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "admin:write:chats" +msgstr "使用管理员 API 更改所有聊天" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "admin:write:follows" +msgstr "使用管理员 API 更改关注关系" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "admin:write:invites" +msgstr "使用管理员 API 创建或吊销邀请码" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "admin:write:media_proxy_caches" +msgstr "使用管理员 API 更改媒体代理缓存" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "admin:write:reports" +msgstr "使用管理员 API 处理举报" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "admin:write:statuses" +msgstr "使用管理员 API 删除动态,更改动态的范围,或标记为敏感动态" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "read:mutes" +msgstr "读取隐藏关系" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "push" +msgstr "推送通知" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "read:backups" +msgstr "读取您的备份" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "read:chats" +msgstr "读取您的聊天" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "read:media" +msgstr "读取媒体附件" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "read:reports" +msgstr "读取您的举报" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "write:chats" +msgstr "添加或移除聊天信息,或者标记它们为已阅" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "write:follow" +msgstr "关注或取消关注任何人" + +#: lib/pleroma/web/api_spec/scopes/translator.ex:5 +#, elixir-autogen, elixir-format +msgid "write:reports" +msgstr "提交举报" diff --git a/priv/gettext/zh_Hans/LC_MESSAGES/posix_errors.po b/priv/gettext/zh_Hans/LC_MESSAGES/posix_errors.po index c486a54864..930f5db1e5 100644 --- a/priv/gettext/zh_Hans/LC_MESSAGES/posix_errors.po +++ b/priv/gettext/zh_Hans/LC_MESSAGES/posix_errors.po @@ -8,9 +8,9 @@ ## to merge POT files into PO files. msgid "" msgstr "" -"PO-Revision-Date: 2022-07-22 19:00+0000\n" -"Last-Translator: Yating Zhan \n" -"Language-Team: Chinese (Simplified) \n" +"Language-Team: Chinese (Simplified) \n" "Language: zh_Hans\n" "Content-Type: text/plain; charset=UTF-8\n" @@ -22,19 +22,19 @@ msgid "eperm" msgstr "不允许的操作" msgid "eacces" -msgstr "权限不够" +msgstr "拒绝访问" msgid "eagain" msgstr "资源暂时不可用" msgid "ebadf" -msgstr "坏的文件描述符" +msgstr "非法的文件描述符" msgid "ebadmsg" -msgstr "坏讯息" +msgstr "非法消息" msgid "ebusy" -msgstr "设备或资源忙" +msgstr "设备或资源繁忙" msgid "edeadlk" msgstr "避免了资源死锁" @@ -46,10 +46,10 @@ msgid "edquot" msgstr "超出了磁盘配额" msgid "eexist" -msgstr "文件存在" +msgstr "文件已存在" msgid "efault" -msgstr "坏地址" +msgstr "非法地址" msgid "efbig" msgstr "文件太大" @@ -61,7 +61,7 @@ msgid "eintr" msgstr "系统调用被中断" msgid "einval" -msgstr "不合法的参数" +msgstr "非法参数" msgid "eio" msgstr "输入/输出错误" @@ -79,7 +79,7 @@ msgid "emlink" msgstr "太多链接" msgid "emultihop" -msgstr "" +msgstr "已尝试多跳" msgid "enametoolong" msgstr "文件名太长" @@ -97,7 +97,7 @@ msgid "enolck" msgstr "没有可用的锁" msgid "enolink" -msgstr "链接被切断了" +msgstr "链接被切断" msgid "enoent" msgstr "没这文件或目录" @@ -109,19 +109,19 @@ msgid "enospc" msgstr "设备上没剩余空间" msgid "enosr" -msgstr "" +msgstr "流资源不足" msgid "enostr" msgstr "设备不是流" msgid "enosys" -msgstr "功能没实现" +msgstr "功能未实现" msgid "enotblk" -msgstr "" +msgstr "需要块设备" msgid "enotdir" -msgstr "" +msgstr "不是目录" msgid "enotsup" msgstr "不受支持的操作" @@ -136,25 +136,25 @@ msgid "eoverflow" msgstr "请为给定类型的数据指定较小的数值" msgid "epipe" -msgstr "" +msgstr "管道中断" msgid "erange" -msgstr "" +msgstr "数值超过范围" msgid "erofs" -msgstr "只读权限文件系统" +msgstr "只读文件系统" msgid "espipe" -msgstr "" +msgstr "非法搜寻" msgid "esrch" -msgstr "具体进程不存在" +msgstr "进程不存在" msgid "estale" -msgstr "" +msgstr "过时的文件句柄" msgid "etxtbsy" -msgstr "文本文件忙碌" +msgstr "文本文件繁忙" msgid "exdev" -msgstr "该多设备链接不可用" +msgstr "非法多设备链接" diff --git a/priv/repo/migrations/20240628160536_deprecate_config_db_workers.exs b/priv/repo/migrations/20240628160536_deprecate_config_db_workers.exs new file mode 100644 index 0000000000..549dd22e9e --- /dev/null +++ b/priv/repo/migrations/20240628160536_deprecate_config_db_workers.exs @@ -0,0 +1,7 @@ +defmodule Pleroma.Repo.Migrations.DeprecateConfigDBWorkers do + use Ecto.Migration + + def change do + execute("DELETE FROM config WHERE config.group = ':workers'") + end +end diff --git a/priv/scrubbers/default.ex b/priv/scrubbers/default.ex index a75a6465d2..dad9dc1a1b 100644 --- a/priv/scrubbers/default.ex +++ b/priv/scrubbers/default.ex @@ -22,7 +22,8 @@ defmodule Pleroma.HTML.Scrubber.Default do "u-url", "mention", "u-url mention", - "mention u-url" + "mention u-url", + "mention hashtag" ]) Meta.allow_tag_with_this_attribute_values(:a, "rel", [ diff --git a/priv/scrubbers/twitter_text.ex b/priv/scrubbers/twitter_text.ex index 6e23b3efbe..4df840735d 100644 --- a/priv/scrubbers/twitter_text.ex +++ b/priv/scrubbers/twitter_text.ex @@ -23,7 +23,8 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do "u-url", "mention", "u-url mention", - "mention u-url" + "mention u-url", + "mention hashtag" ]) Meta.allow_tag_with_this_attribute_values(:a, "rel", [ diff --git a/test/fixtures/bastianallgeier.json b/test/fixtures/bastianallgeier.json deleted file mode 100644 index 6b47e7db9b..0000000000 --- a/test/fixtures/bastianallgeier.json +++ /dev/null @@ -1,117 +0,0 @@ -{ - "@context": [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - { - "Curve25519Key": "toot:Curve25519Key", - "Device": "toot:Device", - "Ed25519Key": "toot:Ed25519Key", - "Ed25519Signature": "toot:Ed25519Signature", - "EncryptedMessage": "toot:EncryptedMessage", - "PropertyValue": "schema:PropertyValue", - "alsoKnownAs": { - "@id": "as:alsoKnownAs", - "@type": "@id" - }, - "cipherText": "toot:cipherText", - "claim": { - "@id": "toot:claim", - "@type": "@id" - }, - "deviceId": "toot:deviceId", - "devices": { - "@id": "toot:devices", - "@type": "@id" - }, - "discoverable": "toot:discoverable", - "featured": { - "@id": "toot:featured", - "@type": "@id" - }, - "featuredTags": { - "@id": "toot:featuredTags", - "@type": "@id" - }, - "fingerprintKey": { - "@id": "toot:fingerprintKey", - "@type": "@id" - }, - "focalPoint": { - "@container": "@list", - "@id": "toot:focalPoint" - }, - "identityKey": { - "@id": "toot:identityKey", - "@type": "@id" - }, - "indexable": "toot:indexable", - "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", - "memorial": "toot:memorial", - "messageFranking": "toot:messageFranking", - "messageType": "toot:messageType", - "movedTo": { - "@id": "as:movedTo", - "@type": "@id" - }, - "publicKeyBase64": "toot:publicKeyBase64", - "schema": "http://schema.org#", - "suspended": "toot:suspended", - "toot": "http://joinmastodon.org/ns#", - "value": "schema:value" - } - ], - "attachment": [ - { - "name": "Website", - "type": "PropertyValue", - "value": "https://bastianallgeier.com" - }, - { - "name": "Project", - "type": "PropertyValue", - "value": "https://getkirby.com" - }, - { - "name": "Github", - "type": "PropertyValue", - "value": "https://github.com/bastianallgeier" - } - ], - "devices": "https://mastodon.social/users/bastianallgeier/collections/devices", - "discoverable": true, - "endpoints": { - "sharedInbox": "https://mastodon.social/inbox" - }, - "featured": "https://mastodon.social/users/bastianallgeier/collections/featured", - "featuredTags": "https://mastodon.social/users/bastianallgeier/collections/tags", - "followers": "https://mastodon.social/users/bastianallgeier/followers", - "following": "https://mastodon.social/users/bastianallgeier/following", - "icon": { - "mediaType": "image/jpeg", - "type": "Image", - "url": "https://files.mastodon.social/accounts/avatars/000/007/393/original/0180a20079617c71.jpg" - }, - "id": "https://mastodon.social/users/bastianallgeier", - "image": { - "mediaType": "image/jpeg", - "type": "Image", - "url": "https://files.mastodon.social/accounts/headers/000/007/393/original/13d644ab46d50478.jpeg" - }, - "inbox": "https://mastodon.social/users/bastianallgeier/inbox", - "indexable": false, - "manuallyApprovesFollowers": false, - "memorial": false, - "name": "Bastian Allgeier", - "outbox": "https://mastodon.social/users/bastianallgeier/outbox", - "preferredUsername": "bastianallgeier", - "publicKey": { - "id": "https://mastodon.social/users/bastianallgeier#main-key", - "owner": "https://mastodon.social/users/bastianallgeier", - "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA3fz+hpgVztO9z6HUhyzv\nwP++ERBBoIwSLKf1TyIM8bvzGFm2YXaO5uxu1HvumYFTYc3ACr3q4j8VUb7NMxkQ\nlzu4QwPjOFJ43O+fY+HSPORXEDW5fXDGC5DGpox4+i08LxRmx7L6YPRUSUuPN8nI\nWyq1Qsq1zOQrNY/rohMXkBdSXxqC3yIRqvtLt4otCgay/5tMogJWkkS6ZKyFhb9z\nwVVy1fsbV10c9C+SHy4NH26CKaTtpTYLRBMjhTCS8bX8iDSjGIf2aZgYs1ir7gEz\n9wf5CvLiENmVWGwm64t6KSEAkA4NJ1hzgHUZPCjPHZE2SmhO/oHaxokTzqtbbENJ\n1QIDAQAB\n-----END PUBLIC KEY-----\n" - }, - "published": "2016-11-01T00:00:00Z", - "summary": "

Designer & developer. Creator of Kirby CMS

", - "tag": [], - "type": "Person", - "url": "https://mastodon.social/@bastianallgeier" -} diff --git a/test/fixtures/receiver_worker_signature_activity.json b/test/fixtures/receiver_worker_signature_activity.json index 3c3fb3fd2f..19dc0087f3 100644 --- a/test/fixtures/receiver_worker_signature_activity.json +++ b/test/fixtures/receiver_worker_signature_activity.json @@ -1,62 +1,109 @@ { "@context": [ "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", { + "claim": { + "@id": "toot:claim", + "@type": "@id" + }, + "memorial": "toot:memorial", "atomUri": "ostatus:atomUri", + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", "blurhash": "toot:blurhash", - "conversation": "ostatus:conversation", + "ostatus": "http://ostatus.org#", + "discoverable": "toot:discoverable", "focalPoint": { "@container": "@list", "@id": "toot:focalPoint" }, - "inReplyToAtomUri": "ostatus:inReplyToAtomUri", - "ostatus": "http://ostatus.org#", + "votersCount": "toot:votersCount", + "Hashtag": "as:Hashtag", + "Emoji": "toot:Emoji", + "alsoKnownAs": { + "@id": "as:alsoKnownAs", + "@type": "@id" + }, "sensitive": "as:sensitive", + "movedTo": { + "@id": "as:movedTo", + "@type": "@id" + }, + "inReplyToAtomUri": "ostatus:inReplyToAtomUri", + "conversation": "ostatus:conversation", + "Device": "toot:Device", + "schema": "http://schema.org#", "toot": "http://joinmastodon.org/ns#", - "votersCount": "toot:votersCount" - } - ], - "atomUri": "https://chaos.social/users/distantnative/statuses/109336635639931467", - "attachment": [ - { - "blurhash": "UAK1zS00OXIUxuMxIUM{?b-:-;W:Di?b%2M{", - "height": 960, - "mediaType": "image/jpeg", - "name": null, - "type": "Document", - "url": "https://assets.chaos.social/media_attachments/files/109/336/634/286/114/657/original/2e6122063d8bfb26.jpeg", - "width": 346 + "cipherText": "toot:cipherText", + "suspended": "toot:suspended", + "messageType": "toot:messageType", + "featuredTags": { + "@id": "toot:featuredTags", + "@type": "@id" + }, + "Curve25519Key": "toot:Curve25519Key", + "deviceId": "toot:deviceId", + "Ed25519Signature": "toot:Ed25519Signature", + "featured": { + "@id": "toot:featured", + "@type": "@id" + }, + "devices": { + "@id": "toot:devices", + "@type": "@id" + }, + "value": "schema:value", + "PropertyValue": "schema:PropertyValue", + "messageFranking": "toot:messageFranking", + "publicKeyBase64": "toot:publicKeyBase64", + "identityKey": { + "@id": "toot:identityKey", + "@type": "@id" + }, + "Ed25519Key": "toot:Ed25519Key", + "indexable": "toot:indexable", + "EncryptedMessage": "toot:EncryptedMessage", + "fingerprintKey": { + "@id": "toot:fingerprintKey", + "@type": "@id" + } } ], - "attributedTo": "https://chaos.social/users/distantnative", - "cc": [ - "https://chaos.social/users/distantnative/followers" - ], - "content": "

Favorite piece of anthropology meta discourse.

", - "contentMap": { - "en": "

Favorite piece of anthropology meta discourse.

" - }, - "conversation": "tag:chaos.social,2022-11-13:objectId=71843781:objectType=Conversation", - "id": "https://chaos.social/users/distantnative/statuses/109336635639931467", + "actor": "https://phpc.social/users/denniskoch", + "cc": [], + "id": "https://phpc.social/users/denniskoch/statuses/112847382711461301/activity", "inReplyTo": null, "inReplyToAtomUri": null, - "published": "2022-11-13T13:04:20Z", - "replies": { - "first": { - "items": [], - "next": "https://chaos.social/users/distantnative/statuses/109336635639931467/replies?only_other_accounts=true&page=true", - "partOf": "https://chaos.social/users/distantnative/statuses/109336635639931467/replies", - "type": "CollectionPage" + "object": { + "atomUri": "https://phpc.social/users/denniskoch/statuses/112847382711461301", + "attachment": [], + "attributedTo": "https://phpc.social/users/denniskoch", + "cc": [], + "content": "

@bastianallgeier @distantnative @kev Another main argument: Discord is popular. Many people have an account, so you can just join an server quickly. Also you know the app and how to get around.

", + "contentMap": { + "en": "

@bastianallgeier @distantnative @kev Another main argument: Discord is popular. Many people have an account, so you can just join an server quickly. Also you know the app and how to get around.

" }, - "id": "https://chaos.social/users/distantnative/statuses/109336635639931467/replies", - "type": "Collection" + "conversation": "tag:mastodon.social,2024-07-25:objectId=760068442:objectType=Conversation", + "id": "https://phpc.social/users/denniskoch/statuses/112847382711461301", + "published": "2024-07-25T13:33:29Z", + "replies": null, + "sensitive": false, + "tag": [], + "to": [ + "https://www.w3.org/ns/activitystreams#Public" + ], + "type": "Note", + "url": "https://phpc.social/@denniskoch/112847382711461301" + }, + "published": "2024-07-25T13:33:29Z", + "signature": { + "created": "2024-07-25T13:33:29Z", + "creator": "https://phpc.social/users/denniskoch#main-key", + "signatureValue": "slz9BKJzd2n1S44wdXGOU+bV/wsskdgAaUpwxj8R16mYOL8+DTpE6VnfSKoZGsBBJT8uG5gnVfVEz1YsTUYtymeUgLMh7cvd8VnJnZPS+oixbmBRVky/Myf91TEgQQE7G4vDmTdB4ii54hZrHcOOYYf5FKPNRSkMXboKA6LMqNtekhbI+JTUJYIB02WBBK6PUyo15f6B1RJ6HGWVgud9NE0y1EZXfrkqUt682p8/9D49ORf7AwjXUJibKic2RbPvhEBj70qUGfBm4vvgdWhSUn1IG46xh+U0+NrTSUED82j1ZVOeua/2k/igkGs8cSBkY35quXTkPz6gbqCCH66CuA==", + "type": "RsaSignature2017" }, - "sensitive": false, - "summary": null, - "tag": [], "to": [ "https://www.w3.org/ns/activitystreams#Public" ], - "type": "Note", - "url": "https://chaos.social/@distantnative/109336635639931467" + "type": "Create" } diff --git a/test/mix/tasks/pleroma/database_test.exs b/test/mix/tasks/pleroma/database_test.exs index a51a3bf3dc..96a9255288 100644 --- a/test/mix/tasks/pleroma/database_test.exs +++ b/test/mix/tasks/pleroma/database_test.exs @@ -623,10 +623,12 @@ defmodule Mix.Tasks.Pleroma.DatabaseTest do expires_at = DateTime.add(DateTime.utc_now(), 60 * 61) - Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ - activity_id: activity_id3, - expires_at: expires_at - }) + Pleroma.Workers.PurgeExpiredActivity.enqueue( + %{ + activity_id: activity_id3 + }, + scheduled_at: expires_at + ) Mix.Tasks.Pleroma.Database.run(["ensure_expiration"]) diff --git a/test/mix/tasks/pleroma/uploads_test.exs b/test/mix/tasks/pleroma/uploads_test.exs index d4ea895014..f3d5aa64fd 100644 --- a/test/mix/tasks/pleroma/uploads_test.exs +++ b/test/mix/tasks/pleroma/uploads_test.exs @@ -3,12 +3,14 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Mix.Tasks.Pleroma.UploadsTest do + alias Pleroma.Config alias Pleroma.Upload - use Pleroma.DataCase + use Pleroma.DataCase, async: false import Mock setup_all do + prep_uploads() Mix.shell(Mix.Shell.Process) on_exit(fn -> @@ -18,6 +20,8 @@ defmodule Mix.Tasks.Pleroma.UploadsTest do :ok end + setup do: clear_config([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local) + describe "running migrate_local" do test "uploads migrated" do with_mock Upload, @@ -53,4 +57,15 @@ defmodule Mix.Tasks.Pleroma.UploadsTest do end end end + + defp prep_uploads do + upload_dir = Config.get([Pleroma.Uploaders.Local, :uploads]) + + if not File.exists?(upload_dir) || File.ls!(upload_dir) == [] do + File.mkdir_p(upload_dir) + + Path.join([upload_dir, "file.txt"]) + |> File.touch() + end + end end diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs index 6704c18db9..215fca5706 100644 --- a/test/pleroma/object/fetcher_test.exs +++ b/test/pleroma/object/fetcher_test.exs @@ -100,7 +100,7 @@ defmodule Pleroma.Object.FetcherTest do test "it returns thread depth exceeded error if thread depth is exceeded" do clear_config([:instance, :federation_incoming_replies_max_depth], 0) - assert {:error, :allowed_depth} = Fetcher.fetch_object_from_id(@ap_id, depth: 1) + assert {:allowed_depth, false} = Fetcher.fetch_object_from_id(@ap_id, depth: 1) end test "it fetches object if max thread depth is restricted to 0 and depth is not specified" do @@ -118,15 +118,18 @@ defmodule Pleroma.Object.FetcherTest do describe "actor origin containment" do test "it rejects objects with a bogus origin" do - {:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity.json") + {:containment, :error} = + Fetcher.fetch_object_from_id("https://info.pleroma.site/activity.json") end test "it rejects objects when attributedTo is wrong (variant 1)" do - {:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity2.json") + {:containment, :error} = + Fetcher.fetch_object_from_id("https://info.pleroma.site/activity2.json") end test "it rejects objects when attributedTo is wrong (variant 2)" do - {:error, _} = Fetcher.fetch_object_from_id("https://info.pleroma.site/activity3.json") + {:containment, :error} = + Fetcher.fetch_object_from_id("https://info.pleroma.site/activity3.json") end end @@ -150,14 +153,14 @@ defmodule Pleroma.Object.FetcherTest do clear_config([:mrf_keyword, :reject], ["yeah"]) clear_config([:mrf, :policies], [Pleroma.Web.ActivityPub.MRF.KeywordPolicy]) - assert {:reject, "[KeywordPolicy] Matches with rejected keyword"} == + assert {:transmogrifier, {:reject, "[KeywordPolicy] Matches with rejected keyword"}} == Fetcher.fetch_object_from_id( "http://mastodon.example.org/@admin/99541947525187367" ) end test "it does not fetch a spoofed object uploaded on an instance as an attachment" do - assert {:error, _} = + assert {:fetch, {:error, {:content_type, "application/json"}}} = Fetcher.fetch_object_from_id( "https://patch.cx/media/03ca3c8b4ac3ddd08bf0f84be7885f2f88de0f709112131a22d83650819e36c2.json" ) diff --git a/test/pleroma/user/import_test.exs b/test/pleroma/user/import_test.exs index f75305e0e5..1d6469a4f6 100644 --- a/test/pleroma/user/import_test.exs +++ b/test/pleroma/user/import_test.exs @@ -25,11 +25,12 @@ defmodule Pleroma.User.ImportTest do user3.nickname ] - {:ok, job} = User.Import.follow_import(user1, identifiers) + {:ok, jobs} = User.Import.follows_import(user1, identifiers) + + for job <- jobs do + assert {:ok, %User{}} = ObanHelpers.perform(job) + end - assert {:ok, result} = ObanHelpers.perform(job) - assert is_list(result) - assert result == [refresh_record(user2), refresh_record(user3)] assert User.following?(user1, user2) assert User.following?(user1, user3) end @@ -44,11 +45,12 @@ defmodule Pleroma.User.ImportTest do user3.nickname ] - {:ok, job} = User.Import.blocks_import(user1, identifiers) + {:ok, jobs} = User.Import.blocks_import(user1, identifiers) + + for job <- jobs do + assert {:ok, %User{}} = ObanHelpers.perform(job) + end - assert {:ok, result} = ObanHelpers.perform(job) - assert is_list(result) - assert result == [user2, user3] assert User.blocks?(user1, user2) assert User.blocks?(user1, user3) end @@ -63,11 +65,12 @@ defmodule Pleroma.User.ImportTest do user3.nickname ] - {:ok, job} = User.Import.mutes_import(user1, identifiers) + {:ok, jobs} = User.Import.mutes_import(user1, identifiers) + + for job <- jobs do + assert {:ok, %User{}} = ObanHelpers.perform(job) + end - assert {:ok, result} = ObanHelpers.perform(job) - assert is_list(result) - assert result == [user2, user3] assert User.mutes?(user1, user2) assert User.mutes?(user1, user3) end diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index 036ae78fbe..06afc0709b 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -1075,6 +1075,21 @@ defmodule Pleroma.UserTest do refute cs.valid? end + + test "it truncates fields" do + clear_config([:instance, :max_remote_account_fields], 2) + + fields = [ + %{"name" => "One", "value" => "Uno"}, + %{"name" => "Two", "value" => "Dos"}, + %{"name" => "Three", "value" => "Tres"} + ] + + cs = User.remote_user_changeset(@valid_remote |> Map.put(:fields, fields)) + + assert [%{"name" => "One", "value" => "Uno"}, %{"name" => "Two", "value" => "Dos"}] == + Ecto.Changeset.get_field(cs, :fields) + end end describe "followers and friends" do diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs index 6aae618355..3bd589f490 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -657,7 +657,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do end test "without valid signature, " <> - "it only accepts Create activities and requires enabled federation", + "it accepts Create activities and requires enabled federation", %{conn: conn} do data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!() non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Jason.decode!() @@ -684,6 +684,54 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do |> json_response(400) end + # When activity is delivered to the inbox and we cannot immediately verify signature + # we capture all the params and process it later in the Oban job. + # Once we begin processing it through Oban we risk fetching the actor to validate the + # activity which just leads to inserting a new user to process a Delete not relevant to us. + test "Activities of certain types from an unknown actor are discarded", %{conn: conn} do + example_bad_types = + Pleroma.Constants.activity_types() -- + Pleroma.Constants.allowed_activity_types_from_strangers() + + Enum.each(example_bad_types, fn bad_type -> + params = + %{ + "type" => bad_type, + "actor" => "https://unknown.mastodon.instance/users/somebody" + } + |> Jason.encode!() + + conn + |> assign(:valid_signature, false) + |> put_req_header("content-type", "application/activity+json") + |> post("/inbox", params) + |> json_response(400) + + assert all_enqueued() == [] + end) + end + + test "Unknown activity types are discarded", %{conn: conn} do + unknown_types = ["Poke", "Read", "Dazzle"] + + Enum.each(unknown_types, fn bad_type -> + params = + %{ + "type" => bad_type, + "actor" => "https://unknown.mastodon.instance/users/somebody" + } + |> Jason.encode!() + + conn + |> assign(:valid_signature, true) + |> put_req_header("content-type", "application/activity+json") + |> post("/inbox", params) + |> json_response(400) + + assert all_enqueued() == [] + end) + end + test "accepts Add/Remove activities", %{conn: conn} do object_id = "c61d6733-e256-4fe1-ab13-1e369789423f" @@ -1747,7 +1795,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do %{conn: conn} do user = insert(:user, hide_followers: true) other_user = insert(:user) - {:ok, _other_user, user, _activity} = CommonAPI.follow(user, other_user) + {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user) result = conn @@ -1843,7 +1891,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do %{conn: conn} do user = insert(:user, hide_follows: true) other_user = insert(:user) - {:ok, user, _other_user, _activity} = CommonAPI.follow(other_user, user) + {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) result = conn diff --git a/test/pleroma/web/activity_pub/mrf/fo_direct_reply_test.exs b/test/pleroma/web/activity_pub/mrf/fo_direct_reply_test.exs new file mode 100644 index 0000000000..2d6af3b68e --- /dev/null +++ b/test/pleroma/web/activity_pub/mrf/fo_direct_reply_test.exs @@ -0,0 +1,117 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.FODirectReplyTest do + use Pleroma.DataCase + import Pleroma.Factory + + require Pleroma.Constants + + alias Pleroma.Object + alias Pleroma.Web.ActivityPub.MRF.FODirectReply + alias Pleroma.Web.CommonAPI + + test "replying to followers-only/private is changed to direct" do + batman = insert(:user, nickname: "batman") + robin = insert(:user, nickname: "robin") + + {:ok, post} = + CommonAPI.post(batman, %{ + status: "Has anyone seen Selina Kyle's latest selfies?", + visibility: "private" + }) + + reply = %{ + "type" => "Create", + "actor" => robin.ap_id, + "to" => [batman.ap_id, robin.follower_address], + "cc" => [], + "object" => %{ + "type" => "Note", + "actor" => robin.ap_id, + "content" => "@batman 🤤 ❤️ 🐈‍⬛", + "to" => [batman.ap_id, robin.follower_address], + "cc" => [], + "inReplyTo" => Object.normalize(post).data["id"] + } + } + + expected_to = [batman.ap_id] + expected_cc = [] + + assert {:ok, filtered} = FODirectReply.filter(reply) + + assert expected_to == filtered["to"] + assert expected_cc == filtered["cc"] + assert expected_to == filtered["object"]["to"] + assert expected_cc == filtered["object"]["cc"] + end + + test "replies to unlisted posts are unmodified" do + batman = insert(:user, nickname: "batman") + robin = insert(:user, nickname: "robin") + + {:ok, post} = + CommonAPI.post(batman, %{ + status: "Has anyone seen Selina Kyle's latest selfies?", + visibility: "unlisted" + }) + + reply = %{ + "type" => "Create", + "actor" => robin.ap_id, + "to" => [batman.ap_id, robin.follower_address], + "cc" => [], + "object" => %{ + "type" => "Note", + "actor" => robin.ap_id, + "content" => "@batman 🤤 ❤️ 🐈<200d>⬛", + "to" => [batman.ap_id, robin.follower_address], + "cc" => [], + "inReplyTo" => Object.normalize(post).data["id"] + } + } + + assert {:ok, filtered} = FODirectReply.filter(reply) + + assert match?(^filtered, reply) + end + + test "replies to public posts are unmodified" do + batman = insert(:user, nickname: "batman") + robin = insert(:user, nickname: "robin") + + {:ok, post} = + CommonAPI.post(batman, %{status: "Has anyone seen Selina Kyle's latest selfies?"}) + + reply = %{ + "type" => "Create", + "actor" => robin.ap_id, + "to" => [batman.ap_id, robin.follower_address], + "cc" => [], + "object" => %{ + "type" => "Note", + "actor" => robin.ap_id, + "content" => "@batman 🤤 ❤️ 🐈<200d>⬛", + "to" => [batman.ap_id, robin.follower_address], + "cc" => [], + "inReplyTo" => Object.normalize(post).data["id"] + } + } + + assert {:ok, filtered} = FODirectReply.filter(reply) + + assert match?(^filtered, reply) + end + + test "non-reply posts are unmodified" do + batman = insert(:user, nickname: "batman") + + {:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"}) + + assert {:ok, filtered} = FODirectReply.filter(post) + + assert match?(^filtered, post) + end +end diff --git a/test/pleroma/web/activity_pub/mrf/quiet_reply_test.exs b/test/pleroma/web/activity_pub/mrf/quiet_reply_test.exs new file mode 100644 index 0000000000..79e64d650c --- /dev/null +++ b/test/pleroma/web/activity_pub/mrf/quiet_reply_test.exs @@ -0,0 +1,140 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.MRF.QuietReplyTest do + use Pleroma.DataCase + import Pleroma.Factory + + require Pleroma.Constants + + alias Pleroma.Object + alias Pleroma.Web.ActivityPub.MRF.QuietReply + alias Pleroma.Web.CommonAPI + + test "replying to public post is forced to be quiet" do + batman = insert(:user, nickname: "batman") + robin = insert(:user, nickname: "robin") + + {:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"}) + + reply = %{ + "type" => "Create", + "actor" => robin.ap_id, + "to" => [ + batman.ap_id, + Pleroma.Constants.as_public() + ], + "cc" => [robin.follower_address], + "object" => %{ + "type" => "Note", + "actor" => robin.ap_id, + "content" => "@batman Wait up, I forgot my spandex!", + "to" => [ + batman.ap_id, + Pleroma.Constants.as_public() + ], + "cc" => [robin.follower_address], + "inReplyTo" => Object.normalize(post).data["id"] + } + } + + expected_to = [batman.ap_id, robin.follower_address] + expected_cc = [Pleroma.Constants.as_public()] + + assert {:ok, filtered} = QuietReply.filter(reply) + + assert expected_to == filtered["to"] + assert expected_cc == filtered["cc"] + assert expected_to == filtered["object"]["to"] + assert expected_cc == filtered["object"]["cc"] + end + + test "replying to unlisted post is unmodified" do + batman = insert(:user, nickname: "batman") + robin = insert(:user, nickname: "robin") + + {:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!", visibility: "private"}) + + reply = %{ + "type" => "Create", + "actor" => robin.ap_id, + "to" => [batman.ap_id], + "cc" => [], + "object" => %{ + "type" => "Note", + "actor" => robin.ap_id, + "content" => "@batman Wait up, I forgot my spandex!", + "to" => [batman.ap_id], + "cc" => [], + "inReplyTo" => Object.normalize(post).data["id"] + } + } + + assert {:ok, filtered} = QuietReply.filter(reply) + + assert match?(^filtered, reply) + end + + test "replying direct is unmodified" do + batman = insert(:user, nickname: "batman") + robin = insert(:user, nickname: "robin") + + {:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"}) + + reply = %{ + "type" => "Create", + "actor" => robin.ap_id, + "to" => [batman.ap_id], + "cc" => [], + "object" => %{ + "type" => "Note", + "actor" => robin.ap_id, + "content" => "@batman Wait up, I forgot my spandex!", + "to" => [batman.ap_id], + "cc" => [], + "inReplyTo" => Object.normalize(post).data["id"] + } + } + + assert {:ok, filtered} = QuietReply.filter(reply) + + assert match?(^filtered, reply) + end + + test "replying followers-only is unmodified" do + batman = insert(:user, nickname: "batman") + robin = insert(:user, nickname: "robin") + + {:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"}) + + reply = %{ + "type" => "Create", + "actor" => robin.ap_id, + "to" => [batman.ap_id, robin.follower_address], + "cc" => [], + "object" => %{ + "type" => "Note", + "actor" => robin.ap_id, + "content" => "@batman Wait up, I forgot my spandex!", + "to" => [batman.ap_id, robin.follower_address], + "cc" => [], + "inReplyTo" => Object.normalize(post).data["id"] + } + } + + assert {:ok, filtered} = QuietReply.filter(reply) + + assert match?(^filtered, reply) + end + + test "non-reply posts are unmodified" do + batman = insert(:user, nickname: "batman") + + {:ok, post} = CommonAPI.post(batman, %{status: "To the Batmobile!"}) + + assert {:ok, filtered} = QuietReply.filter(post) + + assert match?(^filtered, post) + end +end diff --git a/test/pleroma/web/activity_pub/object_validators/follow_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/follow_validation_test.exs index 371368e0e0..acf6e8d8fe 100644 --- a/test/pleroma/web/activity_pub/object_validators/follow_validation_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/follow_validation_test.exs @@ -22,5 +22,15 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.FollowValidationTest do test "validates a basic follow object", %{valid_follow: valid_follow} do assert {:ok, _follow, []} = ObjectValidator.validate(valid_follow, []) end + + test "supports a nil cc", %{valid_follow: valid_follow} do + valid_follow_with_nil_cc = Map.put(valid_follow, "cc", nil) + assert {:ok, _follow, []} = ObjectValidator.validate(valid_follow_with_nil_cc, []) + end + + test "supports an empty cc", %{valid_follow: valid_follow} do + valid_follow_with_empty_cc = Map.put(valid_follow, "cc", []) + assert {:ok, _follow, []} = ObjectValidator.validate(valid_follow_with_empty_cc, []) + end end end diff --git a/test/pleroma/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs index 569b6af1a6..99ed428777 100644 --- a/test/pleroma/web/activity_pub/publisher_test.exs +++ b/test/pleroma/web/activity_pub/publisher_test.exs @@ -3,6 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.ActivityPub.PublisherTest do + use Oban.Testing, repo: Pleroma.Repo use Pleroma.Web.ConnCase import ExUnit.CaptureLog @@ -13,6 +14,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do alias Pleroma.Activity alias Pleroma.Instances alias Pleroma.Object + alias Pleroma.Tests.ObanHelpers alias Pleroma.Web.ActivityPub.Publisher alias Pleroma.Web.CommonAPI @@ -150,32 +152,20 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do _actor = insert(:user) assert {:ok, %{body: "port 42"}} = - Publisher.publish_one(%{ + Publisher.prepare_one(%{ inbox: inbox42, activity_id: activity.id, unreachable_since: true }) + |> Publisher.publish_one() assert {:ok, %{body: "port 80"}} = - Publisher.publish_one(%{ + Publisher.prepare_one(%{ inbox: inbox80, activity_id: activity.id, unreachable_since: true }) - end - - test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is not specified", - Instances, - [:passthrough], - [] do - _actor = insert(:user) - inbox = "http://200.site/users/nick1/inbox" - activity = insert(:note_activity) - - assert {:ok, _} = - Publisher.publish_one(%{inbox: inbox, activity_id: activity.id}) - - assert called(Instances.set_reachable(inbox)) + |> Publisher.publish_one() end test_with_mock "calls `Instances.set_reachable` on successful federation if `unreachable_since` is set", @@ -187,11 +177,12 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do activity = insert(:note_activity) assert {:ok, _} = - Publisher.publish_one(%{ + Publisher.prepare_one(%{ inbox: inbox, activity_id: activity.id, - unreachable_since: NaiveDateTime.utc_now() + unreachable_since: NaiveDateTime.utc_now() |> NaiveDateTime.to_string() }) + |> Publisher.publish_one() assert called(Instances.set_reachable(inbox)) end @@ -205,11 +196,12 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do activity = insert(:note_activity) assert {:ok, _} = - Publisher.publish_one(%{ + Publisher.prepare_one(%{ inbox: inbox, activity_id: activity.id, unreachable_since: nil }) + |> Publisher.publish_one() refute called(Instances.set_reachable(inbox)) end @@ -223,7 +215,8 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do activity = insert(:note_activity) assert {:cancel, _} = - Publisher.publish_one(%{inbox: inbox, activity_id: activity.id}) + Publisher.prepare_one(%{inbox: inbox, activity_id: activity.id}) + |> Publisher.publish_one() assert called(Instances.set_unreachable(inbox)) end @@ -238,10 +231,11 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do assert capture_log(fn -> assert {:error, _} = - Publisher.publish_one(%{ + Publisher.prepare_one(%{ inbox: inbox, activity_id: activity.id }) + |> Publisher.publish_one() end) =~ "connrefused" assert called(Instances.set_unreachable(inbox)) @@ -256,7 +250,8 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do activity = insert(:note_activity) assert {:ok, _} = - Publisher.publish_one(%{inbox: inbox, activity_id: activity.id}) + Publisher.prepare_one(%{inbox: inbox, activity_id: activity.id}) + |> Publisher.publish_one() refute called(Instances.set_unreachable(inbox)) end @@ -271,11 +266,12 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do assert capture_log(fn -> assert {:error, _} = - Publisher.publish_one(%{ + Publisher.prepare_one(%{ inbox: inbox, activity_id: activity.id, - unreachable_since: NaiveDateTime.utc_now() + unreachable_since: NaiveDateTime.utc_now() |> NaiveDateTime.to_string() }) + |> Publisher.publish_one() end) =~ "connrefused" refute called(Instances.set_unreachable(inbox)) @@ -310,12 +306,15 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do assert res == :ok - assert not called( - Publisher.enqueue_one(%{ - inbox: "https://domain.com/users/nick1/inbox", - activity_id: note_activity.id - }) - ) + refute_enqueued( + worker: "Pleroma.Workers.PublisherWorker", + args: %{ + "params" => %{ + inbox: "https://domain.com/users/nick1/inbox", + activity_id: note_activity.id + } + } + ) end test_with_mock "Publishes a non-public activity to non-quarantined instances.", @@ -345,15 +344,16 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do assert res == :ok - assert called( - Publisher.enqueue_one( - %{ - inbox: "https://domain.com/users/nick1/inbox", - activity_id: note_activity.id - }, - priority: 1 - ) - ) + assert_enqueued( + worker: "Pleroma.Workers.PublisherWorker", + args: %{ + "params" => %{ + inbox: "https://domain.com/users/nick1/inbox", + activity_id: note_activity.id + } + }, + priority: 1 + ) end test_with_mock "Publishes to directly addressed actors with higher priority.", @@ -403,12 +403,15 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do res = Publisher.publish(actor, note_activity) assert res == :ok - assert called( - Publisher.enqueue_one(%{ - inbox: "https://domain.com/users/nick1/inbox", - activity_id: note_activity.id - }) - ) + assert_enqueued( + worker: "Pleroma.Workers.PublisherWorker", + args: %{ + "params" => %{ + inbox: "https://domain.com/users/nick1/inbox", + activity_id: note_activity.id + } + } + ) end test_with_mock "publishes a delete activity to peers who signed fetch requests to the create acitvity/object.", @@ -452,25 +455,69 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do res = Publisher.publish(actor, delete) assert res == :ok - assert called( - Publisher.enqueue_one( - %{ - inbox: "https://domain.com/users/nick1/inbox", - activity_id: delete.id - }, - priority: 1 - ) - ) - - assert called( - Publisher.enqueue_one( - %{ - inbox: "https://domain2.com/users/nick1/inbox", - activity_id: delete.id - }, - priority: 1 - ) - ) + assert_enqueued( + worker: "Pleroma.Workers.PublisherWorker", + args: %{ + "params" => %{ + inbox: "https://domain.com/users/nick1/inbox", + activity_id: delete.id + } + }, + priority: 1 + ) + + assert_enqueued( + worker: "Pleroma.Workers.PublisherWorker", + args: %{ + "params" => %{ + inbox: "https://domain2.com/users/nick1/inbox", + activity_id: delete.id + } + }, + priority: 1 + ) end end + + test "cc in prepared json for a follow request is an empty list" do + user = insert(:user) + remote_user = insert(:user, local: false) + + {:ok, _, _, activity} = CommonAPI.follow(remote_user, user) + + assert_enqueued( + worker: "Pleroma.Workers.PublisherWorker", + args: %{ + "activity_id" => activity.id, + "op" => "publish" + } + ) + + ObanHelpers.perform_all() + + expected_params = + %{ + "activity_id" => activity.id, + "inbox" => remote_user.inbox, + "unreachable_since" => nil + } + + assert_enqueued( + worker: "Pleroma.Workers.PublisherWorker", + args: %{ + "op" => "publish_one", + "params" => expected_params + } + ) + + # params need to be atom keys for Publisher.prepare_one. + # this is done in the Oban job. + expected_params = Map.new(expected_params, fn {k, v} -> {String.to_atom(k), v} end) + + %{json: json} = Publisher.prepare_one(expected_params) + + {:ok, decoded} = Jason.decode(json) + + assert decoded["cc"] == [] + end end diff --git a/test/pleroma/web/activity_pub/side_effects_test.exs b/test/pleroma/web/activity_pub/side_effects_test.exs index 68922e536a..4a18cab686 100644 --- a/test/pleroma/web/activity_pub/side_effects_test.exs +++ b/test/pleroma/web/activity_pub/side_effects_test.exs @@ -54,20 +54,17 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do [ stream: fn _, _ -> nil end ] - }, - { - Pleroma.Web.Push, - [], - [ - send: fn _ -> nil end - ] } ]) do SideEffects.handle_after_transaction(meta) assert called(Pleroma.Web.Streamer.stream(["user", "user:notification"], notification)) assert called(Pleroma.Web.Streamer.stream(["user", "user:pleroma_chat"], :_)) - assert called(Pleroma.Web.Push.send(notification)) + + assert_enqueued( + worker: "Pleroma.Workers.WebPusherWorker", + args: %{"notification_id" => notification.id, "op" => "web_push"} + ) end end end diff --git a/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs index da46f063ac..851c60850a 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs @@ -119,8 +119,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier.UserUpdateHandlingTest do user = User.get_cached_by_ap_id(user.ap_id) assert user.fields == [ - %{"name" => "foo", "value" => "updated"}, - %{"name" => "foo1", "value" => "updated"} + %{"name" => "foo", "value" => "bar"}, + %{"name" => "foo11", "value" => "bar11"} ] update_data = diff --git a/test/pleroma/web/activity_pub/views/user_view_test.exs b/test/pleroma/web/activity_pub/views/user_view_test.exs index c94f8a2bc5..651e535ac4 100644 --- a/test/pleroma/web/activity_pub/views/user_view_test.exs +++ b/test/pleroma/web/activity_pub/views/user_view_test.exs @@ -138,7 +138,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do test "sets totalItems to zero when followers are hidden" do user = insert(:user) other_user = insert(:user) - {:ok, _other_user, user, _activity} = CommonAPI.follow(user, other_user) + {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user) assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user}) user = Map.merge(user, %{hide_followers_count: true, hide_followers: true}) refute UserView.render("followers.json", %{user: user}) |> Map.has_key?("totalItems") @@ -147,7 +147,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do test "sets correct totalItems when followers are hidden but the follower counter is not" do user = insert(:user) other_user = insert(:user) - {:ok, _other_user, user, _activity} = CommonAPI.follow(user, other_user) + {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user) assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user}) user = Map.merge(user, %{hide_followers_count: false, hide_followers: true}) assert %{"totalItems" => 1} = UserView.render("followers.json", %{user: user}) @@ -158,7 +158,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do test "sets totalItems to zero when follows are hidden" do user = insert(:user) other_user = insert(:user) - {:ok, user, _other_user, _activity} = CommonAPI.follow(other_user, user) + {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user}) user = Map.merge(user, %{hide_follows_count: true, hide_follows: true}) assert %{"totalItems" => 0} = UserView.render("following.json", %{user: user}) @@ -167,7 +167,7 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do test "sets correct totalItems when follows are hidden but the follow counter is not" do user = insert(:user) other_user = insert(:user) - {:ok, user, _other_user, _activity} = CommonAPI.follow(other_user, user) + {:ok, _other_user, user, _activity} = CommonAPI.follow(other_user, user) assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user}) user = Map.merge(user, %{hide_follows_count: false, hide_follows: true}) assert %{"totalItems" => 1} = UserView.render("following.json", %{user: user}) diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index 4cdd3cffaa..73230a58c5 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -1420,7 +1420,7 @@ defmodule Pleroma.Web.CommonAPITest do describe "follow/2" do test "directly follows a non-locked local user" do [follower, followed] = insert_pair(:user) - {:ok, follower, followed, _} = CommonAPI.follow(followed, follower) + {:ok, followed, follower, _} = CommonAPI.follow(followed, follower) assert User.following?(follower, followed) end @@ -1429,7 +1429,7 @@ defmodule Pleroma.Web.CommonAPITest do describe "unfollow/2" do test "also unsubscribes a user" do [follower, followed] = insert_pair(:user) - {:ok, follower, followed, _} = CommonAPI.follow(followed, follower) + {:ok, followed, follower, _} = CommonAPI.follow(followed, follower) {:ok, _subscription} = User.subscribe(follower, followed) assert User.subscribed_to?(follower, followed) @@ -1441,7 +1441,7 @@ defmodule Pleroma.Web.CommonAPITest do test "also unpins a user" do [follower, followed] = insert_pair(:user) - {:ok, follower, followed, _} = CommonAPI.follow(followed, follower) + {:ok, followed, follower, _} = CommonAPI.follow(followed, follower) {:ok, _endorsement} = User.endorse(follower, followed) assert User.endorses?(follower, followed) @@ -1455,7 +1455,7 @@ defmodule Pleroma.Web.CommonAPITest do follower = insert(:user) followed = insert(:user, is_locked: true) - assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} = + assert {:ok, followed, follower, %{id: activity_id, data: %{"state" => "pending"}}} = CommonAPI.follow(followed, follower) assert User.get_follow_state(follower, followed) == :follow_pending @@ -1477,7 +1477,7 @@ defmodule Pleroma.Web.CommonAPITest do follower = insert(:user) followed = insert(:user, is_locked: true, local: false) - assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} = + assert {:ok, followed, follower, %{id: activity_id, data: %{"state" => "pending"}}} = CommonAPI.follow(followed, follower) assert User.get_follow_state(follower, followed) == :follow_pending diff --git a/test/pleroma/web/mastodon_api/controllers/marker_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/marker_controller_test.exs index d8f7b2638f..4050528ffd 100644 --- a/test/pleroma/web/mastodon_api/controllers/marker_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/marker_controller_test.exs @@ -5,6 +5,10 @@ defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest do use Pleroma.Web.ConnCase, async: true + alias Pleroma.Notification + alias Pleroma.Repo + alias Pleroma.Web.CommonAPI + import Pleroma.Factory describe "GET /api/v1/markers" do @@ -127,5 +131,36 @@ defmodule Pleroma.Web.MastodonAPI.MarkerControllerTest do assert response == %{"error" => "Insufficient permissions: write:statuses."} end + + test "marks notifications as read", %{conn: conn} do + user1 = insert(:user) + token = insert(:oauth_token, user: user1, scopes: ["write:statuses"]) + + user2 = insert(:user) + {:ok, _activity1} = CommonAPI.post(user2, %{status: "hi @#{user1.nickname}"}) + {:ok, _activity2} = CommonAPI.post(user2, %{status: "hi @#{user1.nickname}"}) + {:ok, _activity3} = CommonAPI.post(user2, %{status: "HIE @#{user1.nickname}"}) + + [notification3, notification2, notification1] = Notification.for_user(user1, %{limit: 3}) + + refute Repo.get(Notification, notification1.id).seen + refute Repo.get(Notification, notification2.id).seen + refute Repo.get(Notification, notification3.id).seen + + conn + |> assign(:user, user1) + |> assign(:token, token) + |> put_req_header("content-type", "application/json") + |> post("/api/v1/markers", %{ + notifications: %{last_read_id: to_string(notification2.id)} + }) + |> json_response_and_validate_schema(200) + + [notification3, notification2, notification1] = Notification.for_user(user1, %{limit: 3}) + + assert Repo.get(Notification, notification1.id).seen + assert Repo.get(Notification, notification2.id).seen + refute Repo.get(Notification, notification3.id).seen + end end end diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index 904bf1471a..a4bca6cf9a 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -922,13 +922,23 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do %{id: id1} = insert(:note_activity) %{id: id2} = insert(:note_activity) - query_string = "ids[]=#{id1}&ids[]=#{id2}" + query_string = "id[]=#{id1}&id[]=#{id2}" conn = get(conn, "/api/v1/statuses/?#{query_string}") assert [%{"id" => ^id1}, %{"id" => ^id2}] = Enum.sort_by(json_response_and_validate_schema(conn, :ok), & &1["id"]) end + test "get statuses by IDs falls back to ids[]" do + %{conn: conn} = oauth_access(["read:statuses"]) + %{id: id} = insert(:note_activity) + + query_string = "ids[]=#{id}" + conn = get(conn, "/api/v1/statuses/?#{query_string}") + + assert [%{"id" => ^id}] = json_response_and_validate_schema(conn, 200) + end + describe "getting statuses by ids with restricted unauthenticated for local and remote" do setup do: local_and_remote_activities() @@ -937,7 +947,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true) test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do - res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}") + res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}") assert json_response_and_validate_schema(res_conn, 200) == [] end @@ -945,7 +955,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do test "if user is authenticated", %{local: local, remote: remote} do %{conn: conn} = oauth_access(["read"]) - res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}") + res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}") assert length(json_response_and_validate_schema(res_conn, 200)) == 2 end @@ -957,7 +967,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do setup do: clear_config([:restrict_unauthenticated, :activities, :local], true) test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do - res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}") + res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}") remote_id = remote.id assert [%{"id" => ^remote_id}] = json_response_and_validate_schema(res_conn, 200) @@ -966,7 +976,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do test "if user is authenticated", %{local: local, remote: remote} do %{conn: conn} = oauth_access(["read"]) - res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}") + res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}") assert length(json_response_and_validate_schema(res_conn, 200)) == 2 end @@ -978,7 +988,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true) test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do - res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}") + res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}") local_id = local.id assert [%{"id" => ^local_id}] = json_response_and_validate_schema(res_conn, 200) @@ -987,7 +997,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do test "if user is authenticated", %{local: local, remote: remote} do %{conn: conn} = oauth_access(["read"]) - res_conn = get(conn, "/api/v1/statuses?ids[]=#{local.id}&ids[]=#{remote.id}") + res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}") assert length(json_response_and_validate_schema(res_conn, 200)) == 2 end @@ -2241,7 +2251,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do result = conn - |> get("/api/v1/statuses/?ids[]=#{activity.id}") + |> get("/api/v1/statuses/?id[]=#{activity.id}") |> json_response_and_validate_schema(200) assert [ @@ -2254,7 +2264,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do result = conn - |> get("/api/v1/statuses/?ids[]=#{activity.id}&with_muted=true") + |> get("/api/v1/statuses/?id[]=#{activity.id}&with_muted=true") |> json_response_and_validate_schema(200) assert [ diff --git a/test/pleroma/web/mastodon_api/mastodon_api_controller_test.exs b/test/pleroma/web/mastodon_api/mastodon_api_controller_test.exs index 622c5e3d5f..8e7fe40137 100644 --- a/test/pleroma/web/mastodon_api/mastodon_api_controller_test.exs +++ b/test/pleroma/web/mastodon_api/mastodon_api_controller_test.exs @@ -6,15 +6,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do use Pleroma.Web.ConnCase, async: true describe "empty_array/2 (stubs)" do - test "GET /api/v1/accounts/:id/identity_proofs" do - %{user: user, conn: conn} = oauth_access(["read:accounts"]) - - assert [] == - conn - |> get("/api/v1/accounts/#{user.id}/identity_proofs") - |> json_response(200) - end - test "GET /api/v1/endorsements" do %{conn: conn} = oauth_access(["read:accounts"]) diff --git a/test/pleroma/web/mastodon_api/views/account_view_test.exs b/test/pleroma/web/mastodon_api/views/account_view_test.exs index f0711fa0d6..dca64853d1 100644 --- a/test/pleroma/web/mastodon_api/views/account_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs @@ -493,7 +493,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do user = insert(:user) other_user = insert(:user, is_locked: true) - {:ok, user, other_user, _} = CommonAPI.follow(other_user, user) + {:ok, other_user, user, _} = CommonAPI.follow(other_user, user) user = User.get_cached_by_id(user.id) other_user = User.get_cached_by_id(other_user.id) @@ -560,8 +560,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do test "shows when follows/followers are hidden" do user = insert(:user, hide_followers: true, hide_follows: true) other_user = insert(:user) - {:ok, user, other_user, _activity} = CommonAPI.follow(other_user, user) - {:ok, _other_user, user, _activity} = CommonAPI.follow(user, other_user) + {:ok, other_user, user, _activity} = CommonAPI.follow(other_user, user) + {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user) assert %{ followers_count: 1, @@ -573,11 +573,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do test "shows actual follower/following count to the account owner" do user = insert(:user, hide_followers: true, hide_follows: true) other_user = insert(:user) - {:ok, user, other_user, _activity} = CommonAPI.follow(other_user, user) + {:ok, other_user, user, _activity} = CommonAPI.follow(other_user, user) assert User.following?(user, other_user) assert Pleroma.FollowingRelationship.follower_count(other_user) == 1 - {:ok, _other_user, user, _activity} = CommonAPI.follow(user, other_user) + {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user) assert %{ followers_count: 1, @@ -696,7 +696,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user}) other_user = insert(:user) - {:ok, _other_user, user, _activity} = CommonAPI.follow(user, other_user) + {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user) assert %{locked: true, follow_requests_count: 1} = AccountView.render("show.json", %{user: user, for: user}) @@ -708,7 +708,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user}) other_user = insert(:user) - {:ok, other_user, user, _activity} = CommonAPI.follow(user, other_user) + {:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user) assert %{locked: true, follow_requests_count: 1} = AccountView.render("show.json", %{user: user, for: user}) @@ -725,7 +725,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user}) other_user = insert(:user) - {:ok, other_user, user, _activity} = CommonAPI.follow(user, other_user) + {:ok, user, other_user, _activity} = CommonAPI.follow(user, other_user) assert %{locked: true, follow_requests_count: 1} = AccountView.render("show.json", %{user: user, for: user}) @@ -742,7 +742,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do assert %{locked: true} = AccountView.render("show.json", %{user: user, for: user}) other_user = insert(:user) - {:ok, _other_user, user, _activity} = CommonAPI.follow(user, other_user) + {:ok, user, _other_user, _activity} = CommonAPI.follow(user, other_user) {:ok, user} = User.update_and_set_cache(user, %{is_locked: false}) diff --git a/test/pleroma/web/mastodon_api/views/notification_view_test.exs b/test/pleroma/web/mastodon_api/views/notification_view_test.exs index fae6728713..75ab375aae 100644 --- a/test/pleroma/web/mastodon_api/views/notification_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/notification_view_test.exs @@ -132,7 +132,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationViewTest do test "Follow notification" do follower = insert(:user) followed = insert(:user) - {:ok, follower, followed, _activity} = CommonAPI.follow(followed, follower) + {:ok, followed, follower, _activity} = CommonAPI.follow(followed, follower) notification = Notification |> Repo.one() |> Repo.preload(:activity) expected = %{ diff --git a/test/pleroma/web/pleroma_api/controllers/user_import_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/user_import_controller_test.exs index 52a62e4166..efdc743e3e 100644 --- a/test/pleroma/web/pleroma_api/controllers/user_import_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/user_import_controller_test.exs @@ -22,7 +22,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do test "it returns HTTP 200", %{conn: conn} do user2 = insert(:user) - assert "job started" == + assert "jobs started" == conn |> put_req_header("content-type", "application/json") |> post("/api/pleroma/follow_import", %{"list" => "#{user2.ap_id}"}) @@ -38,7 +38,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do "Account address,Show boosts\n#{user2.ap_id},true" end} ]) do - assert "job started" == + assert "jobs started" == conn |> put_req_header("content-type", "application/json") |> post("/api/pleroma/follow_import", %{ @@ -46,9 +46,9 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do }) |> json_response_and_validate_schema(200) - assert [{:ok, job_result}] = ObanHelpers.perform_all() - assert job_result == [refresh_record(user2)] - assert [%Pleroma.User{follower_count: 1}] = job_result + assert [{:ok, updated_user}] = ObanHelpers.perform_all() + assert updated_user.id == user2.id + assert updated_user.follower_count == 1 end end @@ -63,7 +63,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do }) |> json_response_and_validate_schema(200) - assert response == "job started" + assert response == "jobs started" end test "requires 'follow' or 'write:follows' permissions" do @@ -102,14 +102,20 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do ] |> Enum.join("\n") - assert "job started" == + assert "jobs started" == conn |> put_req_header("content-type", "application/json") |> post("/api/pleroma/follow_import", %{"list" => identifiers}) |> json_response_and_validate_schema(200) - assert [{:ok, job_result}] = ObanHelpers.perform_all() - assert job_result == Enum.map(users, &refresh_record/1) + results = ObanHelpers.perform_all() + + returned_users = + for {_, returned_user} <- results do + returned_user + end + + assert returned_users == Enum.map(users, &refresh_record/1) end end @@ -120,7 +126,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do test "it returns HTTP 200", %{conn: conn} do user2 = insert(:user) - assert "job started" == + assert "jobs started" == conn |> put_req_header("content-type", "application/json") |> post("/api/pleroma/blocks_import", %{"list" => "#{user2.ap_id}"}) @@ -133,7 +139,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do with_mocks([ {File, [], read!: fn "blocks_list.txt" -> "#{user2.ap_id} #{user3.ap_id}" end} ]) do - assert "job started" == + assert "jobs started" == conn |> put_req_header("content-type", "application/json") |> post("/api/pleroma/blocks_import", %{ @@ -141,8 +147,14 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do }) |> json_response_and_validate_schema(200) - assert [{:ok, job_result}] = ObanHelpers.perform_all() - assert job_result == users + results = ObanHelpers.perform_all() + + returned_users = + for {_, returned_user} <- results do + returned_user + end + + assert returned_users == users end end @@ -159,14 +171,25 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do ] |> Enum.join(" ") - assert "job started" == + assert "jobs started" == conn |> put_req_header("content-type", "application/json") |> post("/api/pleroma/blocks_import", %{"list" => identifiers}) |> json_response_and_validate_schema(200) - assert [{:ok, job_result}] = ObanHelpers.perform_all() - assert job_result == users + results = ObanHelpers.perform_all() + + returned_user_ids = + for {_, user} <- results do + user.id + end + + original_user_ids = + for user <- users do + user.id + end + + assert match?(^original_user_ids, returned_user_ids) end end @@ -177,24 +200,25 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do test "it returns HTTP 200", %{user: user, conn: conn} do user2 = insert(:user) - assert "job started" == + assert "jobs started" == conn |> put_req_header("content-type", "application/json") |> post("/api/pleroma/mutes_import", %{"list" => "#{user2.ap_id}"}) |> json_response_and_validate_schema(200) - assert [{:ok, job_result}] = ObanHelpers.perform_all() - assert job_result == [user2] + [{:ok, result_user}] = ObanHelpers.perform_all() + + assert result_user == refresh_record(user2) assert Pleroma.User.mutes?(user, user2) end test "it imports mutes users from file", %{user: user, conn: conn} do - users = [user2, user3] = insert_list(2, :user) + [user2, user3] = insert_list(2, :user) with_mocks([ {File, [], read!: fn "mutes_list.txt" -> "#{user2.ap_id} #{user3.ap_id}" end} ]) do - assert "job started" == + assert "jobs started" == conn |> put_req_header("content-type", "application/json") |> post("/api/pleroma/mutes_import", %{ @@ -202,14 +226,19 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do }) |> json_response_and_validate_schema(200) - assert [{:ok, job_result}] = ObanHelpers.perform_all() - assert job_result == users - assert Enum.all?(users, &Pleroma.User.mutes?(user, &1)) + results = ObanHelpers.perform_all() + + returned_users = + for {_, returned_user} <- results do + returned_user + end + + assert Enum.all?(returned_users, &Pleroma.User.mutes?(user, &1)) end end test "it imports mutes with different nickname variations", %{user: user, conn: conn} do - users = [user2, user3, user4, user5, user6] = insert_list(5, :user) + [user2, user3, user4, user5, user6] = insert_list(5, :user) identifiers = [ @@ -221,15 +250,20 @@ defmodule Pleroma.Web.PleromaAPI.UserImportControllerTest do ] |> Enum.join(" ") - assert "job started" == + assert "jobs started" == conn |> put_req_header("content-type", "application/json") |> post("/api/pleroma/mutes_import", %{"list" => identifiers}) |> json_response_and_validate_schema(200) - assert [{:ok, job_result}] = ObanHelpers.perform_all() - assert job_result == users - assert Enum.all?(users, &Pleroma.User.mutes?(user, &1)) + results = ObanHelpers.perform_all() + + returned_users = + for {_, returned_user} <- results do + returned_user + end + + assert Enum.all?(returned_users, &Pleroma.User.mutes?(user, &1)) end end end diff --git a/test/pleroma/web/views/streamer_view_test.exs b/test/pleroma/web/views/streamer_view_test.exs new file mode 100644 index 0000000000..b1c5f52abe --- /dev/null +++ b/test/pleroma/web/views/streamer_view_test.exs @@ -0,0 +1,100 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.StreamerViewTest do + use Pleroma.Web.ConnCase, async: true + # import ExUnit.CaptureLog + import Pleroma.Factory + + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.StreamerView + + describe "follow_relationships_update.json" do + test "shows follower/following count normally" do + other_user = insert(:user) + %{id: following_id} = following = insert(:user) + follower = insert(:user) + + {:ok, _, _, _} = CommonAPI.follow(other_user, following) + {:ok, following, follower, _activity} = CommonAPI.follow(following, follower) + + result = + StreamerView.render( + "follow_relationships_update.json", + %{follower: follower, following: following, state: :test}, + "user:test" + ) + + {:ok, %{"payload" => payload}} = Jason.decode(result) + + {:ok, decoded_payload} = Jason.decode(payload) + + # check the payload updating the user that was followed + assert match?( + %{"follower_count" => 1, "following_count" => 1, "id" => ^following_id}, + decoded_payload["following"] + ) + end + + test "hides follower count for :hide_followers and :hide_followers_count" do + user_attrs = [%{hide_followers: true}, %{hide_followers_count: true}] + + Enum.each(user_attrs, fn attrs -> + other_user = insert(:user) + %{id: following_id} = following = insert(:user, attrs) + follower = insert(:user) + + {:ok, _, _, _} = CommonAPI.follow(other_user, following) + {:ok, following, follower, _activity} = CommonAPI.follow(following, follower) + + result = + StreamerView.render( + "follow_relationships_update.json", + %{follower: follower, following: following, state: :test}, + "user:test" + ) + + {:ok, %{"payload" => payload}} = Jason.decode(result) + + {:ok, decoded_payload} = Jason.decode(payload) + + # check the payload updating the user that was followed + assert match?( + %{"follower_count" => 0, "following_count" => 1, "id" => ^following_id}, + decoded_payload["following"] + ) + end) + end + + test "hides follows count for :hide_follows and :hide_follows_count" do + user_attrs = [%{hide_follows: true}, %{hide_follows_count: true}] + + Enum.each(user_attrs, fn attrs -> + other_user = insert(:user) + %{id: following_id} = following = insert(:user, attrs) + follower = insert(:user) + + {:ok, _, _, _} = CommonAPI.follow(other_user, following) + {:ok, following, follower, _activity} = CommonAPI.follow(following, follower) + + result = + StreamerView.render( + "follow_relationships_update.json", + %{follower: follower, following: following, state: :test}, + "user:test" + ) + + {:ok, %{"payload" => payload}} = Jason.decode(result) + + {:ok, decoded_payload} = Jason.decode(payload) + + # check the payload updating the user that was followed + assert match?( + %{"follower_count" => 1, "following_count" => 0, "id" => ^following_id}, + decoded_payload["following"] + ) + end) + end + end +end diff --git a/test/pleroma/workers/purge_expired_activity_test.exs b/test/pleroma/workers/purge_expired_activity_test.exs index 040ff6a51c..ea563d3d31 100644 --- a/test/pleroma/workers/purge_expired_activity_test.exs +++ b/test/pleroma/workers/purge_expired_activity_test.exs @@ -14,10 +14,12 @@ defmodule Pleroma.Workers.PurgeExpiredActivityTest do activity = insert(:note_activity) assert {:ok, _} = - PurgeExpiredActivity.enqueue(%{ - activity_id: activity.id, - expires_at: DateTime.add(DateTime.utc_now(), 3601) - }) + PurgeExpiredActivity.enqueue( + %{ + activity_id: activity.id + }, + scheduled_at: DateTime.add(DateTime.utc_now(), 3601) + ) assert_enqueued( worker: Pleroma.Workers.PurgeExpiredActivity, @@ -34,10 +36,12 @@ defmodule Pleroma.Workers.PurgeExpiredActivityTest do activity = insert(:note_activity) assert {:ok, _} = - PurgeExpiredActivity.enqueue(%{ - activity_id: activity.id, - expires_at: DateTime.add(DateTime.utc_now(), 3601) - }) + PurgeExpiredActivity.enqueue( + %{ + activity_id: activity.id + }, + scheduled_at: DateTime.add(DateTime.utc_now(), 3601) + ) user = Pleroma.User.get_by_ap_id(activity.actor) Pleroma.Repo.delete(user) @@ -48,10 +52,12 @@ defmodule Pleroma.Workers.PurgeExpiredActivityTest do test "error if actiivity was not found" do assert {:ok, _} = - PurgeExpiredActivity.enqueue(%{ - activity_id: "some_id", - expires_at: DateTime.add(DateTime.utc_now(), 3601) - }) + PurgeExpiredActivity.enqueue( + %{ + activity_id: "some_id" + }, + scheduled_at: DateTime.add(DateTime.utc_now(), 3601) + ) assert {:cancel, :activity_not_found} = perform_job(Pleroma.Workers.PurgeExpiredActivity, %{activity_id: "some_if"}) diff --git a/test/pleroma/workers/receiver_worker_test.exs b/test/pleroma/workers/receiver_worker_test.exs index 33be910853..4d53c44ed8 100644 --- a/test/pleroma/workers/receiver_worker_test.exs +++ b/test/pleroma/workers/receiver_worker_test.exs @@ -9,6 +9,7 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do import Mock import Pleroma.Factory + alias Pleroma.User alias Pleroma.Web.Federator alias Pleroma.Workers.ReceiverWorker @@ -51,25 +52,106 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do }) end - test "it can validate the signature" do - Tesla.Mock.mock(fn - %{url: "https://mastodon.social/users/bastianallgeier"} -> - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/bastianallgeier.json"), - headers: [{"content-type", "application/activity+json"}] - } + describe "cancels on a failed user fetch" do + setup do + Tesla.Mock.mock(fn + %{url: "https://springfield.social/users/bart"} -> + %Tesla.Env{ + status: 403, + body: "" + } - %{url: "https://mastodon.social/users/bastianallgeier/collections/featured"} -> - %Tesla.Env{ - status: 200, - headers: [{"content-type", "application/activity+json"}], - body: - File.read!("test/fixtures/users_mock/masto_featured.json") - |> String.replace("{{domain}}", "mastodon.social") - |> String.replace("{{nickname}}", "bastianallgeier") - } + %{url: "https://springfield.social/users/troymcclure"} -> + %Tesla.Env{ + status: 404, + body: "" + } + + %{url: "https://springfield.social/users/hankscorpio"} -> + %Tesla.Env{ + status: 410, + body: "" + } + end) + end + + test "when request returns a 403" do + params = + insert(:note_activity).data + |> Map.put("actor", "https://springfield.social/users/bart") + + {:ok, oban_job} = + Federator.incoming_ap_doc(%{ + method: "POST", + req_headers: [], + request_path: "/inbox", + params: params, + query_string: "" + }) + + assert {:cancel, {:error, :forbidden}} = ReceiverWorker.perform(oban_job) + end + test "when request returns a 404" do + params = + insert(:note_activity).data + |> Map.put("actor", "https://springfield.social/users/troymcclure") + + {:ok, oban_job} = + Federator.incoming_ap_doc(%{ + method: "POST", + req_headers: [], + request_path: "/inbox", + params: params, + query_string: "" + }) + + assert {:cancel, {:error, :not_found}} = ReceiverWorker.perform(oban_job) + end + + test "when request returns a 410" do + params = + insert(:note_activity).data + |> Map.put("actor", "https://springfield.social/users/hankscorpio") + + {:ok, oban_job} = + Federator.incoming_ap_doc(%{ + method: "POST", + req_headers: [], + request_path: "/inbox", + params: params, + query_string: "" + }) + + assert {:cancel, {:error, :not_found}} = ReceiverWorker.perform(oban_job) + end + + test "when user account is disabled" do + user = insert(:user) + + fake_activity = URI.parse(user.ap_id) |> Map.put(:path, "/fake-activity") |> to_string + + params = + insert(:note_activity, user: user).data + |> Map.put("id", fake_activity) + + {:ok, %User{}} = User.set_activation(user, false) + + {:ok, oban_job} = + Federator.incoming_ap_doc(%{ + method: "POST", + req_headers: [], + request_path: "/inbox", + params: params, + query_string: "" + }) + + assert {:cancel, {:user_active, false}} = ReceiverWorker.perform(oban_job) + end + end + + test "it can validate the signature" do + Tesla.Mock.mock(fn %{url: "https://phpc.social/users/denniskoch"} -> %Tesla.Env{ status: 200, @@ -86,136 +168,10 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do |> String.replace("{{domain}}", "phpc.social") |> String.replace("{{nickname}}", "denniskoch") } - - %{url: "https://mastodon.social/users/bastianallgeier/statuses/112846516276907281"} -> - %Tesla.Env{ - status: 200, - headers: [{"content-type", "application/activity+json"}], - body: File.read!("test/fixtures/receiver_worker_signature_activity.json") - } end) - params = %{ - "@context" => [ - "https://www.w3.org/ns/activitystreams", - "https://w3id.org/security/v1", - %{ - "claim" => %{"@id" => "toot:claim", "@type" => "@id"}, - "memorial" => "toot:memorial", - "atomUri" => "ostatus:atomUri", - "manuallyApprovesFollowers" => "as:manuallyApprovesFollowers", - "blurhash" => "toot:blurhash", - "ostatus" => "http://ostatus.org#", - "discoverable" => "toot:discoverable", - "focalPoint" => %{"@container" => "@list", "@id" => "toot:focalPoint"}, - "votersCount" => "toot:votersCount", - "Hashtag" => "as:Hashtag", - "Emoji" => "toot:Emoji", - "alsoKnownAs" => %{"@id" => "as:alsoKnownAs", "@type" => "@id"}, - "sensitive" => "as:sensitive", - "movedTo" => %{"@id" => "as:movedTo", "@type" => "@id"}, - "inReplyToAtomUri" => "ostatus:inReplyToAtomUri", - "conversation" => "ostatus:conversation", - "Device" => "toot:Device", - "schema" => "http://schema.org#", - "toot" => "http://joinmastodon.org/ns#", - "cipherText" => "toot:cipherText", - "suspended" => "toot:suspended", - "messageType" => "toot:messageType", - "featuredTags" => %{"@id" => "toot:featuredTags", "@type" => "@id"}, - "Curve25519Key" => "toot:Curve25519Key", - "deviceId" => "toot:deviceId", - "Ed25519Signature" => "toot:Ed25519Signature", - "featured" => %{"@id" => "toot:featured", "@type" => "@id"}, - "devices" => %{"@id" => "toot:devices", "@type" => "@id"}, - "value" => "schema:value", - "PropertyValue" => "schema:PropertyValue", - "messageFranking" => "toot:messageFranking", - "publicKeyBase64" => "toot:publicKeyBase64", - "identityKey" => %{"@id" => "toot:identityKey", "@type" => "@id"}, - "Ed25519Key" => "toot:Ed25519Key", - "indexable" => "toot:indexable", - "EncryptedMessage" => "toot:EncryptedMessage", - "fingerprintKey" => %{"@id" => "toot:fingerprintKey", "@type" => "@id"} - } - ], - "actor" => "https://phpc.social/users/denniskoch", - "cc" => [ - "https://phpc.social/users/denniskoch/followers", - "https://mastodon.social/users/bastianallgeier", - "https://chaos.social/users/distantnative", - "https://fosstodon.org/users/kev" - ], - "id" => "https://phpc.social/users/denniskoch/statuses/112847382711461301/activity", - "object" => %{ - "atomUri" => "https://phpc.social/users/denniskoch/statuses/112847382711461301", - "attachment" => [], - "attributedTo" => "https://phpc.social/users/denniskoch", - "cc" => [ - "https://phpc.social/users/denniskoch/followers", - "https://mastodon.social/users/bastianallgeier", - "https://chaos.social/users/distantnative", - "https://fosstodon.org/users/kev" - ], - "content" => - "

@bastianallgeier @distantnative @kev Another main argument: Discord is popular. Many people have an account, so you can just join an server quickly. Also you know the app and how to get around.

", - "contentMap" => %{ - "en" => - "

@bastianallgeier @distantnative @kev Another main argument: Discord is popular. Many people have an account, so you can just join an server quickly. Also you know the app and how to get around.

" - }, - "conversation" => - "tag:mastodon.social,2024-07-25:objectId=760068442:objectType=Conversation", - "id" => "https://phpc.social/users/denniskoch/statuses/112847382711461301", - "inReplyTo" => - "https://mastodon.social/users/bastianallgeier/statuses/112846516276907281", - "inReplyToAtomUri" => - "https://mastodon.social/users/bastianallgeier/statuses/112846516276907281", - "published" => "2024-07-25T13:33:29Z", - "replies" => %{ - "first" => %{ - "items" => [], - "next" => - "https://phpc.social/users/denniskoch/statuses/112847382711461301/replies?only_other_accounts=true&page=true", - "partOf" => - "https://phpc.social/users/denniskoch/statuses/112847382711461301/replies", - "type" => "CollectionPage" - }, - "id" => "https://phpc.social/users/denniskoch/statuses/112847382711461301/replies", - "type" => "Collection" - }, - "sensitive" => false, - "tag" => [ - %{ - "href" => "https://mastodon.social/users/bastianallgeier", - "name" => "@bastianallgeier@mastodon.social", - "type" => "Mention" - }, - %{ - "href" => "https://chaos.social/users/distantnative", - "name" => "@distantnative@chaos.social", - "type" => "Mention" - }, - %{ - "href" => "https://fosstodon.org/users/kev", - "name" => "@kev@fosstodon.org", - "type" => "Mention" - } - ], - "to" => ["https://www.w3.org/ns/activitystreams#Public"], - "type" => "Note", - "url" => "https://phpc.social/@denniskoch/112847382711461301" - }, - "published" => "2024-07-25T13:33:29Z", - "signature" => %{ - "created" => "2024-07-25T13:33:29Z", - "creator" => "https://phpc.social/users/denniskoch#main-key", - "signatureValue" => - "slz9BKJzd2n1S44wdXGOU+bV/wsskdgAaUpwxj8R16mYOL8+DTpE6VnfSKoZGsBBJT8uG5gnVfVEz1YsTUYtymeUgLMh7cvd8VnJnZPS+oixbmBRVky/Myf91TEgQQE7G4vDmTdB4ii54hZrHcOOYYf5FKPNRSkMXboKA6LMqNtekhbI+JTUJYIB02WBBK6PUyo15f6B1RJ6HGWVgud9NE0y1EZXfrkqUt682p8/9D49ORf7AwjXUJibKic2RbPvhEBj70qUGfBm4vvgdWhSUn1IG46xh+U0+NrTSUED82j1ZVOeua/2k/igkGs8cSBkY35quXTkPz6gbqCCH66CuA==", - "type" => "RsaSignature2017" - }, - "to" => ["https://www.w3.org/ns/activitystreams#Public"], - "type" => "Create" - } + params = + File.read!("test/fixtures/receiver_worker_signature_activity.json") |> Jason.decode!() req_headers = [ ["accept-encoding", "gzip"], @@ -245,4 +201,46 @@ defmodule Pleroma.Workers.ReceiverWorkerTest do assert {:ok, %Pleroma.Activity{}} = ReceiverWorker.perform(oban_job) end + + test "cancels due to origin containment" do + params = + insert(:note_activity).data + |> Map.put("id", "https://notorigindomain.com/activity") + + {:ok, oban_job} = + Federator.incoming_ap_doc(%{ + method: "POST", + req_headers: [], + request_path: "/inbox", + params: params, + query_string: "" + }) + + assert {:cancel, :origin_containment_failed} = ReceiverWorker.perform(oban_job) + end + + test "canceled due to deleted object" do + params = + insert(:announce_activity).data + |> Map.put("object", "http://localhost:4001/deleted") + + Tesla.Mock.mock(fn + %{url: "http://localhost:4001/deleted"} -> + %Tesla.Env{ + status: 404, + body: "" + } + end) + + {:ok, oban_job} = + Federator.incoming_ap_doc(%{ + method: "POST", + req_headers: [], + request_path: "/inbox", + params: params, + query_string: "" + }) + + assert {:cancel, _} = ReceiverWorker.perform(oban_job) + end end diff --git a/test/pleroma/workers/remote_fetcher_worker_test.exs b/test/pleroma/workers/remote_fetcher_worker_test.exs index 2104baab2f..9caddb6008 100644 --- a/test/pleroma/workers/remote_fetcher_worker_test.exs +++ b/test/pleroma/workers/remote_fetcher_worker_test.exs @@ -12,6 +12,7 @@ defmodule Pleroma.Workers.RemoteFetcherWorkerTest do @deleted_object_two "https://deleted-410.example.com/" @unauthorized_object "https://unauthorized.example.com/" @depth_object "https://depth.example.com/" + @content_type_object "https://bad_content_type.example.com/" describe "RemoteFetcherWorker" do setup do @@ -35,34 +36,48 @@ defmodule Pleroma.Workers.RemoteFetcherWorkerTest do %Tesla.Env{ status: 200 } + + %{method: :get, url: @content_type_object} -> + %Tesla.Env{ + status: 200, + headers: [{"content-type", "application/json"}], + body: File.read!("test/fixtures/spoofed-object.json") + } end) end - test "does not requeue a deleted object" do - assert {:cancel, _} = - RemoteFetcherWorker.perform(%Oban.Job{ - args: %{"op" => "fetch_remote", "id" => @deleted_object_one} - }) + test "does not retry jobs for a deleted object" do + [ + %{"op" => "fetch_remote", "id" => @deleted_object_one}, + %{"op" => "fetch_remote", "id" => @deleted_object_two} + ] + |> Enum.each(fn job -> assert {:cancel, _} = perform_job(RemoteFetcherWorker, job) end) + end + test "does not retry jobs for an unauthorized object" do assert {:cancel, _} = - RemoteFetcherWorker.perform(%Oban.Job{ - args: %{"op" => "fetch_remote", "id" => @deleted_object_two} + perform_job(RemoteFetcherWorker, %{ + "op" => "fetch_remote", + "id" => @unauthorized_object }) end - test "does not requeue an unauthorized object" do + test "does not retry jobs for an an object that exceeded depth" do + clear_config([:instance, :federation_incoming_replies_max_depth], 0) + assert {:cancel, _} = - RemoteFetcherWorker.perform(%Oban.Job{ - args: %{"op" => "fetch_remote", "id" => @unauthorized_object} + perform_job(RemoteFetcherWorker, %{ + "op" => "fetch_remote", + "id" => @depth_object, + "depth" => 1 }) end - test "does not requeue an object that exceeded depth" do - clear_config([:instance, :federation_incoming_replies_max_depth], 0) - + test "does not retry jobs for when object returns wrong content type" do assert {:cancel, _} = - RemoteFetcherWorker.perform(%Oban.Job{ - args: %{"op" => "fetch_remote", "id" => @depth_object, "depth" => 1} + perform_job(RemoteFetcherWorker, %{ + "op" => "fetch_remote", + "id" => @content_type_object }) end end diff --git a/test/support/factory.ex b/test/support/factory.ex index b248508fab..8f1c6faf97 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -53,6 +53,13 @@ defmodule Pleroma.Factory do keys: pem } + user + |> Map.put(:raw_bio, user.bio) + |> merge_attributes(Map.delete(attrs, :domain)) + |> make_user_urls(attrs) + end + + defp make_user_urls(user, attrs) do urls = if attrs[:local] == false do base_domain = attrs[:domain] || Enum.random(["domain1.com", "domain2.com", "domain3.com"]) @@ -60,26 +67,22 @@ defmodule Pleroma.Factory do ap_id = "https://#{base_domain}/users/#{user.nickname}" %{ - ap_id: ap_id, - follower_address: ap_id <> "/followers", - following_address: ap_id <> "/following", - featured_address: ap_id <> "/collections/featured" + ap_id: attrs[:ap_id] || ap_id, + follower_address: attrs[:follower_address] || ap_id <> "/followers", + following_address: attrs[:following_address] || ap_id <> "/following", + featured_address: attrs[:featured_address] || ap_id <> "/collections/featured", + inbox: attrs[:inbox] || "https://#{base_domain}/inbox" } else %{ - ap_id: User.ap_id(user), - follower_address: User.ap_followers(user), - following_address: User.ap_following(user), - featured_address: User.ap_featured_collection(user) + ap_id: attrs[:ap_id] || User.ap_id(user), + follower_address: attrs[:follower_address] || User.ap_followers(user), + following_address: attrs[:following_address] || User.ap_following(user), + featured_address: attrs[:featured_address] || User.ap_featured_collection(user) } end - attrs = Map.delete(attrs, :domain) - - user - |> Map.put(:raw_bio, user.bio) - |> Map.merge(urls) - |> merge_attributes(attrs) + Map.merge(user, urls) end def user_relationship_factory(attrs \\ %{}) do