diff options
81 files changed, 1563 insertions, 751 deletions
diff --git a/changelog.d/argon2-passwords.add b/changelog.d/argon2-passwords.add new file mode 100644 index 000000000..36fd7faf2 --- /dev/null +++ b/changelog.d/argon2-passwords.add @@ -0,0 +1 @@ +Added support for argon2 passwords and their conversion for migration from Akkoma fork to upstream. diff --git a/changelog.d/dialyzer.skip b/changelog.d/dialyzer.skip new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/changelog.d/dialyzer.skip diff --git a/changelog.d/docs-fix.skip b/changelog.d/docs-fix.skip new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/changelog.d/docs-fix.skip diff --git a/changelog.d/drop-unwanted.change b/changelog.d/drop-unwanted.change new file mode 100644 index 000000000..459d4bfe6 --- /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/following-state.fix b/changelog.d/following-state.fix new file mode 100644 index 000000000..314ea6210 --- /dev/null +++ b/changelog.d/following-state.fix @@ -0,0 +1 @@ +Resolved edge case where the API can report you are following a user but the relationship is not fully established. diff --git a/changelog.d/ldap-tls.fix b/changelog.d/ldap-tls.fix new file mode 100644 index 000000000..b15137d77 --- /dev/null +++ b/changelog.d/ldap-tls.fix @@ -0,0 +1 @@ +STARTTLS certificate and hostname verification for LDAP authentication diff --git a/changelog.d/list-id-visibility.add b/changelog.d/list-id-visibility.add new file mode 100644 index 000000000..2fea2d771 --- /dev/null +++ b/changelog.d/list-id-visibility.add @@ -0,0 +1 @@ +Include list id in StatusView
\ No newline at end of file diff --git a/changelog.d/manifest-icon-size.skip b/changelog.d/manifest-icon-size.skip new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/changelog.d/manifest-icon-size.skip diff --git a/changelog.d/mogrify.skip b/changelog.d/mogrify.skip new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/changelog.d/mogrify.skip diff --git a/changelog.d/oauth-app-spam.fix b/changelog.d/oauth-app-spam.fix new file mode 100644 index 000000000..cdc2e816d --- /dev/null +++ b/changelog.d/oauth-app-spam.fix @@ -0,0 +1 @@ +Add a rate limiter to the OAuth App creation endpoint and ensure registered apps are assigned to users. diff --git a/changelog.d/oban-recevier-improvements.fix b/changelog.d/oban-recevier-improvements.fix new file mode 100644 index 000000000..f91502ed2 --- /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/oban-uniques.change b/changelog.d/oban-uniques.change new file mode 100644 index 000000000..d9deb4696 --- /dev/null +++ b/changelog.d/oban-uniques.change @@ -0,0 +1 @@ +Adjust more Oban workers to enforce unique job constraints. diff --git a/changelog.d/publisher-reachability.fix b/changelog.d/publisher-reachability.fix new file mode 100644 index 000000000..3f50be581 --- /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 000000000..dcf2b1b31 --- /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 000000000..0bab323aa --- /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 000000000..c12ab1ffb --- /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/todo-cleanup.skip b/changelog.d/todo-cleanup.skip new file mode 100644 index 000000000..e69de29bb --- /dev/null +++ b/changelog.d/todo-cleanup.skip diff --git a/changelog.d/update-oban.change b/changelog.d/update-oban.change new file mode 100644 index 000000000..a67b3e3cf --- /dev/null +++ b/changelog.d/update-oban.change @@ -0,0 +1 @@ +Update Oban to 2.18 diff --git a/changelog.d/user-imports.fix b/changelog.d/user-imports.fix new file mode 100644 index 000000000..0076c73d7 --- /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/well-known.change b/changelog.d/well-known.change new file mode 100644 index 000000000..e928124fb --- /dev/null +++ b/changelog.d/well-known.change @@ -0,0 +1 @@ +Accept application/activity+json for requests to .well-known/nodeinfo diff --git a/config/config.exs b/config/config.exs index ad6b1cb94..cd9a2539f 100644 --- a/config/config.exs +++ b/config/config.exs @@ -344,7 +344,7 @@ config :pleroma, :manifest, icons: [ %{ src: "/static/logo.svg", - sizes: "144x144", + sizes: "512x512", purpose: "any", type: "image/svg+xml" } @@ -597,7 +597,8 @@ config :pleroma, Oban, plugins: [{Oban.Plugins.Pruner, max_age: 900}], crontab: [ {"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker}, - {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker} + {"0 0 * * *", Pleroma.Workers.Cron.NewUsersDigestWorker}, + {"*/10 * * * *", Pleroma.Workers.Cron.AppCleanupWorker} ] config :pleroma, Pleroma.Formatter, @@ -711,6 +712,7 @@ config :pleroma, :rate_limit, timeline: {500, 3}, search: [{1000, 10}, {1000, 30}], app_account_creation: {1_800_000, 25}, + oauth_app_creation: {900_000, 5}, relations_actions: {10_000, 10}, relation_id_action: {60_000, 2}, statuses_actions: {10_000, 15}, diff --git a/docs/development/API/admin_api.md b/docs/development/API/admin_api.md index 5b373b8e1..409e78a1e 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 22a26b77b..cbd0d6bce 100644 --- a/docs/development/API/differences_in_mastoapi_responses.md +++ b/docs/development/API/differences_in_mastoapi_responses.md @@ -42,6 +42,7 @@ Has these additional fields under the `pleroma` object: - `quotes_count`: the count of status quotes. - `non_anonymous`: true if the source post specifies the poll results are not anonymous. Currently only implemented by Smithereen. - `bookmark_folder`: the ID of the folder bookmark is stored within (if any). +- `list_id`: the ID of the list the post is addressed to (if any, only returned to author). The `GET /api/v1/statuses/:id/source` endpoint additionally has the following attributes: diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex index 3a5e35301..5268ebe7a 100644 --- a/lib/pleroma/constants.ex +++ b/lib/pleroma/constants.ex @@ -85,6 +85,36 @@ defmodule Pleroma.Constants do ] ) + const(activity_types, + do: [ + "Create", + "Update", + "Delete", + "Follow", + "Accept", + "Reject", + "Add", + "Remove", + "Like", + "Announce", + "Undo", + "Flag", + "EmojiReact" + ] + ) + + const(allowed_activity_types_from_strangers, + do: [ + "Block", + "Create", + "Flag", + "Follow", + "Like", + "EmojiReact", + "Announce" + ] + ) + # basic regex, just there to weed out potential mistakes # https://datatracker.ietf.org/doc/html/rfc2045#section-5.1 const(mime_regex, diff --git a/lib/pleroma/http/adapter_helper.ex b/lib/pleroma/http/adapter_helper.ex index dcb27a29d..32c1080f7 100644 --- a/lib/pleroma/http/adapter_helper.ex +++ b/lib/pleroma/http/adapter_helper.ex @@ -52,6 +52,7 @@ defmodule Pleroma.HTTP.AdapterHelper do case adapter() do Tesla.Adapter.Gun -> AdapterHelper.Gun Tesla.Adapter.Hackney -> AdapterHelper.Hackney + {Tesla.Adapter.Finch, _} -> AdapterHelper.Finch _ -> AdapterHelper.Default end end @@ -118,4 +119,13 @@ defmodule Pleroma.HTTP.AdapterHelper do host_charlist end end + + @spec can_stream? :: bool() + def can_stream? do + case Application.get_env(:tesla, :adapter) do + Tesla.Adapter.Gun -> true + {Tesla.Adapter.Finch, _} -> true + _ -> false + end + end end diff --git a/lib/pleroma/http/adapter_helper/finch.ex b/lib/pleroma/http/adapter_helper/finch.ex new file mode 100644 index 000000000..181caed7e --- /dev/null +++ b/lib/pleroma/http/adapter_helper/finch.ex @@ -0,0 +1,33 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.HTTP.AdapterHelper.Finch do + @behaviour Pleroma.HTTP.AdapterHelper + + alias Pleroma.Config + alias Pleroma.HTTP.AdapterHelper + + @spec options(keyword(), URI.t()) :: keyword() + def options(incoming_opts \\ [], %URI{} = _uri) do + proxy = + [:http, :proxy_url] + |> Config.get() + |> AdapterHelper.format_proxy() + + config_opts = Config.get([:http, :adapter], []) + + config_opts + |> Keyword.merge(incoming_opts) + |> AdapterHelper.maybe_add_proxy(proxy) + |> maybe_stream() + end + + # Finch uses [response: :stream] + defp maybe_stream(opts) do + case Keyword.pop(opts, :stream, nil) do + {true, opts} -> Keyword.put(opts, :response, :stream) + {_, opts} -> opts + end + end +end diff --git a/lib/pleroma/http/adapter_helper/gun.ex b/lib/pleroma/http/adapter_helper/gun.ex index 1fe8dd4b2..30ba26765 100644 --- a/lib/pleroma/http/adapter_helper/gun.ex +++ b/lib/pleroma/http/adapter_helper/gun.ex @@ -32,6 +32,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do |> AdapterHelper.maybe_add_proxy(proxy) |> Keyword.merge(incoming_opts) |> put_timeout() + |> maybe_stream() end defp add_scheme_opts(opts, %{scheme: "http"}), do: opts @@ -47,6 +48,14 @@ defmodule Pleroma.HTTP.AdapterHelper.Gun do Keyword.put(opts, :timeout, recv_timeout) end + # Gun uses [body_as: :stream] + defp maybe_stream(opts) do + case Keyword.pop(opts, :stream, nil) do + {true, opts} -> Keyword.put(opts, :body_as, :stream) + {_, opts} -> opts + end + end + @spec pool_timeout(pool()) :: non_neg_integer() def pool_timeout(pool) do default = Config.get([:pools, :default, :recv_timeout], 5_000) diff --git a/lib/pleroma/maps.ex b/lib/pleroma/maps.ex index 5020a8ff8..1afbde484 100644 --- a/lib/pleroma/maps.ex +++ b/lib/pleroma/maps.ex @@ -20,15 +20,13 @@ defmodule Pleroma.Maps do end def filter_empty_values(data) do - # TODO: Change to Map.filter in Elixir 1.13+ data - |> Enum.filter(fn + |> Map.filter(fn {_k, nil} -> false {_k, ""} -> false {_k, []} -> false {_k, %{} = v} -> Map.keys(v) != [] {_k, _v} -> true end) - |> Map.new() end end diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index c0f671dd4..69a5f3268 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -58,8 +58,12 @@ defmodule Pleroma.Object.Fetcher do end end + @typep fetcher_errors :: + :error | :reject | :allowed_depth | :fetch | :containment | :transmogrifier + # Note: will create a Create activity, which we need internally at the moment. - @spec fetch_object_from_id(String.t(), list()) :: {:ok, Object.t()} | {:error | :reject, any()} + @spec fetch_object_from_id(String.t(), list()) :: + {:ok, Object.t()} | {fetcher_errors(), any()} | Pipeline.errors() def fetch_object_from_id(id, options \\ []) do with {_, nil} <- {:fetch_object, Object.get_cached_by_ap_id(id)}, {_, true} <- {:allowed_depth, Federator.allowed_thread_distance?(options[:depth])}, @@ -73,50 +77,22 @@ defmodule Pleroma.Object.Fetcher do {:object, data, Object.normalize(activity, fetch: false)} do {:ok, object} else - {:allowed_depth, false} = e -> - log_fetch_error(id, e) - {:error, :allowed_depth} - - {:containment, reason} = e -> - log_fetch_error(id, e) - {:error, reason} - - {:transmogrifier, {:error, {:reject, reason}}} = e -> - log_fetch_error(id, e) - {:reject, reason} - - {:transmogrifier, {:reject, reason}} = e -> - log_fetch_error(id, e) - {:reject, reason} - - {:transmogrifier, reason} = e -> - log_fetch_error(id, e) - {:error, reason} - - {:object, data, nil} -> - reinject_object(%Object{}, data) - {:normalize, object = %Object{}} -> {:ok, object} {:fetch_object, %Object{} = object} -> {:ok, object} - {:fetch, {:error, reason}} = e -> - log_fetch_error(id, e) - {:error, reason} + {:object, data, nil} -> + reinject_object(%Object{}, data) e -> - log_fetch_error(id, e) - {:error, e} + Logger.metadata(object: id) + Logger.error("Object rejected while fetching #{id} #{inspect(e)}") + e end end - defp log_fetch_error(id, error) do - Logger.metadata(object: id) - Logger.error("Object rejected while fetching #{id} #{inspect(error)}") - end - defp prepare_activity_params(data) do %{ "type" => "Create", diff --git a/lib/pleroma/user/backup.ex b/lib/pleroma/user/backup.ex index 7feaa22bf..d77d49890 100644 --- a/lib/pleroma/user/backup.ex +++ b/lib/pleroma/user/backup.ex @@ -92,9 +92,6 @@ defmodule Pleroma.User.Backup do else true -> {:error, "Backup is missing id. Please insert it into the Repo first."} - - e -> - {:error, e} end end @@ -121,14 +118,13 @@ defmodule Pleroma.User.Backup do end defp permitted?(user) do - with {_, %__MODULE__{inserted_at: inserted_at}} <- {:last, get_last(user)}, - days = Config.get([__MODULE__, :limit_days]), - diff = Timex.diff(NaiveDateTime.utc_now(), inserted_at, :days), - {_, true} <- {:diff, diff > days} do - true + with {_, %__MODULE__{inserted_at: inserted_at}} <- {:last, get_last(user)} do + days = Config.get([__MODULE__, :limit_days]) + diff = Timex.diff(NaiveDateTime.utc_now(), inserted_at, :days) + + diff > days else {:last, nil} -> true - {:diff, false} -> false end end @@ -297,9 +293,6 @@ defmodule Pleroma.User.Backup do ) acc - - _ -> - acc end end) diff --git a/lib/pleroma/user/import.ex b/lib/pleroma/user/import.ex index 11905237c..ab6bdb8d4 100644 --- a/lib/pleroma/user/import.ex +++ b/lib/pleroma/user/import.ex @@ -5,87 +5,107 @@ defmodule Pleroma.User.Import do use Ecto.Schema + alias Pleroma.Repo alias Pleroma.User alias Pleroma.Web.CommonAPI alias Pleroma.Workers.BackgroundWorker require Logger - @spec perform(atom(), User.t(), list()) :: :ok | list() | {:error, any()} - def perform(:mutes_import, %User{} = user, [_ | _] = identifiers) do - Enum.map( - identifiers, - fn identifier -> - with {:ok, %User{} = muted_user} <- User.get_or_fetch(identifier), - {:ok, _} <- User.mute(user, muted_user) do - muted_user - else - error -> handle_error(:mutes_import, identifier, error) - end - end - ) + @spec perform(atom(), User.t(), String.t()) :: :ok | {:error, any()} + def perform(:mute_import, %User{} = user, actor) do + with {:ok, %User{} = muted_user} <- User.get_or_fetch(actor), + {_, false} <- {:existing_mute, User.mutes_user?(user, muted_user)}, + {:ok, _} <- User.mute(user, muted_user) do + {:ok, muted_user} + else + {:existing_mute, true} -> :ok + error -> handle_error(:mutes_import, actor, error) + end end - def perform(:blocks_import, %User{} = blocker, [_ | _] = identifiers) do - Enum.map( - identifiers, - fn identifier -> - with {:ok, %User{} = blocked} <- User.get_or_fetch(identifier), - {:ok, _block} <- CommonAPI.block(blocked, blocker) do - blocked - else - error -> handle_error(:blocks_import, identifier, error) - end - end - ) + def perform(:block_import, %User{} = user, actor) do + with {:ok, %User{} = blocked} <- User.get_or_fetch(actor), + {_, false} <- {:existing_block, User.blocks_user?(user, blocked)}, + {:ok, _block} <- CommonAPI.block(blocked, user) do + {:ok, blocked} + else + {:existing_block, true} -> :ok + error -> handle_error(:blocks_import, actor, error) + end end - def perform(:follow_import, %User{} = follower, [_ | _] = identifiers) do - Enum.map( - identifiers, - fn identifier -> - with {:ok, %User{} = followed} <- User.get_or_fetch(identifier), - {:ok, follower, followed} <- User.maybe_direct_follow(follower, followed), - {:ok, _, _, _} <- CommonAPI.follow(followed, follower) do - followed - else - error -> handle_error(:follow_import, identifier, error) - end - end - ) + def perform(:follow_import, %User{} = user, actor) do + with {:ok, %User{} = followed} <- User.get_or_fetch(actor), + {_, false} <- {:existing_follow, User.following?(user, followed)}, + {:ok, user, followed} <- User.maybe_direct_follow(user, followed), + {:ok, _, _, _} <- CommonAPI.follow(followed, user) do + {:ok, followed} + else + {:existing_follow, true} -> :ok + error -> handle_error(:follow_import, actor, error) + end end - def perform(_, _, _), do: :ok - defp handle_error(op, user_id, error) do Logger.debug("#{op} failed for #{user_id} with: #{inspect(error)}") - error + {:error, error} end - def blocks_import(%User{} = blocker, [_ | _] = identifiers) do - BackgroundWorker.new(%{ - "op" => "blocks_import", - "user_id" => blocker.id, - "identifiers" => identifiers - }) - |> Oban.insert() + def blocks_import(%User{} = user, [_ | _] = actors) do + jobs = + Repo.checkout(fn -> + Enum.reduce(actors, [], fn actor, acc -> + {:ok, job} = + BackgroundWorker.new(%{ + "op" => "block_import", + "user_id" => user.id, + "actor" => actor + }) + |> Oban.insert() + + acc ++ [job] + end) + end) + + {:ok, jobs} end - def follow_import(%User{} = follower, [_ | _] = identifiers) do - BackgroundWorker.new(%{ - "op" => "follow_import", - "user_id" => follower.id, - "identifiers" => identifiers - }) - |> Oban.insert() + def follows_import(%User{} = user, [_ | _] = actors) do + jobs = + Repo.checkout(fn -> + Enum.reduce(actors, [], fn actor, acc -> + {:ok, job} = + BackgroundWorker.new(%{ + "op" => "follow_import", + "user_id" => user.id, + "actor" => actor + }) + |> Oban.insert() + + acc ++ [job] + end) + end) + + {:ok, jobs} end - def mutes_import(%User{} = user, [_ | _] = identifiers) do - BackgroundWorker.new(%{ - "op" => "mutes_import", - "user_id" => user.id, - "identifiers" => identifiers - }) - |> Oban.insert() + def mutes_import(%User{} = user, [_ | _] = actors) do + jobs = + Repo.checkout(fn -> + Enum.reduce(actors, [], fn actor, acc -> + {:ok, job} = + BackgroundWorker.new(%{ + "op" => "mute_import", + "user_id" => user.id, + "actor" => actor + }) + |> Oban.insert() + + acc ++ [job] + end) + end) + + {:ok, jobs} end end diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index cdd054e1a..a08eda5f4 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -311,7 +311,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do post_inbox_relayed_create(conn, params) else conn - |> put_status(:bad_request) + |> put_status(403) |> json("Not federating") end end diff --git a/lib/pleroma/web/activity_pub/pipeline.ex b/lib/pleroma/web/activity_pub/pipeline.ex index 7f11a4d67..fc36935d5 100644 --- a/lib/pleroma/web/activity_pub/pipeline.ex +++ b/lib/pleroma/web/activity_pub/pipeline.ex @@ -22,22 +22,27 @@ defmodule Pleroma.Web.ActivityPub.Pipeline do defp activity_pub, do: Config.get([:pipeline, :activity_pub], ActivityPub) defp config, do: Config.get([:pipeline, :config], Config) - @spec common_pipeline(map(), keyword()) :: - {:ok, Activity.t() | Object.t(), keyword()} | {:error | :reject, any()} + @type results :: {:ok, Activity.t() | Object.t(), keyword()} + @type errors :: {:error | :reject, any()} + + # The Repo.transaction will wrap the result in an {:ok, _} + # and only returns an {:error, _} if the error encountered was related + # to the SQL transaction + @spec common_pipeline(map(), keyword()) :: results() | errors() def common_pipeline(object, meta) do case Repo.transaction(fn -> do_common_pipeline(object, meta) end, Utils.query_timeout()) do {:ok, {:ok, activity, meta}} -> side_effects().handle_after_transaction(meta) {:ok, activity, meta} - {:ok, value} -> - value + {:ok, {:error, _} = error} -> + error + + {:ok, {:reject, _} = error} -> + error {:error, e} -> {:error, e} - - {:reject, e} -> - {:reject, e} end end diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 5cd982c6a..0de3a0d43 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -148,12 +148,17 @@ defmodule Pleroma.Web.ActivityPub.Publisher do {"digest", p.digest} ] ) do - maybe_set_reachable(p.unreachable_since, p.inbox) + if not is_nil(p.unreachable_since) do + Instances.set_reachable(p.inbox) + end result else {_post_result, %{status: code} = response} = e -> - maybe_set_unreachable(p.unreachable_since, p.inbox) + if is_nil(p.unreachable_since) do + Instances.set_unreachable(p.inbox) + end + Logger.metadata(activity: p.activity_id, inbox: p.inbox, status: code) Logger.error("Publisher failed to inbox #{p.inbox} with status #{code}") @@ -174,7 +179,10 @@ defmodule Pleroma.Web.ActivityPub.Publisher do connection_pool_snooze() e -> - maybe_set_unreachable(p.unreachable_since, p.inbox) + if is_nil(p.unreachable_since) do + Instances.set_unreachable(p.inbox) + end + Logger.metadata(activity: p.activity_id, inbox: p.inbox) Logger.error("Publisher failed to inbox #{p.inbox} #{inspect(e)}") {:error, e} @@ -183,12 +191,6 @@ defmodule Pleroma.Web.ActivityPub.Publisher do defp connection_pool_snooze, do: {:snooze, 3} - defp maybe_set_reachable(%NaiveDateTime{}, inbox), do: Instances.set_reachable(inbox) - defp maybe_set_reachable(_, _), do: :ok - - defp maybe_set_unreachable(nil, inbox), do: Instances.set_unreachable(inbox) - defp maybe_set_unreachable(%NaiveDateTime{}, _), do: :ok - defp signature_host(%URI{port: port, scheme: scheme, host: host}) do if port == URI.default_port(scheme) do host diff --git a/lib/pleroma/web/api_spec/schemas/status.ex b/lib/pleroma/web/api_spec/schemas/status.ex index 6e537b5da..25548d75b 100644 --- a/lib/pleroma/web/api_spec/schemas/status.ex +++ b/lib/pleroma/web/api_spec/schemas/status.ex @@ -249,6 +249,12 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do nullable: true, description: "A datetime (ISO 8601) that states when the post was pinned or `null` if the post is not pinned" + }, + list_id: %Schema{ + type: :integer, + nullable: true, + description: + "The ID of the list the post is addressed to (if any, only returned to author)" } } }, diff --git a/lib/pleroma/web/auth/ldap_authenticator.ex b/lib/pleroma/web/auth/ldap_authenticator.ex index ea5620cf6..d31f34747 100644 --- a/lib/pleroma/web/auth/ldap_authenticator.ex +++ b/lib/pleroma/web/auth/ldap_authenticator.ex @@ -41,6 +41,7 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do port = Keyword.get(ldap, :port, 389) ssl = Keyword.get(ldap, :ssl, false) sslopts = Keyword.get(ldap, :sslopts, []) + tlsopts = Keyword.get(ldap, :tlsopts, []) options = [{:port, port}, {:ssl, ssl}, {:timeout, @connection_timeout}] ++ @@ -54,7 +55,16 @@ defmodule Pleroma.Web.Auth.LDAPAuthenticator do case :eldap.start_tls( connection, - Keyword.get(ldap, :tlsopts, []), + Keyword.merge( + [ + verify: :verify_peer, + cacerts: :certifi.cacerts(), + customize_hostname_check: [ + fqdn_fun: fn _ -> to_charlist(host) end + ] + ], + tlsopts + ), @connection_timeout ) do :ok -> diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index 921e414c3..412424dae 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -26,7 +26,7 @@ defmodule Pleroma.Web.CommonAPI do require Pleroma.Constants require Logger - @spec block(User.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()} + @spec block(User.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors() def block(blocked, blocker) do with {:ok, block_data, _} <- Builder.block(blocker, blocked), {:ok, block, _} <- Pipeline.common_pipeline(block_data, local: true) do @@ -35,7 +35,7 @@ defmodule Pleroma.Web.CommonAPI do end @spec post_chat_message(User.t(), User.t(), String.t(), list()) :: - {:ok, Activity.t()} | {:error, any()} + {:ok, Activity.t()} | Pipeline.errors() def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]), :ok <- validate_chat_attachment_attribution(maybe_attachment, user), @@ -58,7 +58,7 @@ defmodule Pleroma.Web.CommonAPI do )} do {:ok, activity} else - {:common_pipeline, {:reject, _} = e} -> e + {:common_pipeline, e} -> e e -> e end end @@ -99,7 +99,8 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec unblock(User.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()} + @spec unblock(User.t(), User.t()) :: + {:ok, Activity.t()} | {:ok, :no_activity} | Pipeline.errors() | {:error, :not_blocking} def unblock(blocked, blocker) do with {_, %Activity{} = block} <- {:fetch_block, Utils.fetch_latest_block(blocker, blocked)}, {:ok, unblock_data, _} <- Builder.undo(blocker, block), @@ -120,7 +121,9 @@ defmodule Pleroma.Web.CommonAPI do end @spec follow(User.t(), User.t()) :: - {:ok, User.t(), User.t(), Activity.t() | Object.t()} | {:error, :rejected} + {:ok, User.t(), User.t(), Activity.t() | Object.t()} + | {:error, :rejected} + | Pipeline.errors() def follow(followed, follower) do timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout]) @@ -145,7 +148,7 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec accept_follow_request(User.t(), User.t()) :: {:ok, User.t()} | {:error, any()} + @spec accept_follow_request(User.t(), User.t()) :: {:ok, User.t()} | Pipeline.errors() def accept_follow_request(follower, followed) do with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), {:ok, accept_data, _} <- Builder.accept(followed, follow_activity), @@ -154,7 +157,7 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec reject_follow_request(User.t(), User.t()) :: {:ok, User.t()} | {:error, any()} | nil + @spec reject_follow_request(User.t(), User.t()) :: {:ok, User.t()} | Pipeline.errors() | nil def reject_follow_request(follower, followed) do with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed), {:ok, reject_data, _} <- Builder.reject(followed, follow_activity), @@ -163,7 +166,8 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec delete(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()} + @spec delete(String.t(), User.t()) :: + {:ok, Activity.t()} | Pipeline.errors() | {:error, :not_found | String.t()} def delete(activity_id, user) do with {_, %Activity{data: %{"object" => _, "type" => "Create"}} = activity} <- {:find_activity, Activity.get_by_id(activity_id, filter: [])}, @@ -213,7 +217,7 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec repeat(String.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, any()} + @spec repeat(String.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, :not_found} def repeat(id, user, params \\ %{}) do with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id), object = %Object{} <- Object.normalize(activity, fetch: false), @@ -231,7 +235,7 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec unrepeat(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()} + @spec unrepeat(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, :not_found | String.t()} def unrepeat(id, user) do with {_, %Activity{data: %{"type" => "Create"}} = activity} <- {:find_activity, Activity.get_by_id(id)}, @@ -247,7 +251,8 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec favorite(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()} + @spec favorite(String.t(), User.t()) :: + {:ok, Activity.t()} | {:ok, :already_liked} | {:error, :not_found | String.t()} def favorite(id, %User{} = user) do case favorite_helper(user, id) do {:ok, _} = res -> @@ -285,7 +290,8 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec unfavorite(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, any()} + @spec unfavorite(String.t(), User.t()) :: + {:ok, Activity.t()} | {:error, :not_found | String.t()} def unfavorite(id, user) do with {_, %Activity{data: %{"type" => "Create"}} = activity} <- {:find_activity, Activity.get_by_id(id)}, @@ -302,7 +308,7 @@ defmodule Pleroma.Web.CommonAPI do end @spec react_with_emoji(String.t(), User.t(), String.t()) :: - {:ok, Activity.t()} | {:error, any()} + {:ok, Activity.t()} | {:error, String.t()} def react_with_emoji(id, user, emoji) do with %Activity{} = activity <- Activity.get_by_id(id), object <- Object.normalize(activity, fetch: false), @@ -316,7 +322,7 @@ defmodule Pleroma.Web.CommonAPI do end @spec unreact_with_emoji(String.t(), User.t(), String.t()) :: - {:ok, Activity.t()} | {:error, any()} + {:ok, Activity.t()} | {:error, String.t()} def unreact_with_emoji(id, user, emoji) do with %Activity{} = reaction_activity <- Utils.get_latest_reaction(id, user, emoji), {_, {:ok, _}} <- {:cancel_jobs, maybe_cancel_jobs(reaction_activity)}, @@ -329,7 +335,7 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec vote(Object.t(), User.t(), list()) :: {:ok, list(), Object.t()} | {:error, any()} + @spec vote(Object.t(), User.t(), list()) :: {:ok, list(), Object.t()} | Pipeline.errors() def vote(%Object{data: %{"type" => "Question"}} = object, %User{} = user, choices) do with :ok <- validate_not_author(object, user), :ok <- validate_existing_votes(user, object), @@ -461,7 +467,7 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec update(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, any()} + @spec update(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, nil} def update(orig_activity, %User{} = user, changes) do with orig_object <- Object.normalize(orig_activity), {:ok, new_object} <- make_update_data(user, orig_object, changes), @@ -497,7 +503,7 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, term()} + @spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors() def pin(id, %User{} = user) do with %Activity{} = activity <- create_activity_by_id(id), true <- activity_belongs_to_actor(activity, user.ap_id), @@ -537,7 +543,7 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec unpin(String.t(), User.t()) :: {:ok, Activity.t()} | {:error, term()} + @spec unpin(String.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors() def unpin(id, user) do with %Activity{} = activity <- create_activity_by_id(id), {:ok, unpin_data, _} <- Builder.unpin(user, activity.object), @@ -552,7 +558,7 @@ defmodule Pleroma.Web.CommonAPI do end end - @spec add_mute(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, any()} + @spec add_mute(Activity.t(), User.t(), map()) :: {:ok, Activity.t()} | {:error, String.t()} def add_mute(activity, user, params \\ %{}) do expires_in = Map.get(params, :expires_in, 0) diff --git a/lib/pleroma/web/federator.ex b/lib/pleroma/web/federator.ex index 2df716556..58260afa8 100644 --- a/lib/pleroma/web/federator.ex +++ b/lib/pleroma/web/federator.ex @@ -102,7 +102,8 @@ defmodule Pleroma.Web.Federator do # NOTE: we use the actor ID to do the containment, this is fine because an # actor shouldn't be acting on objects outside their own AP server. - with {_, {:ok, _user}} <- {:actor, User.get_or_fetch_by_ap_id(actor)}, + with {_, {:ok, user}} <- {:actor, User.get_or_fetch_by_ap_id(actor)}, + {:user_active, true} <- {:user_active, match?(true, user.is_active)}, nil <- Activity.normalize(params["id"]), {_, :ok} <- {:correct_origin?, Containment.contain_origin_from_id(actor, params)}, @@ -121,11 +122,6 @@ defmodule Pleroma.Web.Federator do Logger.debug("Unhandled actor #{actor}, #{inspect(e)}") {:error, e} - {:error, {:validate_object, _}} = e -> - Logger.error("Incoming AP doc validation error: #{inspect(e)}") - Logger.debug(Jason.encode!(params, pretty: true)) - e - e -> # Just drop those for now Logger.debug(fn -> "Unhandled activity\n" <> Jason.encode!(params, pretty: true) end) diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex index 844673ae0..6cfeb712e 100644 --- a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex @@ -19,6 +19,8 @@ defmodule Pleroma.Web.MastodonAPI.AppController do action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + plug(Pleroma.Web.Plugs.RateLimiter, [name: :oauth_app_creation] when action == :create) + plug(:skip_auth when action in [:create, :verify_credentials]) plug(Pleroma.Web.ApiSpec.CastAndValidate) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 0643b8f14..7de6745d4 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -92,14 +92,13 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do User.get_follow_state(reading_user, target) end - followed_by = - if following_relationships do - case FollowingRelationship.find(following_relationships, target, reading_user) do - %{state: :follow_accept} -> true - _ -> false - end - else - User.following?(target, reading_user) + followed_by = FollowingRelationship.following?(target, reading_user) + following = FollowingRelationship.following?(reading_user, target) + + requested = + cond do + following -> false + true -> match?(:follow_pending, follow_state) end subscribing = @@ -114,7 +113,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do # NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags %{ id: to_string(target.id), - following: follow_state == :follow_accept, + following: following, followed_by: followed_by, blocking: UserRelationship.exists?( @@ -150,7 +149,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do ), subscribing: subscribing, notifying: subscribing, - requested: follow_state == :follow_pending, + requested: requested, domain_blocking: User.blocks_domain?(reading_user, target), showing_reblogs: not UserRelationship.exists?( diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 747638c53..3bf870c24 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -465,7 +465,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do parent_visible: visible_for_user?(reply_to, opts[:for]), pinned_at: pinned_at, quotes_count: object.data["quotesCount"] || 0, - bookmark_folder: bookmark_folder + bookmark_folder: bookmark_folder, + list_id: get_list_id(object, client_posted_this_activity) } } end @@ -803,19 +804,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do defp build_application(_), do: nil - # Workaround for Elixir issue #10771 - # Avoid applying URI.merge unless necessary - # TODO: revert to always attempting URI.merge(image_url_data, page_url_data) - # when Elixir 1.12 is the minimum supported version - @spec build_image_url(struct() | nil, struct()) :: String.t() | nil - defp build_image_url( - %URI{scheme: image_scheme, host: image_host} = image_url_data, - %URI{} = _page_url_data - ) - when not is_nil(image_scheme) and not is_nil(image_host) do - image_url_data |> to_string - end - + @spec build_image_url(URI.t(), URI.t()) :: String.t() defp build_image_url(%URI{} = image_url_data, %URI{} = page_url_data) do URI.merge(page_url_data, image_url_data) |> to_string end @@ -847,4 +836,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do nil end end + + defp get_list_id(object, client_posted_this_activity) do + with true <- client_posted_this_activity, + %{data: %{"listMessage" => list_ap_id}} when is_binary(list_ap_id) <- object, + %{id: list_id} <- Pleroma.List.get_by_ap_id(list_ap_id) do + list_id + else + _ -> nil + end + end end diff --git a/lib/pleroma/web/o_auth/app.ex b/lib/pleroma/web/o_auth/app.ex index d1bf6dd18..7661c2566 100644 --- a/lib/pleroma/web/o_auth/app.ex +++ b/lib/pleroma/web/o_auth/app.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.OAuth.App do import Ecto.Query alias Pleroma.Repo alias Pleroma.User + alias Pleroma.Web.OAuth.Token @type t :: %__MODULE__{} @@ -155,4 +156,29 @@ defmodule Pleroma.Web.OAuth.App do Map.put(acc, key, error) end) end + + @spec maybe_update_owner(Token.t()) :: :ok + def maybe_update_owner(%Token{app_id: app_id, user_id: user_id}) when not is_nil(user_id) do + __MODULE__.update(app_id, %{user_id: user_id}) + + :ok + end + + def maybe_update_owner(_), do: :ok + + @spec remove_orphans(pos_integer()) :: :ok + def remove_orphans(limit \\ 100) do + fifteen_mins_ago = DateTime.add(DateTime.utc_now(), -900, :second) + + Repo.transaction(fn -> + from(a in __MODULE__, + where: is_nil(a.user_id) and a.inserted_at < ^fifteen_mins_ago, + limit: ^limit + ) + |> Repo.all() + |> Enum.each(&Repo.delete(&1)) + end) + + :ok + end end diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex index 47b03215f..0b3de5481 100644 --- a/lib/pleroma/web/o_auth/o_auth_controller.ex +++ b/lib/pleroma/web/o_auth/o_auth_controller.ex @@ -318,6 +318,8 @@ defmodule Pleroma.Web.OAuth.OAuthController do def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params) def after_token_exchange(%Plug.Conn{} = conn, %{token: token} = view_params) do + App.maybe_update_owner(token) + conn |> AuthHelper.put_session_token(token.token) |> json(OAuthView.render("token.json", view_params)) diff --git a/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex index 96466f192..d65c30dab 100644 --- a/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/user_import_controller.ex @@ -38,8 +38,8 @@ defmodule Pleroma.Web.PleromaAPI.UserImportController do |> Enum.map(&(&1 |> String.trim() |> String.trim_leading("@"))) |> Enum.reject(&(&1 == "")) - User.Import.follow_import(follower, identifiers) - json(conn, "job started") + User.Import.follows_import(follower, identifiers) + json(conn, "jobs started") end def blocks( @@ -55,7 +55,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportController do defp do_block(%{assigns: %{user: blocker}} = conn, list) do User.Import.blocks_import(blocker, prepare_user_identifiers(list)) - json(conn, "job started") + json(conn, "jobs started") end def mutes( @@ -71,7 +71,7 @@ defmodule Pleroma.Web.PleromaAPI.UserImportController do defp do_mute(%{assigns: %{user: user}} = conn, list) do User.Import.mutes_import(user, prepare_user_identifiers(list)) - json(conn, "job started") + json(conn, "jobs started") end defp prepare_user_identifiers(list) do diff --git a/lib/pleroma/web/plugs/authentication_plug.ex b/lib/pleroma/web/plugs/authentication_plug.ex index f912a1542..af7d7f45a 100644 --- a/lib/pleroma/web/plugs/authentication_plug.ex +++ b/lib/pleroma/web/plugs/authentication_plug.ex @@ -47,6 +47,11 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do Pleroma.Password.Pbkdf2.verify_pass(password, password_hash) end + def checkpw(password, "$argon2" <> _ = password_hash) do + # Handle argon2 passwords for Akkoma migration + Argon2.verify_pass(password, password_hash) + end + def checkpw(_password, _password_hash) do Logger.error("Password hash not recognized") false @@ -56,6 +61,10 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlug do do_update_password(user, password) end + def maybe_update_password(%User{password_hash: "$argon2" <> _} = user, password) do + do_update_password(user, password) + end + def maybe_update_password(user, _), do: {:ok, user} defp do_update_password(user, password) do diff --git a/lib/pleroma/web/plugs/inbox_guard_plug.ex b/lib/pleroma/web/plugs/inbox_guard_plug.ex new file mode 100644 index 000000000..0064cce76 --- /dev/null +++ b/lib/pleroma/web/plugs/inbox_guard_plug.ex @@ -0,0 +1,89 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Plugs.InboxGuardPlug do + import Plug.Conn + import Pleroma.Constants, only: [activity_types: 0, allowed_activity_types_from_strangers: 0] + + alias Pleroma.Config + alias Pleroma.User + + def init(options) do + options + end + + def call(%{assigns: %{valid_signature: true}} = conn, _opts) do + with {_, true} <- {:federating, Config.get!([:instance, :federating])} do + conn + |> filter_activity_types() + else + {:federating, false} -> + conn + |> json(403, "Not federating") + |> halt() + end + end + + def call(conn, _opts) do + with {_, true} <- {:federating, Config.get!([:instance, :federating])}, + conn = filter_activity_types(conn), + {:known, true} <- {:known, known_actor?(conn)} do + conn + else + {:federating, false} -> + conn + |> json(403, "Not federating") + |> halt() + + {:known, false} -> + conn + |> filter_from_strangers() + end + end + + # Early rejection of unrecognized types + defp filter_activity_types(%{body_params: %{"type" => type}} = conn) do + with true <- type in activity_types() do + conn + else + _ -> + conn + |> json(400, "Invalid activity type") + |> halt() + end + end + + # If signature failed but we know this actor we should + # accept it as we may only need to refetch their public key + # during processing + defp known_actor?(%{body_params: data}) do + case Pleroma.Object.Containment.get_actor(data) |> User.get_cached_by_ap_id() do + %User{} -> true + _ -> false + end + end + + # Only permit a subset of activity types from strangers + # or else it will add actors you've never interacted with + # to the database + defp filter_from_strangers(%{body_params: %{"type" => type}} = conn) do + with true <- type in allowed_activity_types_from_strangers() do + conn + else + _ -> + conn + |> json(400, "Invalid activity type for an unknown actor") + |> halt() + end + end + + defp json(conn, status, resp) do + json_resp = Jason.encode!(resp) + + conn + |> put_resp_content_type("application/json") + |> resp(status, json_resp) + |> halt() + end +end diff --git a/lib/pleroma/web/rich_media/helpers.ex b/lib/pleroma/web/rich_media/helpers.ex index e2889b351..d4be97957 100644 --- a/lib/pleroma/web/rich_media/helpers.ex +++ b/lib/pleroma/web/rich_media/helpers.ex @@ -11,16 +11,39 @@ defmodule Pleroma.Web.RichMedia.Helpers do @spec rich_media_get(String.t()) :: {:ok, String.t()} | get_errors() def rich_media_get(url) do - headers = [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}] + case Pleroma.HTTP.AdapterHelper.can_stream?() do + true -> stream(url) + false -> head_first(url) + end + |> handle_result(url) + end + + defp stream(url) do + with {_, {:ok, %Tesla.Env{status: 200, body: stream_body, headers: headers}}} <- + {:get, Pleroma.HTTP.get(url, req_headers(), http_options())}, + {_, :ok} <- {:content_type, check_content_type(headers)}, + {_, :ok} <- {:content_length, check_content_length(headers)}, + {:read_stream, {:ok, body}} <- {:read_stream, read_stream(stream_body)} do + {:ok, body} + end + end + defp head_first(url) do with {_, {:ok, %Tesla.Env{status: 200, headers: headers}}} <- - {:head, Pleroma.HTTP.head(url, headers, http_options())}, + {:head, Pleroma.HTTP.head(url, req_headers(), http_options())}, {_, :ok} <- {:content_type, check_content_type(headers)}, {_, :ok} <- {:content_length, check_content_length(headers)}, {_, {:ok, %Tesla.Env{status: 200, body: body}}} <- - {:get, Pleroma.HTTP.get(url, headers, http_options())} do + {:get, Pleroma.HTTP.get(url, req_headers(), http_options())} do {:ok, body} - else + end + end + + defp handle_result(result, url) do + case result do + {:ok, body} -> + {:ok, body} + {:head, _} -> Logger.debug("Rich media error for #{url}: HTTP HEAD failed") {:error, :head} @@ -29,8 +52,12 @@ defmodule Pleroma.Web.RichMedia.Helpers do Logger.debug("Rich media error for #{url}: content-type is #{type}") {:error, :content_type} - {:content_length, {_, length}} -> - Logger.debug("Rich media error for #{url}: content-length is #{length}") + {:content_length, :error} -> + Logger.debug("Rich media error for #{url}: content-length exceeded") + {:error, :body_too_large} + + {:read_stream, :error} -> + Logger.debug("Rich media error for #{url}: content-length exceeded") {:error, :body_too_large} {:get, _} -> @@ -59,7 +86,7 @@ defmodule Pleroma.Web.RichMedia.Helpers do {_, maybe_content_length} -> case Integer.parse(maybe_content_length) do {content_length, ""} when content_length <= max_body -> :ok - {_, ""} -> {:error, maybe_content_length} + {_, ""} -> :error _ -> :ok end @@ -68,13 +95,37 @@ defmodule Pleroma.Web.RichMedia.Helpers do end end - defp http_options do - timeout = Config.get!([:rich_media, :timeout]) + defp read_stream(stream) do + max_body = Keyword.get(http_options(), :max_body) + + try do + result = + Stream.transform(stream, 0, fn chunk, total_bytes -> + new_total = total_bytes + byte_size(chunk) + + if new_total > max_body do + raise("Exceeds max body limit of #{max_body}") + else + {[chunk], new_total} + end + end) + |> Enum.into(<<>>) + {:ok, result} + rescue + _ -> :error + end + end + + defp http_options do [ pool: :rich_media, max_body: Config.get([:rich_media, :max_body], 5_000_000), - tesla_middleware: [{Tesla.Middleware.Timeout, timeout: timeout}] + stream: true ] end + + defp req_headers do + [{"user-agent", Pleroma.Application.user_agent() <> "; Bot"}] + end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 6492e3861..0423ca9e2 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -189,7 +189,7 @@ defmodule Pleroma.Web.Router do end pipeline :well_known do - plug(:accepts, ["json", "jrd", "jrd+json", "xml", "xrd+xml"]) + plug(:accepts, ["activity+json", "json", "jrd", "jrd+json", "xml", "xrd+xml"]) end pipeline :config do @@ -217,6 +217,10 @@ defmodule Pleroma.Web.Router do plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug) end + pipeline :inbox_guard do + plug(Pleroma.Web.Plugs.InboxGuardPlug) + end + pipeline :static_fe do plug(Pleroma.Web.Plugs.StaticFEPlug) end @@ -920,7 +924,7 @@ defmodule Pleroma.Web.Router do end scope "/", Pleroma.Web.ActivityPub do - pipe_through(:activitypub) + pipe_through([:activitypub, :inbox_guard]) post("/inbox", ActivityPubController, :inbox) post("/users/:nickname/inbox", ActivityPubController, :inbox) end diff --git a/lib/pleroma/workers/background_worker.ex b/lib/pleroma/workers/background_worker.ex index 60da2d5ca..4737c6ea2 100644 --- a/lib/pleroma/workers/background_worker.ex +++ b/lib/pleroma/workers/background_worker.ex @@ -19,10 +19,10 @@ defmodule Pleroma.Workers.BackgroundWorker do User.perform(:force_password_reset, user) end - def perform(%Job{args: %{"op" => op, "user_id" => user_id, "identifiers" => identifiers}}) - when op in ["blocks_import", "follow_import", "mutes_import"] do + def perform(%Job{args: %{"op" => op, "user_id" => user_id, "actor" => actor}}) + when op in ["block_import", "follow_import", "mute_import"] do user = User.get_cached_by_id(user_id) - {:ok, User.Import.perform(String.to_existing_atom(op), user, identifiers)} + User.Import.perform(String.to_existing_atom(op), user, actor) end def perform(%Job{ diff --git a/lib/pleroma/workers/cron/app_cleanup_worker.ex b/lib/pleroma/workers/cron/app_cleanup_worker.ex new file mode 100644 index 000000000..ee71cd7b6 --- /dev/null +++ b/lib/pleroma/workers/cron/app_cleanup_worker.ex @@ -0,0 +1,21 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.Cron.AppCleanupWorker do + @moduledoc """ + Cleans up registered apps that were never associated with a user. + """ + + use Oban.Worker, queue: "background" + + alias Pleroma.Web.OAuth.App + + @impl true + def perform(_job) do + App.remove_orphans() + end + + @impl true + def timeout(_job), do: :timer.seconds(30) +end diff --git a/lib/pleroma/workers/receiver_worker.ex b/lib/pleroma/workers/receiver_worker.ex index d4db97b63..11b672bef 100644 --- a/lib/pleroma/workers/receiver_worker.ex +++ b/lib/pleroma/workers/receiver_worker.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Workers.ReceiverWorker do alias Pleroma.User alias Pleroma.Web.Federator - use Oban.Worker, queue: :federator_incoming, max_attempts: 5 + use Oban.Worker, queue: :federator_incoming, max_attempts: 5, unique: [period: :infinity] @impl true @@ -33,7 +33,7 @@ defmodule Pleroma.Workers.ReceiverWorker do query_string: query_string } - with {:ok, %User{} = _actor} <- User.get_or_fetch_by_ap_id(conn_data.params["actor"]), + with {:ok, %User{}} <- User.get_or_fetch_by_ap_id(conn_data.params["actor"]), {:ok, _public_key} <- Signature.refetch_public_key(conn_data), {:signature, true} <- {:signature, Signature.validate_signature(conn_data)}, {:ok, res} <- Federator.perform(:incoming_ap_doc, params) do @@ -56,17 +56,29 @@ defmodule Pleroma.Workers.ReceiverWorker do def timeout(_job), do: :timer.seconds(5) + defp process_errors({:error, {:error, _} = error}), do: process_errors(error) + defp process_errors(errors) do case errors do - {:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed} + # User fetch failures + {:error, :not_found} = reason -> {:cancel, reason} + {:error, :forbidden} = reason -> {:cancel, reason} + # Inactive user + {:error, {:user_active, false} = reason} -> {:cancel, reason} + # Validator will error and return a changeset error + # e.g., duplicate activities or if the object was deleted + {:error, {:validate, {:error, _changeset} = reason}} -> {:cancel, reason} + # Duplicate detection during Normalization {:error, :already_present} -> {:cancel, :already_present} - {:error, {:validate_object, _} = reason} -> {:cancel, reason} - {:error, {:error, {:validate, {:error, _changeset} = reason}}} -> {:cancel, reason} + # MRFs will return a reject {:error, {:reject, _} = reason} -> {:cancel, reason} + # HTTP Sigs {:signature, false} -> {:cancel, :invalid_signature} - {:error, "Object has been deleted"} = reason -> {:cancel, reason} + # Origin / URL validation failed somewhere possibly due to spoofing + {:error, :origin_containment_failed} -> {:cancel, :origin_containment_failed} + # Unclear if this can be reached {:error, {:side_effects, {:error, :no_object_actor}} = reason} -> {:cancel, reason} - {:error, :not_found} = reason -> {:cancel, reason} + # Catchall {:error, _} = e -> e e -> {:error, e} end diff --git a/lib/pleroma/workers/remote_fetcher_worker.ex b/lib/pleroma/workers/remote_fetcher_worker.ex index e43765733..aa09362f5 100644 --- a/lib/pleroma/workers/remote_fetcher_worker.ex +++ b/lib/pleroma/workers/remote_fetcher_worker.ex @@ -5,7 +5,7 @@ defmodule Pleroma.Workers.RemoteFetcherWorker do alias Pleroma.Object.Fetcher - use Oban.Worker, queue: :background + use Oban.Worker, queue: :background, unique: [period: :infinity] @impl true def perform(%Job{args: %{"op" => "fetch_remote", "id" => id} = args}) do @@ -13,17 +13,26 @@ defmodule Pleroma.Workers.RemoteFetcherWorker do {:ok, _object} -> :ok - {:reject, reason} -> + {:allowed_depth, false} -> + {:cancel, :allowed_depth} + + {:containment, reason} -> {:cancel, reason} - {:error, :forbidden} -> - {:cancel, :forbidden} + {:transmogrifier, reason} -> + {:cancel, reason} - {:error, :not_found} -> - {:cancel, :not_found} + {:fetch, {:error, :forbidden = reason}} -> + {:cancel, reason} - {:error, :allowed_depth} -> - {:cancel, :allowed_depth} + {:fetch, {:error, :not_found = reason}} -> + {:cancel, reason} + + {:fetch, {:error, {:content_type, _}} = reason} -> + {:cancel, reason} + + {:fetch, {:error, reason}} -> + {:error, reason} {:error, _} = e -> e diff --git a/lib/pleroma/workers/rich_media_worker.ex b/lib/pleroma/workers/rich_media_worker.ex index d5ba7b63e..e351ecd6e 100644 --- a/lib/pleroma/workers/rich_media_worker.ex +++ b/lib/pleroma/workers/rich_media_worker.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Workers.RichMediaWorker do alias Pleroma.Web.RichMedia.Backfill alias Pleroma.Web.RichMedia.Card - use Oban.Worker, queue: :background, max_attempts: 3, unique: [period: 300] + use Oban.Worker, queue: :background, max_attempts: 3, unique: [period: :infinity] @impl true def perform(%Job{args: %{"op" => "expire", "url" => url} = _args}) do diff --git a/lib/pleroma/workers/user_refresh_worker.ex b/lib/pleroma/workers/user_refresh_worker.ex index 222a4a8f7..ee276774b 100644 --- a/lib/pleroma/workers/user_refresh_worker.ex +++ b/lib/pleroma/workers/user_refresh_worker.ex @@ -3,7 +3,7 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Workers.UserRefreshWorker do - use Oban.Worker, queue: :background, max_attempts: 1, unique: [period: 300] + use Oban.Worker, queue: :background, max_attempts: 1, unique: [period: :infinity] alias Pleroma.User diff --git a/lib/pleroma/workers/web_pusher_worker.ex b/lib/pleroma/workers/web_pusher_worker.ex index f4232d02a..879b26cc3 100644 --- a/lib/pleroma/workers/web_pusher_worker.ex +++ b/lib/pleroma/workers/web_pusher_worker.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Workers.WebPusherWorker do alias Pleroma.Repo alias Pleroma.Web.Push.Impl - use Oban.Worker, queue: :web_push + use Oban.Worker, queue: :web_push, unique: [period: :infinity] @impl true def perform(%Job{args: %{"op" => "web_push", "notification_id" => notification_id}}) do @@ -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"}, @@ -203,6 +203,8 @@ defmodule Pleroma.Mixfile do {:websock_adapter, "~> 0.5.6"}, {:oban_live_dashboard, "~> 0.1.1"}, {:multipart, "~> 0.4.0", optional: true}, + {:argon2_elixir, "~> 4.0"}, + {:certifi, "~> 2.12"}, ## dev & test {:phoenix_live_reload, "~> 1.3.3", only: :dev}, @@ -1,5 +1,6 @@ %{ "accept": {:hex, :accept, "0.3.5", "b33b127abca7cc948bbe6caa4c263369abf1347cfa9d8e699c6d214660f10cd1", [:rebar3], [], "hexpm", "11b18c220bcc2eab63b5470c038ef10eb6783bcb1fcdb11aa4137defa5ac1bb8"}, + "argon2_elixir": {:hex, :argon2_elixir, "4.0.0", "7f6cd2e4a93a37f61d58a367d82f830ad9527082ff3c820b8197a8a736648941", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "f9da27cf060c9ea61b1bd47837a28d7e48a8f6fa13a745e252556c14f9132c7f"}, "bandit": {:hex, :bandit, "1.5.5", "df28f1c41f745401fe9e85a6882033f5f3442ab6d30c8a2948554062a4ab56e0", [:mix], [{:hpax, "~> 0.2.0", [hex: :hpax, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:thousand_island, "~> 1.0", [hex: :thousand_island, repo: "hexpm", optional: false]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "f21579a29ea4bc08440343b2b5f16f7cddf2fea5725d31b72cf973ec729079e1"}, "base62": {:hex, :base62, "1.2.2", "85c6627eb609317b70f555294045895ffaaeb1758666ab9ef9ca38865b11e629", [:mix], [{:custom_base, "~> 0.2.1", [hex: :custom_base, repo: "hexpm", optional: false]}], "hexpm", "d41336bda8eaa5be197f1e4592400513ee60518e5b9f4dcf38f4b4dae6f377bb"}, "bbcode_pleroma": {:hex, :bbcode_pleroma, "0.2.0", "d36f5bca6e2f62261c45be30fa9b92725c0655ad45c99025cb1c3e28e25803ef", [:mix], [{:nimble_parsec, "~> 0.5", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "19851074419a5fedb4ef49e1f01b30df504bb5dbb6d6adfc135238063bebd1c3"}, @@ -22,7 +23,7 @@ "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, - "credo": {:hex, :credo, "1.7.3", "05bb11eaf2f2b8db370ecaa6a6bda2ec49b2acd5e0418bc106b73b07128c0436", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "35ea675a094c934c22fb1dca3696f3c31f2728ae6ef5a53b5d648c11180a4535"}, + "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, @@ -65,7 +66,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,14 +83,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 ff9ad5245..a56c90724 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 <thestrandedvalley@protonmail.com>\n" -"Language-Team: Chinese (Simplified) <http://weblate.pleroma-dev.ebin.club/" +"PO-Revision-Date: 2024-08-02 09:02+0000\n" +"Last-Translator: Eric Zhang <ericzhang456@disroot.org>\n" +"Language-Team: Chinese (Simplified) <https://translate.pleroma.social/" "projects/pleroma/pleroma-backend-domain-config_descriptions/zh_Hans/>\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 ed0d1576b..7d5828fae 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 <thestrandedvalley@protonmail.com>\n" -"Language-Team: Chinese (Simplified) <http://weblate.pleroma-dev.ebin.club/" +"PO-Revision-Date: 2024-08-02 09:02+0000\n" +"Last-Translator: Eric Zhang <ericzhang456@disroot.org>\n" +"Language-Team: Chinese (Simplified) <https://translate.pleroma.social/" "projects/pleroma/pleroma-backend-domain-default/zh_Hans/>\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 4431445e3..668472939 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 <thestrandedvalley@protonmail.com>\n" -"Language-Team: Chinese (Simplified) <http://weblate.pleroma-dev.ebin.club/" +"PO-Revision-Date: 2024-08-01 08:19+0000\n" +"Last-Translator: Eric Zhang <ericzhang456@disroot.org>\n" +"Language-Team: Chinese (Simplified) <https://translate.pleroma.social/" "projects/pleroma/pleroma-backend-domain-errors/zh_Hans/>\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 000000000..204414836 --- /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 <ericzhang456@disroot.org>\n" +"Language-Team: Chinese (Simplified) <https://translate.pleroma.social/" +"projects/pleroma/pleroma-backend-domain-oauth_scopes/zh_Hans/>\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 c486a5486..930f5db1e 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 <thestrandedvalley@protonmail.com>\n" -"Language-Team: Chinese (Simplified) <http://weblate.pleroma-dev.ebin.club/" +"PO-Revision-Date: 2024-08-01 08:19+0000\n" +"Last-Translator: Eric Zhang <ericzhang456@disroot.org>\n" +"Language-Team: Chinese (Simplified) <https://translate.pleroma.social/" "projects/pleroma/pleroma-backend-domain-posix_errors/zh_Hans/>\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/20240904142434_assign_app_user.exs b/priv/repo/migrations/20240904142434_assign_app_user.exs new file mode 100644 index 000000000..11bec529b --- /dev/null +++ b/priv/repo/migrations/20240904142434_assign_app_user.exs @@ -0,0 +1,21 @@ +defmodule Pleroma.Repo.Migrations.AssignAppUser do + use Ecto.Migration + + alias Pleroma.Repo + alias Pleroma.Web.OAuth.App + alias Pleroma.Web.OAuth.Token + + def up do + Repo.all(Token) + |> Enum.group_by(fn x -> Map.get(x, :app_id) end) + |> Enum.each(fn {_app_id, tokens} -> + token = + Enum.filter(tokens, fn x -> not is_nil(x.user_id) end) + |> List.first() + + App.maybe_update_owner(token) + end) + end + + def down, do: :ok +end diff --git a/priv/scrubbers/default.ex b/priv/scrubbers/default.ex index a75a6465d..dad9dc1a1 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 6e23b3efb..4df840735 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 6b47e7db9..000000000 --- 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": "<a href=\"https://bastianallgeier.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">bastianallgeier.com</span><span class=\"invisible\"></span></a>" - }, - { - "name": "Project", - "type": "PropertyValue", - "value": "<a href=\"https://getkirby.com\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">getkirby.com</span><span class=\"invisible\"></span></a>" - }, - { - "name": "Github", - "type": "PropertyValue", - "value": "<a href=\"https://github.com/bastianallgeier\" target=\"_blank\" rel=\"nofollow noopener noreferrer me\" translate=\"no\"><span class=\"invisible\">https://</span><span class=\"\">github.com/bastianallgeier</span><span class=\"invisible\"></span></a>" - } - ], - "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": "<p>Designer & developer. Creator of Kirby CMS</p>", - "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 3c3fb3fd2..19dc0087f 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": "<p>Favorite piece of anthropology meta discourse.</p>", - "contentMap": { - "en": "<p>Favorite piece of anthropology meta discourse.</p>" - }, - "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": "<p><span class=\"h-card\" translate=\"no\"><a href=\"https://mastodon.social/@bastianallgeier\" class=\"u-url mention\">@<span>bastianallgeier</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://chaos.social/@distantnative\" class=\"u-url mention\">@<span>distantnative</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://fosstodon.org/@kev\" class=\"u-url mention\">@<span>kev</span></a></span> 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.</p>", + "contentMap": { + "en": "<p><span class=\"h-card\" translate=\"no\"><a href=\"https://mastodon.social/@bastianallgeier\" class=\"u-url mention\">@<span>bastianallgeier</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://chaos.social/@distantnative\" class=\"u-url mention\">@<span>distantnative</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://fosstodon.org/@kev\" class=\"u-url mention\">@<span>kev</span></a></span> 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.</p>" }, - "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/pleroma/html_test.exs b/test/pleroma/html_test.exs index 1be161971..d17b07540 100644 --- a/test/pleroma/html_test.exs +++ b/test/pleroma/html_test.exs @@ -41,6 +41,10 @@ defmodule Pleroma.HTMLTest do <span class="h-card"><a class="u-url mention animate-spin">@<span>foo</span></a></span> """ + @mention_hashtags_sample """ + <a href="https://mastodon.example/tags/linux" class="mention hashtag" rel="tag">#<span>linux</span></a> + """ + describe "StripTags scrubber" do test "works as expected" do expected = """ @@ -126,6 +130,15 @@ defmodule Pleroma.HTMLTest do Pleroma.HTML.Scrubber.TwitterText ) end + + test "does allow mention hashtags" do + expected = """ + <a href="https://mastodon.example/tags/linux" class="mention hashtag" rel="tag">#<span>linux</span></a> + """ + + assert expected == + HTML.filter_tags(@mention_hashtags_sample, Pleroma.HTML.Scrubber.Default) + end end describe "default scrubber" do @@ -189,6 +202,15 @@ defmodule Pleroma.HTMLTest do Pleroma.HTML.Scrubber.Default ) end + + test "does allow mention hashtags" do + expected = """ + <a href="https://mastodon.example/tags/linux" class="mention hashtag" rel="tag">#<span>linux</span></a> + """ + + assert expected == + HTML.filter_tags(@mention_hashtags_sample, Pleroma.HTML.Scrubber.Default) + end end describe "extract_first_external_url_from_object" do diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs index 6704c18db..215fca570 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 f75305e0e..1d6469a4f 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/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs index af1a32fed..3bd589f49 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" diff --git a/test/pleroma/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs index 3acbac396..99ed42877 100644 --- a/test/pleroma/web/activity_pub/publisher_test.exs +++ b/test/pleroma/web/activity_pub/publisher_test.exs @@ -180,7 +180,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do 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() @@ -269,7 +269,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do 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" 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 0301a4cca..5d24c0e9f 100644 --- a/test/pleroma/web/mastodon_api/views/account_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs @@ -460,6 +460,45 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest do test_relationship_rendering(user, other_user, expected) end + test "relationship does not indicate following if a FollowingRelationship is missing" do + user = insert(:user) + other_user = insert(:user, local: false) + + # Create a follow relationship with the real Follow Activity and Accept it + assert {:ok, _, _, _} = CommonAPI.follow(other_user, user) + assert {:ok, _} = CommonAPI.accept_follow_request(user, other_user) + + assert %{data: %{"state" => "accept"}} = + Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(user, other_user) + + # Fetch the relationship and forcibly delete it to simulate + # a Follow Accept that did not complete processing + %{following_relationships: [relationship]} = + Pleroma.UserRelationship.view_relationships_option(user, [other_user]) + + assert {:ok, _} = Pleroma.Repo.delete(relationship) + + assert %{following_relationships: [], user_relationships: []} == + Pleroma.UserRelationship.view_relationships_option(user, [other_user]) + + expected = + Map.merge( + @blank_response, + %{ + following: false, + followed_by: false, + muting: false, + muting_notifications: false, + subscribing: false, + notifying: false, + showing_reblogs: true, + id: to_string(other_user.id) + } + ) + + test_relationship_rendering(user, other_user, expected) + end + test "represent a relationship for the blocking and blocked user" do user = insert(:user) other_user = insert(:user) diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index afe0ccb28..bc6dec32a 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -342,7 +342,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do parent_visible: false, pinned_at: nil, quotes_count: 0, - bookmark_folder: nil + bookmark_folder: nil, + list_id: nil } } @@ -912,6 +913,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do status = StatusView.render("show.json", activity: activity) assert status.visibility == "list" + assert status.pleroma.list_id == nil + + status = StatusView.render("show.json", activity: activity, for: user) + + assert status.pleroma.list_id == list.id end test "has a field for parent visibility" do diff --git a/test/pleroma/web/node_info_test.exs b/test/pleroma/web/node_info_test.exs index f474220be..afe4ebb36 100644 --- a/test/pleroma/web/node_info_test.exs +++ b/test/pleroma/web/node_info_test.exs @@ -24,6 +24,19 @@ defmodule Pleroma.Web.NodeInfoTest do |> get(href) |> json_response(200) end) + + accept_types = [ + "application/activity+json", + "application/json", + "application/jrd+json" + ] + + for type <- accept_types do + conn + |> put_req_header("accept", type) + |> get("/.well-known/nodeinfo") + |> json_response(200) + end end test "nodeinfo shows staff accounts", %{conn: conn} do diff --git a/test/pleroma/web/o_auth/app_test.exs b/test/pleroma/web/o_auth/app_test.exs index 96a67de6b..44219cf90 100644 --- a/test/pleroma/web/o_auth/app_test.exs +++ b/test/pleroma/web/o_auth/app_test.exs @@ -53,4 +53,21 @@ defmodule Pleroma.Web.OAuth.AppTest do assert Enum.sort(App.get_user_apps(user)) == Enum.sort(apps) end + + test "removes orphaned apps" do + attrs = %{client_name: "Mastodon-Local", redirect_uris: "."} + {:ok, %App{} = old_app} = App.get_or_make(attrs, ["write"]) + + attrs = %{client_name: "PleromaFE", redirect_uris: "."} + {:ok, %App{} = app} = App.get_or_make(attrs, ["write"]) + + # backdate the old app so it's within the threshold for being cleaned up + {:ok, _} = + "UPDATE apps SET inserted_at = now() - interval '1 hour' WHERE id = #{old_app.id}" + |> Pleroma.Repo.query() + + App.remove_orphans() + + assert [app] == Pleroma.Repo.all(App) + end end diff --git a/test/pleroma/web/o_auth/o_auth_controller_test.exs b/test/pleroma/web/o_auth/o_auth_controller_test.exs index 83a08d9fc..260442771 100644 --- a/test/pleroma/web/o_auth/o_auth_controller_test.exs +++ b/test/pleroma/web/o_auth/o_auth_controller_test.exs @@ -12,6 +12,7 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do alias Pleroma.MFA.TOTP alias Pleroma.Repo alias Pleroma.User + alias Pleroma.Web.OAuth.App alias Pleroma.Web.OAuth.Authorization alias Pleroma.Web.OAuth.OAuthController alias Pleroma.Web.OAuth.Token @@ -770,6 +771,9 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do {:ok, auth} = Authorization.create_authorization(app, user, ["write"]) + # Verify app has no associated user yet + assert %Pleroma.Web.OAuth.App{user_id: nil} = Repo.get_by(App, %{id: app.id}) + conn = build_conn() |> post("/oauth/token", %{ @@ -786,6 +790,10 @@ defmodule Pleroma.Web.OAuth.OAuthControllerTest do assert token assert token.scopes == auth.scopes assert user.ap_id == ap_id + + # Verify app has an associated user now + user_id = user.id + assert %Pleroma.Web.OAuth.App{user_id: ^user_id} = Repo.get_by(App, %{id: app.id}) end test "issues a token for `password` grant_type with valid credentials, with full permissions by default" do 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 52a62e416..efdc743e3 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/plugs/authentication_plug_test.exs b/test/pleroma/web/plugs/authentication_plug_test.exs index b8acd01c5..bdbf3de32 100644 --- a/test/pleroma/web/plugs/authentication_plug_test.exs +++ b/test/pleroma/web/plugs/authentication_plug_test.exs @@ -70,6 +70,24 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlugTest do assert "$pbkdf2" <> _ = user.password_hash end + test "with an argon2 hash, it updates to a pkbdf2 hash", %{conn: conn} do + user = insert(:user, password_hash: Argon2.hash_pwd_salt("123")) + assert "$argon2" <> _ = user.password_hash + + conn = + conn + |> assign(:auth_user, user) + |> assign(:auth_credentials, %{password: "123"}) + |> AuthenticationPlug.call(%{}) + + assert conn.assigns.user.id == conn.assigns.auth_user.id + assert conn.assigns.token == nil + assert PlugHelper.plug_skipped?(conn, OAuthScopesPlug) + + user = User.get_by_id(user.id) + assert "$pbkdf2" <> _ = user.password_hash + end + describe "checkpw/2" do test "check pbkdf2 hash" do hash = @@ -86,6 +104,14 @@ defmodule Pleroma.Web.Plugs.AuthenticationPlugTest do refute AuthenticationPlug.checkpw("password1", hash) end + test "check argon2 hash" do + hash = + "$argon2id$v=19$m=65536,t=8,p=2$zEMMsTuK5KkL5AFWbX7jyQ$VyaQD7PF6e9btz0oH1YiAkWwIGZ7WNDZP8l+a/O171g" + + assert AuthenticationPlug.checkpw("password", hash) + refute AuthenticationPlug.checkpw("password1", hash) + end + test "it returns false when hash invalid" do hash = "psBWV8gxkGOZWBz$PmfCycChoxeJ3GgGzwvhlgacb9mUoZ.KUXNCssekER4SJ7bOK53uXrHNb2e4i8yPFgSKyzaW9CcmrDXWIEMtD1" diff --git a/test/pleroma/workers/receiver_worker_test.exs b/test/pleroma/workers/receiver_worker_test.exs index 33be91085..4d53c44ed 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" => - "<p><span class=\"h-card\" translate=\"no\"><a href=\"https://mastodon.social/@bastianallgeier\" class=\"u-url mention\">@<span>bastianallgeier</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://chaos.social/@distantnative\" class=\"u-url mention\">@<span>distantnative</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://fosstodon.org/@kev\" class=\"u-url mention\">@<span>kev</span></a></span> 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.</p>", - "contentMap" => %{ - "en" => - "<p><span class=\"h-card\" translate=\"no\"><a href=\"https://mastodon.social/@bastianallgeier\" class=\"u-url mention\">@<span>bastianallgeier</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://chaos.social/@distantnative\" class=\"u-url mention\">@<span>distantnative</span></a></span> <span class=\"h-card\" translate=\"no\"><a href=\"https://fosstodon.org/@kev\" class=\"u-url mention\">@<span>kev</span></a></span> 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.</p>" - }, - "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 2104baab2..9caddb600 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 |