From b99053d2c2470dc194858f70a629aecac1bc501f Mon Sep 17 00:00:00 2001 From: Norm Date: Sun, 4 Jun 2023 02:43:18 +0000 Subject: [PATCH 001/149] Reload emoji when using mix pleroma.emoji gen-pack and get-packs I think it makes more sense that the emoji cache gets reloaded in Akkoma if you add or create emoji packs. --- lib/mix/tasks/pleroma/emoji.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex index 5dedf276a..fb93538f9 100644 --- a/lib/mix/tasks/pleroma/emoji.ex +++ b/lib/mix/tasks/pleroma/emoji.ex @@ -130,6 +130,7 @@ def run(["get-packs" | args]) do } File.write!(Path.join(pack_path, "pack.json"), Jason.encode!(pack_json, pretty: true)) + Pleroma.Emoji.reload() else IO.puts(IO.ANSI.format([:bright, :red, "No pack named \"#{pack_name}\" found"])) end @@ -235,6 +236,7 @@ def run(["gen-pack" | args]) do IO.puts("#{pack_file} has been created with the #{name} pack") end + Pleroma.Emoji.reload() end def run(["reload"]) do -- 2.43.0 From 9a7c30fc90787308b5dea72f0b49cce3205c46ae Mon Sep 17 00:00:00 2001 From: Norm Date: Sat, 5 Aug 2023 10:39:03 -0400 Subject: [PATCH 002/149] Update OTP docs to mention arm64 in prerequisites --- docs/docs/installation/otp_en.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/installation/otp_en.md b/docs/docs/installation/otp_en.md index b613e17b2..0a9c32ae7 100644 --- a/docs/docs/installation/otp_en.md +++ b/docs/docs/installation/otp_en.md @@ -5,7 +5,7 @@ This guide covers a installation using an OTP release. To install Akkoma from source, please check out the corresponding guide for your distro. ## Pre-requisites -* A machine running Linux with GNU (e.g. Debian, Ubuntu) or musl (e.g. Alpine) libc and an `x86_64` CPU you have root access to. If you are not sure if it's compatible see [Detecting flavour section](#detecting-flavour) below +* A machine running Linux with GNU (e.g. Debian, Ubuntu) or musl (e.g. Alpine) libc and an `x86_64` or `arm64` CPU you have root access to. If you are not sure if it's compatible see [Detecting flavour section](#detecting-flavour) below * For installing OTP releases on RedHat-based distros like Fedora and Centos Stream, please follow [this guide](./otp_redhat_en.md) instead. * A (sub)domain pointed to the machine -- 2.43.0 From 9723264fe53e15bfef9b830249e8e390fbec0d84 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sun, 6 Aug 2023 15:51:21 +0100 Subject: [PATCH 003/149] Add URI matchers --- lib/pleroma/web/plugs/o_auth_scopes_plug.ex | 4 ++- test/pleroma/mfa/totp_test.exs | 9 +++-- test/support/uri_helpers.ex | 37 +++++++++++++++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) create mode 100644 test/support/uri_helpers.ex diff --git a/lib/pleroma/web/plugs/o_auth_scopes_plug.ex b/lib/pleroma/web/plugs/o_auth_scopes_plug.ex index f017c8bc7..e4d098a7d 100644 --- a/lib/pleroma/web/plugs/o_auth_scopes_plug.ex +++ b/lib/pleroma/web/plugs/o_auth_scopes_plug.ex @@ -34,7 +34,9 @@ def perform(%Plug.Conn{assigns: assigns} = conn, %{scopes: scopes} = options) do permissions = Enum.join(missing_scopes, " #{op} ") error_message = - dgettext("errors", "Insufficient permissions: %{permissions}.", permissions: permissions) + dgettext("errors", "Insufficient permissions: %{permissions}.", + permissions: permissions + ) conn |> put_resp_content_type("application/json") diff --git a/test/pleroma/mfa/totp_test.exs b/test/pleroma/mfa/totp_test.exs index 828993866..392f9d02d 100644 --- a/test/pleroma/mfa/totp_test.exs +++ b/test/pleroma/mfa/totp_test.exs @@ -6,16 +6,19 @@ defmodule Pleroma.MFA.TOTPTest do use Pleroma.DataCase, async: true alias Pleroma.MFA.TOTP + import Pleroma.Test.URIHelpers test "create provisioning_uri to generate qrcode" do uri = - TOTP.provisioning_uri("test-secrcet", "test@example.com", + TOTP.provisioning_uri("test-secret", "test@example.com", issuer: "Plerome-42", digits: 8, period: 60 ) - assert uri == - "otpauth://totp/test@example.com?digits=8&issuer=Plerome-42&period=60&secret=test-secrcet" + assert_uri_equals( + uri, + "otpauth://totp/test@example.com?digits=8&issuer=Plerome-42&period=60&secret=test-secret" + ) end end diff --git a/test/support/uri_helpers.ex b/test/support/uri_helpers.ex new file mode 100644 index 000000000..2fa93a38d --- /dev/null +++ b/test/support/uri_helpers.ex @@ -0,0 +1,37 @@ +defmodule Pleroma.Test.URIHelpers do + import ExUnit.Assertions + + def assert_uri_equals(%URI{} = uri_a, %URI{} = uri_b) do + [:scheme, :authority, :userinfo, :host, :port, :path, :fragment] + |> Enum.each(fn attribute -> + if Map.get(uri_a, attribute) == Map.get(uri_b, attribute) do + :ok + else + flunk("Expected #{uri_a} to match #{uri_b} - #{attribute} does not match") + end + end) + + # And the query string + query_a = URI.decode_query(uri_a.query) + query_b = URI.decode_query(uri_b.query) + + if query_a == query_b do + :ok + else + flunk( + "Expected #{uri_a} to match #{uri_b} - query parameters #{inspect(query_a)} do not match #{inspect(query_b)}" + ) + end + end + + def assert_uri_equals(uri_a, uri_b) when is_binary(uri_a) do + uri_a + |> URI.parse() + |> assert_uri_equals(uri_b) + end + + def assert_uri_equals(%URI{} = uri_a, uri_b) when is_binary(uri_b) do + uri_b = URI.parse(uri_b) + assert_uri_equals(uri_a, uri_b) + end +end -- 2.43.0 From ef422a83853c9b9d8bf336c0207c5388c24457c3 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sun, 6 Aug 2023 15:53:04 +0100 Subject: [PATCH 004/149] Put matchers in matchers subpackage --- test/pleroma/mfa/totp_test.exs | 2 +- test/support/{uri_helpers.ex => matchers/uri.ex} | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) rename test/support/{uri_helpers.ex => matchers/uri.ex} (96%) diff --git a/test/pleroma/mfa/totp_test.exs b/test/pleroma/mfa/totp_test.exs index 392f9d02d..b0572ad80 100644 --- a/test/pleroma/mfa/totp_test.exs +++ b/test/pleroma/mfa/totp_test.exs @@ -6,7 +6,7 @@ defmodule Pleroma.MFA.TOTPTest do use Pleroma.DataCase, async: true alias Pleroma.MFA.TOTP - import Pleroma.Test.URIHelpers + import Pleroma.Test.Matchers.URI test "create provisioning_uri to generate qrcode" do uri = diff --git a/test/support/uri_helpers.ex b/test/support/matchers/uri.ex similarity index 96% rename from test/support/uri_helpers.ex rename to test/support/matchers/uri.ex index 2fa93a38d..8bd518a40 100644 --- a/test/support/uri_helpers.ex +++ b/test/support/matchers/uri.ex @@ -1,4 +1,4 @@ -defmodule Pleroma.Test.URIHelpers do +defmodule Pleroma.Test.Matchers.URI do import ExUnit.Assertions def assert_uri_equals(%URI{} = uri_a, %URI{} = uri_b) do -- 2.43.0 From 866672b6a7586f31e1fa9f2284260fd4069aea7e Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sun, 6 Aug 2023 15:58:11 +0100 Subject: [PATCH 005/149] Add unordered list equality matcher --- test/pleroma/healthcheck_test.exs | 18 ++++++++++-------- test/support/matchers/list.ex | 7 +++++++ 2 files changed, 17 insertions(+), 8 deletions(-) create mode 100644 test/support/matchers/list.ex diff --git a/test/pleroma/healthcheck_test.exs b/test/pleroma/healthcheck_test.exs index 469e5b397..9352840b5 100644 --- a/test/pleroma/healthcheck_test.exs +++ b/test/pleroma/healthcheck_test.exs @@ -6,17 +6,19 @@ defmodule Pleroma.HealthcheckTest do use Pleroma.DataCase, async: true alias Pleroma.Healthcheck + import Pleroma.Test.Matchers.List + test "system_info/0" do result = Healthcheck.system_info() |> Map.from_struct() - assert Map.keys(result) == [ - :active, - :healthy, - :idle, - :job_queue_stats, - :memory_used, - :pool_size - ] + assert_unordered_list_equal(Map.keys(result), [ + :active, + :healthy, + :idle, + :job_queue_stats, + :memory_used, + :pool_size + ]) end describe "check_health/1" do diff --git a/test/support/matchers/list.ex b/test/support/matchers/list.ex new file mode 100644 index 000000000..d98a077f7 --- /dev/null +++ b/test/support/matchers/list.ex @@ -0,0 +1,7 @@ +defmodule Pleroma.Test.Matchers.List do + import ExUnit.Assertions + + def assert_unordered_list_equal(list_a, list_b) when is_list(list_a) and is_list(list_b) do + assert Enum.sort(list_a) == Enum.sort(list_b) + end +end -- 2.43.0 From c193b4d507d5dd4a7be545e4368e01f2b692d01f Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sun, 6 Aug 2023 16:20:46 +0100 Subject: [PATCH 006/149] Remove frankly awful config file test --- test/mix/tasks/pleroma/config_test.exs | 76 +++++++++++++++++++++++++- test/support/matchers/list.ex | 2 +- 2 files changed, 75 insertions(+), 3 deletions(-) diff --git a/test/mix/tasks/pleroma/config_test.exs b/test/mix/tasks/pleroma/config_test.exs index 46c109cc1..4eb8d785f 100644 --- a/test/mix/tasks/pleroma/config_test.exs +++ b/test/mix/tasks/pleroma/config_test.exs @@ -182,8 +182,80 @@ test "load a settings with large values and pass to file", %{temp_file: temp_fil assert File.exists?(temp_file) {:ok, file} = File.read(temp_file) - assert file == - "import Config\n\nconfig :pleroma, :instance,\n name: \"Pleroma\",\n email: \"example@example.com\",\n notify_email: \"noreply@example.com\",\n description: \"A Pleroma instance, an alternative fediverse server\",\n limit: 5000,\n remote_limit: 100_000,\n upload_limit: 16_000_000,\n avatar_upload_limit: 2_000_000,\n background_upload_limit: 4_000_000,\n banner_upload_limit: 4_000_000,\n poll_limits: %{\n max_expiration: 31_536_000,\n max_option_chars: 200,\n max_options: 20,\n min_expiration: 0\n },\n registrations_open: true,\n federating: true,\n federation_incoming_replies_max_depth: 100,\n federation_reachability_timeout_days: 7,\n federation_publisher_modules: [Pleroma.Web.ActivityPub.Publisher],\n allow_relay: true,\n public: true,\n quarantined_instances: [],\n managed_config: true,\n static_dir: \"instance/static/\",\n allowed_post_formats: [\"text/plain\", \"text/html\", \"text/markdown\", \"text/bbcode\"],\n autofollowed_nicknames: [],\n max_pinned_statuses: 1,\n attachment_links: false,\n max_report_comment_size: 1000,\n safe_dm_mentions: false,\n healthcheck: false,\n remote_post_retention_days: 90,\n skip_thread_containment: true,\n limit_to_local_content: :unauthenticated,\n user_bio_length: 5000,\n user_name_length: 100,\n max_account_fields: 10,\n max_remote_account_fields: 20,\n account_field_name_length: 512,\n account_field_value_length: 2048,\n external_user_synchronization: true,\n extended_nickname_format: true,\n multi_factor_authentication: [\n totp: [digits: 6, period: 30],\n backup_codes: [number: 2, length: 6]\n ]\n" + {:ok, file_quote} = Code.string_to_quoted(file) + + {:__block__, [], + [ + _, + {:config, _, config_quote} + ]} = file_quote + + assert [ + :pleroma, + :instance, + [ + name: "Pleroma", + email: "example@example.com", + notify_email: "noreply@example.com", + description: "A Pleroma instance, an alternative fediverse server", + limit: 5000, + remote_limit: 100_000, + upload_limit: 16_000_000, + avatar_upload_limit: 2_000_000, + background_upload_limit: 4_000_000, + banner_upload_limit: 4_000_000, + poll_limits: + {_, _, + [ + max_options: 20, + max_option_chars: 200, + min_expiration: 0, + max_expiration: 31_536_000 + ]}, + registrations_open: true, + federating: true, + federation_incoming_replies_max_depth: 100, + federation_reachability_timeout_days: 7, + federation_publisher_modules: [ + {_, _, [:Pleroma, :Web, :ActivityPub, :Publisher]} + ], + allow_relay: true, + public: true, + quarantined_instances: [], + managed_config: true, + static_dir: "instance/static/", + allowed_post_formats: ["text/plain", "text/html", "text/markdown", "text/bbcode"], + autofollowed_nicknames: [], + max_pinned_statuses: 1, + attachment_links: false, + max_report_comment_size: 1000, + safe_dm_mentions: false, + healthcheck: false, + remote_post_retention_days: 90, + skip_thread_containment: true, + limit_to_local_content: :unauthenticated, + user_bio_length: 5000, + user_name_length: 100, + max_account_fields: 10, + max_remote_account_fields: 20, + account_field_name_length: 512, + account_field_value_length: 2048, + external_user_synchronization: true, + extended_nickname_format: true, + multi_factor_authentication: [ + {:totp, + [ + digits: 6, + period: 30 + ]}, + {:backup_codes, + [ + number: 2, + length: 6 + ]} + ] + ] + ] = config_quote end end diff --git a/test/support/matchers/list.ex b/test/support/matchers/list.ex index d98a077f7..78e82586d 100644 --- a/test/support/matchers/list.ex +++ b/test/support/matchers/list.ex @@ -1,6 +1,6 @@ defmodule Pleroma.Test.Matchers.List do import ExUnit.Assertions - + def assert_unordered_list_equal(list_a, list_b) when is_list(list_a) and is_list(list_b) do assert Enum.sort(list_a) == Enum.sort(list_b) end -- 2.43.0 From 215b5503172b5fd0d423e665f2ece5d6d3d417d4 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sun, 6 Aug 2023 16:27:15 +0100 Subject: [PATCH 007/149] Fix keyword ordering reliance --- test/pleroma/repo/migrations/autolinker_to_linkify_test.exs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/pleroma/repo/migrations/autolinker_to_linkify_test.exs b/test/pleroma/repo/migrations/autolinker_to_linkify_test.exs index a7d4d493c..073e70705 100644 --- a/test/pleroma/repo/migrations/autolinker_to_linkify_test.exs +++ b/test/pleroma/repo/migrations/autolinker_to_linkify_test.exs @@ -29,13 +29,13 @@ test "change/0 converts auto_linker opts for Pleroma.Formatter", %{migration: mi %{value: new_opts} = ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Formatter}) - assert new_opts == [ + assert Keyword.equal?(new_opts, class: false, extra: true, new_window: false, rel: "testing", strip_prefix: false - ] + ) clear_config(Pleroma.Formatter, new_opts) assert new_opts == Pleroma.Config.get(Pleroma.Formatter) @@ -67,6 +67,6 @@ test "transform_opts/1 returns a list of compatible opts", %{migration: migratio strip_prefix: false ] - assert migration.transform_opts(old_opts) == expected_opts + assert Keyword.equal?(migration.transform_opts(old_opts), expected_opts) end end -- 2.43.0 From 7956cfb0917283cfefe190e202be39fa40c1af59 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sun, 6 Aug 2023 16:36:18 +0100 Subject: [PATCH 008/149] Another keyword.equal? check --- .../migrations/fix_malformed_formatter_config_test.exs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test/pleroma/repo/migrations/fix_malformed_formatter_config_test.exs b/test/pleroma/repo/migrations/fix_malformed_formatter_config_test.exs index 65c9961b0..9f589882d 100644 --- a/test/pleroma/repo/migrations/fix_malformed_formatter_config_test.exs +++ b/test/pleroma/repo/migrations/fix_malformed_formatter_config_test.exs @@ -26,16 +26,16 @@ test "change/0 converts a map into a list", %{migration: migration} do %{value: new_opts} = ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Formatter}) - assert new_opts == [ + assert Keyword.equal?(new_opts, class: false, extra: true, new_window: false, rel: "F", strip_prefix: false - ] + ) clear_config(Pleroma.Formatter, new_opts) - assert new_opts == Pleroma.Config.get(Pleroma.Formatter) + assert Keyword.equal?(new_opts, Pleroma.Config.get(Pleroma.Formatter)) {text, _mentions, []} = Pleroma.Formatter.linkify( @@ -61,7 +61,7 @@ test "change/0 skips if Pleroma.Formatter config is already a list", %{migration %{value: new_opts} = ConfigDB.get_by_params(%{group: :pleroma, key: Pleroma.Formatter}) - assert new_opts == opts + assert Keyword.equal?(new_opts, opts) end test "change/0 skips if Pleroma.Formatter is empty", %{migration: migration} do -- 2.43.0 From 650c0c0f623c9e403af4536611b60f61e66ebdc5 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sun, 6 Aug 2023 16:44:25 +0100 Subject: [PATCH 009/149] Allow max_id to be at the end of the querystring --- .../web/mastodon_api/controllers/status_controller_test.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index bd3735925..25a18e145 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -1883,7 +1883,7 @@ test "favorites paginate correctly" do # Using the header for pagination works correctly [next, _] = get_resp_header(result, "link") |> hd() |> String.split(", ") - [_, max_id] = Regex.run(~r/max_id=([^&]+)/, next) + [_, max_id] = Regex.run(~r/max_id=([^&>]+)/, next) assert max_id == third_favorite.id -- 2.43.0 From e59fc0677bad55c0582ba4c9d2892c98baad99d4 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Mon, 7 Aug 2023 04:07:42 +0100 Subject: [PATCH 010/149] Update mime dep --- config/config.exs | 4 ++++ mix.exs | 10 +++++++--- mix.lock | 11 ++++++----- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/config/config.exs b/config/config.exs index 3430ee4d7..d8b163c61 100644 --- a/config/config.exs +++ b/config/config.exs @@ -170,6 +170,10 @@ "application/ld+json" => ["activity+json"] } +config :mime, :extensions, %{ + "activity+json" => "application/activity+json" +} + config :tesla, :adapter, {Tesla.Adapter.Finch, name: MyFinch} # Configures http settings, upstream proxy etc. diff --git a/mix.exs b/mix.exs index 27f1aa77a..5ffbf0fe7 100644 --- a/mix.exs +++ b/mix.exs @@ -115,6 +115,7 @@ defp oauth_deps do defp deps do [ {:phoenix, "~> 1.6.15"}, + {:mime, "~> 2.0"}, {:tzdata, "~> 1.1.1"}, {:plug_cowboy, "~> 2.6"}, {:phoenix_pubsub, "~> 2.1"}, @@ -177,7 +178,9 @@ defp deps do git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", ref: "3bbfa8b5ea13accc1b1c40579a380d8e5cfd6ad2"}, {:restarter, path: "./restarter"}, - {:majic, "~> 1.0"}, + {:majic, + git: "https://akkoma.dev/AkkomaGang/majic.git", + ref: "93a3c8e617a96e20f6f17844c5e45b3b30ba1789"}, {:eblurhash, "~> 1.2.2"}, {:open_api_spex, "~> 3.17"}, {:search_parser, @@ -190,7 +193,7 @@ defp deps do git: "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", ref: "main"}, {:mfm_parser, git: "https://akkoma.dev/AkkomaGang/mfm-parser.git", - ref: "912fba81152d4d572e457fd5427f9875b2bc3dbe"}, + ref: "b21ab7754024af096f2d14247574f55f0063295b"}, ## dev & test {:ex_doc, "~> 0.30", only: :dev, runtime: false}, @@ -201,7 +204,8 @@ defp deps do {:mox, "~> 1.0", only: :test}, {:websockex, "~> 0.4.3", only: :test}, {:dialyxir, "~> 1.3", only: [:dev], runtime: false}, - {:mint, "~> 1.5.1", override: true} + {:mint, "~> 1.5.1", override: true}, + {:nimble_pool, "~> 1.0", override: true} ] ++ oauth_deps() end diff --git a/mix.lock b/mix.lock index 19b954d81..aab94708c 100644 --- a/mix.lock +++ b/mix.lock @@ -32,7 +32,7 @@ "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.12", "e3bd8318702b069263d0118e7cdb6c66c5ff0a034f540f4c0158bd769e7dff6a", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "4a1d1d10b74ce033a428a99272038c90e444a0a1912a074e38a71ee9f667714c"}, "ecto_sql": {:hex, :ecto_sql, "3.10.1", "6ea6b3036a0b0ca94c2a02613fd9f742614b5cfe494c41af2e6571bb034dd94c", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6a25bdbbd695f12c8171eaff0851fa4c8e72eec1e98c7364402dda9ce11c56b"}, "elasticsearch": {:git, "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", "6cd946f75f6ab9042521a009d1d32d29a90113ca", [ref: "main"]}, - "elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"}, + "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"}, "ex_aws": {:hex, :ex_aws, "2.4.4", "d7886eaca7e10f7bd3d9e9d2d5414cb336737b3ab2fddd4fa30358b725293fe0", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a7d63e485ca2b16fb804f3f20097827aa69885eea6e69fa75c98f353c9c91dc7"}, @@ -45,6 +45,7 @@ "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"}, "fast_html": {:hex, :fast_html, "2.2.0", "6c5ef1be087a4ed613b0379c13f815c4d11742b36b67bb52cee7859847c84520", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "064c4f23b4a6168f9187dac8984b056f2c531bb0787f559fd6a8b34b38aefbae"}, "fast_sanitize": {:hex, :fast_sanitize, "0.2.3", "67b93dfb34e302bef49fec3aaab74951e0f0602fd9fa99085987af05bd91c7a5", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e8ad286d10d0386e15d67d0ee125245ebcfbc7d7290b08712ba9013c8c5e56e2"}, + "file_ex": {:git, "https://akkoma.dev/AkkomaGang/file_ex.git", "e2ce878c72bfe7a5c1f2795e071c3db589cfd09b", [ref: "e2ce878c72bfe7a5c1f2795e071c3db589cfd09b"]}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "finch": {:hex, :finch, "0.16.0", "40733f02c89f94a112518071c0a91fe86069560f5dbdb39f9150042f44dcfb1a", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f660174c4d519e5fec629016054d60edd822cdfe2b7270836739ac2f97735ec5"}, "flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"}, @@ -64,14 +65,14 @@ "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"}, "linkify": {:git, "https://akkoma.dev/AkkomaGang/linkify.git", "2567e2c1073fa371fd26fd66dfa5bc77b6919c16", []}, "mail": {:hex, :mail, "0.3.0", "f353ef5f41d9f2e483ba7c5df92cdfe27b990b2d8c8c41d84c7b2b40ec33989f", [:mix], [], "hexpm", "523d700b2231d887dc4ee41d3bb13f48358f6f118e55a67cef6d630d7907116c"}, - "majic": {:hex, :majic, "1.0.0", "37e50648db5f5c2ff0c9fb46454d034d11596c03683807b9fb3850676ffdaab3", [:make, :mix], [{:elixir_make, "~> 0.6.1", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:mime, "~> 1.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "7905858f76650d49695f14ea55cd9aaaee0c6654fa391671d4cf305c275a0a9e"}, + "majic": {:git, "https://akkoma.dev/AkkomaGang/majic.git", "93a3c8e617a96e20f6f17844c5e45b3b30ba1789", [ref: "93a3c8e617a96e20f6f17844c5e45b3b30ba1789"]}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, - "mfm_parser": {:git, "https://akkoma.dev/AkkomaGang/mfm-parser.git", "912fba81152d4d572e457fd5427f9875b2bc3dbe", [ref: "912fba81152d4d572e457fd5427f9875b2bc3dbe"]}, - "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, + "mfm_parser": {:git, "https://akkoma.dev/AkkomaGang/mfm-parser.git", "b21ab7754024af096f2d14247574f55f0063295b", [ref: "b21ab7754024af096f2d14247574f55f0063295b"]}, + "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"}, "mock": {:hex, :mock, "0.3.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"}, @@ -79,7 +80,7 @@ "mox": {:hex, :mox, "1.0.2", "dc2057289ac478b35760ba74165b4b3f402f68803dd5aecd3bfd19c183815d64", [:mix], [], "hexpm", "f9864921b3aaf763c8741b5b8e6f908f44566f1e427b2630e89e9a73b981fef2"}, "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, - "nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"}, + "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, "oban": {:hex, :oban, "2.15.2", "8f934a49db39163633965139c8846d8e24c2beb4180f34a005c2c7c3f69a6aa2", [:mix], [{:ecto_sql, "~> 3.6", [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", "0f4a579ea48fc7489e0d84facf8b01566e142bdc6542d7dabce32c10e664f1e9"}, "open_api_spex": {:hex, :open_api_spex, "3.17.3", "ada8e352eb786050dd639db2439d3316e92f3798eb2abd051f55bb9af825b37e", [: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", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "165db21a85ca83cffc8e7c8890f35b354eddda8255de7404a2848ed652b9f0fe"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, -- 2.43.0 From 7825798e32e0414267066dc142367793f23a14a8 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Mon, 7 Aug 2023 11:12:14 +0100 Subject: [PATCH 011/149] Add XML matcher --- lib/pleroma/web/xml.ex | 8 +--- mix.exs | 1 + mix.lock | 2 + test/fixtures/xml_normal.xml | 44 +++++++++++++++++++ .../web_finger/web_finger_controller_test.exs | 5 ++- test/pleroma/web/xml_test.exs | 5 +++ test/support/matchers/xml.ex | 18 ++++++++ 7 files changed, 74 insertions(+), 9 deletions(-) create mode 100644 test/fixtures/xml_normal.xml create mode 100644 test/support/matchers/xml.ex diff --git a/lib/pleroma/web/xml.ex b/lib/pleroma/web/xml.ex index ba8886548..e68341e20 100644 --- a/lib/pleroma/web/xml.ex +++ b/lib/pleroma/web/xml.ex @@ -26,13 +26,7 @@ def string_from_xpath(xpath, doc) do def parse_document(text) do try do - {doc, _rest} = - text - |> :binary.bin_to_list() - |> :xmerl_scan.string( - quiet: true, - allow_entities: false - ) + doc = SweetXml.parse(text, dtd: :none) {:ok, doc} rescue diff --git a/mix.exs b/mix.exs index 5ffbf0fe7..5aa1381d5 100644 --- a/mix.exs +++ b/mix.exs @@ -204,6 +204,7 @@ defp deps do {:mox, "~> 1.0", only: :test}, {:websockex, "~> 0.4.3", only: :test}, {:dialyxir, "~> 1.3", only: [:dev], runtime: false}, + {:elixir_xml_to_map, "~> 3.0", only: :test}, {:mint, "~> 1.5.1", override: true}, {:nimble_pool, "~> 1.0", override: true} ] ++ oauth_deps() diff --git a/mix.lock b/mix.lock index aab94708c..471e61750 100644 --- a/mix.lock +++ b/mix.lock @@ -33,7 +33,9 @@ "ecto_sql": {:hex, :ecto_sql, "3.10.1", "6ea6b3036a0b0ca94c2a02613fd9f742614b5cfe494c41af2e6571bb034dd94c", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6a25bdbbd695f12c8171eaff0851fa4c8e72eec1e98c7364402dda9ce11c56b"}, "elasticsearch": {:git, "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", "6cd946f75f6ab9042521a009d1d32d29a90113ca", [ref: "main"]}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, + "elixir_xml_to_map": {:hex, :elixir_xml_to_map, "3.0.0", "67dcff30ecf72aed37ab08525133e4420717a749436e22bfece431e7dddeea7e", [:mix], [{:erlsom, "~> 1.4", [hex: :erlsom, repo: "hexpm", optional: false]}], "hexpm", "11222dd7f029f8db7a6662b41c992dbdb0e1c6e4fdea6a42056f9d27c847efbb"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, + "erlsom": {:hex, :erlsom, "1.5.1", "c8fe2babd33ff0846403f6522328b8ab676f896b793634cfe7ef181c05316c03", [:rebar3], [], "hexpm", "7965485494c5844dd127656ac40f141aadfa174839ec1be1074e7edf5b4239eb"}, "eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"}, "ex_aws": {:hex, :ex_aws, "2.4.4", "d7886eaca7e10f7bd3d9e9d2d5414cb336737b3ab2fddd4fa30358b725293fe0", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a7d63e485ca2b16fb804f3f20097827aa69885eea6e69fa75c98f353c9c91dc7"}, "ex_aws_s3": {:hex, :ex_aws_s3, "2.4.0", "ce8decb6b523381812798396bc0e3aaa62282e1b40520125d1f4eff4abdff0f4", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "85dda6e27754d94582869d39cba3241d9ea60b6aa4167f9c88e309dc687e56bb"}, diff --git a/test/fixtures/xml_normal.xml b/test/fixtures/xml_normal.xml new file mode 100644 index 000000000..8dfa0af54 --- /dev/null +++ b/test/fixtures/xml_normal.xml @@ -0,0 +1,44 @@ + + + + + Match One + + + 1 + Team One + + + 2 + Team Two + + + + + Match Two + + + 2 + Team Two + + + 3 + Team Three + + + + + Match Three + + + 1 + Team One + + + 3 + Team Three + + + + + diff --git a/test/pleroma/web/web_finger/web_finger_controller_test.exs b/test/pleroma/web/web_finger/web_finger_controller_test.exs index 6b5b7c46c..929ebbed0 100644 --- a/test/pleroma/web/web_finger/web_finger_controller_test.exs +++ b/test/pleroma/web/web_finger/web_finger_controller_test.exs @@ -8,6 +8,7 @@ defmodule Pleroma.Web.WebFinger.WebFingerControllerTest do import ExUnit.CaptureLog import Pleroma.Factory import Tesla.Mock + import Pleroma.Test.Matchers.XML setup do mock(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -23,8 +24,8 @@ test "GET host-meta" do assert response.status == 200 - assert response.resp_body == - ~s() + assert_xml_equals(response.resp_body, + ~s()) end test "Webfinger JRD" do diff --git a/test/pleroma/web/xml_test.exs b/test/pleroma/web/xml_test.exs index 49306430b..0dc8d299e 100644 --- a/test/pleroma/web/xml_test.exs +++ b/test/pleroma/web/xml_test.exs @@ -3,6 +3,11 @@ defmodule Pleroma.Web.XMLTest do alias Pleroma.Web.XML + test "parses normal XML" do + data = File.read!("test/fixtures/xml_normal.xml") + assert {:ok, _} = XML.parse_document(data) + end + test "refuses to parse any entities from XML" do data = File.read!("test/fixtures/xml_billion_laughs.xml") assert(:error == XML.parse_document(data)) diff --git a/test/support/matchers/xml.ex b/test/support/matchers/xml.ex new file mode 100644 index 000000000..9f260ce82 --- /dev/null +++ b/test/support/matchers/xml.ex @@ -0,0 +1,18 @@ +defmodule Pleroma.Test.Matchers.XML do + import ExUnit.Assertions + + def assert_xml_equals(xml_a, xml_b) do + map_a = XmlToMap.naive_map(xml_a) + map_b = XmlToMap.naive_map(xml_b) + + if map_a != map_b do + flunk(~s|Expected XML + #{xml_a} + + to equal + + #{xml_b} + |) + end + end +end -- 2.43.0 From fc3cc61768b21a8ab20fbe4cfb74e0979c61dac4 Mon Sep 17 00:00:00 2001 From: Clovis Date: Mon, 7 Aug 2023 14:39:59 +0200 Subject: [PATCH 012/149] Fix invalid Date HTTP header when signing fetch requests https://akkoma.dev/AkkomaGang/akkoma/issues/503 --- lib/pleroma/signature.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex index b229e6296..3d33fcd62 100644 --- a/lib/pleroma/signature.ex +++ b/lib/pleroma/signature.ex @@ -76,6 +76,6 @@ def sign(%User{keys: keys} = user, headers) do def signed_date, do: signed_date(NaiveDateTime.utc_now()) def signed_date(%NaiveDateTime{} = date) do - Timex.format!(date, "{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT") + Timex.lformat!(date, "{WDshort}, {0D} {Mshort} {YYYY} {h24}:{m}:{s} GMT", "en") end end -- 2.43.0 From 0c2134115695355a34af1702c5899fd5d6d61a8c Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Mon, 7 Aug 2023 16:17:17 +0100 Subject: [PATCH 013/149] Fix signature checking --- .woodpecker/test.yml | 5 +- CHANGELOG.md | 4 ++ mix.exs | 4 +- mix.lock | 2 +- test/pleroma/signature_test.exs | 49 ++++++++++++++++--- .../web_finger/web_finger_controller_test.exs | 6 ++- test/support/matchers/list.ex | 11 ++++- 7 files changed, 65 insertions(+), 16 deletions(-) diff --git a/.woodpecker/test.yml b/.woodpecker/test.yml index be8ea0dfa..d52746929 100644 --- a/.woodpecker/test.yml +++ b/.woodpecker/test.yml @@ -12,9 +12,8 @@ matrix: OTP_VERSION: 25 - ELIXIR_VERSION: 1.15 OTP_VERSION: 25 - # Soon - #- ELIXIR_VERSION: 1.15 - # OTP_VERSION: 26 + - ELIXIR_VERSION: 1.15 + OTP_VERSION: 26 variables: - &scw-secrets diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e26a2a83..004b8b835 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## Unreleased + +## Added + ## 2023.08 ## Added diff --git a/mix.exs b/mix.exs index 5aa1381d5..e31cd62af 100644 --- a/mix.exs +++ b/mix.exs @@ -157,7 +157,9 @@ defp deps do {:timex, "~> 3.7"}, {:ueberauth, "~> 0.10"}, {:linkify, git: "https://akkoma.dev/AkkomaGang/linkify.git"}, - {:http_signatures, "~> 0.1.1"}, + {:http_signatures, + git: "https://akkoma.dev/AkkomaGang/http_signatures.git", + ref: "6640ce7d24c783ac2ef56e27d00d12e8dc85f396"}, {:telemetry, "~> 1.2"}, {:telemetry_poller, "~> 1.0"}, {:telemetry_metrics, "~> 0.6"}, diff --git a/mix.lock b/mix.lock index 471e61750..7e661c139 100644 --- a/mix.lock +++ b/mix.lock @@ -57,7 +57,7 @@ "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~> 2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, - "http_signatures": {:hex, :http_signatures, "0.1.1", "ca7ebc1b61542b163644c8c3b1f0e0f41037d35f2395940d3c6c7deceab41fd8", [:mix], [], "hexpm", "cc3b8a007322cc7b624c0c15eec49ee58ac977254ff529a3c482f681465942a3"}, + "http_signatures": {:git, "https://akkoma.dev/AkkomaGang/http_signatures.git", "6640ce7d24c783ac2ef56e27d00d12e8dc85f396", [ref: "6640ce7d24c783ac2ef56e27d00d12e8dc85f396"]}, "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.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"}, diff --git a/test/pleroma/signature_test.exs b/test/pleroma/signature_test.exs index 1f52484a5..e2d02fe4c 100644 --- a/test/pleroma/signature_test.exs +++ b/test/pleroma/signature_test.exs @@ -71,6 +71,35 @@ test "it returns error when not found user" do end end + defp split_signature(sig) do + sig + |> String.split(",") + |> Enum.map(fn part -> + [key, value] = String.split(part, "=", parts: 2) + [key, String.trim(value, ~s|"|)] + end) + |> Enum.sort_by(fn [k, _] -> k end) + end + + # Break up a signature and check by parts + defp assert_signature_equal(sig_a, sig_b) when is_binary(sig_a) and is_binary(sig_b) do + parts_a = split_signature(sig_a) + parts_b = split_signature(sig_b) + + parts_a + |> Enum.with_index() + |> Enum.each(fn {part_a, index} -> + part_b = Enum.at(parts_b, index) + assert_part_equal(part_a, part_b) + end) + end + + defp assert_part_equal(part_a, part_b) do + if part_a != part_b do + flunk("Signature check failed - expected #{part_a} to equal #{part_b}") + end + end + describe "sign/2" do test "it returns signature headers" do user = @@ -79,14 +108,18 @@ test "it returns signature headers" do keys: @private_key }) - assert Signature.sign( - user, - %{ - host: "test.test", - "content-length": 100 - } - ) == - "keyId=\"https://mastodon.social/users/lambadalambda#main-key\",algorithm=\"rsa-sha256\",headers=\"content-length host\",signature=\"sibUOoqsFfTDerquAkyprxzDjmJm6erYc42W5w1IyyxusWngSinq5ILTjaBxFvfarvc7ci1xAi+5gkBwtshRMWm7S+Uqix24Yg5EYafXRun9P25XVnYBEIH4XQ+wlnnzNIXQkU3PU9e6D8aajDZVp3hPJNeYt1gIPOA81bROI8/glzb1SAwQVGRbqUHHHKcwR8keiR/W2h7BwG3pVRy4JgnIZRSW7fQogKedDg02gzRXwUDFDk0pr2p3q6bUWHUXNV8cZIzlMK+v9NlyFbVYBTHctAR26GIAN6Hz0eV0mAQAePHDY1mXppbA8Gpp6hqaMuYfwifcXmcc+QFm4e+n3A==\"" + headers = %{ + host: "test.test", + "content-length": 100 + } + + assert_signature_equal( + Signature.sign( + user, + headers + ), + "keyId=\"https://mastodon.social/users/lambadalambda#main-key\",algorithm=\"rsa-sha256\",headers=\"content-length host\",signature=\"sibUOoqsFfTDerquAkyprxzDjmJm6erYc42W5w1IyyxusWngSinq5ILTjaBxFvfarvc7ci1xAi+5gkBwtshRMWm7S+Uqix24Yg5EYafXRun9P25XVnYBEIH4XQ+wlnnzNIXQkU3PU9e6D8aajDZVp3hPJNeYt1gIPOA81bROI8/glzb1SAwQVGRbqUHHHKcwR8keiR/W2h7BwG3pVRy4JgnIZRSW7fQogKedDg02gzRXwUDFDk0pr2p3q6bUWHUXNV8cZIzlMK+v9NlyFbVYBTHctAR26GIAN6Hz0eV0mAQAePHDY1mXppbA8Gpp6hqaMuYfwifcXmcc+QFm4e+n3A==\"" + ) end test "it returns error" do diff --git a/test/pleroma/web/web_finger/web_finger_controller_test.exs b/test/pleroma/web/web_finger/web_finger_controller_test.exs index 929ebbed0..fe8301fa4 100644 --- a/test/pleroma/web/web_finger/web_finger_controller_test.exs +++ b/test/pleroma/web/web_finger/web_finger_controller_test.exs @@ -24,8 +24,10 @@ test "GET host-meta" do assert response.status == 200 - assert_xml_equals(response.resp_body, - ~s()) + assert_xml_equals( + response.resp_body, + ~s() + ) end test "Webfinger JRD" do diff --git a/test/support/matchers/list.ex b/test/support/matchers/list.ex index 78e82586d..fa3303e34 100644 --- a/test/support/matchers/list.ex +++ b/test/support/matchers/list.ex @@ -2,6 +2,15 @@ defmodule Pleroma.Test.Matchers.List do import ExUnit.Assertions def assert_unordered_list_equal(list_a, list_b) when is_list(list_a) and is_list(list_b) do - assert Enum.sort(list_a) == Enum.sort(list_b) + list_a = Enum.sort(list_a) + list_b = Enum.sort(list_b) + + if list_a != list_b do + flunk("Expected list + #{inspect(list_a)} + to have the same elements as + #{inspect(list_b)} + ") + end end end -- 2.43.0 From 80cbdc848022e06bb278690d28256b5bc1cda64c Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Mon, 7 Aug 2023 16:27:23 +0100 Subject: [PATCH 014/149] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 004b8b835..cc9b79a4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased ## Added +- Full compatibility with Erlang OTP26 ## 2023.08 -- 2.43.0 From c7aeeec232f5ecf7dcb34142276d8935395a8a55 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Mon, 7 Aug 2023 17:00:16 +0100 Subject: [PATCH 015/149] fix yet another keyword equality check --- test/mix/tasks/pleroma/config_test.exs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/test/mix/tasks/pleroma/config_test.exs b/test/mix/tasks/pleroma/config_test.exs index 4eb8d785f..3b09f656b 100644 --- a/test/mix/tasks/pleroma/config_test.exs +++ b/test/mix/tasks/pleroma/config_test.exs @@ -204,14 +204,7 @@ test "load a settings with large values and pass to file", %{temp_file: temp_fil avatar_upload_limit: 2_000_000, background_upload_limit: 4_000_000, banner_upload_limit: 4_000_000, - poll_limits: - {_, _, - [ - max_options: 20, - max_option_chars: 200, - min_expiration: 0, - max_expiration: 31_536_000 - ]}, + poll_limits: {_, _, poll_limits}, registrations_open: true, federating: true, federation_incoming_replies_max_depth: 100, @@ -256,6 +249,14 @@ test "load a settings with large values and pass to file", %{temp_file: temp_fil ] ] ] = config_quote + + assert Keyword.equal?( + poll_limits, + max_options: 20, + max_option_chars: 200, + min_expiration: 0, + max_expiration: 31_536_000 + ) end end -- 2.43.0 From 63a5b8506cf3207b7164c71144717a50c5557507 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Mon, 7 Aug 2023 17:13:34 +0100 Subject: [PATCH 016/149] update majic --- mix.exs | 2 +- mix.lock | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.exs b/mix.exs index e31cd62af..20fbd627c 100644 --- a/mix.exs +++ b/mix.exs @@ -182,7 +182,7 @@ defp deps do {:restarter, path: "./restarter"}, {:majic, git: "https://akkoma.dev/AkkomaGang/majic.git", - ref: "93a3c8e617a96e20f6f17844c5e45b3b30ba1789"}, + ref: "baf7e236aeceb03387c1314f65654601e3dbb6d1"}, {:eblurhash, "~> 1.2.2"}, {:open_api_spex, "~> 3.17"}, {:search_parser, diff --git a/mix.lock b/mix.lock index 7e661c139..4830e5b4e 100644 --- a/mix.lock +++ b/mix.lock @@ -47,7 +47,7 @@ "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"}, "fast_html": {:hex, :fast_html, "2.2.0", "6c5ef1be087a4ed613b0379c13f815c4d11742b36b67bb52cee7859847c84520", [:make, :mix], [{:elixir_make, "~> 0.4", [hex: :elixir_make, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}], "hexpm", "064c4f23b4a6168f9187dac8984b056f2c531bb0787f559fd6a8b34b38aefbae"}, "fast_sanitize": {:hex, :fast_sanitize, "0.2.3", "67b93dfb34e302bef49fec3aaab74951e0f0602fd9fa99085987af05bd91c7a5", [:mix], [{:fast_html, "~> 2.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "e8ad286d10d0386e15d67d0ee125245ebcfbc7d7290b08712ba9013c8c5e56e2"}, - "file_ex": {:git, "https://akkoma.dev/AkkomaGang/file_ex.git", "e2ce878c72bfe7a5c1f2795e071c3db589cfd09b", [ref: "e2ce878c72bfe7a5c1f2795e071c3db589cfd09b"]}, + "file_ex": {:git, "https://akkoma.dev/AkkomaGang/file_ex.git", "cc7067c7d446c2526e9ecf91d40896b088851569", [ref: "cc7067c7d446c2526e9ecf91d40896b088851569"]}, "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "finch": {:hex, :finch, "0.16.0", "40733f02c89f94a112518071c0a91fe86069560f5dbdb39f9150042f44dcfb1a", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f660174c4d519e5fec629016054d60edd822cdfe2b7270836739ac2f97735ec5"}, "flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"}, @@ -67,7 +67,7 @@ "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"}, "linkify": {:git, "https://akkoma.dev/AkkomaGang/linkify.git", "2567e2c1073fa371fd26fd66dfa5bc77b6919c16", []}, "mail": {:hex, :mail, "0.3.0", "f353ef5f41d9f2e483ba7c5df92cdfe27b990b2d8c8c41d84c7b2b40ec33989f", [:mix], [], "hexpm", "523d700b2231d887dc4ee41d3bb13f48358f6f118e55a67cef6d630d7907116c"}, - "majic": {:git, "https://akkoma.dev/AkkomaGang/majic.git", "93a3c8e617a96e20f6f17844c5e45b3b30ba1789", [ref: "93a3c8e617a96e20f6f17844c5e45b3b30ba1789"]}, + "majic": {:git, "https://akkoma.dev/AkkomaGang/majic.git", "baf7e236aeceb03387c1314f65654601e3dbb6d1", [ref: "baf7e236aeceb03387c1314f65654601e3dbb6d1"]}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, -- 2.43.0 From f2da47679dd92171eb06d25f688aaa4ac8f8ad02 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Mon, 7 Aug 2023 17:24:05 +0100 Subject: [PATCH 017/149] majic --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 20fbd627c..4adc34dee 100644 --- a/mix.exs +++ b/mix.exs @@ -182,7 +182,7 @@ defp deps do {:restarter, path: "./restarter"}, {:majic, git: "https://akkoma.dev/AkkomaGang/majic.git", - ref: "baf7e236aeceb03387c1314f65654601e3dbb6d1"}, + ref: "15138d478bd19163481ae5d82df5fb602ec6ed90"}, {:eblurhash, "~> 1.2.2"}, {:open_api_spex, "~> 3.17"}, {:search_parser, diff --git a/mix.lock b/mix.lock index 4830e5b4e..f5a9f0df2 100644 --- a/mix.lock +++ b/mix.lock @@ -67,7 +67,7 @@ "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"}, "linkify": {:git, "https://akkoma.dev/AkkomaGang/linkify.git", "2567e2c1073fa371fd26fd66dfa5bc77b6919c16", []}, "mail": {:hex, :mail, "0.3.0", "f353ef5f41d9f2e483ba7c5df92cdfe27b990b2d8c8c41d84c7b2b40ec33989f", [:mix], [], "hexpm", "523d700b2231d887dc4ee41d3bb13f48358f6f118e55a67cef6d630d7907116c"}, - "majic": {:git, "https://akkoma.dev/AkkomaGang/majic.git", "baf7e236aeceb03387c1314f65654601e3dbb6d1", [ref: "baf7e236aeceb03387c1314f65654601e3dbb6d1"]}, + "majic": {:git, "https://akkoma.dev/AkkomaGang/majic.git", "15138d478bd19163481ae5d82df5fb602ec6ed90", [ref: "15138d478bd19163481ae5d82df5fb602ec6ed90"]}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, -- 2.43.0 From e7788f3c827c9d13a680eef98b3127294dd9f614 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Tue, 8 Aug 2023 22:42:57 +0100 Subject: [PATCH 018/149] bullseye build (you owe me for this one) --- .woodpecker/build-amd64.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/.woodpecker/build-amd64.yml b/.woodpecker/build-amd64.yml index 61a37cd44..bbfcabf13 100644 --- a/.woodpecker/build-amd64.yml +++ b/.woodpecker/build-amd64.yml @@ -65,6 +65,32 @@ pipeline: - export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-amd64-ubuntu-jammy.zip - /bin/sh /entrypoint.sh + debian-bullseye: + image: hexpm/elixir:1.15.4-erlang-25.3.2.5-debian-bullseye-20230612 + <<: *on-release + environment: + MIX_ENV: prod + DEBIAN_FRONTEND: noninteractive + commands: + - apt-get update && apt-get install -y cmake libmagic-dev rclone zip imagemagick libmagic-dev git build-essential g++ wget + - *clean + - echo "import Config" > config/prod.secret.exs + - *setup-hex + - *tag-build + - mix deps.get --only prod + - mix release --path release + - zip akkoma-amd64-debian-bullseye.zip -r release + + release-debian-bullseye: + image: akkoma/releaser + <<: *on-release + secrets: *scw-secrets + commands: + - export SOURCE=akkoma-amd64-debian-bullseye.zip + # AMD64 + - export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-amd64-debian-bullseye.zip + - /bin/sh /entrypoint.sh + # Canonical amd64-musl musl: image: hexpm/elixir:1.14.3-erlang-25.2.2-alpine-3.18.0 -- 2.43.0 From 73be5c3f305b3a92ca1fcb0f07827041babc7254 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 9 Aug 2023 13:51:49 +0100 Subject: [PATCH 019/149] Bump majic --- mix.exs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 4adc34dee..2cc45ff5a 100644 --- a/mix.exs +++ b/mix.exs @@ -115,7 +115,6 @@ defp oauth_deps do defp deps do [ {:phoenix, "~> 1.6.15"}, - {:mime, "~> 2.0"}, {:tzdata, "~> 1.1.1"}, {:plug_cowboy, "~> 2.6"}, {:phoenix_pubsub, "~> 2.1"}, @@ -182,7 +181,7 @@ defp deps do {:restarter, path: "./restarter"}, {:majic, git: "https://akkoma.dev/AkkomaGang/majic.git", - ref: "15138d478bd19163481ae5d82df5fb602ec6ed90"}, + ref: "dd118c859072dcdcd361149a2beb42cb74a506d9"}, {:eblurhash, "~> 1.2.2"}, {:open_api_spex, "~> 3.17"}, {:search_parser, -- 2.43.0 From 60a07da5efc9998173edec3023539ddcba30dea1 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 9 Aug 2023 13:58:10 +0100 Subject: [PATCH 020/149] Update majic deps --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 2cc45ff5a..698f91390 100644 --- a/mix.exs +++ b/mix.exs @@ -181,7 +181,7 @@ defp deps do {:restarter, path: "./restarter"}, {:majic, git: "https://akkoma.dev/AkkomaGang/majic.git", - ref: "dd118c859072dcdcd361149a2beb42cb74a506d9"}, + ref: "80540b36939ec83f48e76c61e5000e0fd67706f0"}, {:eblurhash, "~> 1.2.2"}, {:open_api_spex, "~> 3.17"}, {:search_parser, diff --git a/mix.lock b/mix.lock index f5a9f0df2..4791d3853 100644 --- a/mix.lock +++ b/mix.lock @@ -67,7 +67,7 @@ "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"}, "linkify": {:git, "https://akkoma.dev/AkkomaGang/linkify.git", "2567e2c1073fa371fd26fd66dfa5bc77b6919c16", []}, "mail": {:hex, :mail, "0.3.0", "f353ef5f41d9f2e483ba7c5df92cdfe27b990b2d8c8c41d84c7b2b40ec33989f", [:mix], [], "hexpm", "523d700b2231d887dc4ee41d3bb13f48358f6f118e55a67cef6d630d7907116c"}, - "majic": {:git, "https://akkoma.dev/AkkomaGang/majic.git", "15138d478bd19163481ae5d82df5fb602ec6ed90", [ref: "15138d478bd19163481ae5d82df5fb602ec6ed90"]}, + "majic": {:git, "https://akkoma.dev/AkkomaGang/majic.git", "80540b36939ec83f48e76c61e5000e0fd67706f0", [ref: "80540b36939ec83f48e76c61e5000e0fd67706f0"]}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, -- 2.43.0 From 7bd4ae54121b93a5d11f239c7b2b54fd12687028 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 9 Aug 2023 14:39:28 +0100 Subject: [PATCH 021/149] Bump builds to OTP26 --- .woodpecker/build-amd64.yml | 6 +++--- .woodpecker/build-arm64.yml | 4 ++-- CHANGELOG.md | 3 +++ Dockerfile | 2 +- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.woodpecker/build-amd64.yml b/.woodpecker/build-amd64.yml index bbfcabf13..938c62665 100644 --- a/.woodpecker/build-amd64.yml +++ b/.woodpecker/build-amd64.yml @@ -37,7 +37,7 @@ variables: pipeline: # Canonical amd64 debian-bookworm: - image: hexpm/elixir:1.15.4-erlang-25.3.2.5-debian-bookworm-20230612 + image: hexpm/elixir:1.15.4-erlang-26.0.2-debian-bookworm-20230612 <<: *on-release environment: MIX_ENV: prod @@ -66,7 +66,7 @@ pipeline: - /bin/sh /entrypoint.sh debian-bullseye: - image: hexpm/elixir:1.15.4-erlang-25.3.2.5-debian-bullseye-20230612 + image: hexpm/elixir:1.15.4-erlang-26.0.2-debian-bullseye-20230612 <<: *on-release environment: MIX_ENV: prod @@ -93,7 +93,7 @@ pipeline: # Canonical amd64-musl musl: - image: hexpm/elixir:1.14.3-erlang-25.2.2-alpine-3.18.0 + image: hexpm/elixir:1.15.4-erlang-26.0.2-alpine-3.18.2 <<: *on-stable environment: MIX_ENV: prod diff --git a/.woodpecker/build-arm64.yml b/.woodpecker/build-arm64.yml index d923e5847..1ce1fac44 100644 --- a/.woodpecker/build-arm64.yml +++ b/.woodpecker/build-arm64.yml @@ -37,7 +37,7 @@ variables: pipeline: # Canonical arm64 debian-bookworm: - image: hexpm/elixir:1.15.4-erlang-25.3.2.5-debian-bookworm-20230612 + image: hexpm/elixir:1.15.4-erlang-26.0.2-debian-bookworm-20230612 <<: *on-release environment: MIX_ENV: prod @@ -65,7 +65,7 @@ pipeline: # Canonical arm64-musl musl: - image: hexpm/elixir:1.15.4-erlang-25.3.2.5-alpine-3.18.2 + image: hexpm/elixir:1.15.4-erlang-26.0.2-alpine-3.18.2 <<: *on-stable environment: MIX_ENV: prod diff --git a/CHANGELOG.md b/CHANGELOG.md index cc9b79a4e..a3938f5ac 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Added - Full compatibility with Erlang OTP26 +## Changed +- OTP builds are now built on erlang OTP26 + ## 2023.08 ## Added diff --git a/Dockerfile b/Dockerfile index f695322e5..aadd08f7a 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM hexpm/elixir:1.15.4-erlang-25.3.2.5-alpine-3.18.2 +FROM hexpm/elixir:1.15.4-erlang-26.0.2-alpine-3.18.2 ENV MIX_ENV=prod ENV ERL_EPMD_ADDRESS=127.0.0.1 -- 2.43.0 From d79c92f9c6315e3bd874a07a80efdfa76a0d828f Mon Sep 17 00:00:00 2001 From: Norm Date: Fri, 11 Aug 2023 11:07:14 -0400 Subject: [PATCH 022/149] meilisearch: Move published date to lower priority Currently, Akkoma sorts by published date first before everything else. This however makes search results pretty bad since Meilisearch uses a bucket sort algorithm in order of the ranking rules specified: https://www.meilisearch.com/docs/learn/core_concepts/relevancy#behavior Since the `published` attribute is a unix timestamp, the resulting buckets are pretty small so the other rules essentially have little to no effect on the rankings of search results. This fixes that issue by moving the `published:desc` rule further down so it still sorts by date, but only after considering everything else. AFAIK attribute and sort doesn't really affect results for Akkoma since the only attribute considered is the `content` attribute and the `sort` parameter isn't used in Akkoma searches. Everything else is made to match more closely to Meilisearch's defaults. --- lib/mix/tasks/pleroma/search/meilisearch.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/mix/tasks/pleroma/search/meilisearch.ex b/lib/mix/tasks/pleroma/search/meilisearch.ex index 27a31afcf..299fb5b14 100644 --- a/lib/mix/tasks/pleroma/search/meilisearch.ex +++ b/lib/mix/tasks/pleroma/search/meilisearch.ex @@ -30,12 +30,12 @@ def run(["index"]) do meili_put( "/indexes/objects/settings/ranking-rules", [ - "published:desc", "words", - "exactness", "proximity", "typo", + "exactness", "attribute", + "published:desc", "sort" ] ) -- 2.43.0 From c22ecac56777cdb93a6b248deb874ac6991f8fca Mon Sep 17 00:00:00 2001 From: Joshua Goins Date: Sat, 12 Aug 2023 08:05:48 -0400 Subject: [PATCH 023/149] mastodon_api: Add /api/v1/preferences endpoint Implements the preferences endpoint in the Mastodon API, but returns default values for most of the preferences right now. The only supported preference we can access is default post visibility, and a relevant test is added as well. --- .../api_spec/operations/account_operation.ex | 14 ++++++++++ .../controllers/account_controller.ex | 7 ++++- .../web/mastodon_api/views/account_view.ex | 11 ++++++++ lib/pleroma/web/router.ex | 2 ++ .../controllers/account_controller_test.exs | 28 +++++++++++++++++++ 5 files changed, 61 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 2d14316d1..9a6dfc1ff 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -451,6 +451,20 @@ def endorsements_operation do } end + def preferences_operation do + %Operation{ + tags: ["Account Preferences"], + description: "Preferences defined by the user in their account settings.", + summary: "Preferred common behaviors to be shared across clients.", + operationId: "AccountController.preferences", + security: [%{"oAuth" => ["read:accounts"]}], + responses: %{ + 200 => Operation.response("Preferences", "application/json", Account), + 401 => Operation.response("Error", "application/json", ApiError) + } + } + end + def identity_proofs_operation do %Operation{ tags: ["Retrieve account information"], diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 26f46cc1a..bbae5a432 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -51,7 +51,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do plug( OAuthScopesPlug, %{scopes: ["read:accounts"]} - when action in [:verify_credentials, :endorsements, :identity_proofs] + when action in [:verify_credentials, :endorsements, :identity_proofs, :preferences] ) plug( @@ -544,4 +544,9 @@ def endorsements(conn, params), do: MastodonAPIController.empty_array(conn, para @doc "GET /api/v1/identity_proofs" def identity_proofs(conn, params), do: MastodonAPIController.empty_array(conn, params) + + @doc "GET /api/v1/preferences" + def preferences(%{assigns: %{user: user}} = conn, params) do + render(conn, "preferences.json", user: user) + end end diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index e3f91a4e3..1ab6cba9b 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -315,6 +315,17 @@ defp do_render("show.json", %{user: user} = opts) do |> maybe_put_email_address(user, opts[:for]) end + def render("preferences.json", %{user: user} = opts) do + # TODO: Do we expose more settings that make sense to plug in here? + %{ + "posting:default:visibility": user.default_scope, + "posting:default:sensitive": false, + "posting:default:language": nil, + "reading:expand:media": "default", + "reading:expand:spoilers": false + } + end + defp username_from_nickname(string) when is_binary(string) do hd(String.split(string, "@")) end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 3ea3c9132..ca4995281 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -629,6 +629,8 @@ defmodule Pleroma.Web.Router do post("/tags/:id/follow", TagController, :follow) post("/tags/:id/unfollow", TagController, :unfollow) get("/followed_tags", TagController, :show_followed) + + get("/preferences", AccountController, :preferences) end scope "/api/web", Pleroma.Web do diff --git a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs index 6ca9cfc50..ede14030b 100644 --- a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs @@ -2060,4 +2060,32 @@ test "removing user from followers errors", %{user: user, conn: conn} do assert %{"error" => "Record not found"} = json_response_and_validate_schema(conn_res, 404) end end + + describe "preferences" do + test "get account preferences" do + user = insert(:user, default_scope: "public") + %{conn: conn} = oauth_access(["read:accounts"], user: user) + + conn = get(conn, "/api/v1/preferences") + response = json_response_and_validate_schema(conn, 200) + + assert %{ + "posting:default:language" => nil, + "posting:default:sensitive" => false, + "posting:default:visibility" => "public", + "reading:expand:media" => "default", + "reading:expand:spoilers" => false + } = response + end + + test "test changing account preferences" do + user = insert(:user, default_scope: "unlisted") + %{conn: conn} = oauth_access(["read:accounts"], user: user) + + conn = get(conn, "/api/v1/preferences") + response = json_response_and_validate_schema(conn, 200) + + assert response["posting:default:visibility"] == "unlisted" + end + end end -- 2.43.0 From 1bd3012c2d9037780e854b8738f99f16fafcad5a Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sat, 12 Aug 2023 15:03:43 +0100 Subject: [PATCH 024/149] Fix compiler warnings --- CHANGELOG.md | 1 + .../controllers/account_controller.ex | 2 +- .../web/mastodon_api/views/account_view.ex | 22 +++++++++---------- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a3938f5ac..e0256a894 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Added - Full compatibility with Erlang OTP26 +- handling of GET /api/v1/preferences ## Changed - OTP builds are now built on erlang OTP26 diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index bbae5a432..6fc88354f 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -546,7 +546,7 @@ def endorsements(conn, params), do: MastodonAPIController.empty_array(conn, para def identity_proofs(conn, params), do: MastodonAPIController.empty_array(conn, params) @doc "GET /api/v1/preferences" - def preferences(%{assigns: %{user: user}} = conn, params) do + def preferences(%{assigns: %{user: user}} = conn, _params) do render(conn, "preferences.json", user: user) end end diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 1ab6cba9b..2f543c08a 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -190,6 +190,17 @@ def render("instance.json", %{instance: %Pleroma.Instances.Instance{} = instance def render("instance.json", _), do: nil + def render("preferences.json", %{user: user} = _opts) do + # TODO: Do we expose more settings that make sense to plug in here? + %{ + "posting:default:visibility": user.default_scope, + "posting:default:sensitive": false, + "posting:default:language": nil, + "reading:expand:media": "default", + "reading:expand:spoilers": false + } + end + defp do_render("show.json", %{user: user} = opts) do user = User.sanitize_html(user, User.html_filter_policy(opts[:for])) display_name = user.name || user.nickname @@ -315,17 +326,6 @@ defp do_render("show.json", %{user: user} = opts) do |> maybe_put_email_address(user, opts[:for]) end - def render("preferences.json", %{user: user} = opts) do - # TODO: Do we expose more settings that make sense to plug in here? - %{ - "posting:default:visibility": user.default_scope, - "posting:default:sensitive": false, - "posting:default:language": nil, - "reading:expand:media": "default", - "reading:expand:spoilers": false - } - end - defp username_from_nickname(string) when is_binary(string) do hd(String.split(string, "@")) end -- 2.43.0 From 655c282de3f7e4634e5c476dbf6572860bab1cc9 Mon Sep 17 00:00:00 2001 From: YokaiRick Date: Sat, 12 Aug 2023 21:59:30 +0000 Subject: [PATCH 025/149] update docs nginx subdir in akkoma/installation is gone --- docs/docs/installation/otp_en.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/installation/otp_en.md b/docs/docs/installation/otp_en.md index 0a9c32ae7..8a8ae077b 100644 --- a/docs/docs/installation/otp_en.md +++ b/docs/docs/installation/otp_en.md @@ -187,18 +187,18 @@ The location of nginx configs is dependent on the distro === "Alpine" ``` - cp /opt/akkoma/installation/nginx/akkoma.nginx /etc/nginx/conf.d/akkoma.conf + cp /opt/akkoma/installation/akkoma.nginx /etc/nginx/conf.d/akkoma.conf ``` === "Debian/Ubuntu" ``` - cp /opt/akkoma/installation/nginx/akkoma.nginx /etc/nginx/sites-available/akkoma.conf + cp /opt/akkoma/installation/akkoma.nginx /etc/nginx/sites-available/akkoma.conf ln -s /etc/nginx/sites-available/akkoma.conf /etc/nginx/sites-enabled/akkoma.conf ``` If your distro does not have either of those you can append `include /etc/nginx/akkoma.conf` to the end of the http section in /etc/nginx/nginx.conf and ```sh -cp /opt/akkoma/installation/nginx/akkoma.nginx /etc/nginx/akkoma.conf +cp /opt/akkoma/installation/akkoma.nginx /etc/nginx/akkoma.conf ``` #### Edit the nginx config -- 2.43.0 From 76ba400c6da1a51d73f59af7e646179cf7886e6c Mon Sep 17 00:00:00 2001 From: YokaiRick Date: Sat, 12 Aug 2023 22:09:32 +0000 Subject: [PATCH 026/149] nginx subdir is missing in otp builds --- docs/docs/installation/otp_redhat_en.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/installation/otp_redhat_en.md b/docs/docs/installation/otp_redhat_en.md index 1490d3139..ea27af6f4 100644 --- a/docs/docs/installation/otp_redhat_en.md +++ b/docs/docs/installation/otp_redhat_en.md @@ -178,7 +178,7 @@ certbot certonly --standalone --preferred-challenges http -d yourinstance.tld #### Copy Akkoma nginx configuration to the nginx folder ```shell -cp /opt/akkoma/installation/nginx/akkoma.nginx /etc/nginx/conf.d/akkoma.conf +cp /opt/akkoma/installation/akkoma.nginx /etc/nginx/conf.d/akkoma.conf ``` #### Edit the nginx config -- 2.43.0 From 6cb40bee26e3a7196628495ecc472a01537a1e01 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Tue, 15 Aug 2023 10:22:18 +0000 Subject: [PATCH 027/149] Migrate to phoenix 1.7 (#626) Closes #612 Co-authored-by: tusooa Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/626 Co-authored-by: FloatingGhost Co-committed-by: FloatingGhost --- .formatter.exs | 13 ++++- .woodpecker/lint.yml | 55 ++++++++++++++++++ .woodpecker/test.yml | 40 ++++--------- CHANGELOG.md | 4 ++ config/config.exs | 16 +---- lib/mix/tasks/pleroma/emoji.ex | 1 + lib/mix/tasks/pleroma/user.ex | 15 +---- lib/phoenix/transports/web_socket/raw.ex | 1 - lib/pleroma/emails/admin_email.ex | 9 ++- lib/pleroma/emails/user_email.ex | 22 ++----- lib/pleroma/user.ex | 9 +-- lib/pleroma/web.ex | 29 ++++++++-- lib/pleroma/web/activity_pub/activity_pub.ex | 5 ++ lib/pleroma/web/activity_pub/builder.ex | 4 +- lib/pleroma/web/activity_pub/utils.ex | 15 ++--- .../web/activity_pub/views/user_view.ex | 14 ++--- .../controllers/admin_api_controller.ex | 4 +- lib/pleroma/web/endpoint.ex | 2 +- lib/pleroma/web/feed/user_controller.ex | 2 +- lib/pleroma/web/masto_fe_controller.ex | 4 +- .../controllers/auth_controller.ex | 9 +-- .../web/mastodon_api/views/status_view.ex | 2 +- .../web/mastodon_api/views/tag_view.ex | 3 +- lib/pleroma/web/metadata/providers/feed.ex | 6 +- .../web/metadata/providers/twitter_card.ex | 4 +- lib/pleroma/web/o_auth/o_auth_controller.ex | 4 +- .../web/o_status/o_status_controller.ex | 3 +- lib/pleroma/web/plugs/http_signature_plug.ex | 7 ++- .../web/static_fe/static_fe_controller.ex | 11 ++-- lib/pleroma/web/static_fe/static_fe_view.ex | 1 - .../frontend_switcher/switch.html.eex | 2 +- .../web/templates/feed/feed/tag.atom.eex | 4 +- .../web/templates/feed/feed/tag.rss.eex | 2 +- .../web/templates/feed/feed/user.atom.eex | 6 +- .../web/templates/feed/feed/user.rss.eex | 6 +- .../web/templates/masto_fe/fedibird.html.heex | 58 +++++++++++++++++++ .../masto_fe/fedibird.index.html.eex | 35 ----------- .../templates/masto_fe/glitchsoc.html.heex | 57 ++++++++++++++++++ .../masto_fe/glitchsoc.index.html.eex | 35 ----------- .../templates/o_auth/mfa/recovery.html.eex | 12 ++-- .../web/templates/o_auth/mfa/totp.html.eex | 12 ++-- .../templates/o_auth/o_auth/consumer.html.eex | 2 +- .../templates/o_auth/o_auth/register.html.eex | 10 ++-- .../web/templates/o_auth/o_auth/show.html.eex | 10 ++-- .../static_fe/static_fe/profile.html.eex | 2 +- .../twitter_api/password/reset.html.eex | 2 +- .../twitter_api/remote_follow/follow.html.eex | 2 +- .../remote_follow/follow_login.html.eex | 2 +- .../remote_follow/follow_mfa.html.eex | 2 +- .../twitter_api/util/status_interact.html.eex | 2 +- .../twitter_api/util/subscribe.html.eex | 2 +- .../controllers/remote_follow_controller.ex | 2 +- lib/pleroma/web/views/embed_view.ex | 3 +- lib/pleroma/web/views/masto_fe_view.ex | 2 +- mix.exs | 7 ++- mix.lock | 14 +++-- test/pleroma/emails/admin_email_test.exs | 3 +- test/pleroma/emails/user_email_test.exs | 10 +--- test/pleroma/integration/federation_test.exs | 5 +- test/pleroma/user_test.exs | 14 +---- .../activity_pub_controller_test.exs | 10 ++-- .../web/activity_pub/activity_pub_test.exs | 8 +++ .../controllers/admin_api_controller_test.exs | 3 +- .../controllers/report_controller_test.exs | 10 ++-- .../controllers/user_controller_test.exs | 6 +- test/pleroma/web/feed/tag_controller_test.exs | 8 +-- .../pleroma/web/feed/user_controller_test.exs | 12 ++-- .../mastodon_api/update_credentials_test.exs | 2 +- .../mastodon_api/views/status_view_test.exs | 2 +- .../metadata/providers/twitter_card_test.exs | 3 +- .../web/mongoose_im_controller_test.exs | 16 ++--- .../web/o_auth/o_auth_controller_test.exs | 12 ++-- .../remote_follow_controller_test.exs | 42 ++++++-------- test/pleroma/web/uploader_controller_test.exs | 4 +- test/support/conn_case.ex | 2 +- test/support/data_case.ex | 2 + 76 files changed, 426 insertions(+), 355 deletions(-) create mode 100644 .woodpecker/lint.yml create mode 100644 lib/pleroma/web/templates/masto_fe/fedibird.html.heex delete mode 100644 lib/pleroma/web/templates/masto_fe/fedibird.index.html.eex create mode 100644 lib/pleroma/web/templates/masto_fe/glitchsoc.html.heex delete mode 100644 lib/pleroma/web/templates/masto_fe/glitchsoc.index.html.eex diff --git a/.formatter.exs b/.formatter.exs index abd91dbbe..a96afe758 100644 --- a/.formatter.exs +++ b/.formatter.exs @@ -1,3 +1,14 @@ [ - inputs: ["mix.exs", "{config,lib,test}/**/*.{ex,exs}", "priv/repo/migrations/*.exs", "priv/repo/optional_migrations/**/*.exs", "priv/scrubbers/*.ex"] + import_deps: [:ecto, :ecto_sql, :phoenix], + subdirectories: ["priv/*/migrations"], + plugins: [Phoenix.LiveView.HTMLFormatter], + inputs: [ + "mix.exs", + "*.{heex,ex,exs}", + "{config,lib,test}/**/*.{heex,ex,exs}", + "priv/*/seeds.exs", + "priv/repo/migrations/*.exs", + "priv/repo/optional_migrations/**/*.exs", + "priv/scrubbers/*.ex" + ] ] diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml new file mode 100644 index 000000000..8308e57d7 --- /dev/null +++ b/.woodpecker/lint.yml @@ -0,0 +1,55 @@ +platform: linux/amd64 + +variables: + - &scw-secrets + - SCW_ACCESS_KEY + - SCW_SECRET_KEY + - SCW_DEFAULT_ORGANIZATION_ID + - &setup-hex "mix local.hex --force && mix local.rebar --force" + - &on-release + when: + event: + - push + - tag + branch: + - develop + - stable + - refs/tags/v* + - refs/tags/stable-* + - &on-stable + when: + event: + - push + - tag + branch: + - stable + - refs/tags/stable-* + - &on-point-release + when: + event: + - push + branch: + - develop + - stable + - &on-pr-open + when: + event: + - pull_request + + - &tag-build "export BUILD_TAG=$${CI_COMMIT_TAG:-\"$CI_COMMIT_BRANCH\"} && export PLEROMA_BUILD_BRANCH=$BUILD_TAG" + + - &clean "(rm -rf release || true) && (rm -rf _build || true) && (rm -rf /root/.mix)" + - &mix-clean "mix deps.clean --all && mix clean" + +pipeline: + lint: + image: akkoma/ci-base:1.15-otp26 + <<: *on-pr-open + environment: + MIX_ENV: test + commands: + - mix local.hex --force + - mix local.rebar --force + - mix deps.get + - mix compile + - mix format --check-formatted diff --git a/.woodpecker/test.yml b/.woodpecker/test.yml index d52746929..16a4067fe 100644 --- a/.woodpecker/test.yml +++ b/.woodpecker/test.yml @@ -1,5 +1,8 @@ platform: linux/amd64 +depends_on: + - lint + matrix: ELIXIR_VERSION: - 1.14 @@ -68,15 +71,7 @@ services: POSTGRES_PASSWORD: postgres pipeline: - lint: - <<: *on-pr-open - image: akkoma/ci-base:1.15 - commands: - - mix local.hex --force - - mix local.rebar --force - - mix format --check-formatted - - build: + test: image: akkoma/ci-base:${ELIXIR_VERSION}-otp${OTP_VERSION} <<: *on-pr-open environment: @@ -90,24 +85,9 @@ pipeline: - mix local.rebar --force - mix deps.get - mix compile - - test: - image: akkoma/ci-base:${ELIXIR_VERSION}-otp${OTP_VERSION} - <<: *on-pr-open - environment: - MIX_ENV: test - POSTGRES_DB: pleroma_test_${ELIXIR_VERSION}_${OTP_VERSION} - POSTGRES_USER: postgres - POSTGRES_PASSWORD: postgres - DB_HOST: postgres - commands: - - mix local.hex --force - - mix local.rebar --force - - mix deps.get - - mix compile - - mix ecto.drop -f -q - - mix ecto.create - - mix ecto.migrate - - mkdir -p test/tmp - - mix test --preload-modules --exclude erratic --exclude federated --exclude mocked - - mix test --preload-modules --only mocked + - mix ecto.drop -f -q + - mix ecto.create + - mix ecto.migrate + - mkdir -p test/tmp + - mix test --preload-modules --exclude erratic --exclude federated --exclude mocked + - mix test --preload-modules --only mocked diff --git a/CHANGELOG.md b/CHANGELOG.md index e0256a894..a0b7fcf79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Changed - OTP builds are now built on erlang OTP26 +- The base Phoenix framework is now updated to 1.7 + +## Fixed +- Documentation issue in which a non-existing nginx file was referenced ## 2023.08 diff --git a/config/config.exs b/config/config.exs index d8b163c61..9a3cdcebf 100644 --- a/config/config.exs +++ b/config/config.exs @@ -110,17 +110,6 @@ "xmpp" ] -websocket_config = [ - path: "/websocket", - serializer: [ - {Phoenix.Socket.V1.JSONSerializer, "~> 1.0.0"}, - {Phoenix.Socket.V2.JSONSerializer, "~> 2.0.0"} - ], - timeout: 60_000, - transport_log: false, - compress: false -] - # Configures the endpoint config :pleroma, Pleroma.Web.Endpoint, url: [host: "localhost"], @@ -130,10 +119,7 @@ {:_, [ {"/api/v1/streaming", Pleroma.Web.MastodonAPI.WebsocketHandler, []}, - {"/websocket", Phoenix.Endpoint.CowboyWebSocket, - {Phoenix.Transports.WebSocket, - {Pleroma.Web.Endpoint, Pleroma.Web.UserSocket, websocket_config}}}, - {:_, Phoenix.Endpoint.Cowboy2Handler, {Pleroma.Web.Endpoint, []}} + {:_, Plug.Cowboy.Handler, {Pleroma.Web.Endpoint, []}} ]} ] ], diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex index fb93538f9..8dda1512d 100644 --- a/lib/mix/tasks/pleroma/emoji.ex +++ b/lib/mix/tasks/pleroma/emoji.ex @@ -236,6 +236,7 @@ def run(["gen-pack" | args]) do IO.puts("#{pack_file} has been created with the #{name} pack") end + Pleroma.Emoji.reload() end diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex index 4ca1c28eb..1a8e866ef 100644 --- a/lib/mix/tasks/pleroma/user.ex +++ b/lib/mix/tasks/pleroma/user.ex @@ -11,6 +11,7 @@ defmodule Mix.Tasks.Pleroma.User do alias Pleroma.UserInviteToken alias Pleroma.Web.ActivityPub.Builder alias Pleroma.Web.ActivityPub.Pipeline + use Pleroma.Web, :verified_routes @shortdoc "Manages Pleroma users" @moduledoc File.read!("docs/docs/administration/CLI_tasks/user.md") @@ -113,11 +114,7 @@ def run(["reset_password", nickname]) do {:ok, token} <- Pleroma.PasswordResetToken.create_token(user) do shell_info("Generated password reset token for #{user.nickname}") - IO.puts( - "URL: #{Pleroma.Web.Router.Helpers.reset_password_url(Pleroma.Web.Endpoint, - :reset, - token.token)}" - ) + IO.puts("URL: #{~p[/api/v1/pleroma/password_reset/#{token.token}]}") else _ -> shell_error("No local user #{nickname}") @@ -303,13 +300,7 @@ def run(["invite" | rest]) do {:ok, invite} <- UserInviteToken.create_invite(options) do shell_info("Generated user invite token " <> String.replace(invite.invite_type, "_", " ")) - url = - Pleroma.Web.Router.Helpers.redirect_url( - Pleroma.Web.Endpoint, - :registration_page, - invite.token - ) - + url = url(~p[/registration/#{invite.token}]) IO.puts(url) else error -> diff --git a/lib/phoenix/transports/web_socket/raw.ex b/lib/phoenix/transports/web_socket/raw.ex index 8ed64eb16..72def9dff 100644 --- a/lib/phoenix/transports/web_socket/raw.ex +++ b/lib/phoenix/transports/web_socket/raw.ex @@ -26,7 +26,6 @@ def init(%Plug.Conn{method: "GET"} = conn, {endpoint, handler, transport}) do conn |> fetch_query_params |> Transport.transport_log(opts[:transport_log]) - |> Transport.force_ssl(handler, endpoint, opts) |> Transport.check_origin(handler, endpoint, opts) case conn do diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex index 88bc78aec..683de8e3b 100644 --- a/lib/pleroma/emails/admin_email.ex +++ b/lib/pleroma/emails/admin_email.ex @@ -6,10 +6,13 @@ defmodule Pleroma.Emails.AdminEmail do @moduledoc "Admin emails" import Swoosh.Email - + use Pleroma.Web, :mailer alias Pleroma.Config alias Pleroma.HTML - alias Pleroma.Web.Router.Helpers + + use Phoenix.VerifiedRoutes, + endpoint: Pleroma.Web.Endpoint, + router: Pleroma.Web.Router defp instance_config, do: Config.get(:instance) defp instance_name, do: instance_config()[:name] @@ -45,7 +48,7 @@ def report(to, reporter, account, statuses, comment) do statuses |> Enum.map(fn %{id: id} -> - status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, id) + status_url = url(~p[/notice/#{id}]) "
  • #{status_url}
  • " %{"id" => id} when is_binary(id) -> diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex index 1588c099c..fe319c775 100644 --- a/lib/pleroma/emails/user_email.ex +++ b/lib/pleroma/emails/user_email.ex @@ -6,12 +6,11 @@ defmodule Pleroma.Emails.UserEmail do @moduledoc "User emails" require Pleroma.Web.Gettext + use Pleroma.Web, :mailer alias Pleroma.Config alias Pleroma.User - alias Pleroma.Web.Endpoint alias Pleroma.Web.Gettext - alias Pleroma.Web.Router import Swoosh.Email import Phoenix.Swoosh, except: [render_body: 3] @@ -75,7 +74,7 @@ def welcome(user, opts \\ %{}) do def password_reset_email(user, token) when is_binary(token) do Gettext.with_locale_or_default user.language do - password_reset_url = Router.Helpers.reset_password_url(Endpoint, :reset, token) + password_reset_url = ~p[/api/v1/pleroma/password_reset/#{token}] html_body = Gettext.dpgettext( @@ -108,12 +107,7 @@ def user_invitation_email( to_name \\ nil ) do Gettext.with_locale_or_default user.language do - registration_url = - Router.Helpers.redirect_url( - Endpoint, - :registration_page, - user_invite_token.token - ) + registration_url = ~p[/registration/#{user_invite_token.token}] html_body = Gettext.dpgettext( @@ -146,13 +140,7 @@ def user_invitation_email( def account_confirmation_email(user) do Gettext.with_locale_or_default user.language do - confirmation_url = - Router.Helpers.confirm_email_url( - Endpoint, - :confirm_email, - user.id, - to_string(user.confirmation_token) - ) + confirmation_url = ~p[/api/account/confirm_email/#{user.id}/#{user.confirmation_token}] html_body = Gettext.dpgettext( @@ -342,7 +330,7 @@ def unsubscribe_url(user, notifications_type) do |> Pleroma.JWT.generate_and_sign!() |> Base.encode64() - Router.Helpers.subscription_url(Endpoint, :unsubscribe, token) + ~p[/mailer/unsubscribe/#{token}] end def backup_is_ready_email(backup, admin_user_id \\ nil) do diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 83b45e3b4..ad87837fa 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -44,6 +44,8 @@ defmodule Pleroma.User do alias Pleroma.Web.RelMe alias Pleroma.Workers.BackgroundWorker + use Pleroma.Web, :verified_routes + require Logger @type t :: %__MODULE__{} @@ -2447,12 +2449,7 @@ defp validate_rel_me_field(changeset, fields, raw_fields, %User{ end if is_url(raw_value) do - frontend_url = - Pleroma.Web.Router.Helpers.redirect_url( - Pleroma.Web.Endpoint, - :redirector_with_meta, - nickname - ) + frontend_url = url(~p[/#{nickname}]) possible_urls = [ap_id, frontend_url] diff --git a/lib/pleroma/web.ex b/lib/pleroma/web.ex index ecd98b6ca..5422e7896 100644 --- a/lib/pleroma/web.ex +++ b/lib/pleroma/web.ex @@ -27,6 +27,7 @@ defmodule Pleroma.Web do alias Pleroma.Web.Plugs.ExpectPublicOrAuthenticatedCheckPlug alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.Plugs.PlugHelper + require Pleroma.Constants def controller do quote do @@ -37,7 +38,7 @@ def controller do import Pleroma.Web.Gettext import Pleroma.Web.TranslationHelpers - alias Pleroma.Web.Router.Helpers, as: Routes + unquote(verified_routes()) plug(:set_put_layout) @@ -184,7 +185,10 @@ def view do # Import convenience functions from controllers import Phoenix.Controller, - only: [get_flash: 1, get_flash: 2, view_module: 1, view_template: 1] + only: [view_module: 1, view_template: 1] + + import Phoenix.Flash + alias Phoenix.Flash # Include shared imports and aliases for views unquote(view_helpers()) @@ -218,7 +222,7 @@ def component do def router do quote do - use Phoenix.Router + use Phoenix.Router, helpers: false import Plug.Conn import Phoenix.Controller @@ -246,7 +250,24 @@ defp view_helpers do import Pleroma.Web.ErrorHelpers import Pleroma.Web.Gettext - alias Pleroma.Web.Router.Helpers, as: Routes + unquote(verified_routes()) + end + end + + def static_paths, do: Pleroma.Constants.static_only_files() + + def verified_routes do + quote do + use Phoenix.VerifiedRoutes, + endpoint: Pleroma.Web.Endpoint, + router: Pleroma.Web.Router, + statics: Pleroma.Web.static_paths() + end + end + + def mailer do + quote do + unquote(verified_routes()) end end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 649bf9095..e4c626d36 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1792,6 +1792,11 @@ def pin_data_from_featured_collection( end) end + def pin_data_from_featured_collection(obj) do + Logger.error("Could not parse featured collection #{inspect(obj)}") + %{} + end + def fetch_and_prepare_featured_from_ap_id(nil) do {:ok, %{}} end diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 6d39ad3a8..e67a14b58 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -18,6 +18,8 @@ defmodule Pleroma.Web.ActivityPub.Builder do alias Pleroma.Web.CommonAPI.ActivityDraft alias Pleroma.Web.Endpoint + use Pleroma.Web, :verified_routes + require Pleroma.Constants def accept_or_reject(actor, activity, type) do @@ -402,6 +404,6 @@ def unpin(%User{} = user, object) do end defp pinned_url(nickname) when is_binary(nickname) do - Pleroma.Web.Router.Helpers.activity_pub_url(Pleroma.Web.Endpoint, :pinned, nickname) + url(~p[/users/#{nickname}/collections/featured]) end end diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 008aec475..f731b5286 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -16,10 +16,11 @@ defmodule Pleroma.Web.ActivityPub.Utils do alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.AdminAPI.AccountView alias Pleroma.Web.Endpoint - alias Pleroma.Web.Router.Helpers import Ecto.Query + use Pleroma.Web, :verified_routes + require Logger require Pleroma.Constants @@ -124,19 +125,15 @@ def make_date do end def generate_activity_id do - generate_id("activities") + url(~p[/activities/#{UUID.generate()}]) end def generate_context_id do - generate_id("contexts") + url(~p[/contexts/#{UUID.generate()}]) end def generate_object_id do - Helpers.o_status_url(Endpoint, :object, UUID.generate()) - end - - def generate_id(type) do - "#{Endpoint.url()}/#{type}/#{UUID.generate()}" + url(~p[/objects/#{UUID.generate()}]) end def get_notified_from_object(%{"type" => type} = object) when type in @supported_object_types do @@ -154,7 +151,7 @@ def get_notified_from_object(object) do Notification.get_notified_from_activity(%Activity{data: object}, false) end - def maybe_create_context(context), do: context || generate_id("contexts") + def maybe_create_context(context), do: context || generate_context_id() @doc """ Enqueues an activity for federation if it's local diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 7333fb2c1..82b59c47f 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -12,24 +12,22 @@ defmodule Pleroma.Web.ActivityPub.UserView do alias Pleroma.Web.ActivityPub.ObjectView alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.ActivityPub.Utils - alias Pleroma.Web.Endpoint - alias Pleroma.Web.Router.Helpers require Pleroma.Web.ActivityPub.Transmogrifier import Ecto.Query def render("endpoints.json", %{user: %User{nickname: nil, local: true} = _user}) do - %{"sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox)} + %{"sharedInbox" => ~p"/inbox"} end def render("endpoints.json", %{user: %User{local: true} = _user}) do %{ - "oauthAuthorizationEndpoint" => Helpers.o_auth_url(Endpoint, :authorize), - "oauthRegistrationEndpoint" => Helpers.app_url(Endpoint, :create), - "oauthTokenEndpoint" => Helpers.o_auth_url(Endpoint, :token_exchange), - "sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox), - "uploadMedia" => Helpers.activity_pub_url(Endpoint, :upload_media) + "oauthAuthorizationEndpoint" => ~p"/oauth/authorize", + "oauthRegistrationEndpoint" => ~p"/api/v1/apps", + "oauthTokenEndpoint" => ~p"/oauth/token", + "sharedInbox" => ~p"/inbox", + "uploadMedia" => ~p"/api/ap/upload_media" } end diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex index 1d7ac78a0..7344e1f77 100644 --- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -17,9 +17,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do alias Pleroma.Web.AdminAPI alias Pleroma.Web.AdminAPI.AccountView alias Pleroma.Web.AdminAPI.ModerationLogView - alias Pleroma.Web.Endpoint alias Pleroma.Web.Plugs.OAuthScopesPlug - alias Pleroma.Web.Router @users_page_size 50 @@ -256,7 +254,7 @@ def get_password_reset(conn, %{"nickname" => nickname}) do conn |> json(%{ token: token.token, - link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token) + link: url(~p[/api/v1/pleroma/password_reset/#{token.token}]) }) end diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index e3a251ca1..64593767d 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -97,7 +97,7 @@ defmodule Pleroma.Web.Endpoint do Plug.Static, at: "/", from: :pleroma, - only: Pleroma.Constants.static_only_files(), + only: Pleroma.Web.static_paths(), # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength gzip: true, cache_control_for_etags: @static_cache_control, diff --git a/lib/pleroma/web/feed/user_controller.ex b/lib/pleroma/web/feed/user_controller.ex index dc3b1f94b..b320c9224 100644 --- a/lib/pleroma/web/feed/user_controller.ex +++ b/lib/pleroma/web/feed/user_controller.ex @@ -30,7 +30,7 @@ def feed_redirect(%{assigns: %{format: format}} = conn, _params) def feed_redirect(conn, %{"nickname" => nickname}) do with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do - redirect(conn, external: "#{Routes.user_feed_url(conn, :feed, user.nickname)}.atom") + redirect(conn, external: "#{url(~p"/users/#{user.nickname}/feed")}.atom") end end diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex index 7b6e01aad..b24f00620 100644 --- a/lib/pleroma/web/masto_fe_controller.ex +++ b/lib/pleroma/web/masto_fe_controller.ex @@ -34,9 +34,9 @@ def index(conn, _params) do index = if flavour == "fedibird-fe" do - "fedibird.index.html" + "fedibird.html" else - "glitchsoc.index.html" + "glitchsoc.html" end conn diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex index a9ccaa982..30e40ac42 100644 --- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex @@ -54,12 +54,7 @@ def login(conn, params) do defp redirect_to_oauth_form(conn, _params) do with {:ok, app} <- local_mastofe_app() do path = - Routes.o_auth_path(conn, :authorize, - response_type: "code", - client_id: app.client_id, - redirect_uri: ".", - scope: Enum.join(app.scopes, " ") - ) + ~p[/oauth/authorize?#{[response_type: "code", client_id: app.client_id, redirect_uri: ".", scope: Enum.join(app.scopes, " ")]}] redirect(conn, to: path) end @@ -91,7 +86,7 @@ def password_reset(conn, params) do defp local_mastodon_post_login_path(conn) do case get_session(conn, :return_to) do nil -> - Routes.masto_fe_path(conn, :index, ["getting-started"]) + ~p"/web/getting-started" return_to -> delete_session(conn, :return_to) diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 47d1616c4..ac0955534 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -322,7 +322,7 @@ def render("show.json", %{activity: %{id: id, data: %{"object" => _object}} = ac url = if user.local do - Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity) + url(~p[/notice/#{activity}]) else object.data["url"] || object.data["external_url"] || object.data["id"] end diff --git a/lib/pleroma/web/mastodon_api/views/tag_view.ex b/lib/pleroma/web/mastodon_api/views/tag_view.ex index 02108c736..6d3ea3c1a 100644 --- a/lib/pleroma/web/mastodon_api/views/tag_view.ex +++ b/lib/pleroma/web/mastodon_api/views/tag_view.ex @@ -1,7 +1,6 @@ defmodule Pleroma.Web.MastodonAPI.TagView do use Pleroma.Web, :view alias Pleroma.User - alias Pleroma.Web.Router.Helpers def render("index.json", %{tags: tags, for_user: user}) do render_many(tags, __MODULE__, "show.json", %{for_user: user}) @@ -17,7 +16,7 @@ def render("show.json", %{tag: tag, for_user: user}) do %{ name: tag.name, - url: Helpers.tag_feed_url(Pleroma.Web.Endpoint, :feed, tag.name), + url: url(~p[/tags/#{tag.name}]), history: [], following: following } diff --git a/lib/pleroma/web/metadata/providers/feed.ex b/lib/pleroma/web/metadata/providers/feed.ex index d0ab5c19e..15f47b843 100644 --- a/lib/pleroma/web/metadata/providers/feed.ex +++ b/lib/pleroma/web/metadata/providers/feed.ex @@ -3,9 +3,9 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Metadata.Providers.Feed do - alias Pleroma.Web.Endpoint alias Pleroma.Web.Metadata.Providers.Provider - alias Pleroma.Web.Router.Helpers + + use Pleroma.Web, :verified_routes @behaviour Provider @@ -16,7 +16,7 @@ def build_tags(%{user: user}) do [ rel: "alternate", type: "application/atom+xml", - href: Helpers.user_feed_path(Endpoint, :feed, user.nickname) <> ".atom" + href: ~p[/users/#{user.nickname}/feed.atom] ], []} ] end diff --git a/lib/pleroma/web/metadata/providers/twitter_card.ex b/lib/pleroma/web/metadata/providers/twitter_card.ex index b2497d14e..ab48ea272 100644 --- a/lib/pleroma/web/metadata/providers/twitter_card.ex +++ b/lib/pleroma/web/metadata/providers/twitter_card.ex @@ -10,6 +10,8 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCard do alias Pleroma.Web.Metadata.Providers.Provider alias Pleroma.Web.Metadata.Utils + use Pleroma.Web, :verified_routes + @behaviour Provider @media_types ["image", "audio", "video"] @@ -112,7 +114,7 @@ defp build_attachments(id, %{data: %{"attachment" => attachments}}) do defp build_attachments(_id, _object), do: [] defp player_url(id) do - Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice_player, id) + url(~p[/notice/#{id}/embed_player]) end # Videos have problems without dimensions, but we used to not provide WxH for images. diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex index 277df1c46..ba33dc9e7 100644 --- a/lib/pleroma/web/o_auth/o_auth_controller.ex +++ b/lib/pleroma/web/o_auth/o_auth_controller.ex @@ -449,7 +449,7 @@ def prepare_request(%Plug.Conn{} = conn, %{ |> Map.put("state", state) # Handing the request to Ueberauth - redirect(conn, to: Routes.o_auth_path(conn, :request, provider, params)) + redirect(conn, to: ~p"/oauth/#{provider}?#{params}") end def request(%Plug.Conn{} = conn, params) do @@ -623,7 +623,7 @@ def login(%User{} = user, %App{} = app, requested_scopes) when is_list(requested end # Special case: Local MastodonFE - defp redirect_uri(%Plug.Conn{} = conn, "."), do: Routes.auth_url(conn, :login) + defp redirect_uri(_, "."), do: url(~p"/web/login") defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri diff --git a/lib/pleroma/web/o_status/o_status_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex index 95a22895e..2b2872c9a 100644 --- a/lib/pleroma/web/o_status/o_status_controller.ex +++ b/lib/pleroma/web/o_status/o_status_controller.ex @@ -14,7 +14,6 @@ defmodule Pleroma.Web.OStatus.OStatusController do alias Pleroma.Web.Fallback.RedirectController alias Pleroma.Web.Metadata.PlayerView alias Pleroma.Web.Plugs.RateLimiter - alias Pleroma.Web.Router plug( RateLimiter, @@ -87,7 +86,7 @@ def notice(%{assigns: %{format: format}} = conn, %{"id" => id}) do %{ activity_id: activity.id, object: object, - url: Router.Helpers.o_status_url(Endpoint, :notice, activity.id), + url: url(~p[/notice/#{activity.id}]), user: user } ) diff --git a/lib/pleroma/web/plugs/http_signature_plug.ex b/lib/pleroma/web/plugs/http_signature_plug.ex index 488108b08..eb6a46736 100644 --- a/lib/pleroma/web/plugs/http_signature_plug.ex +++ b/lib/pleroma/web/plugs/http_signature_plug.ex @@ -5,8 +5,9 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do import Plug.Conn import Phoenix.Controller, only: [get_format: 1] + + use Pleroma.Web, :verified_routes alias Pleroma.Activity - alias Pleroma.Web.Router alias Pleroma.Signature alias Pleroma.Instances require Logger @@ -32,10 +33,10 @@ def call(conn, _opts) do end def route_aliases(%{path_info: ["objects", id], query_string: query_string}) do - ap_id = Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :object, id) + ap_id = url(~p[/objects/#{id}]) with %Activity{} = activity <- Activity.get_by_object_ap_id_with_object(ap_id) do - ["/notice/#{activity.id}", "/notice/#{activity.id}?#{query_string}"] + [~p"/notice/#{activity.id}", "/notice/#{activity.id}?#{query_string}"] else _ -> [] end diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex index 56ee4e41e..f0d45293e 100644 --- a/lib/pleroma/web/static_fe/static_fe_controller.ex +++ b/lib/pleroma/web/static_fe/static_fe_controller.ex @@ -11,7 +11,6 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.Metadata - alias Pleroma.Web.Router.Helpers plug(:put_layout, :static_fe) plug(:assign_id) @@ -111,11 +110,11 @@ def show(%{assigns: %{username_or_id: username_or_id, tab: tab}} = conn, params) end def show(%{assigns: %{object_id: _}} = conn, _params) do - url = Helpers.url(conn) <> conn.request_path + url = unverified_url(conn, conn.request_path) case Activity.get_create_by_object_ap_id_with_object(url) do %Activity{} = activity -> - to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity) + to = ~p[/notice/#{activity}] redirect(conn, to: to) _ -> @@ -124,11 +123,11 @@ def show(%{assigns: %{object_id: _}} = conn, _params) do end def show(%{assigns: %{activity_id: _}} = conn, _params) do - url = Helpers.url(conn) <> conn.request_path + url = unverified_url(conn, conn.request_path) case Activity.get_by_ap_id(url) do %Activity{} = activity -> - to = Helpers.o_status_path(Pleroma.Web.Endpoint, :notice, activity) + to = ~p[/notice/#{activity}] redirect(conn, to: to) _ -> @@ -167,7 +166,7 @@ defp represent(%Activity{object: %Object{data: data}} = activity, selected) do link = case user.local do - true -> Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity) + true -> ~p[/notice/#{activity}] _ -> data["url"] || data["external_url"] || data["id"] end diff --git a/lib/pleroma/web/static_fe/static_fe_view.ex b/lib/pleroma/web/static_fe/static_fe_view.ex index f0c9ddd22..c1d83c5a0 100644 --- a/lib/pleroma/web/static_fe/static_fe_view.ex +++ b/lib/pleroma/web/static_fe/static_fe_view.ex @@ -11,7 +11,6 @@ defmodule Pleroma.Web.StaticFE.StaticFEView do alias Pleroma.Web.Gettext alias Pleroma.Web.MediaProxy alias Pleroma.Web.Metadata.Utils - alias Pleroma.Web.Router.Helpers use Phoenix.HTML diff --git a/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex b/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex index a0b0a2361..010a7fbad 100644 --- a/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex +++ b/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex @@ -2,7 +2,7 @@

    After you submit, you will need to refresh manually to get your new frontend!

    -<%= form_for @conn, Routes.frontend_switcher_path(@conn, :do_switch), fn f -> %> +<%= form_for @conn, ~p"/akkoma/frontend", fn f -> %> <%= select(f, :frontend, @choices) %> <%= submit do: "submit" %> diff --git a/lib/pleroma/web/templates/feed/feed/tag.atom.eex b/lib/pleroma/web/templates/feed/feed/tag.atom.eex index 6d497e84c..e85c08b2b 100644 --- a/lib/pleroma/web/templates/feed/feed/tag.atom.eex +++ b/lib/pleroma/web/templates/feed/feed/tag.atom.eex @@ -9,13 +9,13 @@ xmlns:ostatus="http://ostatus.org/schema/1.0" xmlns:statusnet="http://status.net/schema/api/1/"> - <%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss' %> + <%= '#{url(~p"/tags/#{@tag}")}.rss' %> #<%= @tag %> <%= Gettext.dpgettext("static_pages", "tag feed description", "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse.", tag: @tag) %> <%= feed_logo() %> <%= most_recent_update(@activities) %> - + " type="application/atom+xml"/> <%= for activity <- @activities do %> <%= render @view_module, "_tag_activity.atom", Map.merge(assigns, prepare_activity(activity, actor: true)) %> <% end %> diff --git a/lib/pleroma/web/templates/feed/feed/tag.rss.eex b/lib/pleroma/web/templates/feed/feed/tag.rss.eex index edcc3e436..7ee3ba5a3 100644 --- a/lib/pleroma/web/templates/feed/feed/tag.rss.eex +++ b/lib/pleroma/web/templates/feed/feed/tag.rss.eex @@ -5,7 +5,7 @@ #<%= @tag %> <%= Gettext.dpgettext("static_pages", "tag feed description", "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse.", tag: @tag) %> - <%= '#{Routes.tag_feed_url(@conn, :feed, @tag)}.rss' %> + <%= '#{url(~p"/tags/#{@tag}")}.rss' %> <%= feed_logo() %> 2b90d9 <%= for activity <- @activities do %> diff --git a/lib/pleroma/web/templates/feed/feed/user.atom.eex b/lib/pleroma/web/templates/feed/feed/user.atom.eex index 5c1f0ecbc..03585a9d5 100644 --- a/lib/pleroma/web/templates/feed/feed/user.atom.eex +++ b/lib/pleroma/web/templates/feed/feed/user.atom.eex @@ -6,16 +6,16 @@ xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:ostatus="http://ostatus.org/schema/1.0"> - <%= Routes.user_feed_url(@conn, :feed, @user.nickname) <> ".atom" %> + <%= url(~p"/users/#{@user.nickname}/feed") <> ".atom" %> <%= @user.nickname <> "'s timeline" %> <%= most_recent_update(@activities, @user) %> <%= logo(@user) %> - + " type="application/atom+xml"/> <%= render @view_module, "_author.atom", assigns %> <%= if last_activity(@activities) do %> - + " type="application/atom+xml"/> <% end %> <%= for activity <- @activities do %> diff --git a/lib/pleroma/web/templates/feed/feed/user.rss.eex b/lib/pleroma/web/templates/feed/feed/user.rss.eex index 6b842a085..f2eb7337e 100644 --- a/lib/pleroma/web/templates/feed/feed/user.rss.eex +++ b/lib/pleroma/web/templates/feed/feed/user.rss.eex @@ -1,16 +1,16 @@ - <%= Routes.user_feed_url(@conn, :feed, @user.nickname) <> ".rss" %> + <%= url(~p"/users/#{@user.nickname}/feed") <> ".rss" %> <%= @user.nickname <> "'s timeline" %> <%= most_recent_update(@activities, @user) %> <%= logo(@user) %> - <%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss' %> + <%= '#{url(~p"/users/#{@user.nickname}/feed")}.rss' %> <%= render @view_module, "_author.rss", assigns %> <%= if last_activity(@activities) do %> - <%= '#{Routes.user_feed_url(@conn, :feed, @user.nickname)}.rss?max_id=#{last_activity(@activities).id}' %> + <%= '#{url(~p"/users/#{@user.nickname}/feed")}.rss?max_id=#{last_activity(@activities).id}' %> <% end %> <%= for activity <- @activities do %> diff --git a/lib/pleroma/web/templates/masto_fe/fedibird.html.heex b/lib/pleroma/web/templates/masto_fe/fedibird.html.heex new file mode 100644 index 000000000..7070bd8d8 --- /dev/null +++ b/lib/pleroma/web/templates/masto_fe/fedibird.html.heex @@ -0,0 +1,58 @@ + + + + + + + + <%= Config.get([:instance, :name]) %> + + + + + + + + + + + + + + + + + + + + + + +
    + + diff --git a/lib/pleroma/web/templates/masto_fe/fedibird.index.html.eex b/lib/pleroma/web/templates/masto_fe/fedibird.index.html.eex deleted file mode 100644 index 6730c0ecc..000000000 --- a/lib/pleroma/web/templates/masto_fe/fedibird.index.html.eex +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - -<%= Config.get([:instance, :name]) %> - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - - diff --git a/lib/pleroma/web/templates/masto_fe/glitchsoc.html.heex b/lib/pleroma/web/templates/masto_fe/glitchsoc.html.heex new file mode 100644 index 000000000..469c201a5 --- /dev/null +++ b/lib/pleroma/web/templates/masto_fe/glitchsoc.html.heex @@ -0,0 +1,57 @@ + + + + + + + + <%= Config.get([:instance, :name]) %> + + + + + + + + + + + + + + + + + + + + + + + + +
    + + diff --git a/lib/pleroma/web/templates/masto_fe/glitchsoc.index.html.eex b/lib/pleroma/web/templates/masto_fe/glitchsoc.index.html.eex deleted file mode 100644 index dadf8f413..000000000 --- a/lib/pleroma/web/templates/masto_fe/glitchsoc.index.html.eex +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - -<%= Config.get([:instance, :name]) %> - - - - - - - - - - - - - - - - - - - - - - - - -
    -
    - - diff --git a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex index ee40cf277..b9b08c45d 100644 --- a/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex +++ b/lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex @@ -1,15 +1,15 @@
    - <%= if get_flash(@conn, :info) do %> - + <%= if Flash.get(@flash, :info) do %> + <% end %> - <%= if get_flash(@conn, :error) do %> - + <%= if Flash.get(@flash, :error) do %> + <% end %>
    <%= Gettext.dpgettext("static_pages", "mfa recover page title", "Two-factor recovery") %>
    - <%= form_for @conn, Routes.mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %> + <%= form_for @conn, ~p"/oauth/mfa/verify", [as: "mfa"], fn f -> %>
    <%= label f, :code, Gettext.dpgettext("static_pages", "mfa recover recovery code prompt", "Recovery code") %> <%= text_input f, :code, [autocomplete: false, autocorrect: "off", autocapitalize: "off", autofocus: true, spellcheck: false] %> @@ -21,7 +21,7 @@ <%= submit Gettext.dpgettext("static_pages", "mfa recover verify recovery code button", "Verify") %> <% end %> - "> + "> <%= Gettext.dpgettext("static_pages", "mfa recover use 2fa code link", "Enter a two-factor code") %> diff --git a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex index 734e62112..59827780b 100644 --- a/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex +++ b/lib/pleroma/web/templates/o_auth/mfa/totp.html.eex @@ -1,15 +1,15 @@
    - <%= if get_flash(@conn, :info) do %> - + <%= if Flash.get(@flash, :info) do %> + <% end %> - <%= if get_flash(@conn, :error) do %> - + <%= if Flash.get(@flash, :error) do %> + <% end %>
    <%= Gettext.dpgettext("static_pages", "mfa auth page title", "Two-factor authentication") %>
    - <%= form_for @conn, Routes.mfa_verify_path(@conn, :verify), [as: "mfa"], fn f -> %> + <%= form_for @conn, ~p"/oauth/mfa/verify", [as: "mfa"], fn f -> %>
    <%= label f, :code, Gettext.dpgettext("static_pages", "mfa auth code prompt", "Authentication code") %> <%= text_input f, :code, [autocomplete: "one-time-code", autocorrect: "off", autocapitalize: "off", autofocus: true, pattern: "[0-9]*", spellcheck: false] %> @@ -21,7 +21,7 @@ <%= submit Gettext.dpgettext("static_pages", "mfa auth verify code button", "Verify") %> <% end %> - "> + "> <%= Gettext.dpgettext("static_pages", "mfa auth page use recovery code link", "Enter a two-factor recovery code") %>
    diff --git a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex index 8b894cd58..97f2b770f 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex @@ -1,6 +1,6 @@

    <%= Gettext.dpgettext("static_pages", "oauth external provider page title", "Sign in with external provider") %>

    -<%= form_for @conn, Routes.o_auth_path(@conn, :prepare_request), [as: "authorization", method: "get"], fn f -> %> +<%= form_for @conn, ~p"/oauth/prepare_request", [as: "authorization", method: "get"], fn f -> %>
    <%= render @view_module, "_scopes.html", Map.merge(assigns, %{form: f}) %>
    diff --git a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex index 1f661efb2..601b16b98 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/register.html.eex @@ -1,14 +1,14 @@ -<%= if get_flash(@conn, :info) do %> - +<%= if Flash.get(@flash, :info) do %> + <% end %> -<%= if get_flash(@conn, :error) do %> - +<%= if Flash.get(@flash, :error) do %> + <% end %>

    <%= Gettext.dpgettext("static_pages", "oauth register page title", "Registration Details") %>

    <%= Gettext.dpgettext("static_pages", "oauth register page fill form prompt", "If you'd like to register a new account, please provide the details below.") %>

    -<%= form_for @conn, Routes.o_auth_path(@conn, :register), [as: "authorization"], fn f -> %> +<%= form_for @conn, ~p"/oauth/register", [as: "authorization"], fn f -> %>
    <%= label f, :nickname, Gettext.dpgettext("static_pages", "oauth register page nickname prompt", "Nickname") %> diff --git a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex index 986e6ffce..420a17562 100644 --- a/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex +++ b/lib/pleroma/web/templates/o_auth/o_auth/show.html.eex @@ -1,11 +1,11 @@ -<%= if get_flash(@conn, :info) do %> - +<%= if Flash.get(@flash, :info) do %> + <% end %> -<%= if get_flash(@conn, :error) do %> - +<%= if Flash.get(@flash, :error) do %> + <% end %> -<%= form_for @conn, Routes.o_auth_path(@conn, :authorize), [as: "authorization"], fn f -> %> +<%= form_for @conn, ~p"/oauth/authorize", [as: "authorization"], fn f -> %> <%= if @user do %>
    -
    + "> diff --git a/lib/pleroma/web/templates/twitter_api/password/reset.html.eex b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex index 6a544af51..390a8371f 100644 --- a/lib/pleroma/web/templates/twitter_api/password/reset.html.eex +++ b/lib/pleroma/web/templates/twitter_api/password/reset.html.eex @@ -1,5 +1,5 @@

    Password Reset for <%= @user.nickname %>

    -<%= form_for @conn, Routes.reset_password_path(@conn, :do_reset), [as: "data"], fn f -> %> +<%= form_for @conn, ~p"/api/v1/pleroma/password_reset", [as: "data"], fn f -> %>
    <%= label f, :password, Gettext.dpgettext("static_pages", "password reset form password prompt", "Password") %> <%= password_input f, :password %> diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex index e2d251fac..894b5c6ee 100644 --- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex +++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex @@ -4,7 +4,7 @@

    <%= Gettext.dpgettext("static_pages", "remote follow header", "Remote follow") %>

    <%= @followee.nickname %>

    - <%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "user"], fn f -> %> + <%= form_for @conn, ~p"/ostatus_subscribe", [as: "user"], fn f -> %> <%= hidden_input f, :id, value: @followee.id %> <%= submit Gettext.dpgettext("static_pages", "remote follow authorization button", "Authorize") %> <% end %> diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex index 26340a906..b0084aac4 100644 --- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex +++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex @@ -4,7 +4,7 @@

    <%= Gettext.dpgettext("static_pages", "remote follow header, need login", "Log in to follow") %>

    <%= @followee.nickname %>

    -<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "authorization"], fn f -> %> +<%= form_for @conn, ~p"/ostatus_subscribe", [as: "authorization"], fn f -> %> <%= text_input f, :name, placeholder: Gettext.dpgettext("static_pages", "placeholder text for username entry", "Username"), required: true, autocomplete: "username" %>
    <%= password_input f, :password, placeholder: Gettext.dpgettext("static_pages", "placeholder text for password entry", "Password"), required: true, autocomplete: "password" %> diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex index 638212c1e..f34eba165 100644 --- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex +++ b/lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex @@ -4,7 +4,7 @@

    <%= Gettext.dpgettext("static_pages", "remote follow mfa header", "Two-factor authentication") %>

    <%= @followee.nickname %>

    -<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "mfa"], fn f -> %> +<%= form_for @conn, ~p"/ostatus_subscribe", [as: "mfa"], fn f -> %> <%= text_input f, :code, placeholder: Gettext.dpgettext("static_pages", "placeholder text for auth code entry", "Authentication code"), required: true %>
    <%= hidden_input f, :id, value: @followee.id %> diff --git a/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex b/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex index d77174967..3105772e2 100644 --- a/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex +++ b/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex @@ -2,7 +2,7 @@

    <%= Gettext.dpgettext("static_pages", "status interact error", "Error: %{error}", error: @error) %>

    <% else %>

    <%= raw Gettext.dpgettext("static_pages", "status interact header", "Interacting with %{nickname}'s %{status_link}", nickname: safe_to_string(html_escape(@nickname)), status_link: safe_to_string(link(Gettext.dpgettext("static_pages", "status interact header - status link text", "status"), to: @status_link))) %>

    - <%= form_for @conn, Routes.util_path(@conn, :remote_subscribe), [as: "status"], fn f -> %> + <%= form_for @conn, ~p"/main/ostatus", [as: "status"], fn f -> %> <%= hidden_input f, :status_id, value: @status_id %> <%= text_input f, :profile, placeholder: Gettext.dpgettext("static_pages", "placeholder text for account id", "Your account ID, e.g. lain@quitter.se") %> <%= submit Gettext.dpgettext("static_pages", "status interact authorization button", "Interact") %> diff --git a/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex b/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex index 848660f26..47b73f676 100644 --- a/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex +++ b/lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex @@ -2,7 +2,7 @@

    <%= Gettext.dpgettext("static_pages", "remote follow error", "Error: %{error}", error: @error) %>

    <% else %>

    <%= Gettext.dpgettext("static_pages", "remote follow header", "Remotely follow %{nickname}", nickname: @nickname) %>

    - <%= form_for @conn, Routes.util_path(@conn, :remote_subscribe), [as: "user"], fn f -> %> + <%= form_for @conn, ~p"/main/ostatus", [as: "user"], fn f -> %> <%= hidden_input f, :nickname, value: @nickname %> <%= text_input f, :profile, placeholder: Gettext.dpgettext("static_pages", "placeholder text for account id", "Your account ID, e.g. lain@quitter.se") %> <%= submit Gettext.dpgettext("static_pages", "remote follow authorization button for following with a remote account", "Follow") %> diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex index 42d7601ed..1927d2021 100644 --- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex @@ -38,7 +38,7 @@ def follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do defp follow_status(conn, _user, acct) do with {:ok, object} <- Fetcher.fetch_object_from_id(acct), %Activity{id: activity_id} <- Activity.get_create_by_object_ap_id(object.data["id"]) do - redirect(conn, to: Routes.o_status_path(conn, :notice, activity_id)) + redirect(conn, to: ~p"/notice/#{activity_id}") else error -> handle_follow_error(conn, error) diff --git a/lib/pleroma/web/views/embed_view.ex b/lib/pleroma/web/views/embed_view.ex index 913d717be..fe1009e2f 100644 --- a/lib/pleroma/web/views/embed_view.ex +++ b/lib/pleroma/web/views/embed_view.ex @@ -13,7 +13,6 @@ defmodule Pleroma.Web.EmbedView do alias Pleroma.Web.Gettext alias Pleroma.Web.MediaProxy alias Pleroma.Web.Metadata.Utils - alias Pleroma.Web.Router.Helpers import Phoenix.HTML @@ -48,7 +47,7 @@ defp activity_content(%Activity{object: %Object{data: %{"content" => content}}}) defp activity_content(_), do: nil defp activity_url(%User{local: true}, activity) do - Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, activity) + ~p[/notice/#{activity}] end defp activity_url(%User{local: false}, %Activity{object: %Object{data: data}}) do diff --git a/lib/pleroma/web/views/masto_fe_view.ex b/lib/pleroma/web/views/masto_fe_view.ex index 305368c9d..dfb235976 100644 --- a/lib/pleroma/web/views/masto_fe_view.ex +++ b/lib/pleroma/web/views/masto_fe_view.ex @@ -85,7 +85,7 @@ def render("manifest.json", _params) do background_color: Config.get([:manifest, :background_color]), display: "standalone", scope: Pleroma.Web.Endpoint.url(), - start_url: Routes.masto_fe_path(Pleroma.Web.Endpoint, :index, ["getting-started"]), + start_url: ~p"/web/getting-started", categories: [ "social" ], diff --git a/mix.exs b/mix.exs index 698f91390..e2243f3ba 100644 --- a/mix.exs +++ b/mix.exs @@ -7,7 +7,7 @@ def project do version: version("3.10.3"), elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), - compilers: [:phoenix] ++ Mix.compilers(), + compilers: Mix.compilers(), elixirc_options: [warnings_as_errors: warnings_as_errors()], xref: [exclude: [:eldap]], start_permanent: Mix.env() == :prod, @@ -114,7 +114,9 @@ defp oauth_deps do # Type `mix help deps` for examples and options. defp deps do [ - {:phoenix, "~> 1.6.15"}, + {:phoenix, "~> 1.7.0"}, + {:phoenix_view, "~> 2.0"}, + {:phoenix_live_dashboard, "~> 0.7.2"}, {:tzdata, "~> 1.1.1"}, {:plug_cowboy, "~> 2.6"}, {:phoenix_pubsub, "~> 2.1"}, @@ -188,7 +190,6 @@ defp deps do git: "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git", ref: "08971a81e68686f9ac465cfb6661d51c5e4e1e7f"}, {:nimble_parsec, "~> 1.3", override: true}, - {:phoenix_live_dashboard, "~> 0.7.2"}, {:ecto_psql_extras, "~> 0.7"}, {:elasticsearch, git: "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", ref: "main"}, diff --git a/mix.lock b/mix.lock index 4791d3853..bbc1d9d2b 100644 --- a/mix.lock +++ b/mix.lock @@ -40,7 +40,7 @@ "ex_aws": {:hex, :ex_aws, "2.4.4", "d7886eaca7e10f7bd3d9e9d2d5414cb336737b3ab2fddd4fa30358b725293fe0", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a7d63e485ca2b16fb804f3f20097827aa69885eea6e69fa75c98f353c9c91dc7"}, "ex_aws_s3": {:hex, :ex_aws_s3, "2.4.0", "ce8decb6b523381812798396bc0e3aaa62282e1b40520125d1f4eff4abdff0f4", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "85dda6e27754d94582869d39cba3241d9ea60b6aa4167f9c88e309dc687e56bb"}, "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"}, - "ex_doc": {:hex, :ex_doc, "0.30.3", "bfca4d340e3b95f2eb26e72e4890da83e2b3a5c5b0e52607333bf5017284b063", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "fbc8702046c1d25edf79de376297e608ac78cdc3a29f075484773ad1718918b6"}, + "ex_doc": {:hex, :ex_doc, "0.30.5", "aa6da96a5c23389d7dc7c381eba862710e108cee9cfdc629b7ec021313900e9e", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "88a1e115dcb91cefeef7e22df4a6ebbe4634fbf98b38adcbc25c9607d6d9d8e6"}, "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, "ex_syslogger": {:hex, :ex_syslogger, "2.0.0", "de6de5c5472a9c4fdafb28fa6610e381ae79ebc17da6490b81d785d68bd124c9", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "a52b2fe71764e9e6ecd149ab66635812f68e39279cbeee27c52c0e35e8b8019e"}, "excoveralls": {:hex, :excoveralls, "0.16.1", "0bd42ed05c7d2f4d180331a20113ec537be509da31fed5c8f7047ce59ee5a7c5", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dae763468e2008cf7075a64cb1249c97cb4bc71e236c5c2b5e5cdf1cfa2bf138"}, @@ -66,7 +66,7 @@ "jose": {:hex, :jose, "1.11.6", "613fda82552128aa6fb804682e3a616f4bc15565a048dabd05b1ebd5827ed965", [:mix, :rebar3], [], "hexpm", "6275cb75504f9c1e60eeacb771adfeee4905a9e182103aa59b53fed651ff9738"}, "jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"}, "linkify": {:git, "https://akkoma.dev/AkkomaGang/linkify.git", "2567e2c1073fa371fd26fd66dfa5bc77b6919c16", []}, - "mail": {:hex, :mail, "0.3.0", "f353ef5f41d9f2e483ba7c5df92cdfe27b990b2d8c8c41d84c7b2b40ec33989f", [:mix], [], "hexpm", "523d700b2231d887dc4ee41d3bb13f48358f6f118e55a67cef6d630d7907116c"}, + "mail": {:hex, :mail, "0.3.1", "cb0a14e4ed8904e4e5a08214e686ccf6f9099346885db17d8c309381f865cc5c", [:mix], [], "hexpm", "1db701e89865c1d5fa296b2b57b1cd587587cca8d8a1a22892b35ef5a8e352a6"}, "majic": {:git, "https://akkoma.dev/AkkomaGang/majic.git", "80540b36939ec83f48e76c61e5000e0fd67706f0", [ref: "80540b36939ec83f48e76c61e5000e0fd67706f0"]}, "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, @@ -83,12 +83,12 @@ "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, - "oban": {:hex, :oban, "2.15.2", "8f934a49db39163633965139c8846d8e24c2beb4180f34a005c2c7c3f69a6aa2", [:mix], [{:ecto_sql, "~> 3.6", [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", "0f4a579ea48fc7489e0d84facf8b01566e142bdc6542d7dabce32c10e664f1e9"}, + "oban": {:hex, :oban, "2.15.4", "d49ab4ffb7153010e32f80fe9e56f592706238149ec579eb50f8a4e41d218856", [:mix], [{:ecto_sql, "~> 3.6", [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", "5fce611fdfffb13e9148df883116e5201adf1e731eb302cc88cde0588510079c"}, "open_api_spex": {:hex, :open_api_spex, "3.17.3", "ada8e352eb786050dd639db2439d3316e92f3798eb2abd051f55bb9af825b37e", [: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", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "165db21a85ca83cffc8e7c8890f35b354eddda8255de7404a2848ed652b9f0fe"}, "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, - "phoenix": {:hex, :phoenix, "1.6.16", "e5bdd18c7a06da5852a25c7befb72246de4ddc289182285f8685a40b7b5f5451", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "e15989ff34f670a96b95ef6d1d25bad0d9c50df5df40b671d8f4a669e050ac39"}, + "phoenix": {:hex, :phoenix, "1.7.7", "4cc501d4d823015007ba3cdd9c41ecaaf2ffb619d6fb283199fa8ddba89191e0", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8966e15c395e5e37591b6ed0bd2ae7f48e961f0f60ac4c733f9566b519453085"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.2", "b21bd01fdeffcfe2fab49e4942aa938b6d3e89e93a480d4aee58085560a0bc0d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "70242edd4601d50b69273b057ecf7b684644c19ee750989fd555625ae4ce8f5d"}, - "phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"}, + "phoenix_html": {:hex, :phoenix_html, "3.3.2", "d6ce982c6d8247d2fc0defe625255c721fb8d5f1942c5ac051f6177bffa5973f", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "44adaf8e667c1c20fb9d284b6b0fa8dc7946ce29e81ce621860aa7e96de9a11d"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"}, "phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, @@ -111,7 +111,7 @@ "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, "sweet_xml": {:hex, :sweet_xml, "0.7.3", "debb256781c75ff6a8c5cbf7981146312b66f044a2898f453709a53e5031b45b", [:mix], [], "hexpm", "e110c867a1b3fe74bfc7dd9893aa851f0eed5518d0d7cad76d7baafd30e4f5ba"}, - "swoosh": {:hex, :swoosh, "1.11.4", "9b353f998cba3c5e101a0669559c2fb2757b5d9eb7db058bf08687d82e93e416", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d3390914022a456ae1604bfcb3431bd12509b2afe8c70296bae6c9dca4903d0f"}, + "swoosh": {:hex, :swoosh, "1.11.5", "429dccde78e2f60c6339e96917efecebca9d1f254d2878a150f580d2f782260b", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "21ee57dcd68d2f56d3bbe11e76d56d142b221bb12b6018c551cc68442b800040"}, "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"}, "table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, @@ -129,5 +129,7 @@ "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"}, "vex": {:hex, :vex, "0.9.0", "613ea5eb3055662e7178b83e25b2df0975f68c3d8bb67c1645f0573e1a78d606", [:mix], [], "hexpm", "c69fff44d5c8aa3f1faee71bba1dcab05dd36364c5a629df8bb11751240c857f"}, "web_push_encryption": {:hex, :web_push_encryption, "0.3.1", "76d0e7375142dfee67391e7690e89f92578889cbcf2879377900b5620ee4708d", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.1", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2"}, + "websock": {:hex, :websock, "0.5.2", "b3c08511d8d79ed2c2f589ff430bd1fe799bb389686dafce86d28801783d8351", [:mix], [], "hexpm", "925f5de22fca6813dfa980fb62fd542ec43a2d1a1f83d2caec907483fe66ff05"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.3", "4908718e42e4a548fc20e00e70848620a92f11f7a6add8cf0886c4232267498d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "cbe5b814c1f86b6ea002b52dd99f345aeecf1a1a6964e209d208fb404d930d3d"}, "websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"}, } diff --git a/test/pleroma/emails/admin_email_test.exs b/test/pleroma/emails/admin_email_test.exs index e65752c23..6b0acf817 100644 --- a/test/pleroma/emails/admin_email_test.exs +++ b/test/pleroma/emails/admin_email_test.exs @@ -7,7 +7,6 @@ defmodule Pleroma.Emails.AdminEmailTest do import Pleroma.Factory alias Pleroma.Emails.AdminEmail - alias Pleroma.Web.Router.Helpers test "build report email" do config = Pleroma.Config.get(:instance) @@ -18,7 +17,7 @@ test "build report email" do res = AdminEmail.report(to_user, reporter, account, [%{name: "Test", id: "12"}], "Test comment") - status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, "12") + status_url = url(~p[/notice/12]) reporter_url = reporter.ap_id account_url = account.ap_id diff --git a/test/pleroma/emails/user_email_test.exs b/test/pleroma/emails/user_email_test.exs index 564552004..259efbb65 100644 --- a/test/pleroma/emails/user_email_test.exs +++ b/test/pleroma/emails/user_email_test.exs @@ -6,8 +6,6 @@ defmodule Pleroma.Emails.UserEmailTest do use Pleroma.DataCase, async: true alias Pleroma.Emails.UserEmail - alias Pleroma.Web.Endpoint - alias Pleroma.Web.Router import Pleroma.Factory @@ -18,7 +16,7 @@ test "build password reset email" do assert email.from == {config[:name], config[:notify_email]} assert email.to == [{user.name, user.email}] assert email.subject == "Password reset" - assert email.html_body =~ Router.Helpers.reset_password_url(Endpoint, :reset, "test_token") + assert email.html_body =~ ~p"/api/v1/pleroma/password_reset/test_token" end test "build user invitation email" do @@ -30,8 +28,7 @@ test "build user invitation email" do assert email.subject == "Invitation to Akkoma" assert email.to == [{"Jonh", "test@test.com"}] - assert email.html_body =~ - Router.Helpers.redirect_url(Endpoint, :registration_page, token.token) + assert email.html_body =~ ~p[/registration/#{token.token}] end test "build account confirmation email" do @@ -42,8 +39,7 @@ test "build account confirmation email" do assert email.to == [{user.name, user.email}] assert email.subject == "#{config[:name]} account confirmation" - assert email.html_body =~ - Router.Helpers.confirm_email_url(Endpoint, :confirm_email, user.id, "conf-token") + assert email.html_body =~ ~p[/account/confirm_email/#{user.id}/conf-token] end test "build approval pending email" do diff --git a/test/pleroma/integration/federation_test.exs b/test/pleroma/integration/federation_test.exs index da433e2c0..e4e37aa9f 100644 --- a/test/pleroma/integration/federation_test.exs +++ b/test/pleroma/integration/federation_test.exs @@ -31,10 +31,9 @@ test "within/2 captures local bindings and executes block on remote node" do test "runs webserver on customized port" do {nickname, url, url_404} = within @federated1 do - import Pleroma.Web.Router.Helpers user = Pleroma.Factory.insert(:user) - user_url = account_url(Pleroma.Web.Endpoint, :show, user) - url_404 = account_url(Pleroma.Web.Endpoint, :show, "not-exists") + user_url = ~p[/api/v1/accounts/#{user}] + url_404 = ~p"/api/v1/accounts/not-exists" {user.nickname, user_url, url_404} end diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index c33528a67..d067d6a92 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -1004,23 +1004,13 @@ test "it doesn't fail on invalid alsoKnownAs entries" do test "returns an ap_id for a user" do user = insert(:user) - assert User.ap_id(user) == - Pleroma.Web.Router.Helpers.user_feed_url( - Pleroma.Web.Endpoint, - :feed_redirect, - user.nickname - ) + assert User.ap_id(user) == url(@endpoint, ~p[/users/#{user.nickname}]) end test "returns an ap_followers link for a user" do user = insert(:user) - assert User.ap_followers(user) == - Pleroma.Web.Router.Helpers.user_feed_url( - Pleroma.Web.Endpoint, - :feed_redirect, - user.nickname - ) <> "/followers" + assert User.ap_followers(user) == url(@endpoint, ~p[/users/#{user.nickname}/followers]) end describe "remote user changeset" do diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs index 4ef0c9302..dcb5f143c 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -39,7 +39,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest do test "with the relay active, it returns the relay user", %{conn: conn} do res = conn - |> get(activity_pub_path(conn, :relay)) + |> get(~p"/relay") |> json_response(200) assert res["id"] =~ "/relay" @@ -49,7 +49,7 @@ test "with the relay disabled, it returns 404", %{conn: conn} do clear_config([:instance, :allow_relay], false) conn - |> get(activity_pub_path(conn, :relay)) + |> get(~p"/relay") |> json_response(404) end @@ -59,7 +59,7 @@ test "on non-federating instance, it returns 404", %{conn: conn} do conn |> assign(:user, user) - |> get(activity_pub_path(conn, :relay)) + |> get(~p"/relay") |> json_response(404) end end @@ -68,7 +68,7 @@ test "on non-federating instance, it returns 404", %{conn: conn} do test "it returns the internal fetch user", %{conn: conn} do res = conn - |> get(activity_pub_path(conn, :internal_fetch)) + |> get(~p"/internal/fetch") |> json_response(200) assert res["id"] =~ "/fetch" @@ -80,7 +80,7 @@ test "on non-federating instance, it returns 404", %{conn: conn} do conn |> assign(:user, user) - |> get(activity_pub_path(conn, :internal_fetch)) + |> get(~p"/internal/fetch") |> json_response(404) end end diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index d4d1c2aac..5d5388cf5 100644 --- a/test/pleroma/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -2629,6 +2629,14 @@ test "allow fetching of accounts with an empty string name field" do assert user.name == " " end + test "pin_data_from_featured_collection will ignore unsupported values" do + assert %{} == + ActivityPub.pin_data_from_featured_collection(%{ + "type" => "CollectionThatIsNotRealAndCannotHurtMe", + "first" => "https://social.example/users/alice/collections/featured?page=true" + }) + end + describe "persist/1" do test "should not persist remote delete activities" do poster = insert(:user, local: false) diff --git a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs index e0a2cb9de..68d77ae5a 100644 --- a/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/admin_api_controller_test.exs @@ -783,7 +783,8 @@ test "it confirms emails of two users", %{conn: conn, admin: admin} do describe "PATCH /resend_confirmation_email" do test "it resend emails for two users", %{conn: conn, admin: admin} do - [first_user, second_user] = insert_pair(:user, is_confirmed: false) + [first_user, second_user] = + insert_pair(:user, is_confirmed: false, confirmation_token: "something") ret_conn = patch(conn, "/api/v1/pleroma/admin/users/resend_confirmation_email", %{ diff --git a/test/pleroma/web/admin_api/controllers/report_controller_test.exs b/test/pleroma/web/admin_api/controllers/report_controller_test.exs index 2ec9ca969..ace71785b 100644 --- a/test/pleroma/web/admin_api/controllers/report_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/report_controller_test.exs @@ -212,7 +212,7 @@ test "updates state of multiple reports", %{ test "returns empty response when no reports created", %{conn: conn} do response = conn - |> get(report_path(conn, :index)) + |> get(~p"/api/v1/pleroma/admin/reports") |> json_response_and_validate_schema(:ok) assert Enum.empty?(response["reports"]) @@ -232,7 +232,7 @@ test "returns reports", %{conn: conn} do response = conn - |> get(report_path(conn, :index)) + |> get(~p"/api/v1/pleroma/admin/reports") |> json_response_and_validate_schema(:ok) [report] = response["reports"] @@ -264,7 +264,7 @@ test "returns reports with specified state", %{conn: conn} do response = conn - |> get(report_path(conn, :index, %{state: "open"})) + |> get(~p[/api/v1/pleroma/admin/reports?#{[state: "open"]}]) |> json_response_and_validate_schema(:ok) assert [open_report] = response["reports"] @@ -276,7 +276,7 @@ test "returns reports with specified state", %{conn: conn} do response = conn - |> get(report_path(conn, :index, %{state: "closed"})) + |> get(~p[/api/v1/pleroma/admin/reports?#{[state: "closed"]}]) |> json_response_and_validate_schema(:ok) assert [closed_report] = response["reports"] @@ -288,7 +288,7 @@ test "returns reports with specified state", %{conn: conn} do assert %{"total" => 0, "reports" => []} == conn - |> get(report_path(conn, :index, %{state: "resolved"})) + |> get(~p[/api/v1/pleroma/admin/reports?#{[state: "resolved"]}]) |> json_response_and_validate_schema(:ok) end diff --git a/test/pleroma/web/admin_api/controllers/user_controller_test.exs b/test/pleroma/web/admin_api/controllers/user_controller_test.exs index ba6465630..6dcef1a39 100644 --- a/test/pleroma/web/admin_api/controllers/user_controller_test.exs +++ b/test/pleroma/web/admin_api/controllers/user_controller_test.exs @@ -685,7 +685,7 @@ test "load users with actor_type is Person", %{admin: admin, conn: conn} do response = conn - |> get(user_path(conn, :index), %{actor_types: ["Person"]}) + |> get(~p"/api/v1/pleroma/admin/users", %{actor_types: ["Person"]}) |> json_response_and_validate_schema(200) users = [ @@ -706,7 +706,7 @@ test "load users with actor_type is Person and Service", %{admin: admin, conn: c response = conn - |> get(user_path(conn, :index), %{actor_types: ["Person", "Service"]}) + |> get(~p"/api/v1/pleroma/admin/users", %{actor_types: ["Person", "Service"]}) |> json_response_and_validate_schema(200) users = [ @@ -727,7 +727,7 @@ test "load users with actor_type is Service", %{conn: conn} do response = conn - |> get(user_path(conn, :index), %{actor_types: ["Service"]}) + |> get(~p"/api/v1/pleroma/admin/users", %{actor_types: ["Service"]}) |> json_response_and_validate_schema(200) users = [user_response(user_service, %{"actor_type" => "Service"})] diff --git a/test/pleroma/web/feed/tag_controller_test.exs b/test/pleroma/web/feed/tag_controller_test.exs index fe7940f41..f67d5485d 100644 --- a/test/pleroma/web/feed/tag_controller_test.exs +++ b/test/pleroma/web/feed/tag_controller_test.exs @@ -50,7 +50,7 @@ test "gets a feed (ATOM)", %{conn: conn} do response = conn |> put_req_header("accept", "application/atom+xml") - |> get(tag_feed_path(conn, :feed, "pleromaart.atom")) + |> get(~p"/tags/pleromaart.atom") |> response(200) xml = parse(response) @@ -117,7 +117,7 @@ test "gets a feed (RSS)", %{conn: conn} do response = conn |> put_req_header("accept", "application/rss+xml") - |> get(tag_feed_path(conn, :feed, "pleromaart.rss")) + |> get(~p"/tags/pleromaart.rss") |> response(200) xml = parse(response) @@ -157,7 +157,7 @@ test "gets a feed (RSS)", %{conn: conn} do response = conn |> put_req_header("accept", "application/rss+xml") - |> get(tag_feed_path(conn, :feed, "pleromaart.rss")) + |> get(~p"/tags/pleromaart.rss") |> response(200) xml = parse(response) @@ -188,7 +188,7 @@ test "gets a feed (RSS)", %{conn: conn} do test "returns 404 for tags feed", %{conn: conn} do conn |> put_req_header("accept", "application/rss+xml") - |> get(tag_feed_path(conn, :feed, "pleromaart.rss")) + |> get(~p"/tags/pleromaart.rss") |> response(404) end end diff --git a/test/pleroma/web/feed/user_controller_test.exs b/test/pleroma/web/feed/user_controller_test.exs index 245ffcf0a..d5d9faf06 100644 --- a/test/pleroma/web/feed/user_controller_test.exs +++ b/test/pleroma/web/feed/user_controller_test.exs @@ -66,7 +66,7 @@ test "gets an atom feed", %{conn: conn, user: user, object: object, max_id: max_ resp = conn |> put_req_header("accept", "application/atom+xml") - |> get(user_feed_path(conn, :feed, user.nickname)) + |> get(~p[/users/#{user.nickname}/feed]) |> response(200) activity_titles = @@ -128,7 +128,7 @@ test "returns 404 for a missing feed", %{conn: conn} do conn = conn |> put_req_header("accept", "application/atom+xml") - |> get(user_feed_path(conn, :feed, "nonexisting")) + |> get(~p"/users/nonexisting/feed") assert response(conn, 404) end @@ -144,7 +144,7 @@ test "returns feed with public and unlisted activities", %{conn: conn} do resp = conn |> put_req_header("accept", "application/atom+xml") - |> get(user_feed_path(conn, :feed, user.nickname)) + |> get(~p[/users/#{user.nickname}/feed]) |> response(200) activity_titles = @@ -163,7 +163,7 @@ test "returns 404 when the user is remote", %{conn: conn} do assert conn |> put_req_header("accept", "application/atom+xml") - |> get(user_feed_path(conn, :feed, user.nickname)) + |> get(~p[/users/#{user.nickname}/feed]) |> response(404) end @@ -240,7 +240,7 @@ test "with non-html / non-json format, it returns error when user is not found", response = conn |> put_req_header("accept", "application/xml") - |> get(user_feed_path(conn, :feed, "jimm")) + |> get(~p"/users/jimm/feed") |> response(404) assert response == ~S({"error":"Not found"}) @@ -258,7 +258,7 @@ test "returns 404 for user feed", %{conn: conn} do assert conn |> put_req_header("accept", "application/atom+xml") - |> get(user_feed_path(conn, :feed, user.nickname)) + |> get(~p[/users/#{user.nickname}/feed]) |> response(404) end end diff --git a/test/pleroma/web/mastodon_api/update_credentials_test.exs b/test/pleroma/web/mastodon_api/update_credentials_test.exs index a347c7987..ebd536b0b 100644 --- a/test/pleroma/web/mastodon_api/update_credentials_test.exs +++ b/test/pleroma/web/mastodon_api/update_credentials_test.exs @@ -529,7 +529,7 @@ test "update fields with a link to content with rel=me, with frontend path", %{ user: user, conn: conn } do - fe_url = "#{Pleroma.Web.Endpoint.url()}/#{user.nickname}" + fe_url = url(~p[/#{user.nickname}]) Tesla.Mock.mock(fn %{url: "http://example.com/rel_me/fe_path"} -> 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 682c633f4..22f65a0d1 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -258,7 +258,7 @@ test "a note activity" do expected = %{ id: to_string(note.id), uri: object_data["id"], - url: Pleroma.Web.Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, note), + url: url(~p[/notice/#{note}]), account: AccountView.render("show.json", %{user: user, skip_visibility_check: true}), in_reply_to_id: nil, in_reply_to_account_id: nil, diff --git a/test/pleroma/web/metadata/providers/twitter_card_test.exs b/test/pleroma/web/metadata/providers/twitter_card_test.exs index 731447094..cd3f5eced 100644 --- a/test/pleroma/web/metadata/providers/twitter_card_test.exs +++ b/test/pleroma/web/metadata/providers/twitter_card_test.exs @@ -12,7 +12,6 @@ defmodule Pleroma.Web.Metadata.Providers.TwitterCardTest do alias Pleroma.Web.MediaProxy alias Pleroma.Web.Metadata.Providers.TwitterCard alias Pleroma.Web.Metadata.Utils - alias Pleroma.Web.Router setup do: clear_config([Pleroma.Web.Metadata, :unfurl_nsfw]) @@ -189,7 +188,7 @@ test "it renders supported types of attachments and skips unknown types" do {:meta, [ name: "twitter:player", - content: Router.Helpers.o_status_url(Endpoint, :notice_player, activity.id) + content: url(Endpoint, ~p[/notice/#{activity.id}/embed_player]) ], []}, {:meta, [name: "twitter:player:width", content: "800"], []}, {:meta, [name: "twitter:player:height", content: "600"], []}, diff --git a/test/pleroma/web/mongoose_im_controller_test.exs b/test/pleroma/web/mongoose_im_controller_test.exs index bda7c613f..cf7db6d93 100644 --- a/test/pleroma/web/mongoose_im_controller_test.exs +++ b/test/pleroma/web/mongoose_im_controller_test.exs @@ -13,28 +13,28 @@ test "/user_exists", %{conn: conn} do res = conn - |> get(mongoose_im_path(conn, :user_exists), user: "lain") + |> get(~p"/user_exists", user: "lain") |> json_response(200) assert res == true res = conn - |> get(mongoose_im_path(conn, :user_exists), user: "alice") + |> get(~p"/user_exists", user: "alice") |> json_response(404) assert res == false res = conn - |> get(mongoose_im_path(conn, :user_exists), user: "bob") + |> get(~p"/user_exists", user: "bob") |> json_response(404) assert res == false res = conn - |> get(mongoose_im_path(conn, :user_exists), user: "konata") + |> get(~p"/user_exists", user: "konata") |> json_response(404) assert res == false @@ -52,28 +52,28 @@ test "/check_password", %{conn: conn} do res = conn - |> get(mongoose_im_path(conn, :check_password), user: user.nickname, pass: "cool") + |> get(~p"/check_password", user: user.nickname, pass: "cool") |> json_response(200) assert res == true res = conn - |> get(mongoose_im_path(conn, :check_password), user: user.nickname, pass: "uncool") + |> get(~p"/check_password", user: user.nickname, pass: "uncool") |> json_response(403) assert res == false res = conn - |> get(mongoose_im_path(conn, :check_password), user: "konata", pass: "cool") + |> get(~p"/check_password", user: "konata", pass: "cool") |> json_response(404) assert res == false res = conn - |> get(mongoose_im_path(conn, :check_password), user: "nobody", pass: "cool") + |> get(~p"/check_password", user: "nobody", pass: "cool") |> json_response(404) assert res == false 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 3748940fd..e0ba339db 100644 --- a/test/pleroma/web/o_auth/o_auth_controller_test.exs +++ b/test/pleroma/web/o_auth/o_auth_controller_test.exs @@ -57,7 +57,7 @@ test "GET /oauth/authorize renders auth forms, including OAuth consumer form", % assert response = html_response(conn, 200) assert response =~ "Sign in with Twitter" - assert response =~ o_auth_path(conn, :prepare_request) + assert response =~ ~p"/prepare_request" end test "GET /oauth/prepare_request encodes parameters as `state` and redirects", %{ @@ -186,7 +186,9 @@ test "on authentication error, GET /oauth//callback redirects to `redi assert html_response(conn, 302) assert redirected_to(conn) == app.redirect_uris - assert get_flash(conn, :error) == "Failed to authenticate: (error description)." + + assert Phoenix.Flash.get(conn.assigns.flash, :error) == + "Failed to authenticate: (error description)." end test "GET /oauth/registration_details renders registration details form", %{ @@ -307,7 +309,9 @@ test "with invalid params, POST /oauth/register?op=register renders registration |> post("/oauth/register", bad_params) assert html_response(conn, 403) =~ ~r/name="op" type="submit" value="register"/ - assert get_flash(conn, :error) == "Error: #{bad_param} has already been taken." + + assert Phoenix.Flash.get(conn.assigns.flash, :error) == + "Error: #{bad_param} has already been taken." end end @@ -398,7 +402,7 @@ test "with invalid params, POST /oauth/register?op=connect renders registration_ |> post("/oauth/register", params) assert html_response(conn, 401) =~ ~r/name="op" type="submit" value="connect"/ - assert get_flash(conn, :error) == "Invalid Username/Password" + assert Phoenix.Flash.get(conn.assigns.flash, :error) == "Invalid Username/Password" end end diff --git a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs index dc300296c..d2bc7840f 100644 --- a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs +++ b/test/pleroma/web/twitter_api/remote_follow_controller_test.exs @@ -48,9 +48,7 @@ test "adds status to pleroma instance if the `acct` is a status", %{conn: conn} assert conn |> get( - remote_follow_path(conn, :follow, %{ - acct: "https://mastodon.social/users/emelie/statuses/101849165031453009" - }) + ~p[/ostatus_subscribe?#{[acct: "https://mastodon.social/users/emelie/statuses/101849165031453009"]}] ) |> redirected_to() =~ "/notice/" end @@ -77,7 +75,7 @@ test "show follow account page if the `acct` is a account link", %{conn: conn} d response = conn - |> get(remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"})) + |> get(~p[/ostatus_subscribe?#{[acct: "https://mastodon.social/users/emelie"]}]) |> html_response(200) assert response =~ "Log in to follow" @@ -108,7 +106,7 @@ test "show follow page if the `acct` is a account link", %{conn: conn} do response = conn |> assign(:user, user) - |> get(remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"})) + |> get(~p[/ostatus_subscribe?#{[acct: "https://mastodon.social/users/emelie"]}]) |> html_response(200) assert response =~ "Remote follow" @@ -129,9 +127,7 @@ test "show follow page with error when user can not be fetched by `acct` link", conn |> assign(:user, user) |> get( - remote_follow_path(conn, :follow, %{ - acct: "https://mastodon.social/users/not_found" - }) + ~p[/ostatus_subscribe?#{[acct: "https://mastodon.social/users/not_found"]}] ) |> html_response(200) @@ -151,7 +147,7 @@ test "required `follow | write:follows` scope", %{conn: conn} do conn |> assign(:user, user) |> assign(:token, read_token) - |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) + |> post(~p"/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) |> response(200) assert response =~ "Error following account" @@ -166,7 +162,7 @@ test "follows user", %{conn: conn} do conn |> assign(:user, user) |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:follows"])) - |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) + |> post(~p"/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) assert redirected_to(conn) == "/users/#{user2.id}" end @@ -178,7 +174,7 @@ test "returns error when user is deactivated", %{conn: conn} do response = conn |> assign(:user, user) - |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) + |> post(~p"/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) |> response(200) assert response =~ "Error following account" @@ -194,7 +190,7 @@ test "returns error when user is blocked", %{conn: conn} do response = conn |> assign(:user, user) - |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) + |> post(~p"/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) |> response(200) assert response =~ "Error following account" @@ -206,7 +202,7 @@ test "returns error when followee not found", %{conn: conn} do response = conn |> assign(:user, user) - |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => "jimm"}}) + |> post(~p"/ostatus_subscribe", %{"user" => %{"id" => "jimm"}}) |> response(200) assert response =~ "Error following account" @@ -221,7 +217,7 @@ test "returns success result when user already in followers", %{conn: conn} do conn |> assign(:user, refresh_record(user)) |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:follows"])) - |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) + |> post(~p"/ostatus_subscribe", %{"user" => %{"id" => user2.id}}) assert redirected_to(conn) == "/users/#{user2.id}" end @@ -243,7 +239,7 @@ test "render the MFA login form", %{conn: conn} do response = conn - |> post(remote_follow_path(conn, :do_follow), %{ + |> post(~p"/ostatus_subscribe", %{ "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} }) |> response(200) @@ -271,7 +267,7 @@ test "returns error when password is incorrect", %{conn: conn} do response = conn - |> post(remote_follow_path(conn, :do_follow), %{ + |> post(~p"/ostatus_subscribe", %{ "authorization" => %{"name" => user.nickname, "password" => "test1", "id" => user2.id} }) |> response(200) @@ -299,7 +295,7 @@ test "follows", %{conn: conn} do conn = conn |> post( - remote_follow_path(conn, :do_follow), + ~p"/ostatus_subscribe", %{ "mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id} } @@ -328,7 +324,7 @@ test "returns error when auth code is incorrect", %{conn: conn} do response = conn |> post( - remote_follow_path(conn, :do_follow), + ~p"/ostatus_subscribe", %{ "mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id} } @@ -347,7 +343,7 @@ test "follows", %{conn: conn} do conn = conn - |> post(remote_follow_path(conn, :do_follow), %{ + |> post(~p"/ostatus_subscribe", %{ "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} }) @@ -360,7 +356,7 @@ test "returns error when followee not found", %{conn: conn} do response = conn - |> post(remote_follow_path(conn, :do_follow), %{ + |> post(~p"/ostatus_subscribe", %{ "authorization" => %{"name" => user.nickname, "password" => "test", "id" => "jimm"} }) |> response(200) @@ -373,7 +369,7 @@ test "returns error when login invalid", %{conn: conn} do response = conn - |> post(remote_follow_path(conn, :do_follow), %{ + |> post(~p"/ostatus_subscribe", %{ "authorization" => %{"name" => "jimm", "password" => "test", "id" => user.id} }) |> response(200) @@ -387,7 +383,7 @@ test "returns error when password invalid", %{conn: conn} do response = conn - |> post(remote_follow_path(conn, :do_follow), %{ + |> post(~p"/ostatus_subscribe", %{ "authorization" => %{"name" => user.nickname, "password" => "42", "id" => user2.id} }) |> response(200) @@ -403,7 +399,7 @@ test "returns error when user is blocked", %{conn: conn} do response = conn - |> post(remote_follow_path(conn, :do_follow), %{ + |> post(~p"/ostatus_subscribe", %{ "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} }) |> response(200) diff --git a/test/pleroma/web/uploader_controller_test.exs b/test/pleroma/web/uploader_controller_test.exs index 032895e71..e11a7367f 100644 --- a/test/pleroma/web/uploader_controller_test.exs +++ b/test/pleroma/web/uploader_controller_test.exs @@ -10,7 +10,7 @@ defmodule Pleroma.Web.UploaderControllerTest do test "it returns 400 response when process callback isn't alive", %{conn: conn} do res = conn - |> post(uploader_path(conn, :callback, "test-path")) + |> post(~p"/api/v1/pleroma/uploader_callback/test-path") assert res.status == 400 assert res.resp_body == "{\"error\":\"bad request\"}" @@ -34,7 +34,7 @@ test "it returns success result", %{conn: conn} do res = conn - |> post(uploader_path(conn, :callback, "test-path")) + |> post(~p"/api/v1/pleroma/uploader_callback/test-path") |> json_response(200) assert res == %{"upload_path" => "test-path"} diff --git a/test/support/conn_case.ex b/test/support/conn_case.ex index eab469833..21f031bba 100644 --- a/test/support/conn_case.ex +++ b/test/support/conn_case.ex @@ -27,12 +27,12 @@ defmodule Pleroma.Web.ConnCase do import Plug.Conn import Phoenix.ConnTest use Pleroma.Tests.Helpers - import Pleroma.Web.Router.Helpers alias Pleroma.Config # The default endpoint for testing @endpoint Pleroma.Web.Endpoint + use Pleroma.Web, :verified_routes # Sets up OAuth access with specified scopes defp oauth_access(scopes, opts \\ []) do diff --git a/test/support/data_case.ex b/test/support/data_case.ex index 0ee2aa4a2..5eacc148d 100644 --- a/test/support/data_case.ex +++ b/test/support/data_case.ex @@ -23,12 +23,14 @@ defmodule Pleroma.DataCase do using do quote do alias Pleroma.Repo + @endpoint Pleroma.Web.Endpoint import Ecto import Ecto.Changeset import Ecto.Query import Pleroma.DataCase use Pleroma.Tests.Helpers + use Pleroma.Web, :verified_routes # Sets up OAuth access with specified scopes defp oauth_access(scopes, opts \\ []) do -- 2.43.0 From 063e3c0d34669bc9bf0ab9ee70e78e6311e191e8 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Tue, 15 Aug 2023 23:12:04 +0100 Subject: [PATCH 028/149] Disallow nil hosts in should_federate --- CHANGELOG.md | 1 + lib/pleroma/web/activity_pub/publisher.ex | 6 +++++- test/pleroma/web/activity_pub/publisher_test.exs | 7 +++++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a0b7fcf79..71949e2e4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Fixed - Documentation issue in which a non-existing nginx file was referenced +- Issue where a bad inbox URL could break federation ## 2023.08 diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 3071c1b77..20004c4fa 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -115,13 +115,17 @@ defp allowed_instances do def should_federate?(url) do %{host: host} = URI.parse(url) - with allowed <- allowed_instances(), + with {:nil, false} <- {:nil, is_nil(host)}, + allowed <- allowed_instances(), false <- Enum.empty?(allowed) do allowed |> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples() |> Pleroma.Web.ActivityPub.MRF.subdomains_regex() |> Pleroma.Web.ActivityPub.MRF.subdomain_match?(host) else + # oi! + {:nil, true} -> + false _ -> quarantined_instances = blocked_instances() diff --git a/test/pleroma/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs index d993ab1d4..87930b7b1 100644 --- a/test/pleroma/web/activity_pub/publisher_test.exs +++ b/test/pleroma/web/activity_pub/publisher_test.exs @@ -487,4 +487,11 @@ test "publish to url with with different ports" do ) end end + + describe "should_federate/1" do + test "should not obliterate itself if the inbox URL is bad" do + url = "/inbox" + refute Pleroma.Web.ActivityPub.Publisher.should_federate?(url) + end + end end -- 2.43.0 From f3cc60b202c398275153a812b091f8b7c1e32015 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Tue, 15 Aug 2023 23:23:59 +0100 Subject: [PATCH 029/149] INBOX NEEDS TO BE A FULL URL YOU IDIOT AM BAKA I SHOULD JUST COMMIT SUDOKU RIGHT NOW --- lib/pleroma/web/activity_pub/views/user_view.ex | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 82b59c47f..558024d42 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -23,11 +23,11 @@ def render("endpoints.json", %{user: %User{nickname: nil, local: true} = _user}) def render("endpoints.json", %{user: %User{local: true} = _user}) do %{ - "oauthAuthorizationEndpoint" => ~p"/oauth/authorize", - "oauthRegistrationEndpoint" => ~p"/api/v1/apps", - "oauthTokenEndpoint" => ~p"/oauth/token", - "sharedInbox" => ~p"/inbox", - "uploadMedia" => ~p"/api/ap/upload_media" + "oauthAuthorizationEndpoint" => url(~p"/oauth/authorize"), + "oauthRegistrationEndpoint" => url(~p"/api/v1/apps"), + "oauthTokenEndpoint" => url(~p"/oauth/token"), + "sharedInbox" => url(~p"/inbox"), + "uploadMedia" => url(~p"/api/ap/upload_media") } end -- 2.43.0 From 9bc0345e5776144374161949de7be4e4e6300932 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Tue, 15 Aug 2023 23:26:08 +0100 Subject: [PATCH 030/149] AND THAT ONE TOO AND ALL --- lib/pleroma/web/activity_pub/views/user_view.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 558024d42..d58e28c9f 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -18,7 +18,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do import Ecto.Query def render("endpoints.json", %{user: %User{nickname: nil, local: true} = _user}) do - %{"sharedInbox" => ~p"/inbox"} + %{"sharedInbox" => url(~p"/inbox")} end def render("endpoints.json", %{user: %User{local: true} = _user}) do -- 2.43.0 From 98f0820ca455d6adfd7a742ade5a6fcdce0c1c96 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Tue, 15 Aug 2023 23:26:22 +0100 Subject: [PATCH 031/149] MIX FORMAT --- lib/pleroma/web/activity_pub/publisher.ex | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 20004c4fa..4fe394be6 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -115,7 +115,7 @@ defp allowed_instances do def should_federate?(url) do %{host: host} = URI.parse(url) - with {:nil, false} <- {:nil, is_nil(host)}, + with {nil, false} <- {nil, is_nil(host)}, allowed <- allowed_instances(), false <- Enum.empty?(allowed) do allowed @@ -124,8 +124,9 @@ def should_federate?(url) do |> Pleroma.Web.ActivityPub.MRF.subdomain_match?(host) else # oi! - {:nil, true} -> + {nil, true} -> false + _ -> quarantined_instances = blocked_instances() -- 2.43.0 From 6ec543729405c1d874da57cd47476161a3f52e69 Mon Sep 17 00:00:00 2001 From: YokaiRick Date: Wed, 16 Aug 2023 20:58:21 +0000 Subject: [PATCH 032/149] added support for arm64 added arm64 support for update. Tested on Arch amd64, Debian arm64 and Alpine amd64 --- rel/files/bin/pleroma_ctl | 31 +++++++++++++++++++------------ 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/rel/files/bin/pleroma_ctl b/rel/files/bin/pleroma_ctl index f05e3cd57..42fa06a53 100755 --- a/rel/files/bin/pleroma_ctl +++ b/rel/files/bin/pleroma_ctl @@ -2,21 +2,28 @@ # XXX: This should be removed when elixir's releases get custom command support detect_flavour() { - arch="amd64" - if getconf GNU_LIBC_VERSION >/dev/null; then - libc_postfix="" - elif [ "$(ldd 2>&1 | head -c 9)" = "musl libc" ]; then - libc_postfix="-musl" - elif [ "$(find /lib/libc.musl* | wc -l)" ]; then - libc_postfix="-musl" - else - echo "Unsupported libc" >&2 - exit 1 + machine_type=$(uname -m) + if [ ${machine_type} = 'aarch64' ] ; then + arch='arm64' + elif [ ${machine_type} = 'x86_64' ] ; then + arch='amd64' + if getconf GNU_LIBC_VERSION >/dev/null; then + libc_postfix="" + elif [ "$(ldd 2>&1 | head -c 9)" = "musl libc" ]; then + libc_postfix="-musl" + elif [ "$(find /lib/libc.musl* | wc -l)" ]; then + libc_postfix="-musl" + else + echo "Unsupported libc" >&2 + exit 1 + fi + else + echo "Could not detect your architecture please pass them via --flavour" + exit 1 fi - echo "$arch$libc_postfix" + echo "$arch$libc_postfix" } - detect_branch() { version="$(cut -d' ' -f2 <"$RELEASE_ROOT"/releases/start_erl.data)" # Expected format: major.minor.patch_version(-number_of_commits_ahead_of_tag-gcommit_hash).branch -- 2.43.0 From 6139c3346d9495c58b67c01447af6344488a0c4b Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 16 Aug 2023 22:49:23 +0100 Subject: [PATCH 033/149] Add extra rollbacks to pleroma develop --- docs/docs/installation/migrating_to_akkoma.md | 2 +- .../20230422154018_drop_unused_indexes.exs | 74 +++++++++++++++++++ .../20230306112859_instances_add_metadata.exs | 15 ++++ .../20230504173400_remove_user_ap_enabled.exs | 9 +++ 4 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 priv/repo/optional_migrations/20230422154018_drop_unused_indexes.exs create mode 100644 priv/repo/optional_migrations/pleroma_develop_rollbacks/20230306112859_instances_add_metadata.exs create mode 100644 priv/repo/optional_migrations/pleroma_develop_rollbacks/20230504173400_remove_user_ap_enabled.exs diff --git a/docs/docs/installation/migrating_to_akkoma.md b/docs/docs/installation/migrating_to_akkoma.md index 4a58e836e..9c30d9586 100644 --- a/docs/docs/installation/migrating_to_akkoma.md +++ b/docs/docs/installation/migrating_to_akkoma.md @@ -40,7 +40,7 @@ If you are on pleroma develop, and have updated since 2022-08, you may have issu Please roll back the given migrations: ```bash -MIX_ENV=prod mix ecto.rollback --migrations-path priv/repo/optional_migrations/pleroma_develop_rollbacks -n3 +MIX_ENV=prod mix ecto.rollback --migrations-path priv/repo/optional_migrations/pleroma_develop_rollbacks -n5 ``` Then compile, migrate and restart as usual. diff --git a/priv/repo/optional_migrations/20230422154018_drop_unused_indexes.exs b/priv/repo/optional_migrations/20230422154018_drop_unused_indexes.exs new file mode 100644 index 000000000..d8acb1034 --- /dev/null +++ b/priv/repo/optional_migrations/20230422154018_drop_unused_indexes.exs @@ -0,0 +1,74 @@ +defmodule Pleroma.Repo.Migrations.DropUnusedIndexes do + use Ecto.Migration + + @disable_ddl_transaction true + + @disable_migration_lock true + + def up do + drop_if_exists( + index(:activities, ["(data->>'actor')", "inserted_at desc"], name: :activities_actor_index) + ) + + drop_if_exists(index(:activities, ["(data->'to')"], name: :activities_to_index)) + + drop_if_exists(index(:activities, ["(data->'cc')"], name: :activities_cc_index)) + + drop_if_exists(index(:activities, ["(split_part(actor, '/', 3))"], name: :activities_hosts)) + + drop_if_exists( + index(:activities, ["(data->'object'->>'inReplyTo')"], name: :activities_in_reply_to) + ) + + drop_if_exists( + index(:activities, ["((data #> '{\"object\",\"likes\"}'))"], name: :activities_likes) + ) + end + + def down do + create_if_not_exists( + index(:activities, ["(data->>'actor')", "inserted_at desc"], + name: :activities_actor_index, + concurrently: true + ) + ) + + create_if_not_exists( + index(:activities, ["(data->'to')"], + name: :activities_to_index, + using: :gin, + concurrently: true + ) + ) + + create_if_not_exists( + index(:activities, ["(data->'cc')"], + name: :activities_cc_index, + using: :gin, + concurrently: true + ) + ) + + create_if_not_exists( + index(:activities, ["(split_part(actor, '/', 3))"], + name: :activities_hosts, + concurrently: true + ) + ) + + create_if_not_exists( + index(:activities, ["(data->'object'->>'inReplyTo')"], + name: :activities_in_reply_to, + concurrently: true + ) + ) + + create_if_not_exists( + index(:activities, ["((data #> '{\"object\",\"likes\"}'))"], + name: :activities_likes, + using: :gin, + concurrently: true + ) + ) + end +end diff --git a/priv/repo/optional_migrations/pleroma_develop_rollbacks/20230306112859_instances_add_metadata.exs b/priv/repo/optional_migrations/pleroma_develop_rollbacks/20230306112859_instances_add_metadata.exs new file mode 100644 index 000000000..ab8ac83a0 --- /dev/null +++ b/priv/repo/optional_migrations/pleroma_develop_rollbacks/20230306112859_instances_add_metadata.exs @@ -0,0 +1,15 @@ +defmodule Pleroma.Repo.Migrations.InstancesAddMetadata do + use Ecto.Migration + + def down do + alter table(:instances) do + remove_if_exists(:metadata, :map) + end + end + + def up do + alter table(:instances) do + add_if_not_exists(:metadata, :map) + end + end +end diff --git a/priv/repo/optional_migrations/pleroma_develop_rollbacks/20230504173400_remove_user_ap_enabled.exs b/priv/repo/optional_migrations/pleroma_develop_rollbacks/20230504173400_remove_user_ap_enabled.exs new file mode 100644 index 000000000..d00bfd725 --- /dev/null +++ b/priv/repo/optional_migrations/pleroma_develop_rollbacks/20230504173400_remove_user_ap_enabled.exs @@ -0,0 +1,9 @@ +defmodule Pleroma.Repo.Migrations.RemoveUserApEnabled do + use Ecto.Migration + + def change do + alter table(:users) do + remove(:ap_enabled, :boolean, default: false, null: false) + end + end +end -- 2.43.0 From f7ea0a124811fe1475ec43fa463a736c7a5ba20b Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 16 Aug 2023 23:01:02 +0100 Subject: [PATCH 034/149] bump OTP required --- docs/docs/installation/generic_dependencies.include | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/installation/generic_dependencies.include b/docs/docs/installation/generic_dependencies.include index d8cf9f9da..b23736d85 100644 --- a/docs/docs/installation/generic_dependencies.include +++ b/docs/docs/installation/generic_dependencies.include @@ -2,7 +2,7 @@ * PostgreSQL 9.6+ * Elixir 1.14+ -* Erlang OTP 24+ +* Erlang OTP 25+ * git * file / libmagic * gcc (clang might also work) -- 2.43.0 From 5c164028cf7e45811ddcf8b9cfd3dcab56717827 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 16 Aug 2023 23:11:36 +0100 Subject: [PATCH 035/149] ensure ap_enabled true if coming back pleroma --- .../20230504173400_remove_user_ap_enabled.exs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/priv/repo/optional_migrations/pleroma_develop_rollbacks/20230504173400_remove_user_ap_enabled.exs b/priv/repo/optional_migrations/pleroma_develop_rollbacks/20230504173400_remove_user_ap_enabled.exs index d00bfd725..f399d9fd7 100644 --- a/priv/repo/optional_migrations/pleroma_develop_rollbacks/20230504173400_remove_user_ap_enabled.exs +++ b/priv/repo/optional_migrations/pleroma_develop_rollbacks/20230504173400_remove_user_ap_enabled.exs @@ -1,9 +1,15 @@ defmodule Pleroma.Repo.Migrations.RemoveUserApEnabled do use Ecto.Migration - def change do + def up do alter table(:users) do - remove(:ap_enabled, :boolean, default: false, null: false) + remove_if_exists(:ap_enabled, :boolean) + end + end + + def down do + alter table(:users) do + add_if_not_exists(:ap_enabled, :boolean, default: true, null: false) end end end -- 2.43.0 From 0617090743c187e08bc3e530a59f072335f62221 Mon Sep 17 00:00:00 2001 From: y0nei Date: Thu, 17 Aug 2023 16:51:53 +0200 Subject: [PATCH 036/149] Note about Docker installations in backup section --- docs/docs/administration/backup.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/docs/administration/backup.md b/docs/docs/administration/backup.md index 5c5df88ce..6aa79043c 100644 --- a/docs/docs/administration/backup.md +++ b/docs/docs/administration/backup.md @@ -45,3 +45,16 @@ 8. Remove the dependencies that you don't need anymore (see installation guide). Make sure you don't remove packages that are still needed for other software that you have running! [¹]: We assume the database name and user are both "akkoma". If not, you can find the correct name in your config files. + +## Docker installations + +If running behind Docker, it is required to run the above commands inside of a running database container. + +### Example +Running `docker compose run --rm db pg_dump <...>` will fail and return: +``` +pg_dump: error: connection to server on socket "/run/postgresql/.s.PGSQL.5432" failed: No such file or directory +Is the server running locally and accepting connections on that socket?" +``` +However, first starting just the database container with `docker compose up db -d`, and then running `docker compose exec db pg_dump -d akkoma --format=custom -f ` will successfully generate a database dump. +Then to make the file accessible on the host system you can run `docker compose cp db: ` to copy if from the container. -- 2.43.0 From 32422a7a045527c6849a5c592347c65e097fd1f9 Mon Sep 17 00:00:00 2001 From: yuki joou Date: Fri, 18 Aug 2023 13:25:52 +0000 Subject: [PATCH 037/149] docs: Fixed wrong command for robots_txt CLI task This is according to the error message displayed when trying to run the command in the current version of the docs --- docs/docs/administration/CLI_tasks/robots_txt.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/administration/CLI_tasks/robots_txt.md b/docs/docs/administration/CLI_tasks/robots_txt.md index 924f2e319..6cb9fd673 100644 --- a/docs/docs/administration/CLI_tasks/robots_txt.md +++ b/docs/docs/administration/CLI_tasks/robots_txt.md @@ -11,11 +11,11 @@ If you want to generate a restrictive `robots.txt`, you can run the following mi === "OTP" ```sh - ./bin/pleroma_ctl robotstxt disallow_all + ./bin/pleroma_ctl robots_txt disallow_all ``` === "From Source" ```sh - mix pleroma.robotstxt disallow_all + mix pleroma.robots_txt disallow_all ``` -- 2.43.0 From 1b9edcba64de810e7c7258741a9b02bf3135cd4d Mon Sep 17 00:00:00 2001 From: Koneko Toujou Date: Mon, 21 Aug 2023 21:50:10 +0000 Subject: [PATCH 038/149] Add shm_size to the Database container --- docker-compose.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/docker-compose.yml b/docker-compose.yml index 0dedbc87e..58abf189d 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,6 +4,7 @@ services: db: image: akkoma-db:latest build: ./docker-resources/database + shm_size: 4gb restart: unless-stopped user: ${DOCKER_USER} environment: { -- 2.43.0 From c8e08e9cc35c4e856994e8577913e848e42153ad Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Fri, 25 Aug 2023 11:00:49 +0100 Subject: [PATCH 039/149] fix issue with API cascading domain blocks but not honouring them --- lib/pleroma/user.ex | 8 ++++++-- test/pleroma/user_test.exs | 40 ++++++++++++++++++++------------------ 2 files changed, 27 insertions(+), 21 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index ad87837fa..1b78d0479 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1605,9 +1605,13 @@ def blocks_user?(%User{} = user, %User{} = target) do def blocks_user?(_, _), do: false def blocks_domain?(%User{} = user, %User{} = target) do - domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks) %{host: host} = URI.parse(target.ap_id) - Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host) + Enum.member?(user.domain_blocks, host) + # TODO: functionality should probably be changed such that subdomains block as well, + # but as it stands, this just hecks up the relationships endpoint + # domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.domain_blocks) + # %{host: host} = URI.parse(target.ap_id) + # Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, host) end def blocks_domain?(_, _), do: false diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index d067d6a92..5a0d77cab 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -1353,25 +1353,27 @@ test "does not block domain with same end if wildcard added" do refute User.blocks?(user, collateral_user) end - test "blocks domain with wildcard for subdomain" do - user = insert(:user) - - user_from_subdomain = - insert(:user, %{ap_id: "https://subdomain.awful-and-rude-instance.com/user/bully"}) - - user_with_two_subdomains = - insert(:user, %{ - ap_id: "https://subdomain.second_subdomain.awful-and-rude-instance.com/user/bully" - }) - - user_domain = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"}) - - {:ok, user} = User.block_domain(user, "awful-and-rude-instance.com") - - assert User.blocks?(user, user_from_subdomain) - assert User.blocks?(user, user_with_two_subdomains) - assert User.blocks?(user, user_domain) - end + # This behaviour is not honoured by the timeline query + # re-add at a later date when UX is established + # test "blocks domain with wildcard for subdomain" do + # user = insert(:user) + # + # user_from_subdomain = + # insert(:user, %{ap_id: "https://subdomain.awful-and-rude-instance.com/user/bully"}) + # + # user_with_two_subdomains = + # insert(:user, %{ + # ap_id: "https://subdomain.second_subdomain.awful-and-rude-instance.com/user/bully" + # }) + # + # user_domain = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"}) + # + # {:ok, user} = User.block_domain(user, "awful-and-rude-instance.com") + # + # assert User.blocks?(user, user_from_subdomain) + # assert User.blocks?(user, user_with_two_subdomains) + # assert User.blocks?(user, user_domain) + # end test "unblocks domains" do user = insert(:user) -- 2.43.0 From 3e7446d177aedaaf468316e1a3293628e91de011 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Fri, 15 Sep 2023 11:58:56 +0100 Subject: [PATCH 040/149] Add various both-ugc-and-tag setups --- priv/scrubbers/default.ex | 4 +++- priv/scrubbers/links_only.ex | 4 +++- priv/scrubbers/twitter_text.ex | 4 +++- 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/priv/scrubbers/default.ex b/priv/scrubbers/default.ex index 37ffaef3a..6a97cbfd4 100644 --- a/priv/scrubbers/default.ex +++ b/priv/scrubbers/default.ex @@ -26,7 +26,9 @@ defmodule Pleroma.HTML.Scrubber.Default do "nofollow", "noopener", "noreferrer", - "ugc" + "ugc", + "tag ugc", + "ugc tag" ]) Meta.allow_tag_with_these_attributes(:a, ["name", "title"]) diff --git a/priv/scrubbers/links_only.ex b/priv/scrubbers/links_only.ex index b30a00589..5ec35c027 100644 --- a/priv/scrubbers/links_only.ex +++ b/priv/scrubbers/links_only.ex @@ -19,7 +19,9 @@ defmodule Pleroma.HTML.Scrubber.LinksOnly do "noopener", "noreferrer", "me", - "ugc" + "ugc", + "tag ugc", + "ugc tag" ]) Meta.allow_tag_with_these_attributes(:a, ["name", "title"]) diff --git a/priv/scrubbers/twitter_text.ex b/priv/scrubbers/twitter_text.ex index c4e796cad..9cb455a27 100644 --- a/priv/scrubbers/twitter_text.ex +++ b/priv/scrubbers/twitter_text.ex @@ -26,7 +26,9 @@ defmodule Pleroma.HTML.Scrubber.TwitterText do "tag", "nofollow", "noopener", - "noreferrer" + "noreferrer", + "tag ugc", + "ugc tag" ]) Meta.allow_tag_with_these_attributes(:a, ["name", "title"]) -- 2.43.0 From d1af78aba11a20da17bbe2a7c0bf303bf30f6323 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Fri, 15 Sep 2023 12:00:45 +0100 Subject: [PATCH 041/149] changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 71949e2e4..26729a038 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Fixed - Documentation issue in which a non-existing nginx file was referenced - Issue where a bad inbox URL could break federation +- Issue where hashtag rel values would be scrubbed ## 2023.08 -- 2.43.0 From 033b7b04e081d4db5ff150a6b5fef03ead205e19 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Fri, 20 Oct 2023 13:30:29 +0100 Subject: [PATCH 042/149] update captcha version --- mix.exs | 2 +- mix.lock | 2 +- test/pleroma/web/mastodon_api/update_credentials_test.exs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mix.exs b/mix.exs index e2243f3ba..16f867c4f 100644 --- a/mix.exs +++ b/mix.exs @@ -179,7 +179,7 @@ defp deps do {:remote_ip, "~> 1.1.0"}, {:captcha, git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", - ref: "3bbfa8b5ea13accc1b1c40579a380d8e5cfd6ad2"}, + ref: "90f6ce7672f70f56708792a98d98bd05176c9176"}, {:restarter, path: "./restarter"}, {:majic, git: "https://akkoma.dev/AkkomaGang/majic.git", diff --git a/mix.lock b/mix.lock index bbc1d9d2b..28b7d5eac 100644 --- a/mix.lock +++ b/mix.lock @@ -7,7 +7,7 @@ "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, "cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"}, "calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.1.201603 or ~> 0.5.20 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"}, - "captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "3bbfa8b5ea13accc1b1c40579a380d8e5cfd6ad2", [ref: "3bbfa8b5ea13accc1b1c40579a380d8e5cfd6ad2"]}, + "captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "90f6ce7672f70f56708792a98d98bd05176c9176", [ref: "90f6ce7672f70f56708792a98d98bd05176c9176"]}, "castore": {:hex, :castore, "1.0.3", "7130ba6d24c8424014194676d608cb989f62ef8039efd50ff4b3f33286d06db8", [:mix], [], "hexpm", "680ab01ef5d15b161ed6a95449fac5c6b8f60055677a8e79acf01b27baa4390b"}, "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, diff --git a/test/pleroma/web/mastodon_api/update_credentials_test.exs b/test/pleroma/web/mastodon_api/update_credentials_test.exs index ebd536b0b..92a5726bf 100644 --- a/test/pleroma/web/mastodon_api/update_credentials_test.exs +++ b/test/pleroma/web/mastodon_api/update_credentials_test.exs @@ -91,7 +91,7 @@ test "updates the user's bio", %{conn: conn} do assert user_data = json_response_and_validate_schema(conn, 200) assert user_data["note"] == - ~s(I drink #cofe with @#{user2.nickname}

    suya..) + ~s(I drink #cofe with @#{user2.nickname}

    suya..) assert user_data["source"]["note"] == raw_bio -- 2.43.0 From 36f4f18aa548bfd2ffeeb1909c51dc9fe0f2e96c Mon Sep 17 00:00:00 2001 From: Rohan Kumar Date: Wed, 1 Nov 2023 17:47:52 -0700 Subject: [PATCH 043/149] Add more image mimetypes to reverse proxy Add JPEG-XL, AVIF, and WebP support to the reverse proxy. All three are supported in WebKit browsers; the latter two are supported in Gecko and Blink. --- lib/pleroma/reverse_proxy.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pleroma/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex index b44f0b90a..bb4f4def3 100644 --- a/lib/pleroma/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy.ex @@ -61,11 +61,14 @@ def default_cache_control_header, do: @default_cache_control_header """ @inline_content_types [ + "image/avif", "image/gif", "image/jpeg", "image/jpg", + "image/jxl", "image/png", "image/svg+xml", + "image/webp", "audio/mpeg", "audio/mp3", "video/webm", -- 2.43.0 From fb700a956a0fb8369270cd5a27a5ee31ab877c1a Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Thu, 2 Nov 2023 11:40:19 +0000 Subject: [PATCH 044/149] correct link --- docs/docs/configuration/howto_theming_your_instance.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/configuration/howto_theming_your_instance.md b/docs/docs/configuration/howto_theming_your_instance.md index 093c12763..e29143db8 100644 --- a/docs/docs/configuration/howto_theming_your_instance.md +++ b/docs/docs/configuration/howto_theming_your_instance.md @@ -60,7 +60,7 @@ Example of `my-awesome-theme.json` where we add the name "My Awesome Theme" ### Set as default theme -Now we can set the new theme as default in the [Pleroma FE configuration](https://docs-fe.akkoma.dev/stable/CONFIGURATION). +Now we can set the new theme as default in the [Pleroma FE configuration](https://docs-fe.akkoma.dev/stable/CONFIGURATION/). Example of adding the new theme in the back-end config files ```elixir -- 2.43.0 From 6cc523bd23528b6097d426f560a77b0316c3ac2a Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Thu, 2 Nov 2023 11:49:03 +0000 Subject: [PATCH 045/149] Correct email links to be absolute URLs --- lib/pleroma/emails/user_email.ex | 8 ++++---- test/pleroma/emails/user_email_test.exs | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/pleroma/emails/user_email.ex b/lib/pleroma/emails/user_email.ex index fe319c775..a7bcc03b6 100644 --- a/lib/pleroma/emails/user_email.ex +++ b/lib/pleroma/emails/user_email.ex @@ -74,7 +74,7 @@ def welcome(user, opts \\ %{}) do def password_reset_email(user, token) when is_binary(token) do Gettext.with_locale_or_default user.language do - password_reset_url = ~p[/api/v1/pleroma/password_reset/#{token}] + password_reset_url = url(~p[/api/v1/pleroma/password_reset/#{token}]) html_body = Gettext.dpgettext( @@ -107,7 +107,7 @@ def user_invitation_email( to_name \\ nil ) do Gettext.with_locale_or_default user.language do - registration_url = ~p[/registration/#{user_invite_token.token}] + registration_url = url(~p[/registration/#{user_invite_token.token}]) html_body = Gettext.dpgettext( @@ -140,7 +140,7 @@ def user_invitation_email( def account_confirmation_email(user) do Gettext.with_locale_or_default user.language do - confirmation_url = ~p[/api/account/confirm_email/#{user.id}/#{user.confirmation_token}] + confirmation_url = url(~p[/api/account/confirm_email/#{user.id}/#{user.confirmation_token}]) html_body = Gettext.dpgettext( @@ -330,7 +330,7 @@ def unsubscribe_url(user, notifications_type) do |> Pleroma.JWT.generate_and_sign!() |> Base.encode64() - ~p[/mailer/unsubscribe/#{token}] + url(~p[/mailer/unsubscribe/#{token}]) end def backup_is_ready_email(backup, admin_user_id \\ nil) do diff --git a/test/pleroma/emails/user_email_test.exs b/test/pleroma/emails/user_email_test.exs index 259efbb65..ee76dc45c 100644 --- a/test/pleroma/emails/user_email_test.exs +++ b/test/pleroma/emails/user_email_test.exs @@ -16,7 +16,7 @@ test "build password reset email" do assert email.from == {config[:name], config[:notify_email]} assert email.to == [{user.name, user.email}] assert email.subject == "Password reset" - assert email.html_body =~ ~p"/api/v1/pleroma/password_reset/test_token" + assert email.html_body =~ url(~p"/api/v1/pleroma/password_reset/test_token") end test "build user invitation email" do @@ -28,7 +28,7 @@ test "build user invitation email" do assert email.subject == "Invitation to Akkoma" assert email.to == [{"Jonh", "test@test.com"}] - assert email.html_body =~ ~p[/registration/#{token.token}] + assert email.html_body =~ url(~p[/registration/#{token.token}]) end test "build account confirmation email" do @@ -39,7 +39,7 @@ test "build account confirmation email" do assert email.to == [{user.name, user.email}] assert email.subject == "#{config[:name]} account confirmation" - assert email.html_body =~ ~p[/account/confirm_email/#{user.id}/conf-token] + assert email.html_body =~ url(~p[/api/account/confirm_email/#{user.id}/conf-token]) end test "build approval pending email" do -- 2.43.0 From 74d5e22fc534452f19982dcadf42a6b1d915f80c Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Fri, 15 Dec 2023 16:23:20 +0000 Subject: [PATCH 046/149] fix robotstxt on OTP --- docs/docs/administration/CLI_tasks/robots_txt.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/administration/CLI_tasks/robots_txt.md b/docs/docs/administration/CLI_tasks/robots_txt.md index 6cb9fd673..de27e5dca 100644 --- a/docs/docs/administration/CLI_tasks/robots_txt.md +++ b/docs/docs/administration/CLI_tasks/robots_txt.md @@ -11,7 +11,7 @@ If you want to generate a restrictive `robots.txt`, you can run the following mi === "OTP" ```sh - ./bin/pleroma_ctl robots_txt disallow_all + ./bin/pleroma_ctl robotstxt disallow_all ``` === "From Source" -- 2.43.0 From 8a0e797cff1e55d16c4e09207becb5d18760b38e Mon Sep 17 00:00:00 2001 From: Yonle Date: Fri, 15 Dec 2023 07:13:31 +0700 Subject: [PATCH 047/149] ap userview: add outbox field. Signed-off-by: Yonle --- changelog.d/add-outbox.fix | 1 + lib/pleroma/web/activity_pub/views/user_view.ex | 1 + 2 files changed, 2 insertions(+) create mode 100644 changelog.d/add-outbox.fix diff --git a/changelog.d/add-outbox.fix b/changelog.d/add-outbox.fix new file mode 100644 index 000000000..f3de5338d --- /dev/null +++ b/changelog.d/add-outbox.fix @@ -0,0 +1 @@ +ap userview: add outbox field. diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index d58e28c9f..d6d62a9a4 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -46,6 +46,7 @@ def render("service.json", %{user: user}) do "following" => "#{user.ap_id}/following", "followers" => "#{user.ap_id}/followers", "inbox" => "#{user.ap_id}/inbox", + "outbox" => "#{user.ap_id}/outbox", "name" => "Pleroma", "summary" => "An internal service actor for this Pleroma instance. No user-serviceable parts inside.", -- 2.43.0 From c3098e9c5636fbc12998cba0ba744a753a824483 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Fri, 15 Dec 2023 10:01:31 +0400 Subject: [PATCH 048/149] UserViewTest: Add basice service actor test. --- test/pleroma/web/activity_pub/views/user_view_test.exs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test/pleroma/web/activity_pub/views/user_view_test.exs b/test/pleroma/web/activity_pub/views/user_view_test.exs index 5501e64d6..ef1bd4fde 100644 --- a/test/pleroma/web/activity_pub/views/user_view_test.exs +++ b/test/pleroma/web/activity_pub/views/user_view_test.exs @@ -76,6 +76,15 @@ test "renders an invisible user with the invisible property set to true" do assert %{"invisible" => true} = UserView.render("service.json", %{user: user}) end + test "service has a few essential fields" do + user = insert(:user) + result = UserView.render("service.json", %{user: user}) + assert result["id"] + assert result["type"] == "Application" + assert result["inbox"] + assert result["outbox"] + end + test "renders AKAs" do akas = ["https://i.tusooa.xyz/users/test-pleroma"] user = insert(:user, also_known_as: akas) -- 2.43.0 From 2858cd81e11a3beaea3a5984e8e49d520e849669 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Fri, 15 Dec 2023 16:32:41 +0000 Subject: [PATCH 049/149] Move changelog into our format --- CHANGELOG.md | 1 + changelog.d/add-outbox.fix | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) delete mode 100644 changelog.d/add-outbox.fix diff --git a/CHANGELOG.md b/CHANGELOG.md index 26729a038..e2a3635f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Changed - OTP builds are now built on erlang OTP26 - The base Phoenix framework is now updated to 1.7 +- An `outbox` field has been added to actor profiles to comply with AP spec ## Fixed - Documentation issue in which a non-existing nginx file was referenced diff --git a/changelog.d/add-outbox.fix b/changelog.d/add-outbox.fix deleted file mode 100644 index f3de5338d..000000000 --- a/changelog.d/add-outbox.fix +++ /dev/null @@ -1 +0,0 @@ -ap userview: add outbox field. -- 2.43.0 From 6fb91d79f37736ce1b3ac2ff1217f9a71bfcd44e Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Fri, 15 Dec 2023 16:32:53 +0000 Subject: [PATCH 050/149] bump deps --- mix.exs | 2 +- mix.lock | 84 ++++++++++++++++++++++++++++---------------------------- 2 files changed, 43 insertions(+), 43 deletions(-) diff --git a/mix.exs b/mix.exs index 16f867c4f..2789a46e7 100644 --- a/mix.exs +++ b/mix.exs @@ -148,7 +148,7 @@ defp deps do {:argon2_elixir, "~> 3.1"}, {:cors_plug, "~> 3.0"}, {:web_push_encryption, "~> 0.3.1"}, - {:swoosh, "~> 1.11"}, + {:swoosh, "~> 1.14.2"}, # for gmail adapter in swoosh {:mail, ">= 0.0.0"}, {:phoenix_swoosh, "~> 1.2"}, diff --git a/mix.lock b/mix.lock index 28b7d5eac..501ed8b0a 100644 --- a/mix.lock +++ b/mix.lock @@ -1,46 +1,46 @@ %{ - "argon2_elixir": {:hex, :argon2_elixir, "3.1.0", "4135e0a1b4ff800d42c85aa663e068efa3cb356297189b5b65caa992db8ec8cf", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "c08feae0ee0292165d1b945003363c7cd8523d002e0483c627dfca930291dd73"}, + "argon2_elixir": {:hex, :argon2_elixir, "3.2.1", "f47740bf9f2a39ffef79ba48eb25dea2ee37bcc7eadf91d49615591d1a6fce1a", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "a813b78217394530b5fcf4c8070feee43df03ffef938d044019169c766315690"}, "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"}, "bcrypt_elixir": {:hex, :bcrypt_elixir, "3.0.1", "9be815469e6bfefec40fa74658ecbbe6897acfb57614df1416eeccd4903f602c", [:make, :mix], [{:comeonin, "~> 5.3", [hex: :comeonin, repo: "hexpm", optional: false]}, {:elixir_make, "~> 0.6", [hex: :elixir_make, repo: "hexpm", optional: false]}], "hexpm", "486bb95efb645d1efc6794c1ddd776a186a9a713abf06f45708a6ce324fb96cf"}, - "benchee": {:hex, :benchee, "1.1.0", "f3a43817209a92a1fade36ef36b86e1052627fd8934a8b937ac9ab3a76c43062", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}], "hexpm", "7da57d545003165a012b587077f6ba90b89210fd88074ce3c60ce239eb5e6d93"}, + "benchee": {:hex, :benchee, "1.2.0", "afd2f0caec06ce3a70d9c91c514c0b58114636db9d83c2dc6bfd416656618353", [:mix], [{:deep_merge, "~> 1.0", [hex: :deep_merge, repo: "hexpm", optional: false]}, {:statistex, "~> 1.0", [hex: :statistex, repo: "hexpm", optional: false]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "ee729e53217898b8fd30aaad3cce61973dab61574ae6f48229fe7ff42d5e4457"}, "bunt": {:hex, :bunt, "0.2.1", "e2d4792f7bc0ced7583ab54922808919518d0e57ee162901a16a1b6664ef3b14", [:mix], [], "hexpm", "a330bfb4245239787b15005e66ae6845c9cd524a288f0d141c148b02603777a5"}, "cachex": {:hex, :cachex, "3.6.0", "14a1bfbeee060dd9bec25a5b6f4e4691e3670ebda28c8ba2884b12fe30b36bf8", [:mix], [{:eternal, "~> 1.2", [hex: :eternal, repo: "hexpm", optional: false]}, {:jumper, "~> 1.0", [hex: :jumper, repo: "hexpm", optional: false]}, {:sleeplocks, "~> 1.1", [hex: :sleeplocks, repo: "hexpm", optional: false]}, {:unsafe, "~> 1.0", [hex: :unsafe, repo: "hexpm", optional: false]}], "hexpm", "ebf24e373883bc8e0c8d894a63bbe102ae13d918f790121f5cfe6e485cc8e2e2"}, "calendar": {:hex, :calendar, "1.0.0", "f52073a708528482ec33d0a171954ca610fe2bd28f1e871f247dc7f1565fa807", [:mix], [{:tzdata, "~> 0.1.201603 or ~> 0.5.20 or ~> 1.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "990e9581920c82912a5ee50e62ff5ef96da6b15949a2ee4734f935fdef0f0a6f"}, "captcha": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", "90f6ce7672f70f56708792a98d98bd05176c9176", [ref: "90f6ce7672f70f56708792a98d98bd05176c9176"]}, - "castore": {:hex, :castore, "1.0.3", "7130ba6d24c8424014194676d608cb989f62ef8039efd50ff4b3f33286d06db8", [:mix], [], "hexpm", "680ab01ef5d15b161ed6a95449fac5c6b8f60055677a8e79acf01b27baa4390b"}, - "certifi": {:hex, :certifi, "2.9.0", "6f2a475689dd47f19fb74334859d460a2dc4e3252a3324bd2111b8f0429e7e21", [:rebar3], [], "hexpm", "266da46bdb06d6c6d35fde799bcb28d36d985d424ad7c08b5bb48f5b5cdd4641"}, + "castore": {:hex, :castore, "1.0.5", "9eeebb394cc9a0f3ae56b813459f990abb0a3dedee1be6b27fdb50301930502f", [:mix], [], "hexpm", "8d7c597c3e4a64c395980882d4bca3cebb8d74197c590dc272cfd3b6a6310578"}, + "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, - "comeonin": {:hex, :comeonin, "5.3.3", "2c564dac95a35650e9b6acfe6d2952083d8a08e4a89b93a481acb552b325892e", [:mix], [], "hexpm", "3e38c9c2cb080828116597ca8807bb482618a315bfafd98c90bc22a821cc84df"}, + "comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"}, "concurrent_limiter": {:git, "https://akkoma.dev/AkkomaGang/concurrent-limiter.git", "a9e0b3d64574bdba761f429bb4fba0cf687b3338", [ref: "a9e0b3d64574bdba761f429bb4fba0cf687b3338"]}, "connection": {:hex, :connection, "1.1.0", "ff2a49c4b75b6fb3e674bfc5536451607270aac754ffd1bdfe175abe4a6d7a68", [:mix], [], "hexpm", "722c1eb0a418fbe91ba7bd59a47e28008a189d47e37e0e7bb85585a016b2869c"}, "cors_plug": {:hex, :cors_plug, "3.0.3", "7c3ac52b39624bc616db2e937c282f3f623f25f8d550068b6710e58d04a0e330", [:mix], [{:plug, "~> 1.13", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3f2d759e8c272ed3835fab2ef11b46bddab8c1ab9528167bd463b6452edf830d"}, "cowboy": {:hex, :cowboy, "2.10.0", "ff9ffeff91dae4ae270dd975642997afe2a1179d94b1887863e43f681a203e26", [:make, :rebar3], [{:cowlib, "2.12.1", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "3afdccb7183cc6f143cb14d3cf51fa00e53db9ec80cdcd525482f5e99bc41d6b"}, "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.12.1", "a9fa9a625f1d2025fe6b462cb865881329b5caff8f1854d1cbc9f9533f00e1e1", [:make, :rebar3], [], "hexpm", "163b73f6367a7341b33c794c4e88e7dbfe6498ac42dcd69ef44c5bc5507c8db0"}, - "credo": {:hex, :credo, "1.7.0", "6119bee47272e85995598ee04f2ebbed3e947678dee048d10b5feca139435f75", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "6839fcf63d1f0d1c0f450abc8564a57c43d644077ab96f2934563e68b8a769d7"}, + "credo": {:hex, :credo, "1.7.1", "6e26bbcc9e22eefbff7e43188e69924e78818e2fe6282487d0703652bc20fd62", [:mix], [{:bunt, "~> 0.2.1", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2.8", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "e9871c6095a4c0381c89b6aa98bc6260a8ba6addccf7f6a53da8849c748a58a2"}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, - "db_connection": {:hex, :db_connection, "2.5.0", "bb6d4f30d35ded97b29fe80d8bd6f928a1912ca1ff110831edcd238a1973652c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c92d5ba26cd69ead1ff7582dbb860adeedfff39774105a4f1c92cbb654b55aa2"}, + "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"}, - "dialyxir": {:hex, :dialyxir, "1.3.0", "fd1672f0922b7648ff9ce7b1b26fcf0ef56dda964a459892ad15f6b4410b5284", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "00b2a4bcd6aa8db9dcb0b38c1225b7277dca9bc370b6438715667071a304696f"}, - "earmark": {:hex, :earmark, "1.4.39", "acdb2f02c536471029dbcc509fbd6b94b89f40ad7729fb3f68f4b6944843f01d", [:mix], [{:earmark_parser, "~> 1.4.33", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "156c9d8ec3cbeccdbf26216d8247bdeeacc8c76b4d9eee7554be2f1b623ea440"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.33", "3c3fd9673bb5dcc9edc28dd90f50c87ce506d1f71b70e3de69aa8154bc695d44", [:mix], [], "hexpm", "2d526833729b59b9fdb85785078697c72ac5e5066350663e5be6a1182da61b8f"}, + "dialyxir": {:hex, :dialyxir, "1.4.2", "764a6e8e7a354f0ba95d58418178d486065ead1f69ad89782817c296d0d746a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "516603d8067b2fd585319e4b13d3674ad4f314a5902ba8130cd97dc902ce6bbd"}, + "earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, "eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"}, "ecto": {:hex, :ecto, "3.10.3", "eb2ae2eecd210b4eb8bece1217b297ad4ff824b4384c0e3fdd28aaf96edd6135", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "44bec74e2364d491d70f7e42cd0d690922659d329f6465e89feb8a34e8cd3433"}, "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"}, - "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.12", "e3bd8318702b069263d0118e7cdb6c66c5ff0a034f540f4c0158bd769e7dff6a", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "4a1d1d10b74ce033a428a99272038c90e444a0a1912a074e38a71ee9f667714c"}, - "ecto_sql": {:hex, :ecto_sql, "3.10.1", "6ea6b3036a0b0ca94c2a02613fd9f742614b5cfe494c41af2e6571bb034dd94c", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f6a25bdbbd695f12c8171eaff0851fa4c8e72eec1e98c7364402dda9ce11c56b"}, + "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.14", "7a20cfe913b0476542b43870e67386461258734896035e3f284039fd18bd4c4c", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "22f5f98592dd597db9416fcef00effae0787669fdcb6faf447e982b553798e98"}, + "ecto_sql": {:hex, :ecto_sql, "3.10.2", "6b98b46534b5c2f8b8b5f03f126e75e2a73c64f3c071149d32987a5378b0fdbd", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.10.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "68c018debca57cb9235e3889affdaec7a10616a4e3a80c99fa1d01fdafaa9007"}, "elasticsearch": {:git, "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", "6cd946f75f6ab9042521a009d1d32d29a90113ca", [ref: "main"]}, "elixir_make": {:hex, :elixir_make, "0.7.7", "7128c60c2476019ed978210c245badf08b03dbec4f24d05790ef791da11aa17c", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}], "hexpm", "5bc19fff950fad52bbe5f211b12db9ec82c6b34a9647da0c2224b8b8464c7e6c"}, "elixir_xml_to_map": {:hex, :elixir_xml_to_map, "3.0.0", "67dcff30ecf72aed37ab08525133e4420717a749436e22bfece431e7dddeea7e", [:mix], [{:erlsom, "~> 1.4", [hex: :erlsom, repo: "hexpm", optional: false]}], "hexpm", "11222dd7f029f8db7a6662b41c992dbdb0e1c6e4fdea6a42056f9d27c847efbb"}, "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, "erlsom": {:hex, :erlsom, "1.5.1", "c8fe2babd33ff0846403f6522328b8ab676f896b793634cfe7ef181c05316c03", [:rebar3], [], "hexpm", "7965485494c5844dd127656ac40f141aadfa174839ec1be1074e7edf5b4239eb"}, "eternal": {:hex, :eternal, "1.2.2", "d1641c86368de99375b98d183042dd6c2b234262b8d08dfd72b9eeaafc2a1abd", [:mix], [], "hexpm", "2c9fe32b9c3726703ba5e1d43a1d255a4f3f2d8f8f9bc19f094c7cb1a7a9e782"}, - "ex_aws": {:hex, :ex_aws, "2.4.4", "d7886eaca7e10f7bd3d9e9d2d5414cb336737b3ab2fddd4fa30358b725293fe0", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a7d63e485ca2b16fb804f3f20097827aa69885eea6e69fa75c98f353c9c91dc7"}, - "ex_aws_s3": {:hex, :ex_aws_s3, "2.4.0", "ce8decb6b523381812798396bc0e3aaa62282e1b40520125d1f4eff4abdff0f4", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "85dda6e27754d94582869d39cba3241d9ea60b6aa4167f9c88e309dc687e56bb"}, + "ex_aws": {:hex, :ex_aws, "2.5.0", "1785e69350b16514c1049330537c7da10039b1a53e1d253bbd703b135174aec3", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 2.8 or ~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:mime, "~> 1.2 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:sweet_xml, "~> 0.7", [hex: :sweet_xml, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "971b86e5495fc0ae1c318e35e23f389e74cf322f2c02d34037c6fc6d405006f1"}, + "ex_aws_s3": {:hex, :ex_aws_s3, "2.5.2", "cee302b8e9ee198cc0d89f1de2a7d6a8921e1a556574476cf5590d2156590fe3", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "cc5bd945a22a99eece4721d734ae2452d3717e81c357a781c8574663254df4a1"}, "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"}, - "ex_doc": {:hex, :ex_doc, "0.30.5", "aa6da96a5c23389d7dc7c381eba862710e108cee9cfdc629b7ec021313900e9e", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "88a1e115dcb91cefeef7e22df4a6ebbe4634fbf98b38adcbc25c9607d6d9d8e6"}, + "ex_doc": {:hex, :ex_doc, "0.31.0", "06eb1dfd787445d9cab9a45088405593dd3bb7fe99e097eaa71f37ba80c7a676", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.1", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "5350cafa6b7f77bdd107aa2199fe277acf29d739aba5aee7e865fc680c62a110"}, "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, "ex_syslogger": {:hex, :ex_syslogger, "2.0.0", "de6de5c5472a9c4fdafb28fa6610e381ae79ebc17da6490b81d785d68bd124c9", [:mix], [{:jason, "~> 1.2", [hex: :jason, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "a52b2fe71764e9e6ecd149ab66635812f68e39279cbeee27c52c0e35e8b8019e"}, "excoveralls": {:hex, :excoveralls, "0.16.1", "0bd42ed05c7d2f4d180331a20113ec537be509da31fed5c8f7047ce59ee5a7c5", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "dae763468e2008cf7075a64cb1249c97cb4bc71e236c5c2b5e5cdf1cfa2bf138"}, @@ -51,10 +51,10 @@ "file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"}, "finch": {:hex, :finch, "0.16.0", "40733f02c89f94a112518071c0a91fe86069560f5dbdb39f9150042f44dcfb1a", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f660174c4d519e5fec629016054d60edd822cdfe2b7270836739ac2f97735ec5"}, "flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"}, - "floki": {:hex, :floki, "0.34.3", "5e2dcaec5d7c228ce5b1d3501502e308b2d79eb655e4191751a1fe491c37feac", [:mix], [], "hexpm", "9577440eea5b97924b4bf3c7ea55f7b8b6dce589f9b28b096cc294a8dc342341"}, + "floki": {:hex, :floki, "0.35.2", "87f8c75ed8654b9635b311774308b2760b47e9a579dabf2e4d5f1e1d42c39e0b", [:mix], [], "hexpm", "6b05289a8e9eac475f644f09c2e4ba7e19201fd002b89c28c1293e7bd16773d9"}, "gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"}, "gettext": {:hex, :gettext, "0.22.3", "c8273e78db4a0bb6fba7e9f0fd881112f349a3117f7f7c598fa18c66c888e524", [:mix], [{:expo, "~> 0.4.0", [hex: :expo, repo: "hexpm", optional: false]}], "hexpm", "935f23447713954a6866f1bb28c3a878c4c011e802bcd68a726f5e558e4b64bd"}, - "hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~> 2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"}, + "hackney": {:hex, :hackney, "1.20.1", "8d97aec62ddddd757d128bfd1df6c5861093419f8f7a4223823537bad5d064e2", [:rebar3], [{:certifi, "~> 2.12.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~> 6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~> 1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~> 1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.4.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~> 1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "fe9094e5f1a2a2c0a7d10918fee36bfec0ec2a979994cff8cfe8058cd9af38e3"}, "hpax": {:hex, :hpax, "0.1.2", "09a75600d9d8bbd064cdd741f21fc06fc1f4cf3d0fcc335e5aa19be1a7235c84", [:mix], [], "hexpm", "2c87843d5a23f5f16748ebe77969880e29809580efdaccd615cd3bed628a8c13"}, "html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"}, "http_signatures": {:git, "https://akkoma.dev/AkkomaGang/http_signatures.git", "6640ce7d24c783ac2ef56e27d00d12e8dc85f396", [ref: "6640ce7d24c783ac2ef56e27d00d12e8dc85f396"]}, @@ -64,54 +64,54 @@ "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, "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.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"}, + "jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"}, "linkify": {:git, "https://akkoma.dev/AkkomaGang/linkify.git", "2567e2c1073fa371fd26fd66dfa5bc77b6919c16", []}, "mail": {:hex, :mail, "0.3.1", "cb0a14e4ed8904e4e5a08214e686ccf6f9099346885db17d8c309381f865cc5c", [:mix], [], "hexpm", "1db701e89865c1d5fa296b2b57b1cd587587cca8d8a1a22892b35ef5a8e352a6"}, "majic": {:git, "https://akkoma.dev/AkkomaGang/majic.git", "80540b36939ec83f48e76c61e5000e0fd67706f0", [ref: "80540b36939ec83f48e76c61e5000e0fd67706f0"]}, - "makeup": {:hex, :makeup, "1.1.0", "6b67c8bc2882a6b6a445859952a602afc1a41c2e08379ca057c0f525366fc3ca", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "0a45ed501f4a8897f580eabf99a2e5234ea3e75a4373c8a52824f6e873be57a6"}, + "makeup": {:hex, :makeup, "1.1.1", "fa0bc768698053b2b3869fa8a62616501ff9d11a562f3ce39580d60860c3a55e", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "5dc62fbdd0de44de194898b6710692490be74baa02d9d108bc29f007783b0b48"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.1", "cc9e3ca312f1cfeccc572b37a09980287e243648108384b97ff2b76e505c3555", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "e127a341ad1b209bd80f7bd1620a15693a9908ed780c3b763bccf7d200c767c6"}, - "makeup_erlang": {:hex, :makeup_erlang, "0.1.2", "ad87296a092a46e03b7e9b0be7631ddcf64c790fa68a9ef5323b6cbb36affc72", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "f3f5a1ca93ce6e092d92b6d9c049bcda58a3b617a8d888f8e7231c85630e8108"}, + "makeup_erlang": {:hex, :makeup_erlang, "0.1.3", "d684f4bac8690e70b06eb52dad65d26de2eefa44cd19d64a8095e1417df7c8fd", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "b78dc853d2e670ff6390b605d807263bf606da3c82be37f9d7f68635bd886fc9"}, "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "mfm_parser": {:git, "https://akkoma.dev/AkkomaGang/mfm-parser.git", "b21ab7754024af096f2d14247574f55f0063295b", [ref: "b21ab7754024af096f2d14247574f55f0063295b"]}, "mime": {:hex, :mime, "2.0.5", "dc34c8efd439abe6ae0343edbb8556f4d63f178594894720607772a041b04b02", [:mix], [], "hexpm", "da0d64a365c45bc9935cc5c8a7fc5e49a0e0f9932a761c55d6c52b142780a05c"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, - "mint": {:hex, :mint, "1.5.1", "8db5239e56738552d85af398798c80648db0e90f343c8469f6c6d8898944fb6f", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "4a63e1e76a7c3956abd2c72f370a0d0aecddc3976dea5c27eccbecfa5e7d5b1e"}, + "mint": {:hex, :mint, "1.5.2", "4805e059f96028948870d23d7783613b7e6b0e2fb4e98d720383852a760067fd", [:mix], [{:castore, "~> 0.1.0 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "d77d9e9ce4eb35941907f1d3df38d8f750c357865353e21d335bdcdf6d892a02"}, "mock": {:hex, :mock, "0.3.8", "7046a306b71db2488ef54395eeb74df0a7f335a7caca4a3d3875d1fc81c884dd", [:mix], [{:meck, "~> 0.9.2", [hex: :meck, repo: "hexpm", optional: false]}], "hexpm", "7fa82364c97617d79bb7d15571193fc0c4fe5afd0c932cef09426b3ee6fe2022"}, "mogrify": {:hex, :mogrify, "0.9.3", "238c782f00271dace01369ad35ae2e9dd020feee3443b9299ea5ea6bed559841", [:mix], [], "hexpm", "0189b1e1de27455f2b9ae8cf88239cefd23d38de9276eb5add7159aea51731e6"}, - "mox": {:hex, :mox, "1.0.2", "dc2057289ac478b35760ba74165b4b3f402f68803dd5aecd3bfd19c183815d64", [:mix], [], "hexpm", "f9864921b3aaf763c8741b5b8e6f908f44566f1e427b2630e89e9a73b981fef2"}, - "nimble_options": {:hex, :nimble_options, "1.0.2", "92098a74df0072ff37d0c12ace58574d26880e522c22801437151a159392270e", [:mix], [], "hexpm", "fd12a8db2021036ce12a309f26f564ec367373265b53e25403f0ee697380f1b8"}, - "nimble_parsec": {:hex, :nimble_parsec, "1.3.1", "2c54013ecf170e249e9291ed0a62e5832f70a476c61da16f6aac6dca0189f2af", [:mix], [], "hexpm", "2682e3c0b2eb58d90c6375fc0cc30bc7be06f365bf72608804fb9cffa5e1b167"}, + "mox": {:hex, :mox, "1.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"}, + "nimble_options": {:hex, :nimble_options, "1.1.0", "3b31a57ede9cb1502071fade751ab0c7b8dbe75a9a4c2b5bbb0943a690b63172", [:mix], [], "hexpm", "8bbbb3941af3ca9acc7835f5655ea062111c9c27bcac53e004460dfd19008a99"}, + "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "nimble_pool": {:hex, :nimble_pool, "1.0.0", "5eb82705d138f4dd4423f69ceb19ac667b3b492ae570c9f5c900bb3d2f50a847", [:mix], [], "hexpm", "80be3b882d2d351882256087078e1b1952a28bf98d0a287be87e4a24a710b67a"}, "oban": {:hex, :oban, "2.15.4", "d49ab4ffb7153010e32f80fe9e56f592706238149ec579eb50f8a4e41d218856", [:mix], [{:ecto_sql, "~> 3.6", [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", "5fce611fdfffb13e9148df883116e5201adf1e731eb302cc88cde0588510079c"}, - "open_api_spex": {:hex, :open_api_spex, "3.17.3", "ada8e352eb786050dd639db2439d3316e92f3798eb2abd051f55bb9af825b37e", [: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", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "165db21a85ca83cffc8e7c8890f35b354eddda8255de7404a2848ed652b9f0fe"}, - "parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"}, - "phoenix": {:hex, :phoenix, "1.7.7", "4cc501d4d823015007ba3cdd9c41ecaaf2ffb619d6fb283199fa8ddba89191e0", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "8966e15c395e5e37591b6ed0bd2ae7f48e961f0f60ac4c733f9566b519453085"}, - "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.2", "b21bd01fdeffcfe2fab49e4942aa938b6d3e89e93a480d4aee58085560a0bc0d", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "70242edd4601d50b69273b057ecf7b684644c19ee750989fd555625ae4ce8f5d"}, - "phoenix_html": {:hex, :phoenix_html, "3.3.2", "d6ce982c6d8247d2fc0defe625255c721fb8d5f1942c5ac051f6177bffa5973f", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "44adaf8e667c1c20fb9d284b6b0fa8dc7946ce29e81ce621860aa7e96de9a11d"}, + "open_api_spex": {:hex, :open_api_spex, "3.18.0", "f9952b6bc8a1bf14168f3754981b7c8d72d015112bfedf2588471dd602e1e715", [: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", "37849887ab67efab052376401fac28c0974b273ffaecd98f4532455ca0886464"}, + "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, + "phoenix": {:hex, :phoenix, "1.7.10", "02189140a61b2ce85bb633a9b6fd02dff705a5f1596869547aeb2b2b95edd729", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:websock_adapter, "~> 0.5.3", [hex: :websock_adapter, repo: "hexpm", optional: false]}], "hexpm", "cf784932e010fd736d656d7fead6a584a4498efefe5b8227e9f383bf15bb79d0"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.3", "86e9878f833829c3f66da03d75254c155d91d72a201eb56ae83482328dc7ca93", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "d36c401206f3011fefd63d04e8ef626ec8791975d9d107f9a0817d426f61ac07"}, + "phoenix_html": {:hex, :phoenix_html, "3.3.3", "380b8fb45912b5638d2f1d925a3771b4516b9a78587249cabe394e0a5d579dc9", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "923ebe6fec6e2e3b3e569dfbdc6560de932cd54b000ada0208b5f45024bdd76c"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"}, "phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.3", "3168d78ba41835aecad272d5e8cd51aa87a7ac9eb836eabc42f6e57538e3731d", [:mix], [], "hexpm", "bba06bc1dcfd8cb086759f0edc94a8ba2bc8896d5331a1e2c2902bf8e36ee502"}, "phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.0", "a544d83fde4a767efb78f45404a74c9e37b2a9c5ea3339692e65a6966731f935", [:mix], [{:finch, "~> 0.8", [hex: :finch, repo: "hexpm", optional: true]}, {:hackney, "~> 1.10", [hex: :hackney, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6", [hex: :phoenix, repo: "hexpm", optional: true]}, {:phoenix_html, "~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_view, "~> 1.0 or ~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.5", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "e88d117251e89a16b92222415a6d87b99a96747ddf674fc5c7631de734811dba"}, "phoenix_template": {:hex, :phoenix_template, "1.0.3", "32de561eefcefa951aead30a1f94f1b5f0379bc9e340bb5c667f65f1edfa4326", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "16f4b6588a4152f3cc057b9d0c0ba7e82ee23afa65543da535313ad8d25d8e2c"}, - "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, - "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"}, + "phoenix_view": {:hex, :phoenix_view, "2.0.3", "4d32c4817fce933693741deeb99ef1392619f942633dde834a5163124813aad3", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "cd34049af41be2c627df99cd4eaa71fc52a328c0c3d8e7d4aa28f880c30e7f64"}, + "plug": {:hex, :plug, "1.15.2", "94cf1fa375526f30ff8770837cb804798e0045fd97185f0bb9e5fcd858c792a3", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2 or ~> 2.0", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "02731fa0c2dcb03d8d21a1d941bdbbe99c2946c0db098eee31008e04c6283615"}, "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"}, - "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, + "plug_crypto": {:hex, :plug_crypto, "2.0.0", "77515cc10af06645abbfb5e6ad7a3e9714f805ae118fa1a70205f80d2d70fe73", [:mix], [], "hexpm", "53695bae57cc4e54566d993eb01074e4d894b65a3766f1c43e2c61a1b0f45ea9"}, "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"}, "poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, - "postgrex": {:hex, :postgrex, "0.17.2", "a3ec9e3239d9b33f1e5841565c4eb200055c52cc0757a22b63ca2d529bbe764c", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "80a918a9e9531d39f7bd70621422f3ebc93c01618c645f2d91306f50041ed90c"}, + "postgrex": {:hex, :postgrex, "0.17.4", "5777781f80f53b7c431a001c8dad83ee167bcebcf3a793e3906efff680ab62b3", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "6458f7d5b70652bc81c3ea759f91736c16a31be000f306d3c64bcdfe9a18b3cc"}, "pot": {:hex, :pot, "1.0.2", "13abb849139fdc04ab8154986abbcb63bdee5de6ed2ba7e1713527e33df923dd", [:rebar3], [], "hexpm", "78fe127f5a4f5f919d6ea5a2a671827bd53eb9d37e5b4128c0ad3df99856c2e0"}, "ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"}, - "recon": {:hex, :recon, "2.5.3", "739107b9050ea683c30e96de050bc59248fd27ec147696f79a8797ff9fa17153", [:mix, :rebar3], [], "hexpm", "6c6683f46fd4a1dfd98404b9f78dcabc7fcd8826613a89dcb984727a8c3099d7"}, + "recon": {:hex, :recon, "2.5.4", "05dd52a119ee4059fa9daa1ab7ce81bc7a8161a2f12e9d42e9d551ffd2ba901c", [:mix, :rebar3], [], "hexpm", "e9ab01ac7fc8572e41eb59385efeb3fb0ff5bf02103816535bacaedf327d0263"}, "remote_ip": {:hex, :remote_ip, "1.1.0", "cb308841595d15df3f9073b7c39243a1dd6ca56e5020295cb012c76fbec50f2d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "616ffdf66aaad6a72fc546dabf42eed87e2a99e97b09cbd92b10cc180d02ed74"}, "search_parser": {:git, "https://github.com/FloatingGhost/pleroma-contrib-search-parser.git", "08971a81e68686f9ac465cfb6661d51c5e4e1e7f", [ref: "08971a81e68686f9ac465cfb6661d51c5e4e1e7f"]}, "sleeplocks": {:hex, :sleeplocks, "1.1.2", "d45aa1c5513da48c888715e3381211c859af34bee9b8290490e10c90bb6ff0ca", [:rebar3], [], "hexpm", "9fe5d048c5b781d6305c1a3a0f40bb3dfc06f49bf40571f3d2d0c57eaa7f59a5"}, "ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.7", "354c321cf377240c7b8716899e182ce4890c5938111a1296add3ec74cf1715df", [:make, :mix, :rebar3], [], "hexpm", "fe4c190e8f37401d30167c8c405eda19469f34577987c76dde613e838bbc67f8"}, "statistex": {:hex, :statistex, "1.0.0", "f3dc93f3c0c6c92e5f291704cf62b99b553253d7969e9a5fa713e5481cd858a5", [:mix], [], "hexpm", "ff9d8bee7035028ab4742ff52fc80a2aa35cece833cf5319009b52f1b5a86c27"}, - "sweet_xml": {:hex, :sweet_xml, "0.7.3", "debb256781c75ff6a8c5cbf7981146312b66f044a2898f453709a53e5031b45b", [:mix], [], "hexpm", "e110c867a1b3fe74bfc7dd9893aa851f0eed5518d0d7cad76d7baafd30e4f5ba"}, - "swoosh": {:hex, :swoosh, "1.11.5", "429dccde78e2f60c6339e96917efecebca9d1f254d2878a150f580d2f782260b", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "21ee57dcd68d2f56d3bbe11e76d56d142b221bb12b6018c551cc68442b800040"}, + "sweet_xml": {:hex, :sweet_xml, "0.7.4", "a8b7e1ce7ecd775c7e8a65d501bc2cd933bff3a9c41ab763f5105688ef485d08", [:mix], [], "hexpm", "e7c4b0bdbf460c928234951def54fe87edf1a170f6896675443279e2dbeba167"}, + "swoosh": {:hex, :swoosh, "1.14.2", "cf686f92ad3b21e6651b20c50eeb1781f581dc7097ef6251b4d322a9f1d19339", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: true]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:req, "~> 0.4 or ~> 1.0", [hex: :req, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "01d8fae72930a0b5c1bb9725df0408602ed8c5c3d59dc6e7a39c57b723cd1065"}, "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"}, "table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"}, "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, @@ -120,16 +120,16 @@ "telemetry_metrics_prometheus_core": {:hex, :telemetry_metrics_prometheus_core, "1.1.0", "4e15f6d7dbedb3a4e3aed2262b7e1407f166fcb9c30ca3f96635dfbbef99965c", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0dd10e7fe8070095df063798f82709b0a1224c31b8baf6278b423898d591a069"}, "telemetry_poller": {:hex, :telemetry_poller, "1.0.0", "db91bb424e07f2bb6e73926fcafbfcbcb295f0193e0a00e825e589a0a47e8453", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "b3a24eafd66c3f42da30fc3ca7dda1e9d546c12250a2d60d7b81d264fbec4f6e"}, "temple": {:git, "https://akkoma.dev/AkkomaGang/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]}, - "tesla": {:hex, :tesla, "1.7.0", "a62dda2f80d4f8a925eb7b8c5b78c461e0eb996672719fe1a63b26321a5f8b4e", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "2e64f01ebfdb026209b47bc651a0e65203fcff4ae79c11efb73c4852b00dc313"}, + "tesla": {:hex, :tesla, "1.8.0", "d511a4f5c5e42538d97eef7c40ec4f3e44effdc5068206f42ed859e09e51d1fd", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.13", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, ">= 1.0.0", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.2", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:msgpax, "~> 2.3", [hex: :msgpax, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "10501f360cd926a309501287470372af1a6e1cbed0f43949203a4c13300bc79f"}, "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"}, "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"}, "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, - "ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"}, + "ueberauth": {:hex, :ueberauth, "0.10.6", "8dbefd5aec30c5830af2b6ce6e03f62cc28ae0757f34e2986454f54b8dca3f65", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b0ad1c7508f3cfd5c2c1c668d1a32bafd77de4c56af82c7bfd7e54ed078a7928"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, - "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"}, - "vex": {:hex, :vex, "0.9.0", "613ea5eb3055662e7178b83e25b2df0975f68c3d8bb67c1645f0573e1a78d606", [:mix], [], "hexpm", "c69fff44d5c8aa3f1faee71bba1dcab05dd36364c5a629df8bb11751240c857f"}, + "unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"}, + "vex": {:hex, :vex, "0.9.1", "cb65348ebd1c4002861b65bef36e524c29d9a879c90119b2d0e674e323124277", [:mix], [], "hexpm", "a0f9f3959d127ad6a6a617c3f607ecfb1bc6f3c59f9c3614a901a46d1765bafe"}, "web_push_encryption": {:hex, :web_push_encryption, "0.3.1", "76d0e7375142dfee67391e7690e89f92578889cbcf2879377900b5620ee4708d", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.1", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2"}, - "websock": {:hex, :websock, "0.5.2", "b3c08511d8d79ed2c2f589ff430bd1fe799bb389686dafce86d28801783d8351", [:mix], [], "hexpm", "925f5de22fca6813dfa980fb62fd542ec43a2d1a1f83d2caec907483fe66ff05"}, - "websock_adapter": {:hex, :websock_adapter, "0.5.3", "4908718e42e4a548fc20e00e70848620a92f11f7a6add8cf0886c4232267498d", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "cbe5b814c1f86b6ea002b52dd99f345aeecf1a1a6964e209d208fb404d930d3d"}, + "websock": {:hex, :websock, "0.5.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.5", "9dfeee8269b27e958a65b3e235b7e447769f66b5b5925385f5a569269164a210", [:mix], [{:bandit, ">= 0.6.0", [hex: :bandit, repo: "hexpm", optional: true]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.6", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:websock, "~> 0.5", [hex: :websock, repo: "hexpm", optional: false]}], "hexpm", "4b977ba4a01918acbf77045ff88de7f6972c2a009213c515a445c48f224ffce9"}, "websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"}, } -- 2.43.0 From e2f749b5b020bafb0362ce98f37396ab6045fc05 Mon Sep 17 00:00:00 2001 From: Aria Date: Sun, 17 Dec 2023 18:56:15 +0000 Subject: [PATCH 051/149] don't select ueberauth 0.10.6, as it is broken see https://github.com/ueberauth/ueberauth/issues/194 --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 2789a46e7..23f1761b5 100644 --- a/mix.exs +++ b/mix.exs @@ -156,7 +156,7 @@ defp deps do {:ex_syslogger, "~> 2.0.0"}, {:floki, "~> 0.34"}, {:timex, "~> 3.7"}, - {:ueberauth, "~> 0.10"}, + {:ueberauth, "== 0.10.5"}, {:linkify, git: "https://akkoma.dev/AkkomaGang/linkify.git"}, {:http_signatures, git: "https://akkoma.dev/AkkomaGang/http_signatures.git", diff --git a/mix.lock b/mix.lock index 501ed8b0a..810356345 100644 --- a/mix.lock +++ b/mix.lock @@ -124,7 +124,7 @@ "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"}, "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"}, "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, - "ueberauth": {:hex, :ueberauth, "0.10.6", "8dbefd5aec30c5830af2b6ce6e03f62cc28ae0757f34e2986454f54b8dca3f65", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "b0ad1c7508f3cfd5c2c1c668d1a32bafd77de4c56af82c7bfd7e54ed078a7928"}, + "ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"}, "unicode_util_compat": {:hex, :unicode_util_compat, "0.7.0", "bc84380c9ab48177092f43ac89e4dfa2c6d62b40b8bd132b1059ecc7232f9a78", [:rebar3], [], "hexpm", "25eee6d67df61960cf6a794239566599b09e17e668d3700247bc498638152521"}, "unsafe": {:hex, :unsafe, "1.0.2", "23c6be12f6c1605364801f4b47007c0c159497d0446ad378b5cf05f1855c0581", [:mix], [], "hexpm", "b485231683c3ab01a9cd44cb4a79f152c6f3bb87358439c6f68791b85c2df675"}, "vex": {:hex, :vex, "0.9.1", "cb65348ebd1c4002861b65bef36e524c29d9a879c90119b2d0e674e323124277", [:mix], [], "hexpm", "a0f9f3959d127ad6a6a617c3f607ecfb1bc6f3c59f9c3614a901a46d1765bafe"}, -- 2.43.0 From eb0dbf6b79f2b6055adad2f188c18f0633a50c55 Mon Sep 17 00:00:00 2001 From: Aria Date: Sun, 17 Dec 2023 19:27:36 +0000 Subject: [PATCH 052/149] fix oauth consumer mode the previous code passed a state parameter to ueberauth with info about where to go after the user logged in, etc. since ueberauth 0.7, this parameter is ignored and oauth state is used for actual CSRF reasons. we now set a cookie with the state we need to keep track of, and read it once the callback happens. --- lib/pleroma/web/o_auth/o_auth_controller.ex | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex index ba33dc9e7..29cbd6aa6 100644 --- a/lib/pleroma/web/o_auth/o_auth_controller.ex +++ b/lib/pleroma/web/o_auth/o_auth_controller.ex @@ -443,13 +443,10 @@ def prepare_request(%Plug.Conn{} = conn, %{ |> Map.put("scope", scope) |> Jason.encode!() - params = - auth_attrs - |> Map.drop(~w(scope scopes client_id redirect_uri)) - |> Map.put("state", state) - # Handing the request to Ueberauth - redirect(conn, to: ~p"/oauth/#{provider}?#{params}") + conn + |> put_resp_cookie("akkoma_oauth_state", state) + |> redirect(to: ~p"/oauth/#{provider}") end def request(%Plug.Conn{} = conn, params) do @@ -468,7 +465,7 @@ def request(%Plug.Conn{} = conn, params) do end def callback(%Plug.Conn{assigns: %{ueberauth_failure: failure}} = conn, params) do - params = callback_params(params) + params = callback_params(conn, params) messages = for e <- Map.get(failure, :errors, []), do: e.message message = Enum.join(messages, "; ") @@ -481,7 +478,7 @@ def callback(%Plug.Conn{assigns: %{ueberauth_failure: failure}} = conn, params) end def callback(%Plug.Conn{} = conn, params) do - params = callback_params(params) + params = callback_params(conn, params) with {:ok, registration} <- Authenticator.get_registration(conn) do auth_attrs = Map.take(params, ~w(client_id redirect_uri scope scopes state)) @@ -511,8 +508,9 @@ def callback(%Plug.Conn{} = conn, params) do end end - defp callback_params(%{"state" => state} = params) do - Map.merge(params, Jason.decode!(state)) + defp callback_params(%Plug.Conn{} = conn, params) do + fetch_cookies(conn) + Map.merge(params, Jason.decode!(Map.get(conn.req_cookies, "akkoma_oauth_state", "{}"))) end def registration_details(%Plug.Conn{} = conn, %{"authorization" => auth_attrs}) do -- 2.43.0 From a074be24ca8dedebd6e2e4df3d59a66104667b70 Mon Sep 17 00:00:00 2001 From: Aria Date: Sun, 17 Dec 2023 19:36:27 +0000 Subject: [PATCH 053/149] add bit about frontend configuration to oauth consumer docs --- docs/docs/configuration/cheatsheet.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index 73fdf9eea..2f53f0c78 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -958,6 +958,15 @@ config :ueberauth, Ueberauth, ] ``` +You may also need to set up your frontend to use oauth logins. For example, for `akkoma-fe`: + +```elixir +config :pleroma, :frontend_configurations, + pleroma_fe: %{ + loginMethod: "token" + } +``` + ## Link parsing ### :uri_schemes -- 2.43.0 From 77000b8ffd92f18d7c901a545b48df7d5a5d41a4 Mon Sep 17 00:00:00 2001 From: Aria Date: Sun, 17 Dec 2023 21:47:47 +0000 Subject: [PATCH 054/149] update tests for oauth consumer --- lib/pleroma/web/o_auth/o_auth_controller.ex | 23 +++++--- .../web/o_auth/o_auth_controller_test.exs | 55 +++++++++++++------ 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/lib/pleroma/web/o_auth/o_auth_controller.ex b/lib/pleroma/web/o_auth/o_auth_controller.ex index 29cbd6aa6..29aa8c10e 100644 --- a/lib/pleroma/web/o_auth/o_auth_controller.ex +++ b/lib/pleroma/web/o_auth/o_auth_controller.ex @@ -39,6 +39,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do action_fallback(Pleroma.Web.OAuth.FallbackController) @oob_token_redirect_uri "urn:ietf:wg:oauth:2.0:oob" + @state_cookie_name "akkoma_oauth_state" # Note: this definition is only called from error-handling methods with `conn.params` as 2nd arg def authorize(%Plug.Conn{} = conn, %{"authorization" => _} = params) do @@ -445,7 +446,7 @@ def prepare_request(%Plug.Conn{} = conn, %{ # Handing the request to Ueberauth conn - |> put_resp_cookie("akkoma_oauth_state", state) + |> put_resp_cookie(@state_cookie_name, state) |> redirect(to: ~p"/oauth/#{provider}") end @@ -469,12 +470,18 @@ def callback(%Plug.Conn{assigns: %{ueberauth_failure: failure}} = conn, params) messages = for e <- Map.get(failure, :errors, []), do: e.message message = Enum.join(messages, "; ") - conn - |> put_flash( - :error, - dgettext("errors", "Failed to authenticate: %{message}.", message: message) - ) - |> redirect(external: redirect_uri(conn, params["redirect_uri"])) + error_message = dgettext("errors", "Failed to authenticate: %{message}.", message: message) + + if params["redirect_uri"] do + conn + |> put_flash( + :error, + error_message + ) + |> redirect(external: redirect_uri(conn, params["redirect_uri"])) + else + send_resp(conn, :bad_request, error_message) + end end def callback(%Plug.Conn{} = conn, params) do @@ -510,7 +517,7 @@ def callback(%Plug.Conn{} = conn, params) do defp callback_params(%Plug.Conn{} = conn, params) do fetch_cookies(conn) - Map.merge(params, Jason.decode!(Map.get(conn.req_cookies, "akkoma_oauth_state", "{}"))) + Map.merge(params, Jason.decode!(Map.get(conn.req_cookies, @state_cookie_name, "{}"))) end def registration_details(%Plug.Conn{} = conn, %{"authorization" => auth_attrs}) do 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 e0ba339db..d0703f44c 100644 --- a/test/pleroma/web/o_auth/o_auth_controller_test.exs +++ b/test/pleroma/web/o_auth/o_auth_controller_test.exs @@ -81,9 +81,7 @@ test "GET /oauth/prepare_request encodes parameters as `state` and redirects", % assert html_response(conn, 302) - redirect_query = URI.parse(redirected_to(conn)).query - assert %{"state" => state_param} = URI.decode_query(redirect_query) - assert {:ok, state_components} = Jason.decode(state_param) + assert {:ok, state_components} = Jason.decode(conn.resp_cookies["akkoma_oauth_state"].value) expected_client_id = app.client_id expected_redirect_uri = app.redirect_uris @@ -97,7 +95,7 @@ test "GET /oauth/prepare_request encodes parameters as `state` and redirects", % end test "with user-bound registration, GET /oauth//callback redirects to `redirect_uri` with `code`", - %{app: app, conn: conn} do + %{app: app, conn: _} do registration = insert(:registration) redirect_uri = OAuthController.default_redirect_uri(app) @@ -109,15 +107,17 @@ test "with user-bound registration, GET /oauth//callback redirects to } conn = - conn + build_conn() + |> put_req_cookie("akkoma_oauth_state", Jason.encode!(state_params)) + |> Plug.Session.call(Plug.Session.init(@session_opts)) + |> fetch_session() |> assign(:ueberauth_auth, %{provider: registration.provider, uid: registration.uid}) |> get( "/oauth/twitter/callback", %{ "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM", "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs", - "provider" => "twitter", - "state" => Jason.encode!(state_params) + "provider" => "twitter" } ) @@ -162,15 +162,42 @@ test "with user-unbound registration, GET /oauth//callback renders reg test "on authentication error, GET /oauth//callback redirects to `redirect_uri`", %{ app: app, - conn: conn + conn: _ } do state_params = %{ "scope" => Enum.join(app.scopes, " "), "client_id" => app.client_id, - "redirect_uri" => OAuthController.default_redirect_uri(app), - "state" => "" + "redirect_uri" => OAuthController.default_redirect_uri(app) } + conn = + build_conn() + |> put_req_cookie("akkoma_oauth_state", Jason.encode!(state_params)) + |> Plug.Session.call(Plug.Session.init(@session_opts)) + |> fetch_session() + |> assign(:ueberauth_failure, %{errors: [%{message: "(error description)"}]}) + |> get( + "/oauth/twitter/callback", + %{ + "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM", + "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs", + "provider" => "twitter", + "state" => "" + } + ) + + assert html_response(conn, 302) + assert redirected_to(conn) == app.redirect_uris + + assert Phoenix.Flash.get(conn.assigns.flash, :error) == + "Failed to authenticate: (error description)." + end + + test "on authentication error with no prior state, GET /oauth//callback returns 400", + %{ + app: _, + conn: conn + } do conn = conn |> assign(:ueberauth_failure, %{errors: [%{message: "(error description)"}]}) @@ -180,15 +207,11 @@ test "on authentication error, GET /oauth//callback redirects to `redi "oauth_token" => "G-5a3AAAAAAAwMH9AAABaektfSM", "oauth_verifier" => "QZl8vUqNvXMTKpdmUnGejJxuHG75WWWs", "provider" => "twitter", - "state" => Jason.encode!(state_params) + "state" => "" } ) - assert html_response(conn, 302) - assert redirected_to(conn) == app.redirect_uris - - assert Phoenix.Flash.get(conn.assigns.flash, :error) == - "Failed to authenticate: (error description)." + assert response(conn, 400) end test "GET /oauth/registration_details renders registration details form", %{ -- 2.43.0 From e47c50666d2dd66eb3751daca5efd069adac67f7 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 28 Jan 2024 22:15:54 +0100 Subject: [PATCH 055/149] Fix obfuscation of short domains Fixes https://akkoma.dev/AkkomaGang/akkoma/issues/645 --- CHANGELOG.md | 1 + .../web/activity_pub/mrf/simple_policy.ex | 16 +++++++++++++++- .../web/activity_pub/mrf/simple_policy_test.exs | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e2a3635f6..8a38e80ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Documentation issue in which a non-existing nginx file was referenced - Issue where a bad inbox URL could break federation - Issue where hashtag rel values would be scrubbed +- Issue where short domains listed in `transparency_obfuscate_domains` were not actually obfuscated ## 2023.08 diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index ba54eb674..c2e17ca9e 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -314,6 +314,20 @@ def filter(object) when is_binary(object) do def filter(object), do: {:ok, object} defp obfuscate(string) when is_binary(string) do + # Want to strip at least two neighbouring chars + # to ensure at least one non-dot char is in the obfuscation area + stripped = String.length(string) - 6 + + {keepstart, keepend} = + if stripped > 1 do + {3, 3} + else + { + 2 - div(1 - stripped, 2), + 2 + div(stripped, 2) + } + end + string |> to_charlist() |> Enum.with_index() @@ -322,7 +336,7 @@ defp obfuscate(string) when is_binary(string) do ?. {char, index} -> - if 3 <= index && index < String.length(string) - 3, do: ?*, else: char + if keepstart <= index && index < String.length(string) - keepend, do: ?*, else: char end) |> to_string() end diff --git a/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs b/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs index 875cf8f43..c6600f001 100644 --- a/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs @@ -283,7 +283,7 @@ test "obfuscates domains listed in :transparency_obfuscate_domains" do assert {:ok, %{ - mrf_simple: %{reject: ["rem***.*****nce", "a.b"]}, + mrf_simple: %{reject: ["rem***.*****nce", "*.b"]}, mrf_simple_info: %{reject: %{"rem***.*****nce" => %{}}} }} = SimplePolicy.describe() end -- 2.43.0 From 3cd882528eb816d18c7d79ea5d797dfb3599c757 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 28 Jan 2024 23:12:59 +0100 Subject: [PATCH 056/149] More prominently document MRF transparency and obfuscation And point to the cheat sheet for all other MRF policies and their configuration details. --- docs/docs/configuration/mrf.md | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/docs/docs/configuration/mrf.md b/docs/docs/configuration/mrf.md index a74cfa90d..170b26792 100644 --- a/docs/docs/configuration/mrf.md +++ b/docs/docs/configuration/mrf.md @@ -61,6 +61,32 @@ config :pleroma, :mrf_simple, The effects of MRF policies can be very drastic. It is important to use this functionality carefully. Always try to talk to an admin before writing an MRF policy concerning their instance. +## Hiding or Obfuscating Policies + +You can opt out of publicly displaying all MRF policies or only hide or obfuscate selected domains. + +To just hide everything set: + +```elixir +config :pleroma, :mrf, + ... + transparency: false, +``` + +To hide or obfuscate only select entries, use: + +```elixir +config :pleroma, :mrf, + ... + transparency_obfuscate_domains: ["handholdi.ng", "badword.com"], + transparency_exclusions: [{"ghost.club", "even a fragment is too spoopy for humans"}] +``` + +## More MRF Policies + +See the [documentation cheatsheet](cheatsheet.md) +for all available MRF policies and their options. + ## Writing your own MRF Policy As discussed above, the MRF system is a modular system that supports pluggable policies. This means that an admin may write a custom MRF policy in Elixir or any other language that runs on the Erlang VM, by specifying the module name in the `policies` config setting. -- 2.43.0 From d7d159c49f1f2c21214d88d9caf5bbd785653d02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20=C5=9Awi=C4=85tkowski?= Date: Sat, 3 Feb 2024 14:24:03 +0100 Subject: [PATCH 057/149] Fix OpenAPI spec for preferred_frontend endpoint The spec was copied from another endpoint, including the operation id, leading to scrubbing the valid parameters from the request and simply not working. --- .../operations/frontend_settings_operation.ex | 14 ++++++++------ .../frontend_settings_controller_test.exs | 14 ++++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/lib/pleroma/web/api_spec/operations/frontend_settings_operation.ex b/lib/pleroma/web/api_spec/operations/frontend_settings_operation.ex index 867a751b3..ede66709c 100644 --- a/lib/pleroma/web/api_spec/operations/frontend_settings_operation.ex +++ b/lib/pleroma/web/api_spec/operations/frontend_settings_operation.ex @@ -111,9 +111,9 @@ def available_frontends_operation() do def update_preferred_frontend_operation() do %Operation{ tags: ["Frontends"], - summary: "Frontend Settings Profiles", - description: "List frontend setting profiles", - operationId: "AkkomaAPI.FrontendSettingsController.available_frontends", + summary: "Update preferred frontend setting", + description: "Store preferred frontend in cookies", + operationId: "AkkomaAPI.FrontendSettingsController.update_preferred_frontend", requestBody: request_body( "Frontend", @@ -132,9 +132,11 @@ def update_preferred_frontend_operation() do responses: %{ 200 => Operation.response("Frontends", "application/json", %Schema{ - type: :array, - items: %Schema{ - type: :string + type: :object, + properties: %{ + frontend_name: %Schema{ + type: :string + } } }) } diff --git a/test/pleroma/web/akkoma_api/frontend_settings_controller_test.exs b/test/pleroma/web/akkoma_api/frontend_settings_controller_test.exs index 07e45d9b9..7b1329593 100644 --- a/test/pleroma/web/akkoma_api/frontend_settings_controller_test.exs +++ b/test/pleroma/web/akkoma_api/frontend_settings_controller_test.exs @@ -119,4 +119,18 @@ test "deletes a config" do ) == nil end end + + describe "PUT /api/v1/akkoma/preferred_frontend" do + test "sets a cookie with selected frontend" do + %{conn: conn} = oauth_access(["read"]) + + response = + conn + |> put_req_header("content-type", "application/json") + |> put("/api/v1/akkoma/preferred_frontend", %{"frontend_name" => "pleroma-fe/stable"}) + + json_response_and_validate_schema(response, 200) + assert %{"preferred_frontend" => %{value: "pleroma-fe/stable"}} = response.resp_cookies + end + end end -- 2.43.0 From df21b6182975046b00d97b1a644ea8fe4d6a1053 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20=C5=9Awi=C4=85tkowski?= Date: Mon, 5 Feb 2024 21:42:15 +0100 Subject: [PATCH 058/149] Return last_status_at as date, not datetime --- lib/pleroma/web/mastodon_api/views/account_view.ex | 5 ++++- test/pleroma/web/mastodon_api/views/account_view_test.exs | 5 +++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 2f543c08a..8a18d99c8 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -261,6 +261,9 @@ defp do_render("show.json", %{user: user} = opts) do |> MediaProxy.url() end + last_status_at = + if is_nil(user.last_status_at), do: nil, else: NaiveDateTime.to_date(user.last_status_at) + %{ id: to_string(user.id), username: username_from_nickname(user.nickname), @@ -289,7 +292,7 @@ defp do_render("show.json", %{user: user} = opts) do actor_type: user.actor_type } }, - last_status_at: user.last_status_at, + last_status_at: last_status_at, akkoma: %{ instance: render("instance.json", %{instance: instance}), status_ttl_days: user.status_ttl_days 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 5b8aea8ac..5d069e6e1 100644 --- a/test/pleroma/web/mastodon_api/views/account_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs @@ -40,7 +40,8 @@ test "Represent a user account" do emoji: %{"karjalanpiirakka" => "/file.png"}, raw_bio: "valid html. a\nb\nc\nd\nf '&<>\"", also_known_as: ["https://shitposter.zone/users/shp"], - status_ttl_days: 5 + status_ttl_days: 5, + last_status_at: ~N[2023-12-31T15:06:17] }) insert(:instance, %{host: "example.com", nodeinfo: %{version: "2.1"}}) @@ -91,7 +92,7 @@ test "Represent a user account" do fields: [] }, fqn: "shp@shitposter.club", - last_status_at: nil, + last_status_at: ~D[2023-12-31], pleroma: %{ ap_id: user.ap_id, also_known_as: ["https://shitposter.zone/users/shp"], -- 2.43.0 From 5f7d47dcb7ac3f5397ef5f8e6726f9f819fca84a Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 11 Feb 2024 01:19:03 +0100 Subject: [PATCH 059/149] Drop obolete chat/shoutbox config options Their functions were purged in 0f132b802dde7f217ecb07767e0d34e3edb517b7 --- config/config.exs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/config/config.exs b/config/config.exs index 9a3cdcebf..27c314e01 100644 --- a/config/config.exs +++ b/config/config.exs @@ -290,7 +290,6 @@ alwaysShowSubjectInput: true, background: "/images/city.jpg", collapseMessageWithSubject: false, - disableChat: false, greentext: false, hideFilteredStatuses: false, hideMutedPosts: false, @@ -453,10 +452,6 @@ image_quality: 85, min_content_length: 100 * 1024 -config :pleroma, :shout, - enabled: true, - limit: 5_000 - config :phoenix, :format_encoders, json: Jason, "activity+json": Jason config :phoenix, :json_library, Jason -- 2.43.0 From 8cf183cb428ee5f0e368d872ae4e6e63b6f63c12 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 11 Feb 2024 02:02:24 +0100 Subject: [PATCH 060/149] Drop Chat tables Chats were removed in 0f132b802dde7f217ecb07767e0d34e3edb517b7 --- .../20240210000000_drop_chat_tables.exs | 50 +++++++++++++++++++ test/mix/tasks/pleroma/database_test.exs | 2 - 2 files changed, 50 insertions(+), 2 deletions(-) create mode 100644 priv/repo/migrations/20240210000000_drop_chat_tables.exs diff --git a/priv/repo/migrations/20240210000000_drop_chat_tables.exs b/priv/repo/migrations/20240210000000_drop_chat_tables.exs new file mode 100644 index 000000000..f83524e4d --- /dev/null +++ b/priv/repo/migrations/20240210000000_drop_chat_tables.exs @@ -0,0 +1,50 @@ +defmodule Pleroma.Repo.Migrations.DropChatTables do + use Ecto.Migration + + def up do + # Automatically drops associated indices and constraints + drop table(:chat_message_references) + drop table(:chats) + end + + def down do + # Ecto's default primary key is bigserial, thus configure manually + create table(:chats, primary_key: false) do + add(:id, :uuid, primary_key: true, autogenerated: true) + + add( + :user_id, + references(:users, type: :uuid, on_delete: :delete_all) + # yes, this was nullable + ) + + add( + :recipient, + references(:users, column: :ap_id, type: :string, on_delete: :delete_all) + # yes, this was nullable + ) + + timestamps() + end + + create(index(:chats, [:user_id, :recipient], unique: true)) + + create table(:chat_message_references, primary_key: false) do + add(:id, :uuid, primary_key: true, autogenerated: true) + add(:chat_id, references(:chats, type: :uuid, on_delete: :delete_all), null: false) + add(:object_id, references(:objects, on_delete: :delete_all), null: false) + add(:unread, :boolean, default: true, null: false) + timestamps() + end + + create(index(:chat_message_references, [:chat_id, "id desc"])) + create(unique_index(:chat_message_references, [:object_id, :chat_id])) + + create( + index(:chat_message_references, [:chat_id], + where: "unread = true", + name: "unread_messages_count_index" + ) + ) + end +end diff --git a/test/mix/tasks/pleroma/database_test.exs b/test/mix/tasks/pleroma/database_test.exs index 40c5fd402..97fa830ff 100644 --- a/test/mix/tasks/pleroma/database_test.exs +++ b/test/mix/tasks/pleroma/database_test.exs @@ -371,8 +371,6 @@ test "We don't have unexpected tables which may contain objects that are referen ["apps"], ["backups"], ["bookmarks"], - ["chat_message_references"], - ["chats"], ["config"], ["conversation_participation_recipient_ships"], ["conversation_participations"], -- 2.43.0 From a6df71eebb6fcb29460b510315048cea4cb32f07 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sat, 3 Feb 2024 17:30:00 +0100 Subject: [PATCH 061/149] Don't add summary metrics to prometheus MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The exporter doesn’t support them thus we don't lose anything by this, but it avoids a bunch of warnings each time the server starts up. --- lib/pleroma/web/telemetry.ex | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/telemetry.ex b/lib/pleroma/web/telemetry.ex index b03850600..eecaffe88 100644 --- a/lib/pleroma/web/telemetry.ex +++ b/lib/pleroma/web/telemetry.ex @@ -101,6 +101,7 @@ defp distribution_metrics do ] end + # Summary metrics are currently not (yet) supported by the prometheus exporter defp summary_metrics do [ # Phoenix Metrics @@ -121,7 +122,12 @@ defp summary_metrics do summary("vm.memory.total", unit: {:byte, :kilobyte}), summary("vm.total_run_queue_lengths.total"), summary("vm.total_run_queue_lengths.cpu"), - summary("vm.total_run_queue_lengths.io"), + summary("vm.total_run_queue_lengths.io") + ] + end + + defp common_metrics do + [ last_value("pleroma.local_users.total"), last_value("pleroma.domains.total"), last_value("pleroma.local_statuses.total"), @@ -129,8 +135,8 @@ defp summary_metrics do ] end - def prometheus_metrics, do: summary_metrics() ++ distribution_metrics() - def live_dashboard_metrics, do: summary_metrics() + def prometheus_metrics, do: common_metrics() ++ distribution_metrics() + def live_dashboard_metrics, do: common_metrics() ++ summary_metrics() defp periodic_measurements do [ -- 2.43.0 From 18ecae61839dd3d6e4d2aaaf2d0c01bc448fcfb5 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sat, 3 Feb 2024 17:51:40 +0100 Subject: [PATCH 062/149] Use fully qualified function capture for telementry event Otherwise we get warnings on startup as local captures and anonymous functions are supposedly less performant. --- lib/pleroma/job_queue_monitor.ex | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/job_queue_monitor.ex b/lib/pleroma/job_queue_monitor.ex index b5f124923..8d81ffcac 100644 --- a/lib/pleroma/job_queue_monitor.ex +++ b/lib/pleroma/job_queue_monitor.ex @@ -15,8 +15,19 @@ def start_link(_) do @impl true def init(state) do - :telemetry.attach("oban-monitor-failure", [:oban, :job, :exception], &handle_event/4, nil) - :telemetry.attach("oban-monitor-success", [:oban, :job, :stop], &handle_event/4, nil) + :telemetry.attach( + "oban-monitor-failure", + [:oban, :job, :exception], + &Pleroma.JobQueueMonitor.handle_event/4, + nil + ) + + :telemetry.attach( + "oban-monitor-success", + [:oban, :job, :stop], + &Pleroma.JobQueueMonitor.handle_event/4, + nil + ) {:ok, state} end -- 2.43.0 From 8f8e1ff2145d4b48b6c45cf46bc8f85e6222c7c0 Mon Sep 17 00:00:00 2001 From: Oneric Date: Thu, 8 Feb 2024 00:10:46 +0100 Subject: [PATCH 063/149] Purge unused function scrub_css Commit e9f1897cfdb32c890e9eaf2e894128be5c7e1123 added this private function but it never had any users resulting in warnings each startup --- priv/scrubbers/default.ex | 2 -- 1 file changed, 2 deletions(-) diff --git a/priv/scrubbers/default.ex b/priv/scrubbers/default.ex index 6a97cbfd4..74de910fd 100644 --- a/priv/scrubbers/default.ex +++ b/priv/scrubbers/default.ex @@ -128,6 +128,4 @@ defmodule Pleroma.HTML.Scrubber.Default do Meta.allow_tag_with_these_attributes(:small, []) Meta.strip_everything_not_covered() - - defp scrub_css(value), do: value end -- 2.43.0 From 16197ff57a181c4202317519b33d19826b53fbba Mon Sep 17 00:00:00 2001 From: Oneric Date: Sat, 3 Feb 2024 18:21:09 +0100 Subject: [PATCH 064/149] Display memory as MB in live dashboard MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit With kilobyte the resulting numbers got too large and were cut off in the charts, making them useless. However, even an idle Akkoma server’s memory usage is in the lower hundreths of megabytes, so we don’t need this much precision to begin with for the dashboard. Other metric users might prefer base units and can handle scaling in a smarter way, so keep this configurable. --- lib/pleroma/web/telemetry.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/telemetry.ex b/lib/pleroma/web/telemetry.ex index eecaffe88..3ea88b31d 100644 --- a/lib/pleroma/web/telemetry.ex +++ b/lib/pleroma/web/telemetry.ex @@ -102,7 +102,7 @@ defp distribution_metrics do end # Summary metrics are currently not (yet) supported by the prometheus exporter - defp summary_metrics do + defp summary_metrics(byte_unit) do [ # Phoenix Metrics summary("phoenix.endpoint.stop.duration", @@ -119,7 +119,7 @@ defp summary_metrics do summary("pleroma.repo.query.idle_time", unit: {:native, :millisecond}), # VM Metrics - summary("vm.memory.total", unit: {:byte, :kilobyte}), + summary("vm.memory.total", unit: {:byte, byte_unit}), summary("vm.total_run_queue_lengths.total"), summary("vm.total_run_queue_lengths.cpu"), summary("vm.total_run_queue_lengths.io") @@ -136,7 +136,7 @@ defp common_metrics do end def prometheus_metrics, do: common_metrics() ++ distribution_metrics() - def live_dashboard_metrics, do: common_metrics() ++ summary_metrics() + def live_dashboard_metrics, do: common_metrics() ++ summary_metrics(:megabyte) defp periodic_measurements do [ -- 2.43.0 From 29f564f700e4c6998f0bbb830128e9d520cd1905 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sat, 3 Feb 2024 18:28:55 +0100 Subject: [PATCH 065/149] Use fallbacks of summary metrics for prometheus --- lib/pleroma/web/telemetry.ex | 87 +++++++++++++++++++++++++++++++++++- 1 file changed, 86 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/telemetry.ex b/lib/pleroma/web/telemetry.ex index 3ea88b31d..269f9f238 100644 --- a/lib/pleroma/web/telemetry.ex +++ b/lib/pleroma/web/telemetry.ex @@ -126,6 +126,89 @@ defp summary_metrics(byte_unit) do ] end + defp sum_counter_pair(basename, opts) do + [ + sum(basename <> ".psum", opts), + counter(basename <> ".pcount", opts) + ] + end + + # Prometheus exporter doesn't support summaries, so provide fallbacks + defp summary_fallback_metrics(byte_unit \\ :byte) do + # Summary metrics are not supported by the Prometheus exporter + # https://github.com/beam-telemetry/telemetry_metrics_prometheus_core/issues/11 + # and sum metrics currently only work with integers + # https://github.com/beam-telemetry/telemetry_metrics_prometheus_core/issues/35 + # + # For VM metrics this is kindof ok as they appear to always be integers + # and we can use sum + counter to get the average between polls from their change + # But for repo query times we need to use a full distribution + + simple_buckets = [0, 1, 2, 4, 8, 16] + simple_buckets_quick = for t <- simple_buckets, do: t / 100.0 + + # Already included in distribution metrics anyway: + # phoenix.router_dispatch.stop.duration + # pleroma.repo.query.total_time + # pleroma.repo.query.queue_time + dist_metrics = + [ + distribution("phoenix.endpoint.stop.duration.fdist", + event_name: [:phoenix, :endpoint, :stop], + measurement: :duration, + unit: {:native, :millisecond}, + reporter_options: [ + buckets: simple_buckets + ] + ), + distribution("pleroma.repo.query.decode_time.fdist", + event_name: [:pleroma, :repo, :query], + measurement: :decode_time, + unit: {:native, :millisecond}, + reporter_options: [ + buckets: simple_buckets_quick + ] + ), + distribution("pleroma.repo.query.query_time.fdist", + event_name: [:pleroma, :repo, :query], + measurement: :query_time, + unit: {:native, :millisecond}, + reporter_options: [ + buckets: simple_buckets + ] + ), + distribution("pleroma.repo.query.idle_time.fdist", + event_name: [:pleroma, :repo, :query], + measurement: :idle_time, + unit: {:native, :millisecond}, + reporter_options: [ + buckets: simple_buckets + ] + ) + ] + + vm_metrics = + sum_counter_pair("vm.memory.total", + event_name: [:vm, :memory], + measurement: :total, + unit: {:byte, byte_unit} + ) ++ + sum_counter_pair("vm.total_run_queue_lengths.total", + event_name: [:vm, :total_run_queue_lengths], + measurement: :total + ) ++ + sum_counter_pair("vm.total_run_queue_lengths.cpu", + event_name: [:vm, :total_run_queue_lengths], + measurement: :cpu + ) ++ + sum_counter_pair("vm.total_run_queue_lengths.io.fsum", + event_name: [:vm, :total_run_queue_lengths], + measurement: :io + ) + + dist_metrics ++ vm_metrics + end + defp common_metrics do [ last_value("pleroma.local_users.total"), @@ -135,7 +218,9 @@ defp common_metrics do ] end - def prometheus_metrics, do: common_metrics() ++ distribution_metrics() + def prometheus_metrics, + do: common_metrics() ++ distribution_metrics() ++ summary_fallback_metrics() + def live_dashboard_metrics, do: common_metrics() ++ summary_metrics(:megabyte) defp periodic_measurements do -- 2.43.0 From 192480093c5b35e43fc34327a76c51b62d2ea7ec Mon Sep 17 00:00:00 2001 From: Oneric Date: Mon, 12 Feb 2024 18:27:40 +0000 Subject: [PATCH 066/149] Provide sane defaults for SMTP MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit OTP’s default SSL/TLS settings are rather restricitive and in particular do not use system CA certs. In our case using system CA certs is virtually always desired and the lack of it leads to non-obvious errors. Manually configuring system CA certs from in-database config also isn’t straightforward. Furthermore, gen_smtp uses a different set of connection options for direct SSL/TLS and a later TLS upgrade providing additional confusion and complexity in how to configure this. Thus provide some suitable defaults for sending SMTP emails. Everything can still be overriden by admins if necessary. Note: defaults are not appended when validating the config in hopes of improving the error message (as the required relay key is already accessed to generate defaults for optional fields) Fixes: https://akkoma.dev/AkkomaGang/akkoma/issues/660 --- lib/pleroma/emails/mailer.ex | 55 ++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/emails/mailer.ex b/lib/pleroma/emails/mailer.ex index d42236c5e..6a79a7694 100644 --- a/lib/pleroma/emails/mailer.ex +++ b/lib/pleroma/emails/mailer.ex @@ -55,12 +55,61 @@ def deliver!(email, config) do @doc false def validate_dependency do - parse_config([]) + parse_config([], defaults: false) |> Keyword.get(:adapter) |> Swoosh.Mailer.validate_dependency() end - defp parse_config(config) do - Swoosh.Mailer.parse_config(@otp_app, __MODULE__, @mailer_config, config) + defp ensure_charlist(input) do + case input do + i when is_binary(i) -> String.to_charlist(input) + i when is_list(i) -> i + end + end + + defp default_config(adapter, conf, opts) + + defp default_config(_, _, defaults: false) do + [] + end + + defp default_config(Swoosh.Adapters.SMTP, conf, _) do + # gen_smtp and Erlang's tls defaults are very barebones, if nothing is set. + # Add sane defaults for our usecase to make config less painful for admins + relay = ensure_charlist(Keyword.get(conf, :relay)) + ssl_disabled = Keyword.get(conf, :ssl) === false + os_cacerts = :public_key.cacerts_get() + + common_tls_opts = [ + cacerts: os_cacerts, + versions: [:"tlsv1.2", :"tlsv1.3"], + verify: :verify_peer, + # some versions have supposedly issues verifying wildcard certs without this + server_name_indication: relay, + # the default of 10 is too restrictive + depth: 32 + ] + + [ + auth: :always, + no_mx_lookups: false, + # Direct SSL/TLS + # (if ssl was explicitly disabled, we must not pass TLS options to the socket) + ssl: true, + sockopts: if(ssl_disabled, do: [], else: common_tls_opts), + # STARTTLS upgrade (can't be set to :always when already using direct TLS) + tls: :if_available, + tls_options: common_tls_opts + ] + end + + defp default_config(_, _, _), do: [] + + defp parse_config(config, opts \\ []) do + conf = Swoosh.Mailer.parse_config(@otp_app, __MODULE__, @mailer_config, config) + adapter = Keyword.get(conf, :adapter) + + default_config(adapter, conf, opts) + |> Keyword.merge(conf) end end -- 2.43.0 From 13e62b4e5190f755cdf11e843216e9af47e31a4f Mon Sep 17 00:00:00 2001 From: Oneric Date: Wed, 17 Jan 2024 19:12:49 +0000 Subject: [PATCH 067/149] Fix schema and docs for status_ttl_days and instance Fixes misspelling and omission of and example in commit 0cfd5b4e89b02688342345755577e58eece3db0f which added the status_ttl_property. This was the only place this commit referred to the property as note_ttl_days. Partially fixes the omitted schema update of the instance metadata addition from commit b7e8ce235073ab45db24dfc6c27bc6998b43a200. A proper full schema for nodeinfo is still missing. --- .../API/differences_in_mastoapi_responses.md | 5 ++++ lib/pleroma/web/api_spec/schemas/account.ex | 23 ++++++++++++++++++- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/docs/docs/development/API/differences_in_mastoapi_responses.md b/docs/docs/development/API/differences_in_mastoapi_responses.md index 053dc9663..9c822fbfc 100644 --- a/docs/docs/development/API/differences_in_mastoapi_responses.md +++ b/docs/docs/development/API/differences_in_mastoapi_responses.md @@ -113,6 +113,11 @@ Has these additional fields under the `pleroma` object: - `notification_settings`: object, can be absent. See `/api/v1/pleroma/notification_settings` for the parameters/keys returned. - `favicon`: nullable URL string, Favicon image of the user's instance +Has these additional fields under the `akkoma` object: + +- `instance`: nullable object with metadata about the user’s instance +- `status_ttl_days`: nullable int, default time after which statuses are deleted + ### Source Has these additional fields under the `pleroma` object: diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex index 2693eaceb..2a87065a1 100644 --- a/lib/pleroma/web/api_spec/schemas/account.ex +++ b/lib/pleroma/web/api_spec/schemas/account.ex @@ -112,7 +112,17 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do akkoma: %Schema{ type: :object, properties: %{ - note_ttl_days: %Schema{type: :integer} + instance: %Schema{ + type: :object, + nullable: true, + properties: %{ + name: %Schema{type: :string}, + favicon: %Schema{type: :string, format: :uri, nullable: true}, + # XXX: proper nodeinfo schema + nodeinfo: %Schema{type: :object, nullable: true} + } + }, + status_ttl_days: %Schema{type: :integer, nullable: true} } }, source: %Schema{ @@ -205,6 +215,17 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do "pleroma-fe" => %{} } }, + "akkoma" => %{ + "instance" => %{ + "name" => "ihatebeinga.live", + "favicon" => "https://ihatebeinga.live/favicon.png", + "nodeinfo" => + %{ + # XXX: nodeinfo schema + } + }, + "status_ttl_days" => nil + }, "source" => %{ "fields" => [], "note" => "foobar", -- 2.43.0 From 376f6b15ca3537e5a24c28e2df57fee8f33cc570 Mon Sep 17 00:00:00 2001 From: Oneric Date: Wed, 17 Jan 2024 19:13:29 +0000 Subject: [PATCH 068/149] Add ability to auto-approve followbacks Resolves: https://akkoma.dev/AkkomaGang/akkoma/issues/148 --- CHANGELOG.md | 1 + .../API/differences_in_mastoapi_responses.md | 1 + lib/pleroma/user.ex | 26 ++++++++++++---- lib/pleroma/web/activity_pub/side_effects.ex | 2 +- .../api_spec/operations/account_operation.ex | 7 +++++ lib/pleroma/web/api_spec/schemas/account.ex | 6 ++-- .../controllers/account_controller.ex | 1 + .../web/mastodon_api/views/account_view.ex | 3 +- .../20240213120000_add_permit_followback.exs | 9 ++++++ test/pleroma/web/common_api_test.exs | 30 +++++++++++++++++++ .../mastodon_api/views/account_view_test.exs | 6 ++-- 11 files changed, 80 insertions(+), 12 deletions(-) create mode 100644 priv/repo/migrations/20240213120000_add_permit_followback.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a38e80ba..e87110526 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Added - Full compatibility with Erlang OTP26 - handling of GET /api/v1/preferences +- ability to auto-approve follow requests from users you are already following ## Changed - OTP builds are now built on erlang OTP26 diff --git a/docs/docs/development/API/differences_in_mastoapi_responses.md b/docs/docs/development/API/differences_in_mastoapi_responses.md index 9c822fbfc..196166853 100644 --- a/docs/docs/development/API/differences_in_mastoapi_responses.md +++ b/docs/docs/development/API/differences_in_mastoapi_responses.md @@ -117,6 +117,7 @@ Has these additional fields under the `akkoma` object: - `instance`: nullable object with metadata about the user’s instance - `status_ttl_days`: nullable int, default time after which statuses are deleted +- `permit_followback`: boolean, whether follows from followed accounts are auto-approved ### Source diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 1b78d0479..35f416e6c 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -160,6 +160,7 @@ defmodule Pleroma.User do field(:last_status_at, :naive_datetime) field(:language, :string) field(:status_ttl_days, :integer, default: nil) + field(:permit_followback, :boolean, default: false) field(:accepts_direct_messages_from, Ecto.Enum, values: [:everybody, :people_i_follow, :nobody], @@ -544,6 +545,7 @@ def update_changeset(struct, params \\ %{}) do :actor_type, :disclose_client, :status_ttl_days, + :permit_followback, :accepts_direct_messages_from ] ) @@ -972,16 +974,21 @@ def needs_update?(%User{local: false} = user) do def needs_update?(_), do: true + # "Locked" (self-locked) users demand explicit authorization of follow requests + @spec can_direct_follow_local(User.t(), User.t()) :: true | false + def can_direct_follow_local(%User{} = follower, %User{local: true} = followed) do + !followed.is_locked || (followed.permit_followback and is_friend_of(follower, followed)) + end + @spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t(), User.t()} | {:error, String.t()} - # "Locked" (self-locked) users demand explicit authorization of follow requests - def maybe_direct_follow(%User{} = follower, %User{local: true, is_locked: true} = followed) do - follow(follower, followed, :follow_pending) - end - def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do - follow(follower, followed) + if can_direct_follow_local(follower, followed) do + follow(follower, followed) + else + follow(follower, followed, :follow_pending) + end end def maybe_direct_follow(%User{} = follower, %User{} = followed) do @@ -1331,6 +1338,13 @@ def get_friends_ids(%User{} = user, page \\ nil) do |> Repo.all() end + def is_friend_of(%User{} = potential_friend, %User{local: true} = user) do + user + |> get_friends_query() + |> where(id: ^potential_friend.id) + |> Repo.exists?() + end + def increase_note_count(%User{} = user) do User |> where(id: ^user.id) diff --git a/lib/pleroma/web/activity_pub/side_effects.ex b/lib/pleroma/web/activity_pub/side_effects.ex index 34617a218..963c6d8c6 100644 --- a/lib/pleroma/web/activity_pub/side_effects.ex +++ b/lib/pleroma/web/activity_pub/side_effects.ex @@ -109,7 +109,7 @@ def handle( %User{} = followed <- User.get_cached_by_ap_id(followed_user), {_, {:ok, _, _}, _, _} <- {:following, User.follow(follower, followed, :follow_pending), follower, followed} do - if followed.local && !followed.is_locked do + if followed.local && User.can_direct_follow_local(follower, followed) do {:ok, accept_data, _} = Builder.accept(followed, object) {:ok, _activity, _} = Pipeline.common_pipeline(accept_data, local: true) end diff --git a/lib/pleroma/web/api_spec/operations/account_operation.ex b/lib/pleroma/web/api_spec/operations/account_operation.ex index 9a6dfc1ff..c15a246f2 100644 --- a/lib/pleroma/web/api_spec/operations/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/account_operation.ex @@ -723,6 +723,12 @@ defp update_credentials_request do description: "Number of days after which statuses will be deleted. Set to -1 to disable." }, + permit_followback: %Schema{ + allOf: [BooleanLike], + nullable: true, + description: + "Whether follow requests from accounts the user is already following are auto-approved (when locked)." + }, accepts_direct_messages_from: %Schema{ type: :string, enum: [ @@ -754,6 +760,7 @@ defp update_credentials_request do discoverable: false, actor_type: "Person", status_ttl_days: 30, + permit_followback: true, accepts_direct_messages_from: "everybody" } } diff --git a/lib/pleroma/web/api_spec/schemas/account.ex b/lib/pleroma/web/api_spec/schemas/account.ex index 2a87065a1..a922f6d1b 100644 --- a/lib/pleroma/web/api_spec/schemas/account.ex +++ b/lib/pleroma/web/api_spec/schemas/account.ex @@ -122,7 +122,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do nodeinfo: %Schema{type: :object, nullable: true} } }, - status_ttl_days: %Schema{type: :integer, nullable: true} + status_ttl_days: %Schema{type: :integer, nullable: true}, + permit_followback: %Schema{type: :boolean} } }, source: %Schema{ @@ -224,7 +225,8 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do # XXX: nodeinfo schema } }, - "status_ttl_days" => nil + "status_ttl_days" => nil, + "permit_followback" => true }, "source" => %{ "fields" => [], diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 6fc88354f..2c7d6a893 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -222,6 +222,7 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p |> Maps.put_if_present(:language, Pleroma.Web.Gettext.normalize_locale(params[:language])) |> Maps.put_if_present(:status_ttl_days, params[:status_ttl_days], status_ttl_days_value) |> Maps.put_if_present(:accepts_direct_messages_from, params[:accepts_direct_messages_from]) + |> Maps.put_if_present(:permit_followback, params[:permit_followback]) # What happens here: # diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 2f543c08a..1cb025bd0 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -292,7 +292,8 @@ defp do_render("show.json", %{user: user} = opts) do last_status_at: user.last_status_at, akkoma: %{ instance: render("instance.json", %{instance: instance}), - status_ttl_days: user.status_ttl_days + status_ttl_days: user.status_ttl_days, + permit_followback: user.permit_followback }, # Pleroma extensions # Note: it's insecure to output :email but fully-qualified nickname may serve as safe stub diff --git a/priv/repo/migrations/20240213120000_add_permit_followback.exs b/priv/repo/migrations/20240213120000_add_permit_followback.exs new file mode 100644 index 000000000..72475a58e --- /dev/null +++ b/priv/repo/migrations/20240213120000_add_permit_followback.exs @@ -0,0 +1,9 @@ +defmodule Pleroma.Repo.Migrations.AddPermitFollowback do + use Ecto.Migration + + def change do + alter table(:users) do + add(:permit_followback, :boolean, null: false, default: false) + end + end +end diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index f2675ad01..379cf85b8 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -1061,6 +1061,36 @@ test "directly follows a non-locked local user" do assert User.following?(follower, followed) end + + test "directly follows back a locked, but followback-allowing local user" do + uopen = insert(:user, is_locked: false) + uselective = insert(:user, is_locked: true, permit_followback: true) + + assert {:ok, uselective, uopen, %{data: %{"state" => "accept"}}} = + CommonAPI.follow(uselective, uopen) + + assert User.get_follow_state(uselective, uopen) == :follow_accept + + assert {:ok, uopen, uselective, %{data: %{"state" => "accept"}}} = + CommonAPI.follow(uopen, uselective) + + assert User.get_follow_state(uopen, uselective) == :follow_accept + end + + test "creates a pending request for locked, non-followback local user" do + uopen = insert(:user, is_locked: false) + ulocked = insert(:user, is_locked: true, permit_followback: false) + + assert {:ok, ulocked, uopen, %{data: %{"state" => "accept"}}} = + CommonAPI.follow(ulocked, uopen) + + assert User.get_follow_state(ulocked, uopen) == :follow_accept + + assert {:ok, uopen, ulocked, %{data: %{"state" => "pending"}}} = + CommonAPI.follow(uopen, ulocked) + + assert User.get_follow_state(uopen, ulocked) == :follow_pending + end end describe "unfollow/2" do 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 5b8aea8ac..b05759d54 100644 --- a/test/pleroma/web/mastodon_api/views/account_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs @@ -65,7 +65,8 @@ test "Represent a user account" do }, favicon: nil }, - status_ttl_days: 5 + status_ttl_days: 5, + permit_followback: false }, avatar: "http://localhost:4001/images/avi.png", avatar_static: "http://localhost:4001/images/avi.png", @@ -248,7 +249,8 @@ test "Represent a Service(bot) account" do favicon: "http://localhost:4001/favicon.png", nodeinfo: %{version: "2.0"} }, - status_ttl_days: nil + status_ttl_days: nil, + permit_followback: false }, pleroma: %{ ap_id: user.ap_id, -- 2.43.0 From cb7eaccecb37e89afff3cb0f3e0eed13e44c0779 Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Wed, 14 Feb 2024 18:16:54 +0100 Subject: [PATCH 069/149] =?UTF-8?q?Config:=20Check=20the=20permissions=20o?= =?UTF-8?q?f=20the=20linked=20file=20instead=20of=20the=20symlink=E2=86=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pleroma/config/release_runtime_provider.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/config/release_runtime_provider.ex b/lib/pleroma/config/release_runtime_provider.ex index a829a0206..d8818dfee 100644 --- a/lib/pleroma/config/release_runtime_provider.ex +++ b/lib/pleroma/config/release_runtime_provider.ex @@ -23,7 +23,7 @@ def load(config, opts) do with_runtime_config = if File.exists?(config_path) do # - %File.Stat{mode: mode} = File.lstat!(config_path) + %File.Stat{mode: mode} = File.stat!(config_path) if Bitwise.band(mode, 0o007) > 0 do raise "Configuration at #{config_path} has world-permissions, execute the following: chmod o= #{config_path}" -- 2.43.0 From 7493d8f49da64ae83b08b70b8ad18af62e6a65de Mon Sep 17 00:00:00 2001 From: Oneric Date: Tue, 16 Jan 2024 22:12:52 +0100 Subject: [PATCH 070/149] Document live dashboard --- docs/docs/administration/monitoring.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/docs/administration/monitoring.md b/docs/docs/administration/monitoring.md index 9233fbe34..b7a748731 100644 --- a/docs/docs/administration/monitoring.md +++ b/docs/docs/administration/monitoring.md @@ -3,7 +3,7 @@ If you run akkoma, you may be inclined to collect metrics to ensure your instance is running smoothly, and that there's nothing quietly failing in the background. -To facilitate this, akkoma exposes prometheus metrics to be scraped. +To facilitate this, akkoma exposes a dashboard and prometheus metrics to be scraped. ## Prometheus @@ -31,3 +31,15 @@ Once you have your token of the form `Bearer $ACCESS_TOKEN`, you can use that in - targets: - example.com ``` + +## Dashboard + +Administrators can access a live dashboard under `/phoenix/live_dashboard` +giving an overview of uptime, software versions, database stats and more. + +The dashboard also includes a variation of the prometheus metrics, however +they do not exactly match due to respective limitations of the dashboard +and the prometheus exporter. +Even more important, the dashboard collects metrics locally in the browser +only while the page is open and cannot give a view on their past history. +For proper monitoring it is recommended to set up prometheus. -- 2.43.0 From 6bb455702d90ea27fdc8802bedea299654a7be07 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 28 Jan 2024 19:49:20 +0100 Subject: [PATCH 071/149] Document Akkoma API --- CHANGELOG.md | 1 + docs/docs/development/API/akkoma_api.md | 140 ++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 docs/docs/development/API/akkoma_api.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a38e80ba..504d70765 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Added - Full compatibility with Erlang OTP26 - handling of GET /api/v1/preferences +- Akkoma API is now documented ## Changed - OTP builds are now built on erlang OTP26 diff --git a/docs/docs/development/API/akkoma_api.md b/docs/docs/development/API/akkoma_api.md new file mode 100644 index 000000000..786445459 --- /dev/null +++ b/docs/docs/development/API/akkoma_api.md @@ -0,0 +1,140 @@ +# Akkoma API + +Request authentication (if required) and parameters work the same as for [Pleroma API](pleroma_api.md). + +## `/api/v1/akkoma/preferred_frontend/available` +### Returns the available frontends which can be picked as the preferred choice +* Method: `GET` +* Authentication: not required +* Params: none +* Response: JSON +* Example response: +```json +["pleroma-fe/stable"] +``` + +!!! note + There’s also a browser UI under `/akkoma/frontend` + for interactively querying and changing this. + +## `/api/v1/akkoma/preferred_frontend` +### Configures the preferred frontend of this session +* Method: `PUT` +* Authentication: not required +* Params: + * `frontend_name`: STRING containing one of the available frontends +* Response: JSON +* Example response: +```json +{"frontend_name":"pleroma-fe/stable"} +``` + +!!! note + There’s also a browser UI under `/akkoma/frontend` + for interactively querying and changing this. + +## `/api/v1/akkoma/metrics` +### Provides metrics for Prometheus to scrape +* Method: `GET` +* Authentication: required (admin:metrics) +* Params: none +* Response: text +* Example response: +``` +# HELP pleroma_remote_users_total +# TYPE pleroma_remote_users_total gauge +pleroma_remote_users_total 25 +# HELP pleroma_local_statuses_total +# TYPE pleroma_local_statuses_total gauge +pleroma_local_statuses_total 17 +# HELP pleroma_domains_total +# TYPE pleroma_domains_total gauge +pleroma_domains_total 4 +# HELP pleroma_local_users_total +# TYPE pleroma_local_users_total gauge +pleroma_local_users_total 3 +... +``` + +## `/api/v1/akkoma/translation/languages` +### Returns available source and target languages for automated text translation +* Method: `GET` +* Authentication: required +* Params: none +* Response: JSON +* Example response: +```json +{ + "source": [ + {"code":"LV", "name":"Latvian"}, + {"code":"ZH", "name":"Chinese (traditional)"}, + {"code":"EN-US", "name":"English (American)"} + ], + "target": [ + {"code":"EN-GB", "name":"English (British)"}, + {"code":"JP", "name":"Japanese"} + ] +} +``` + +## `/api/v1/akkoma/frontend_settings/:frontend_name` +### Lists all configuration profiles of the selected frontend for the current user +* Method: `GET` +* Authentication: required +* Params: none +* Response: JSON +* Example response: +```json +[ + {"name":"default","version":31} +] +``` + +## `/api/v1/akkoma/frontend_settings/:frontend_name/:profile_name` +### Returns the full selected frontend settings profile of the current user +* Method: `GET` +* Authentication: required +* Params: none +* Response: JSON +* Example response: +```json +{ + "version": 31, + "settings": { + "streaming": true, + "conversationDisplay": "tree", + ... + } +} +``` + +## `/api/v1/akkoma/frontend_settings/:frontend_name/:profile_name` +### Updates the frontend settings profile +* Method: `PUT` +* Authentication: required +* Params: + * `version`: INTEGER + * `settings`: JSON object containing the entire new settings +* Response: JSON +* Example response: +```json +{ + "streaming": false, + "conversationDisplay": "tree", + ... +} +``` + +!!! note + The `version` field must be increased by exactly one on each update + +## `/api/v1/akkoma/frontend_settings/:frontend_name/:profile_name` +### Drops the specified frontend settings profile +* Method: `DELETE` +* Authentication: required +* Params: none +* Response: JSON +* Example response: +```json +{"deleted":"ok"} +``` -- 2.43.0 From 711043f57dc4fba13cfc02a3cc9fe93a7f28a05f Mon Sep 17 00:00:00 2001 From: Oneric Date: Thu, 15 Feb 2024 16:01:43 +0100 Subject: [PATCH 072/149] Document bubble timeline API It was added in cb6e7359af353bb19262ac94b92b41a62819523e. --- docs/docs/development/API/akkoma_api.md | 6 ++++++ .../API/differences_in_mastoapi_responses.md | 14 +++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/docs/docs/development/API/akkoma_api.md b/docs/docs/development/API/akkoma_api.md index 786445459..cc3f7ddec 100644 --- a/docs/docs/development/API/akkoma_api.md +++ b/docs/docs/development/API/akkoma_api.md @@ -138,3 +138,9 @@ pleroma_local_users_total 3 ```json {"deleted":"ok"} ``` + + +## `/api/v1/timelines/bubble` +### Returns a timeline for the local and closely related instances +Works like all other Mastodon-API timeline queries with the documented +[Akkoma-specific additions and tweaks](./differences_in_mastoapi_responses.md#timelines). diff --git a/docs/docs/development/API/differences_in_mastoapi_responses.md b/docs/docs/development/API/differences_in_mastoapi_responses.md index 053dc9663..207ecd6e7 100644 --- a/docs/docs/development/API/differences_in_mastoapi_responses.md +++ b/docs/docs/development/API/differences_in_mastoapi_responses.md @@ -8,20 +8,28 @@ Akkoma uses 128-bit ids as opposed to Mastodon's 64 bits. However, just like Mas ## Timelines +In addition to Mastodon’s timelines, there is also a “bubble timeline” showing +posts from the local instance and a set of closely related instances as chosen +by the administrator. It is available under `/api/v1/timelines/bubble`. + Adding the parameter `with_muted=true` to the timeline queries will also return activities by muted (not by blocked!) users. Adding the parameter `exclude_visibilities` to the timeline queries will exclude the statuses with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`), e.g., `exclude_visibilities[]=direct&exclude_visibilities[]=private`. -Adding the parameter `reply_visibility` to the public and home timelines queries will filter replies. Possible values: without parameter (default) shows all replies, `following` - replies directed to you or users you follow, `self` - replies directed to you. +Adding the parameter `reply_visibility` to the public, bubble or home timelines queries will filter replies. Possible values: without parameter (default) shows all replies, `following` - replies directed to you or users you follow, `self` - replies directed to you. Adding the parameter `instance=lain.com` to the public timeline will show only statuses originating from `lain.com` (or any remote instance). -Home, public, hashtag & list timelines accept these parameters: +All but the direct timeline accept these parameters: - `only_media`: show only statuses with media attached -- `local`: show only local statuses - `remote`: show only remote statuses +Home, public, hashtag & list timelines further accept: + +- `local`: show only local statuses + + ## Statuses - `visibility`: has additional possible values `list` and `local` (for local-only statuses) -- 2.43.0 From cda597a05cd58448df4c054d76b696f4530afd2b Mon Sep 17 00:00:00 2001 From: Oneric Date: Thu, 15 Feb 2024 16:02:13 +0100 Subject: [PATCH 073/149] doc: fix Akkoma identification name Akkoma stopped pretending to be Pleroma here when the mix project name was changed in c07fcdbf2b3c28ae4d1d11df4d872f230410f2b5. --- .../docs/development/API/differences_in_mastoapi_responses.md | 2 +- lib/pleroma/web/api_spec/operations/instance_operation.ex | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/development/API/differences_in_mastoapi_responses.md b/docs/docs/development/API/differences_in_mastoapi_responses.md index 207ecd6e7..8451558ff 100644 --- a/docs/docs/development/API/differences_in_mastoapi_responses.md +++ b/docs/docs/development/API/differences_in_mastoapi_responses.md @@ -1,6 +1,6 @@ # Differences in Mastodon API responses from vanilla Mastodon -A Akkoma instance can be identified by " (compatible; Pleroma )" present in `version` field in response from `/api/v1/instance` +A Akkoma instance can be identified by " (compatible; Akkoma )" present in `version` field in response from `/api/v1/instance` ## Flake IDs diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index 9384acc32..c72fee197 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -137,7 +137,7 @@ defp instance do "background_upload_limit" => 4_000_000, "background_image" => "/static/image.png", "banner_upload_limit" => 4_000_000, - "description" => "Pleroma: An efficient and flexible fediverse server", + "description" => "Akkoma: The cooler fediverse server", "email" => "lain@lain.com", "languages" => ["en"], "max_toot_chars" => 5000, @@ -160,7 +160,7 @@ defp instance do "urls" => %{ "streaming_api" => "wss://lain.com" }, - "version" => "2.7.2 (compatible; Pleroma 2.0.50-536-g25eec6d7-develop)" + "version" => "2.7.2 (compatible; Akkoma 3.9.3-232-g6fde75e1-develop)" } } end -- 2.43.0 From 7622aa27ca1424c73a9cf1d24655674c0f5c4a85 Mon Sep 17 00:00:00 2001 From: Oneric Date: Fri, 9 Feb 2024 21:03:02 +0100 Subject: [PATCH 074/149] Federate user profile background MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently our own frontend doesn’t show backgrounds of other users, this property is already publicly readable via REST API and likely was always intended to be shown and federated. Recently Sharkey added support for profile backgrounds and immediately made them federate and be displayed to others. We use the same AP field as Sharkey here which should make it interoperable both ways out-of-the-box. Ref.: https://activitypub.software/TransFem-org/Sharkey/-/commit/4e6439763544f7b96009dd1411035343fb561d2a --- CHANGELOG.md | 1 + lib/pleroma/user.ex | 5 +++++ lib/pleroma/web/activity_pub/activity_pub.ex | 1 + lib/pleroma/web/activity_pub/views/user_view.ex | 9 ++++++++- test/pleroma/web/activity_pub/side_effects_test.exs | 11 ++++++++++- .../pleroma/web/activity_pub/views/user_view_test.exs | 5 ++++- 6 files changed, 29 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a481ee66b..acf134e06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - OTP builds are now built on erlang OTP26 - The base Phoenix framework is now updated to 1.7 - An `outbox` field has been added to actor profiles to comply with AP spec +- User profile backgrounds do now federate with other Akkoma instances and Sharkey ## Fixed - Documentation issue in which a non-existing nginx file was referenced diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 35f416e6c..8449af620 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -382,6 +382,10 @@ def banner_url(user, options \\ []) do do_optional_url(user.banner, "#{Endpoint.url()}/images/banner.png", options) end + def background_url(user) do + do_optional_url(user.background, nil, no_default: true) + end + defp do_optional_url(field, default, options) do case field do %{"url" => [%{"href" => href} | _]} when is_binary(href) -> @@ -466,6 +470,7 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do :avatar, :ap_enabled, :banner, + :background, :is_locked, :last_refreshed_at, :uri, diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index e4c626d36..4a8ce2d3d 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1603,6 +1603,7 @@ defp object_to_user_data(data, additional) do uri: get_actor_url(data["url"]), ap_enabled: true, banner: normalize_image(data["image"]), + background: normalize_image(data["backgroundUrl"]), fields: fields, emoji: emojis, is_locked: is_locked, diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index d6d62a9a4..fe70022f1 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -112,6 +112,8 @@ def render("user.json", %{user: user}) do } |> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user)) |> Map.merge(maybe_make_image(&User.banner_url/2, "image", user)) + # Yes, the key is named ...Url eventhough it is a whole 'Image' object + |> Map.merge(maybe_insert_image("backgroundUrl", User.background_url(user))) |> Map.merge(Utils.make_json_ld_header()) end @@ -287,7 +289,12 @@ def collection(collection, iri, page, show_items \\ true, total \\ nil) do end defp maybe_make_image(func, key, user) do - if image = func.(user, no_default: true) do + image = func.(user, no_default: true) + maybe_insert_image(key, image) + end + + defp maybe_insert_image(key, image) do + if image do %{ key => %{ "type" => "Image", diff --git a/test/pleroma/web/activity_pub/side_effects_test.exs b/test/pleroma/web/activity_pub/side_effects_test.exs index 80714b1db..28a591d3c 100644 --- a/test/pleroma/web/activity_pub/side_effects_test.exs +++ b/test/pleroma/web/activity_pub/side_effects_test.exs @@ -155,7 +155,13 @@ test "it blocks but does not unfollow if the relevant setting is set", %{ user = insert(:user, local: false) {:ok, update_data, []} = - Builder.update(user, %{"id" => user.ap_id, "type" => "Person", "name" => "new name!"}) + Builder.update(user, %{ + "id" => user.ap_id, + "type" => "Person", + "name" => "new name!", + "icon" => %{"type" => "Image", "url" => "https://example.org/icon.png"}, + "backgroundUrl" => %{"type" => "Image", "url" => "https://example.org/bg.jxl"} + }) {:ok, update, _meta} = ActivityPub.persist(update_data, local: true) @@ -165,7 +171,10 @@ test "it blocks but does not unfollow if the relevant setting is set", %{ test "it updates the user", %{user: user, update: update} do {:ok, _, _} = SideEffects.handle(update) user = User.get_by_id(user.id) + assert user.name == "new name!" + assert [%{"href" => "https://example.org/icon.png"}] = user.avatar["url"] + assert [%{"href" => "https://example.org/bg.jxl"}] = user.background["url"] end test "it uses a given changeset to update", %{user: user, update: update} do diff --git a/test/pleroma/web/activity_pub/views/user_view_test.exs b/test/pleroma/web/activity_pub/views/user_view_test.exs index ef1bd4fde..abe91cdea 100644 --- a/test/pleroma/web/activity_pub/views/user_view_test.exs +++ b/test/pleroma/web/activity_pub/views/user_view_test.exs @@ -58,16 +58,19 @@ test "Does not add an avatar image if the user hasn't set one" do result = UserView.render("user.json", %{user: user}) refute result["icon"] refute result["image"] + refute result["backgroundUrl"] user = insert(:user, avatar: %{"url" => [%{"href" => "https://someurl"}]}, - banner: %{"url" => [%{"href" => "https://somebanner"}]} + banner: %{"url" => [%{"href" => "https://somebanner"}]}, + background: %{"url" => [%{"href" => "https://somebackground"}]} ) result = UserView.render("user.json", %{user: user}) assert result["icon"]["url"] == "https://someurl" assert result["image"]["url"] == "https://somebanner" + assert result["backgroundUrl"]["url"] == "https://somebackground" end test "renders an invisible user with the invisible property set to true" do -- 2.43.0 From e99e2407f3731eba2937b7a2db7b2ef462c99e3b Mon Sep 17 00:00:00 2001 From: Oneric Date: Sat, 10 Feb 2024 17:27:11 +0100 Subject: [PATCH 075/149] Add background_removal to SimplePolicy MRF --- CHANGELOG.md | 1 + docs/docs/configuration/cheatsheet.md | 1 + docs/docs/configuration/mrf.md | 1 + .../web/activity_pub/mrf/simple_policy.ex | 25 ++++++++++- .../activity_pub/mrf/simple_policy_test.exs | 41 +++++++++++++++++++ 5 files changed, 68 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index acf134e06..fee4d95e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - handling of GET /api/v1/preferences - Akkoma API is now documented - ability to auto-approve follow requests from users you are already following +- The SimplePolicy MRF can now strip user backgrounds from selected remote hosts ## Changed - OTP builds are now built on erlang OTP26 diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index 2f53f0c78..0933b29c5 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -144,6 +144,7 @@ To add configuration to your config file, you can copy it from the base config. * `report_removal`: List of instances to reject reports from and the reason for doing so. * `avatar_removal`: List of instances to strip avatars from and the reason for doing so. * `banner_removal`: List of instances to strip banners from and the reason for doing so. +* `background_removal`: List of instances to strip user backgrounds from and the reason for doing so. * `reject_deletes`: List of instances to reject deletions from and the reason for doing so. #### :mrf_subchain diff --git a/docs/docs/configuration/mrf.md b/docs/docs/configuration/mrf.md index 170b26792..0a17b3112 100644 --- a/docs/docs/configuration/mrf.md +++ b/docs/docs/configuration/mrf.md @@ -35,6 +35,7 @@ Once `SimplePolicy` is enabled, you can configure various groups in the `:mrf_si * `media_removal`: Servers in this group will have media stripped from incoming messages. * `avatar_removal`: Avatars from these servers will be stripped from incoming messages. * `banner_removal`: Banner images from these servers will be stripped from incoming messages. +* `background_removal`: User background images from these servers will be stripped from incoming messages. * `report_removal`: Servers in this group will have their reports (flags) rejected. * `federated_timeline_removal`: Servers in this group will have their messages unlisted from the public timelines by flipping the `to` and `cc` fields. * `reject_deletes`: Deletion requests will be rejected from these servers. diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex index c2e17ca9e..0b8b846ec 100644 --- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex @@ -178,6 +178,23 @@ defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image defp check_banner_removal(_actor_info, object), do: {:ok, object} + defp check_background_removal( + %{host: actor_host} = _actor_info, + %{"backgroundUrl" => _bg} = object + ) do + background_removal = + instance_list(:background_removal) + |> MRF.subdomains_regex() + + if MRF.subdomain_match?(background_removal, actor_host) do + {:ok, Map.delete(object, "backgroundUrl")} + else + {:ok, object} + end + end + + defp check_background_removal(_actor_info, object), do: {:ok, object} + defp extract_context_uri(%{"conversation" => "tag:" <> rest}) do rest |> String.split(",", parts: 2, trim: true) @@ -283,7 +300,8 @@ def filter(%{"id" => actor, "type" => obj_type} = object) with {:ok, _} <- check_accept(actor_info), {:ok, _} <- check_reject(actor_info), {:ok, object} <- check_avatar_removal(actor_info, object), - {:ok, object} <- check_banner_removal(actor_info, object) do + {:ok, object} <- check_banner_removal(actor_info, object), + {:ok, object} <- check_background_removal(actor_info, object) do {:ok, object} else {:reject, nil} -> {:reject, "[SimplePolicy]"} @@ -447,6 +465,11 @@ def config_description do key: :banner_removal, description: "List of instances to strip banners from and the reason for doing so" }, + %{ + key: :background_removal, + description: + "List of instances to strip user backgrounds from and the reason for doing so" + }, %{ key: :reject_deletes, description: "List of instances to reject deletions from and the reason for doing so" diff --git a/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs b/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs index c6600f001..1ae42036d 100644 --- a/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/simple_policy_test.exs @@ -19,6 +19,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicyTest do accept: [], avatar_removal: [], banner_removal: [], + background_removal: [], reject_deletes: [] ) @@ -618,6 +619,42 @@ test "match with wildcard domain" do end end + describe "when :background_removal" do + test "is empty" do + clear_config([:mrf_simple, :background_removal], []) + + remote_user = build_remote_user() + + assert SimplePolicy.filter(remote_user) == {:ok, remote_user} + end + + test "is not empty but it doesn't have a matching host" do + clear_config([:mrf_simple, :background_removal], [{"non.matching.remote", ""}]) + + remote_user = build_remote_user() + + assert SimplePolicy.filter(remote_user) == {:ok, remote_user} + end + + test "has a matching host" do + clear_config([:mrf_simple, :background_removal], [{"remote.instance", ""}]) + + remote_user = build_remote_user() + {:ok, filtered} = SimplePolicy.filter(remote_user) + + refute filtered["backgroundUrl"] + end + + test "match with wildcard domain" do + clear_config([:mrf_simple, :background_removal], [{"*.remote.instance", ""}]) + + remote_user = build_remote_user() + {:ok, filtered} = SimplePolicy.filter(remote_user) + + refute filtered["backgroundUrl"] + end + end + describe "when :reject_deletes is empty" do setup do: clear_config([:mrf_simple, :reject_deletes], []) @@ -701,6 +738,10 @@ defp build_remote_user do "url" => "http://example.com/image.jpg", "type" => "Image" }, + "backgroundUrl" => %{ + "url" => "http://example.com/background.jpg", + "type" => "Image" + }, "type" => "Person" } end -- 2.43.0 From 3b0714c4fde2785f8f38619a188b04e5528812a1 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sat, 17 Feb 2024 01:59:50 +0000 Subject: [PATCH 076/149] Fix SimplePolicy blocking account updates MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This fixes an oversight in e99e2407f3731eba2937b7a2db7b2ef462c99e3b which added background_removal as a possible SimplePolicy setting. However, it did _not_ add a default value to the base config and as it turns out instance_list doesn’t handle unset options well. In effect this caused federating instances with SimplePolicy enabled but background_removal not explicitly configured to always trip up for outgoing account updates in check_background_removal (and incoming updates from Sharkey). For added ""fun"" this error was able to block account updates made e.g. via /api/v1/accounts/update_credentials. Tests were unaffected since they explicitly override all relevant config options. Set a default to avoid all this (note to self: don’t forget next time, baka!) --- config/config.exs | 1 + 1 file changed, 1 insertion(+) diff --git a/config/config.exs b/config/config.exs index 27c314e01..a8581cbe0 100644 --- a/config/config.exs +++ b/config/config.exs @@ -377,6 +377,7 @@ accept: [], avatar_removal: [], banner_removal: [], + background_removal: [], reject_deletes: [], handle_threads: true -- 2.43.0 From 7a0e27a7467058fc67bbbbb6c3e23ab62044dd8e Mon Sep 17 00:00:00 2001 From: Erin Shepherd Date: Sat, 17 Feb 2024 13:21:56 +0100 Subject: [PATCH 077/149] Disable busy waits in the default OTP `vm.args` configuration. This vastly reduces idle CPU usage, which should generally be beneficial for most small-to-medium sized instances. Additionally update the documentation to specify how to override the vm.args file for OTP installs --- docs/docs/configuration/optimisation/optimizing_beam.md | 7 ++++++- rel/vm.args.eex | 6 ++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/docs/docs/configuration/optimisation/optimizing_beam.md b/docs/docs/configuration/optimisation/optimizing_beam.md index bbdc49725..747773f3e 100644 --- a/docs/docs/configuration/optimisation/optimizing_beam.md +++ b/docs/docs/configuration/optimisation/optimizing_beam.md @@ -25,11 +25,14 @@ Tuning the BEAM requires you provide a config file normally called [vm.args](htt `ExecStart=/usr/bin/elixir --erl '-args_file /opt/akkoma/config/vm.args' -S /usr/bin/mix phx.server` +If using an OTP release, set the `RELEASE_VM_ARGS` environment variable to the path to the vm.args file. + Check your OS documentation to adopt a similar strategy on other platforms. ### Virtual Machine and/or few CPU cores -Disable the busy-waiting. This should generally only be done if you're on a platform that does burst scheduling, like AWS. +Disable the busy-waiting. This should generally be done if you're on a platform that does burst scheduling, like AWS, or if you're running other +services on the same machine. **vm.args:** @@ -39,6 +42,8 @@ Disable the busy-waiting. This should generally only be done if you're on a plat +sbwtdio none ``` +These settings are enabled by default for OTP releases + ### Dedicated Hardware Enable more busy waiting, increase the internal maximum limit of BEAM processes and ports. You can use this if you run on dedicated hardware, but it is not necessary. diff --git a/rel/vm.args.eex b/rel/vm.args.eex index 71e803264..6c252186e 100644 --- a/rel/vm.args.eex +++ b/rel/vm.args.eex @@ -9,3 +9,9 @@ ## Tweak GC to run more often ##-env ERL_FULLSWEEP_AFTER 10 + +## Disable busy waits; vastly reduces CPU usage while idle +## See https://docs.akkoma.dev/stable/configuration/optimisation/optimizing_beam/#virtual-machine-andor-few-cpu-cores ++sbwt none ++sbwtdcpu none ++sbwtdio none -- 2.43.0 From 1ef8b967d2084ce31df98b7c5fa6cc8229f28c39 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sat, 10 Feb 2024 01:46:10 +0100 Subject: [PATCH 078/149] test: fix typos affecting remove factory Apparently nothing used this factory until now --- test/support/factory.ex | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/support/factory.ex b/test/support/factory.ex index 42c940c52..321caf1f4 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -307,7 +307,7 @@ def add_activity_factory(attrs \\ %{}) do featured_collection_activity(attrs, "Add") end - def remove_activity_factor(attrs \\ %{}) do + def remove_activity_factory(attrs \\ %{}) do featured_collection_activity(attrs, "Remove") end @@ -328,7 +328,7 @@ defp featured_collection_activity(attrs, type) do "target" => user.featured_address, "object" => note.data["object"], "actor" => note.data["actor"], - "type" => "Add", + "type" => type, "to" => [Pleroma.Constants.as_public()], "cc" => [user.follower_address] } -- 2.43.0 From 1a7839eaf26e6fe502f778b12bab9e165108e8be Mon Sep 17 00:00:00 2001 From: Oneric Date: Fri, 9 Feb 2024 23:26:23 +0100 Subject: [PATCH 079/149] Prune old Update activities Once processed they serve no purpose anymore afaict. Therefor, lets prune them like other transient activities to not unnecessarily bloat the table. --- lib/pleroma/activity/pruner.ex | 9 ++++ .../workers/cron/database_prune_worker.ex | 3 ++ test/pleroma/activity/pruner_test.exs | 38 ++++++++++++-- test/support/factory.ex | 49 +++++++++++++++++++ 4 files changed, 95 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/activity/pruner.ex b/lib/pleroma/activity/pruner.ex index 7f561ebae..54a40b534 100644 --- a/lib/pleroma/activity/pruner.ex +++ b/lib/pleroma/activity/pruner.ex @@ -26,6 +26,15 @@ def prune_undos do |> Repo.delete_all(timeout: :infinity) end + def prune_updates do + before_time = cutoff() + + from(a in Activity, + where: fragment("?->>'type' = ?", a.data, "Update") and a.inserted_at < ^before_time + ) + |> Repo.delete_all(timeout: :infinity) + end + def prune_removes do before_time = cutoff() diff --git a/lib/pleroma/workers/cron/database_prune_worker.ex b/lib/pleroma/workers/cron/database_prune_worker.ex index 58995c69a..ced4b27b7 100644 --- a/lib/pleroma/workers/cron/database_prune_worker.ex +++ b/lib/pleroma/workers/cron/database_prune_worker.ex @@ -21,6 +21,9 @@ def perform(_job) do Logger.info("Pruning old undos") ActivityPruner.prune_undos() + Logger.info("Pruning old updates") + ActivityPruner.prune_updates() + Logger.info("Pruning old removes") ActivityPruner.prune_removes() diff --git a/test/pleroma/activity/pruner_test.exs b/test/pleroma/activity/pruner_test.exs index e8d4b30aa..26db60c1c 100644 --- a/test/pleroma/activity/pruner_test.exs +++ b/test/pleroma/activity/pruner_test.exs @@ -6,22 +6,52 @@ defmodule Pleroma.Activity.PrunerTest do import Pleroma.Factory - describe "prune_deletes" do - test "it prunes old delete objects" do + describe "prune_transient_activities" do + test "it prunes old transient activities" do user = insert(:user) + old_time = DateTime.utc_now() |> DateTime.add(-31 * 24, :hour) new_delete = insert(:delete_activity, type: "Delete", user: user) old_delete = insert(:delete_activity, - type: "Delete", user: user, - inserted_at: DateTime.utc_now() |> DateTime.add(-31 * 24, :hour) + inserted_at: old_time ) + new_update = insert(:update_activity, type: "Update", user: user) + + old_update = + insert(:update_activity, + type: "Update", + user: user, + inserted_at: old_time + ) + + new_undo = insert(:undo_activity) + + old_undo = insert(:undo_activity, inserted_at: old_time) + + new_remove = insert(:remove_activity) + + old_remove = insert(:remove_activity, inserted_at: old_time) + Pruner.prune_deletes() + Pruner.prune_updates() + Pruner.prune_undos() + Pruner.prune_removes() + assert Activity.get_by_id(new_delete.id) refute Activity.get_by_id(old_delete.id) + + assert Activity.get_by_id(new_update.id) + refute Activity.get_by_id(old_update.id) + + assert Activity.get_by_id(new_undo.id) + refute Activity.get_by_id(old_undo.id) + + assert Activity.get_by_id(new_remove.id) + refute Activity.get_by_id(old_remove.id) end end diff --git a/test/support/factory.ex b/test/support/factory.ex index 321caf1f4..e21b8fc1e 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Factory do alias Pleroma.Object alias Pleroma.User + alias Pleroma.Web.ActivityPub.UserView @rsa_keys [ "test/fixtures/rsa_keys/key_1.pem", @@ -554,6 +555,54 @@ def delete_activity_factory(attrs \\ %{}) do |> Map.merge(attrs) end + def undo_activity_factory(attrs \\ %{}) do + like_activity = attrs[:like_activity] || insert(:like_activity) + attrs = Map.drop(attrs, [:like_activity]) + + data = + %{ + "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(), + "type" => "Undo", + "actor" => like_activity.data["actor"], + "to" => like_activity.data["to"], + "object" => like_activity.data["id"], + "published" => DateTime.utc_now() |> DateTime.to_iso8601(), + "context" => like_activity.data["context"] + } + + %Pleroma.Activity{ + data: data, + actor: data["actor"], + recipients: data["to"] + } + |> Map.merge(attrs) + end + + def update_activity_factory(attrs \\ %{}) do + user = attrs[:user] || insert(:user, nickname: "testuser") + attrs = Map.drop(attrs, [:user]) + + user_data = + UserView.render("user.json", %{user: user}) + |> Map.merge(%{"name" => "new display name"}) + + data = %{ + "type" => "Update", + "to" => [ + user_data["followers"], + "https://www.w3.org/ns/activitystreams#Public" + ], + "actor" => user_data["id"], + "object" => user_data + } + + %Pleroma.Activity{ + actor: user_data["id"], + data: data + } + |> Map.merge(attrs) + end + def oauth_app_factory do %Pleroma.Web.OAuth.App{ client_name: sequence(:client_name, &"Some client #{&1}"), -- 2.43.0 From 12e7d0a25ce3317af2ee1b82ba1337a158798c3e Mon Sep 17 00:00:00 2001 From: rick Date: Sat, 17 Feb 2024 22:25:12 +0100 Subject: [PATCH 080/149] added doc for mrf_reject_newly_created_account_notes --- docs/docs/configuration/cheatsheet.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index 0933b29c5..71d6dc09f 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -124,6 +124,7 @@ To add configuration to your config file, you can copy it from the base config. * `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)). * `Pleroma.Web.ActivityPub.MRF.NormalizeMarkup`: Pass inbound HTML through a scrubber to make sure it doesn't have anything unusual in it. On by default, cannot be turned off. * `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Append a link to a post that quotes another post with the link to the quoted post, to ensure that software that does not understand quotes can have full context. On by default, cannot be turned off. + * `Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy`: Drops posts of users which are newer than the configured time. For exmple it drops all post of users which where created one hour ago. Great to block spam accounts. (See [`:mrf_reject_newly_created_account_notes`](#:mrf_reject_newly_created_account_notes)) * `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo). * `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. * `transparency_obfuscate_domains`: Show domains with `*` in the middle, to censor them if needed. For example, `ridingho.me` will show as `rid*****.me` @@ -223,6 +224,18 @@ Notes: - The hashtags in the configuration do not have a leading `#`. - This MRF Policy is always enabled, if you want to disable it you have to set empty lists +#### :mrf_reject_newly_created_account_notes +This drops all posts of users which where created within the configured timeframe. +It only drops posts. Follows, reposts and so one are not effected. + +* `age`: Time in seconds of which posts for newly created users are dropped. + +An example: + +```elixir +config :pleroma, :mrf_reject_newly_created_account_notes, age: 86400 +``` + ### :activitypub * `unfollow_blocked`: Whether blocks result in people getting unfollowed * `outgoing_blocks`: Whether to federate blocks to other instances -- 2.43.0 From 718104117f102177727e38393ce9514797464bd3 Mon Sep 17 00:00:00 2001 From: rick Date: Sat, 17 Feb 2024 22:34:55 +0100 Subject: [PATCH 081/149] fix link --- docs/docs/configuration/cheatsheet.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index 71d6dc09f..2e09082fc 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -124,7 +124,7 @@ To add configuration to your config file, you can copy it from the base config. * `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)). * `Pleroma.Web.ActivityPub.MRF.NormalizeMarkup`: Pass inbound HTML through a scrubber to make sure it doesn't have anything unusual in it. On by default, cannot be turned off. * `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Append a link to a post that quotes another post with the link to the quoted post, to ensure that software that does not understand quotes can have full context. On by default, cannot be turned off. - * `Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy`: Drops posts of users which are newer than the configured time. For exmple it drops all post of users which where created one hour ago. Great to block spam accounts. (See [`:mrf_reject_newly_created_account_notes`](#:mrf_reject_newly_created_account_notes)) + * `Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy`: Drops posts of users which are newer than the configured time. For exmple it drops all post of users which where created one hour ago. Great to block spam accounts. (See [`:mrf_reject_newly_created_account_notes`](#mrf_reject_newly_created_account_notes)) * `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo). * `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. * `transparency_obfuscate_domains`: Show domains with `*` in the middle, to censor them if needed. For example, `ridingho.me` will show as `rid*****.me` -- 2.43.0 From db49daa4a5e69ebddf5d07c5ce2629e71fda663e Mon Sep 17 00:00:00 2001 From: rick Date: Sat, 17 Feb 2024 22:57:56 +0100 Subject: [PATCH 082/149] make it clearer what it affects --- docs/docs/configuration/cheatsheet.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index 2e09082fc..93fc68d6e 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -124,7 +124,7 @@ To add configuration to your config file, you can copy it from the base config. * `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)). * `Pleroma.Web.ActivityPub.MRF.NormalizeMarkup`: Pass inbound HTML through a scrubber to make sure it doesn't have anything unusual in it. On by default, cannot be turned off. * `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Append a link to a post that quotes another post with the link to the quoted post, to ensure that software that does not understand quotes can have full context. On by default, cannot be turned off. - * `Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy`: Drops posts of users which are newer than the configured time. For exmple it drops all post of users which where created one hour ago. Great to block spam accounts. (See [`:mrf_reject_newly_created_account_notes`](#mrf_reject_newly_created_account_notes)) + * `Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy`: Drops posts of users, whose accounts are younger/newer than the configured time (in seconds). For exmple it drops all post of users which where created one hour ago. Great to block spam accounts. (See [`:mrf_reject_newly_created_account_notes`](#mrf_reject_newly_created_account_notes)) * `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo). * `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. * `transparency_obfuscate_domains`: Show domains with `*` in the middle, to censor them if needed. For example, `ridingho.me` will show as `rid*****.me` -- 2.43.0 From b4c832471c79ee88783fcef5ebed167d018289c5 Mon Sep 17 00:00:00 2001 From: stefan230 Date: Sat, 17 Feb 2024 22:09:47 +0000 Subject: [PATCH 083/149] docs/docs/configuration/cheatsheet.md aktualisiert fixed up some grammer / wording. removed a setence and made wording more in line with what I could find in Admin-FE (especially wording of "rejecting" vs. dropping) --- docs/docs/configuration/cheatsheet.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index 93fc68d6e..a4132e618 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -124,7 +124,7 @@ To add configuration to your config file, you can copy it from the base config. * `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)). * `Pleroma.Web.ActivityPub.MRF.NormalizeMarkup`: Pass inbound HTML through a scrubber to make sure it doesn't have anything unusual in it. On by default, cannot be turned off. * `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Append a link to a post that quotes another post with the link to the quoted post, to ensure that software that does not understand quotes can have full context. On by default, cannot be turned off. - * `Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy`: Drops posts of users, whose accounts are younger/newer than the configured time (in seconds). For exmple it drops all post of users which where created one hour ago. Great to block spam accounts. (See [`:mrf_reject_newly_created_account_notes`](#mrf_reject_newly_created_account_notes)) + * `Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy`: Rejects posts of users, whose accounts are more recent than the configured time (in seconds). Great to block spam accounts. (See [`:mrf_reject_newly_created_account_notes`](#mrf_reject_newly_created_account_notes)) * `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo). * `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. * `transparency_obfuscate_domains`: Show domains with `*` in the middle, to censor them if needed. For example, `ridingho.me` will show as `rid*****.me` @@ -225,12 +225,12 @@ Notes: - This MRF Policy is always enabled, if you want to disable it you have to set empty lists #### :mrf_reject_newly_created_account_notes -This drops all posts of users which where created within the configured timeframe. -It only drops posts. Follows, reposts and so one are not effected. +Rejects posts of users, whose accounts are more recent than the configured time (in seconds). +Only drops posts. Follows, reposts, etc. are not effected. -* `age`: Time in seconds of which posts for newly created users are dropped. +* `age`: Time below which to reject (in seconds) -An example: +An example: (86400 seconds = 24 hours) ```elixir config :pleroma, :mrf_reject_newly_created_account_notes, age: 86400 -- 2.43.0 From 37e2a35b865e395ef635bfd6cfb0cb7bca985048 Mon Sep 17 00:00:00 2001 From: Oneric Date: Fri, 16 Feb 2024 04:32:09 +0100 Subject: [PATCH 084/149] Fix Twitter metadata MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This partly reverts 1d884fd9143dc165d745bf1b5e86bcc65332d6b9 while fixing both the issue it addressed and the issue it caused. The above commit successfully fixed OpenGraph metadata tags which until then always showed the user bio instead of post content by handing the activities AP ID as url to the Metadata builder _instead_ of passing the internal ID as activity_id. However, in doing so the commit instead inflicted this very problem onto Twitter metadata tags which ironically are used by akkoma-fe. This is because while the OpenGraph builder wants an URL as url, the Twitter builder needs the internal ID to build the URL to the embedded player for videos and has no URL property. Thanks to twpol for tracking down this root cause in #644. Now, once identified the problem is simple, but this simplicity invites multiple possible solutions to bikeshed about. 1. Just pass both properties to the builder and let them pick 2. Drop the url parameter from the OpenGraph builder and instead a) build static-fe URL of the post from the ID (like Twitter) b) use the passed-in object’s AP ID as an URL Approach 2a has the disadvantage of hardcoding the expected URL outside the router, which will be problematic should it ever change. Approach 2b is conceptually similar to how the builder works atm. However, the og:url is supposed to be a _permanent_ ID, by changing it we might, afaiui, technically violate OpenGraph specs(?). (Though its real-world consequence may very well be near non-existent.) This leaves just approach 1, which this commit implements. Albeit it too is not without nits to pick, as it leaves the metadata builders with an inconsistent interface. Additionally, this will resolve the subotpimal Discord previews for content-less image posts reported in #664. Discord already prefers OpenGraph metadata, so it’s mostly unaffected. However, it appears when encountering an explicitly empty OpenGraph description and a non-empty Twitter description, it replaces just the empty field with its Twitter counterpart, resulting in the user’s bio slipping into the preview. Secondly, regardless of any OpenGraph tags, Discord uses twitter:card to decide how prominently images should be, but due to the bug the card type was stuck as "summary", forcing images to always remain small. Root cause identified by: twpol Fixes: https://akkoma.dev/AkkomaGang/akkoma/issues/644 Fixes: https://akkoma.dev/AkkomaGang/akkoma/issues/664 --- lib/pleroma/web/static_fe/static_fe_controller.ex | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex index f0d45293e..b1ea3178d 100644 --- a/lib/pleroma/web/static_fe/static_fe_controller.ex +++ b/lib/pleroma/web/static_fe/static_fe_controller.ex @@ -24,7 +24,13 @@ def show(%{assigns: %{notice_id: notice_id}} = conn, _params) do true <- Visibility.is_public?(activity.object), {_, true} <- {:visible?, Visibility.visible_for_user?(activity, _reading_user = nil)}, %User{} = user <- User.get_by_ap_id(activity.object.data["actor"]) do - meta = Metadata.build_tags(%{url: activity.data["id"], object: activity.object, user: user}) + meta = + Metadata.build_tags(%{ + activity_id: notice_id, + url: activity.data["id"], + object: activity.object, + user: user + }) timeline = activity.object.data["context"] -- 2.43.0 From 1ec6e193e693b221fffa77b623ed0e418ac08add Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 18 Feb 2024 22:47:02 +0100 Subject: [PATCH 085/149] doc: clarify RejectNewlyCreated uses local account discovery --- docs/docs/configuration/cheatsheet.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index a4132e618..4d63c94a4 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -124,7 +124,7 @@ To add configuration to your config file, you can copy it from the base config. * `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)). * `Pleroma.Web.ActivityPub.MRF.NormalizeMarkup`: Pass inbound HTML through a scrubber to make sure it doesn't have anything unusual in it. On by default, cannot be turned off. * `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Append a link to a post that quotes another post with the link to the quoted post, to ensure that software that does not understand quotes can have full context. On by default, cannot be turned off. - * `Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy`: Rejects posts of users, whose accounts are more recent than the configured time (in seconds). Great to block spam accounts. (See [`:mrf_reject_newly_created_account_notes`](#mrf_reject_newly_created_account_notes)) + * `Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy`: Rejects posts of users the server only recently learned about for a while. Great to block spam accounts. (See [`:mrf_reject_newly_created_account_notes`](#mrf_reject_newly_created_account_notes)) * `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo). * `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. * `transparency_obfuscate_domains`: Show domains with `*` in the middle, to censor them if needed. For example, `ridingho.me` will show as `rid*****.me` @@ -225,7 +225,8 @@ Notes: - This MRF Policy is always enabled, if you want to disable it you have to set empty lists #### :mrf_reject_newly_created_account_notes -Rejects posts of users, whose accounts are more recent than the configured time (in seconds). +After initially encountering an user, all their posts +will be rejected for the configured time (in seconds). Only drops posts. Follows, reposts, etc. are not effected. * `age`: Time below which to reject (in seconds) -- 2.43.0 From 8f1776a8a7930f1e39febc862ad747d06d1d84d9 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 18 Feb 2024 22:25:42 +0100 Subject: [PATCH 086/149] Purge leftovers from FollowBot MRF It was dropped in 9db4c2429fce2d9f56cc59de64b3d6b62e1a7071 --- config/config.exs | 2 -- docs/docs/configuration/cheatsheet.md | 1 - 2 files changed, 3 deletions(-) diff --git a/config/config.exs b/config/config.exs index a8581cbe0..1c531344c 100644 --- a/config/config.exs +++ b/config/config.exs @@ -406,8 +406,6 @@ threshold: 604_800, actions: [:delist, :strip_followers] -config :pleroma, :mrf_follow_bot, follower_nickname: nil - config :pleroma, :mrf_reject_newly_created_account_notes, age: 86_400 config :pleroma, :rich_media, diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index 4d63c94a4..afa9b2571 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -119,7 +119,6 @@ To add configuration to your config file, you can copy it from the base config. * `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)). * `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.Workers.PurgeExpiredActivity` to be enabled for processing the scheduled delections. * `Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy`: Makes all bot posts to disappear from public timelines. - * `Pleroma.Web.ActivityPub.MRF.FollowBotPolicy`: Automatically follows newly discovered users from the specified bot account. Local accounts, locked accounts, and users with "#nobot" in their bio are respected and excluded from being followed. * `Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy`: Drops follow requests from followbots. Users can still allow bots to follow them by first following the bot. * `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)). * `Pleroma.Web.ActivityPub.MRF.NormalizeMarkup`: Pass inbound HTML through a scrubber to make sure it doesn't have anything unusual in it. On by default, cannot be turned off. -- 2.43.0 From 1640d19448ead4892c8d9d2188a31f4ca9b0f1f6 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 18 Feb 2024 22:52:43 +0100 Subject: [PATCH 087/149] doc/cheatsheet: move :activitypub section ahead Else it is too easy to mistake for another MRF policy. --- docs/docs/configuration/cheatsheet.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index afa9b2571..334ad411b 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -129,6 +129,15 @@ To add configuration to your config file, you can copy it from the base config. * `transparency_obfuscate_domains`: Show domains with `*` in the middle, to censor them if needed. For example, `ridingho.me` will show as `rid*****.me` ## Federation +### :activitypub +* `unfollow_blocked`: Whether blocks result in people getting unfollowed +* `outgoing_blocks`: Whether to federate blocks to other instances +* `blockers_visible`: Whether a user can see the posts of users who blocked them +* `deny_follow_blocked`: Whether to disallow following an account that has blocked the user in question +* `sign_object_fetches`: Sign object fetches with HTTP signatures +* `authorized_fetch_mode`: Require HTTP signatures for AP fetches +* `max_collection_objects`: The maximum number of objects to fetch from a remote AP collection. + ### MRF policies !!! note @@ -236,15 +245,6 @@ An example: (86400 seconds = 24 hours) config :pleroma, :mrf_reject_newly_created_account_notes, age: 86400 ``` -### :activitypub -* `unfollow_blocked`: Whether blocks result in people getting unfollowed -* `outgoing_blocks`: Whether to federate blocks to other instances -* `blockers_visible`: Whether a user can see the posts of users who blocked them -* `deny_follow_blocked`: Whether to disallow following an account that has blocked the user in question -* `sign_object_fetches`: Sign object fetches with HTTP signatures -* `authorized_fetch_mode`: Require HTTP signatures for AP fetches -* `max_collection_objects`: The maximum number of objects to fetch from a remote AP collection. - ## Pleroma.User * `restricted_nicknames`: List of nicknames users may not register with. -- 2.43.0 From 8e7a89605d5696754418ff857bf759f01eab3ae6 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 18 Feb 2024 22:54:02 +0100 Subject: [PATCH 088/149] doc/cheatsheet: move MRF policies key to end of section This makes it easier to spot the transparency options --- docs/docs/configuration/cheatsheet.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index 334ad411b..4769cdb4f 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -104,6 +104,9 @@ To add configuration to your config file, you can copy it from the base config. ## Message rewrite facility ### :mrf +* `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo). +* `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. +* `transparency_obfuscate_domains`: Show domains with `*` in the middle, to censor them if needed. For example, `ridingho.me` will show as `rid*****.me` * `policies`: Message Rewrite Policy, either one or a list. Here are the ones available by default: * `Pleroma.Web.ActivityPub.MRF.NoOpPolicy`: Doesn’t modify activities (default). * `Pleroma.Web.ActivityPub.MRF.DropPolicy`: Drops all activities. It generally doesn’t makes sense to use in production. @@ -124,9 +127,6 @@ To add configuration to your config file, you can copy it from the base config. * `Pleroma.Web.ActivityPub.MRF.NormalizeMarkup`: Pass inbound HTML through a scrubber to make sure it doesn't have anything unusual in it. On by default, cannot be turned off. * `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Append a link to a post that quotes another post with the link to the quoted post, to ensure that software that does not understand quotes can have full context. On by default, cannot be turned off. * `Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy`: Rejects posts of users the server only recently learned about for a while. Great to block spam accounts. (See [`:mrf_reject_newly_created_account_notes`](#mrf_reject_newly_created_account_notes)) -* `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo). -* `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. -* `transparency_obfuscate_domains`: Show domains with `*` in the middle, to censor them if needed. For example, `ridingho.me` will show as `rid*****.me` ## Federation ### :activitypub -- 2.43.0 From 7a2d68c3ab79ce352d2b719b683bbf11965539d5 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 18 Feb 2024 23:12:30 +0100 Subject: [PATCH 089/149] doc/cheatsheet: add link to ActivityExpiration config details --- docs/docs/configuration/cheatsheet.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index 4769cdb4f..1e5ce6700 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -121,6 +121,7 @@ To add configuration to your config file, you can copy it from the base config. * `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (See [`:mrf_vocabulary`](#mrf_vocabulary)). * `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)). * `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.Workers.PurgeExpiredActivity` to be enabled for processing the scheduled delections. + (See [`:mrf_activity_expiration`](#mrf_activity_expiration)) * `Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy`: Makes all bot posts to disappear from public timelines. * `Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy`: Drops follow requests from followbots. Users can still allow bots to follow them by first following the bot. * `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)). -- 2.43.0 From da4190c46ee02c0731c27b8728d211cf9757c261 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 18 Feb 2024 23:02:48 +0100 Subject: [PATCH 090/149] doc/cheatsheet: split out always active MRFs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit It doesn’t make sense to add/remove them from the policies list --- docs/docs/configuration/cheatsheet.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index 1e5ce6700..e535c2324 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -125,10 +125,14 @@ To add configuration to your config file, you can copy it from the base config. * `Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy`: Makes all bot posts to disappear from public timelines. * `Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy`: Drops follow requests from followbots. Users can still allow bots to follow them by first following the bot. * `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)). - * `Pleroma.Web.ActivityPub.MRF.NormalizeMarkup`: Pass inbound HTML through a scrubber to make sure it doesn't have anything unusual in it. On by default, cannot be turned off. - * `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Append a link to a post that quotes another post with the link to the quoted post, to ensure that software that does not understand quotes can have full context. On by default, cannot be turned off. * `Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy`: Rejects posts of users the server only recently learned about for a while. Great to block spam accounts. (See [`:mrf_reject_newly_created_account_notes`](#mrf_reject_newly_created_account_notes)) +Additionally the following MRFs will *always* be aplied and cannot be disabled: + +* `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Append a link to a post that quotes another post with the link to the quoted post, to ensure that software that does not understand quotes can have full context. On by default, cannot be turned off. +* `Pleroma.Web.ActivityPub.MRF.NormalizeMarkup`: Pass inbound HTML through a scrubber to make sure it doesn't have anything unusual in it. On by default, cannot be turned off. + + ## Federation ### :activitypub * `unfollow_blocked`: Whether blocks result in people getting unfollowed -- 2.43.0 From f254e4f5307566085887b8e9de96cf0e7353744c Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 18 Feb 2024 23:18:50 +0100 Subject: [PATCH 091/149] doc/cheatsheet: add missing MRF config detail docs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit And remove “on by default” text from individual entries. They are now laready in the “on by default” section. --- docs/docs/configuration/cheatsheet.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index e535c2324..ccb3dd71f 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -129,8 +129,10 @@ To add configuration to your config file, you can copy it from the base config. Additionally the following MRFs will *always* be aplied and cannot be disabled: -* `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Append a link to a post that quotes another post with the link to the quoted post, to ensure that software that does not understand quotes can have full context. On by default, cannot be turned off. -* `Pleroma.Web.ActivityPub.MRF.NormalizeMarkup`: Pass inbound HTML through a scrubber to make sure it doesn't have anything unusual in it. On by default, cannot be turned off. +* `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Append a link to a post that quotes another post with the link to the quoted post, to ensure that software that does not understand quotes can have full context. + (See [`:mrf_inline_quote`](#mrf_inline_quote)) +* `Pleroma.Web.ActivityPub.MRF.NormalizeMarkup`: Pass inbound HTML through a scrubber to make sure it doesn't have anything unusual in it. + (See [`:mrf_normalize_markup`](#mrf_normalize_markup)) ## Federation @@ -250,6 +252,12 @@ An example: (86400 seconds = 24 hours) config :pleroma, :mrf_reject_newly_created_account_notes, age: 86400 ``` +#### :mrf_inline_quote +* `prefix`: what prefix to prepend to quoted URLs + +#### :mrf_normalize_markup +* `scrub_policy`: the scrubbing module to use (by default a built-in HTML sanitiser) + ## Pleroma.User * `restricted_nicknames`: List of nicknames users may not register with. -- 2.43.0 From 9830d54fa1a2daef19232c10448005817b3b3154 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 18 Feb 2024 23:29:43 +0100 Subject: [PATCH 092/149] doc/cheatsheet: sort main MRF list alphabetically It is too cumbersome to find a specific policy atm or to check if all are docuemtned yet. Trivial placeholder policies are excluded from this. --- docs/docs/configuration/cheatsheet.md | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index ccb3dd71f..b4399e129 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -110,22 +110,22 @@ To add configuration to your config file, you can copy it from the base config. * `policies`: Message Rewrite Policy, either one or a list. Here are the ones available by default: * `Pleroma.Web.ActivityPub.MRF.NoOpPolicy`: Doesn’t modify activities (default). * `Pleroma.Web.ActivityPub.MRF.DropPolicy`: Drops all activities. It generally doesn’t makes sense to use in production. - * `Pleroma.Web.ActivityPub.MRF.SimplePolicy`: Restrict the visibility of activities from certains instances (See [`:mrf_simple`](#mrf_simple)). - * `Pleroma.Web.ActivityPub.MRF.TagPolicy`: Applies policies to individual users based on tags, which can be set using pleroma-fe/admin-fe/any other app that supports Pleroma Admin API. For example it allows marking posts from individual users nsfw (sensitive). - * `Pleroma.Web.ActivityPub.MRF.SubchainPolicy`: Selectively runs other MRF policies when messages match (See [`:mrf_subchain`](#mrf_subchain)). - * `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See [`:mrf_rejectnonpublic`](#mrf_rejectnonpublic)). - * `Pleroma.Web.ActivityPub.MRF.EnsureRePrepended`: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:. - * `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links. - * `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed. - * `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (See [`:mrf_mention`](#mrf_mention)). - * `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (See [`:mrf_vocabulary`](#mrf_vocabulary)). - * `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)). * `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Sets a default expiration on all posts made by users of the local instance. Requires `Pleroma.Workers.PurgeExpiredActivity` to be enabled for processing the scheduled delections. (See [`:mrf_activity_expiration`](#mrf_activity_expiration)) - * `Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy`: Makes all bot posts to disappear from public timelines. * `Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy`: Drops follow requests from followbots. Users can still allow bots to follow them by first following the bot. + * `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links. + * `Pleroma.Web.ActivityPub.MRF.EnsureRePrepended`: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:. + * `Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy`: Makes all bot posts to disappear from public timelines. * `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)). + * `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed. + * `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (See [`:mrf_mention`](#mrf_mention)). + * `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)). * `Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy`: Rejects posts of users the server only recently learned about for a while. Great to block spam accounts. (See [`:mrf_reject_newly_created_account_notes`](#mrf_reject_newly_created_account_notes)) + * `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See [`:mrf_rejectnonpublic`](#mrf_rejectnonpublic)). + * `Pleroma.Web.ActivityPub.MRF.SimplePolicy`: Restrict the visibility of activities from certains instances (See [`:mrf_simple`](#mrf_simple)). + * `Pleroma.Web.ActivityPub.MRF.SubchainPolicy`: Selectively runs other MRF policies when messages match (See [`:mrf_subchain`](#mrf_subchain)). + * `Pleroma.Web.ActivityPub.MRF.TagPolicy`: Applies policies to individual users based on tags, which can be set using pleroma-fe/admin-fe/any other app that supports Pleroma Admin API. For example it allows marking posts from individual users nsfw (sensitive). + * `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (See [`:mrf_vocabulary`](#mrf_vocabulary)). Additionally the following MRFs will *always* be aplied and cannot be disabled: -- 2.43.0 From 41dd37d796bc78d2d3d995d49b023d167dca81ba Mon Sep 17 00:00:00 2001 From: Oneric Date: Mon, 19 Feb 2024 00:13:10 +0100 Subject: [PATCH 093/149] doc/cheatsheet: add missing MRFs Or mentions of MRFs in the main list whose options were already documented. --- docs/docs/configuration/cheatsheet.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index b4399e129..91a215c36 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -116,19 +116,32 @@ To add configuration to your config file, you can copy it from the base config. * `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links. * `Pleroma.Web.ActivityPub.MRF.EnsureRePrepended`: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:. * `Pleroma.Web.ActivityPub.MRF.ForceBotUnlistedPolicy`: Makes all bot posts to disappear from public timelines. + * `Pleroma.Web.ActivityPub.MRF.HellthreadPolicy`: Blocks messages with too many mentions. + (See [`mrf_hellthread`](#mrf_hellthread)) * `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)). * `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed. * `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (See [`:mrf_mention`](#mrf_mention)). + * `Pleroma.Web.ActivityPub.MRF.NoEmptyPolicy`: Drops local activities which have no actual content. + (e.g. no attachments and only consists of mentions) + * `Pleroma.Web.ActivityPub.MRF.NoPlaceholderTextPolicy`: Strips content placeholders from posts + (such as the dot from mastodon) * `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)). * `Pleroma.Web.ActivityPub.MRF.RejectNewlyCreatedAccountNotesPolicy`: Rejects posts of users the server only recently learned about for a while. Great to block spam accounts. (See [`:mrf_reject_newly_created_account_notes`](#mrf_reject_newly_created_account_notes)) * `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See [`:mrf_rejectnonpublic`](#mrf_rejectnonpublic)). * `Pleroma.Web.ActivityPub.MRF.SimplePolicy`: Restrict the visibility of activities from certains instances (See [`:mrf_simple`](#mrf_simple)). + * `Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy`: Steals all eligible emoji encountered in posts from remote instances + (See [`:mrf_steal_emoji`](#mrf_steal_emoji)) * `Pleroma.Web.ActivityPub.MRF.SubchainPolicy`: Selectively runs other MRF policies when messages match (See [`:mrf_subchain`](#mrf_subchain)). * `Pleroma.Web.ActivityPub.MRF.TagPolicy`: Applies policies to individual users based on tags, which can be set using pleroma-fe/admin-fe/any other app that supports Pleroma Admin API. For example it allows marking posts from individual users nsfw (sensitive). + * `Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy`: Drops all posts except from users specified in a list. + (See [`:mrf_user_allowlist`](#mrf_user_allowlist)) * `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (See [`:mrf_vocabulary`](#mrf_vocabulary)). Additionally the following MRFs will *always* be aplied and cannot be disabled: +* `Pleroma.Web.ActivityPub.MRF.DirectMessageDisabledPolicy`: Strips users limiting who can send them DMs from the recipients of non-eligible DMs +* `Pleroma.Web.ActivityPub.MRF.HashtagPolicy`: Depending on a post’s hashtags it can be rejected, get its sensitive flags force-enabled or removed from the global timeline + (See [`:mrf_hashtag`](#mrf_hashtag)) * `Pleroma.Web.ActivityPub.MRF.InlineQuotePolicy`: Append a link to a post that quotes another post with the link to the quoted post, to ensure that software that does not understand quotes can have full context. (See [`:mrf_inline_quote`](#mrf_inline_quote)) * `Pleroma.Web.ActivityPub.MRF.NormalizeMarkup`: Pass inbound HTML through a scrubber to make sure it doesn't have anything unusual in it. -- 2.43.0 From c25cfe9b7aadb70388e0819ade3f9aa9be0f41ad Mon Sep 17 00:00:00 2001 From: rick Date: Mon, 19 Feb 2024 23:25:20 +0100 Subject: [PATCH 094/149] fixed spelling --- docs/docs/configuration/cheatsheet.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index 91a215c36..3c9113f88 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -255,7 +255,7 @@ Notes: #### :mrf_reject_newly_created_account_notes After initially encountering an user, all their posts will be rejected for the configured time (in seconds). -Only drops posts. Follows, reposts, etc. are not effected. +Only drops posts. Follows, reposts, etc. are not affected. * `age`: Time below which to reject (in seconds) -- 2.43.0 From 7d94476dd6e46071cf5b8662055a109f9ef904ef Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 20 Feb 2024 08:45:48 +0100 Subject: [PATCH 095/149] StealEmojiPolicy: Sanitize shortcodes Closes: https://git.pleroma.social/pleroma/pleroma/-/issues/3245 --- .../activity_pub/mrf/steal_emoji_policy.ex | 2 ++ .../mrf/steal_emoji_policy_test.exs | 26 +++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex index 45b2d4ca7..3e1dc7d43 100644 --- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex @@ -34,6 +34,7 @@ defp steal_emoji({shortcode, url}, emoji_dir_path) do |> Path.basename() |> Path.extname() + shortcode = Path.basename(shortcode) file_path = Path.join(emoji_dir_path, shortcode <> (extension || ".png")) case File.write(file_path, response.body) do @@ -76,6 +77,7 @@ def filter(%{"object" => %{"emoji" => foreign_emojis, "actor" => actor}} = messa new_emojis = foreign_emojis |> Enum.reject(fn {shortcode, _url} -> shortcode in installed_emoji end) + |> Enum.reject(fn {shortcode, _url} -> String.contains?(shortcode, ["/", "\\"]) end) |> Enum.filter(fn {shortcode, _url} -> reject_emoji? = [:mrf_steal_emoji, :rejected_shortcodes] diff --git a/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs index b0a7e8993..59baa3a43 100644 --- a/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs @@ -60,6 +60,32 @@ test "Steals emoji on unknown shortcode from allowed remote host", %{ |> File.exists?() end + test "rejects invalid shortcodes", %{path: path} do + message = %{ + "type" => "Create", + "object" => %{ + "emoji" => [{"fired/fox", "https://example.org/emoji/firedfox"}], + "actor" => "https://example.org/users/admin" + } + } + + fullpath = Path.join(path, "fired/fox.png") + + Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox"} -> + %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")} + end) + + clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468) + + refute "firedfox" in installed() + refute File.exists?(path) + + assert {:ok, _message} = StealEmojiPolicy.filter(message) + + refute "fired/fox" in installed() + refute File.exists?(fullpath) + end + test "reject regex shortcode", %{message: message} do refute "firedfox" in installed() -- 2.43.0 From b387f4a1c1ff02573f16de0b25403cf501afc3b4 Mon Sep 17 00:00:00 2001 From: Erin Shepherd Date: Tue, 20 Feb 2024 11:31:29 +0100 Subject: [PATCH 096/149] Don't steal emoji who's shortcodes have dots or colons in their name Mastodon at the very least seems to prevent the creation of emoji with dots in their name (and refuses to accept them in federation). It feels like being cautious in what we accept is reasonable here. Colons are the emoji separator and so obviously should be blocked. Perhaps instead of filtering out things like this we should just do a regex match on `[a-zA-Z0-9_-]`? But that's plausibly a decision for another day Perhaps we should also have a centralised "is this a valid emoji shortcode?" function --- lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex index 3e1dc7d43..6c43afa58 100644 --- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex @@ -77,7 +77,7 @@ def filter(%{"object" => %{"emoji" => foreign_emojis, "actor" => actor}} = messa new_emojis = foreign_emojis |> Enum.reject(fn {shortcode, _url} -> shortcode in installed_emoji end) - |> Enum.reject(fn {shortcode, _url} -> String.contains?(shortcode, ["/", "\\"]) end) + |> Enum.reject(fn {shortcode, _url} -> String.contains?(shortcode, ["/", "\\", ".", ":"]) end) |> Enum.filter(fn {shortcode, _url} -> reject_emoji? = [:mrf_steal_emoji, :rejected_shortcodes] -- 2.43.0 From 3111181d3c0c46c695e387d39521ccb9163c2320 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Tue, 20 Feb 2024 15:09:04 +0000 Subject: [PATCH 097/149] mix format --- lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex index 6c43afa58..02a107c27 100644 --- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex @@ -77,7 +77,9 @@ def filter(%{"object" => %{"emoji" => foreign_emojis, "actor" => actor}} = messa new_emojis = foreign_emojis |> Enum.reject(fn {shortcode, _url} -> shortcode in installed_emoji end) - |> Enum.reject(fn {shortcode, _url} -> String.contains?(shortcode, ["/", "\\", ".", ":"]) end) + |> Enum.reject(fn {shortcode, _url} -> + String.contains?(shortcode, ["/", "\\", ".", ":"]) + end) |> Enum.filter(fn {shortcode, _url} -> reject_emoji? = [:mrf_steal_emoji, :rejected_shortcodes] -- 2.43.0 From c08f49d88ebbef62cdfa4bf4004eac236c0e42af Mon Sep 17 00:00:00 2001 From: Oneric Date: Wed, 21 Feb 2024 00:33:32 +0000 Subject: [PATCH 098/149] Add tests for static-fe metadata tags --- .../static_fe/static_fe_controller_test.exs | 159 +++++++++++++++++- 1 file changed, 157 insertions(+), 2 deletions(-) diff --git a/test/pleroma/web/static_fe/static_fe_controller_test.exs b/test/pleroma/web/static_fe/static_fe_controller_test.exs index 935e44171..79d4d6261 100644 --- a/test/pleroma/web/static_fe/static_fe_controller_test.exs +++ b/test/pleroma/web/static_fe/static_fe_controller_test.exs @@ -19,9 +19,26 @@ defmodule Pleroma.Web.StaticFE.StaticFEControllerTest do setup %{conn: conn} do conn = put_req_header(conn, "accept", "text/html") - user = insert(:user) - %{conn: conn, user: user} + user_avatar_url = "https://example.org/akko.png" + + user = + insert(:user, + local: true, + name: "Akko", + nickname: "atsuko", + bio: "A believing heart is my magic!", + raw_bio: "A believing heart is my magic!", + avatar: %{ + "url" => [ + %{ + "href" => user_avatar_url + } + ] + } + ) + + %{conn: conn, user: user, user_avatar_url: user_avatar_url} end describe "user profile html" do @@ -291,4 +308,142 @@ test "returns 404 for local public activity with `restrict_unauthenticated/activ |> html_response(404) end end + + defp meta_content(metadata_tag) do + :proplists.get_value("content", metadata_tag) + end + + defp meta_find_og(document, name) do + Floki.find(document, "head>meta[property=\"og:" <> name <> "\"]") + end + + defp meta_find_twitter(document, name) do + Floki.find(document, "head>meta[name=\"twitter:" <> name <> "\"]") + end + + # Detailed metadata tests are already done for each builder individually, so just + # one check per type of content should suffice to ensure we're calling the providers correctly + describe "metadata tags for" do + setup do + clear_config([Pleroma.Web.Metadata, :providers], [ + Pleroma.Web.Metadata.Providers.OpenGraph, + Pleroma.Web.Metadata.Providers.TwitterCard + ]) + end + + test "user profile", %{conn: conn, user: user, user_avatar_url: user_avatar_url} do + conn = get(conn, "/users/#{user.nickname}") + html = html_response(conn, 200) + + {:ok, document} = Floki.parse_document(html) + + [{"meta", og_type, _}] = meta_find_og(document, "type") + [{"meta", og_title, _}] = meta_find_og(document, "title") + [{"meta", og_url, _}] = meta_find_og(document, "url") + [{"meta", og_desc, _}] = meta_find_og(document, "description") + [{"meta", og_img, _}] = meta_find_og(document, "image") + [{"meta", og_imgw, _}] = meta_find_og(document, "image:width") + [{"meta", og_imgh, _}] = meta_find_og(document, "image:height") + + [{"meta", tw_card, _}] = meta_find_twitter(document, "card") + [{"meta", tw_title, _}] = meta_find_twitter(document, "title") + [{"meta", tw_desc, _}] = meta_find_twitter(document, "description") + [{"meta", tw_img, _}] = meta_find_twitter(document, "image") + + assert meta_content(og_type) == "article" + assert meta_content(og_title) == Pleroma.Web.Metadata.Utils.user_name_string(user) + assert meta_content(og_url) == user.ap_id + assert meta_content(og_desc) == user.bio + assert meta_content(og_img) == user_avatar_url + assert meta_content(og_imgw) == "150" + assert meta_content(og_imgh) == "150" + + assert meta_content(tw_card) == "summary" + assert meta_content(tw_title) == meta_content(og_title) + assert meta_content(tw_desc) == meta_content(og_desc) + assert meta_content(tw_img) == meta_content(og_img) + end + + test "text-only post", %{conn: conn, user: user, user_avatar_url: user_avatar_url} do + post_text = "How are lessons about magic t h i s boring?!" + {:ok, activity} = CommonAPI.post(user, %{status: post_text}) + + conn = get(conn, "/notice/#{activity.id}") + html = html_response(conn, 200) + + {:ok, document} = Floki.parse_document(html) + + [{"meta", og_type, _}] = meta_find_og(document, "type") + [{"meta", og_title, _}] = meta_find_og(document, "title") + [{"meta", og_url, _}] = meta_find_og(document, "url") + [{"meta", og_desc, _}] = meta_find_og(document, "description") + [{"meta", og_img, _}] = meta_find_og(document, "image") + [{"meta", og_imgw, _}] = meta_find_og(document, "image:width") + [{"meta", og_imgh, _}] = meta_find_og(document, "image:height") + + [{"meta", tw_card, _}] = meta_find_twitter(document, "card") + [{"meta", tw_title, _}] = meta_find_twitter(document, "title") + [{"meta", tw_desc, _}] = meta_find_twitter(document, "description") + [{"meta", tw_img, _}] = meta_find_twitter(document, "image") + + assert meta_content(og_type) == "article" + assert meta_content(og_title) == Pleroma.Web.Metadata.Utils.user_name_string(user) + assert meta_content(og_url) == activity.data["id"] + assert meta_content(og_desc) == post_text + assert meta_content(og_img) == user_avatar_url + assert meta_content(og_imgw) == "150" + assert meta_content(og_imgh) == "150" + + assert meta_content(tw_card) == "summary" + assert meta_content(tw_title) == meta_content(og_title) + assert meta_content(tw_desc) == meta_content(og_desc) + assert meta_content(tw_img) == meta_content(og_img) + end + + test "post with attachments", %{conn: conn, user: user} do + file = %Plug.Upload{ + content_type: "image/jpeg", + path: Path.absname("test/fixtures/image.jpg"), + filename: "an_image.jpg" + } + + alt_text = "The rarest of all Shiny Chariot cards" + {:ok, upload_data} = ActivityPub.upload(file, actor: user.ap_id, description: alt_text) + + %{id: media_id, data: %{"url" => [%{"href" => media_url}]}} = upload_data + + post_text = "Look!" + {:ok, activity} = CommonAPI.post(user, %{status: post_text, media_ids: [media_id]}) + + conn = get(conn, "/notice/#{activity.id}") + html = html_response(conn, 200) + + {:ok, document} = Floki.parse_document(html) + + [{"meta", og_type, _}] = meta_find_og(document, "type") + [{"meta", og_title, _}] = meta_find_og(document, "title") + [{"meta", og_url, _}] = meta_find_og(document, "url") + [{"meta", og_desc, _}] = meta_find_og(document, "description") + [{"meta", og_img, _}] = meta_find_og(document, "image") + [{"meta", og_alt, _}] = meta_find_og(document, "image:alt") + + [{"meta", tw_card, _}] = meta_find_twitter(document, "card") + [{"meta", tw_title, _}] = meta_find_twitter(document, "title") + [{"meta", tw_desc, _}] = meta_find_twitter(document, "description") + [{"meta", tw_player, _}] = meta_find_twitter(document, "player") + + assert meta_content(og_type) == "article" + assert meta_content(og_title) == Pleroma.Web.Metadata.Utils.user_name_string(user) + assert meta_content(og_url) == activity.data["id"] + assert meta_content(og_desc) == post_text + assert meta_content(og_img) == media_url + assert meta_content(og_alt) == alt_text + + # Audio and video attachments use "player" and have some more metadata + assert meta_content(tw_card) == "summary_large_image" + assert meta_content(tw_title) == meta_content(og_title) + assert meta_content(tw_desc) == meta_content(og_desc) + assert meta_content(tw_player) == meta_content(og_img) + end + end end -- 2.43.0 From 7964272c98fec4d83d8432f372b6718f850c2c8c Mon Sep 17 00:00:00 2001 From: Oneric Date: Mon, 19 Feb 2024 17:33:14 +0100 Subject: [PATCH 099/149] Document how to avoid data loss on migration from Pleroma --- docs/docs/installation/migrating_to_akkoma.md | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/docs/docs/installation/migrating_to_akkoma.md b/docs/docs/installation/migrating_to_akkoma.md index 9c30d9586..40eb6cac8 100644 --- a/docs/docs/installation/migrating_to_akkoma.md +++ b/docs/docs/installation/migrating_to_akkoma.md @@ -21,6 +21,33 @@ fork of Akkoma - luckily this isn't very hard. You'll need to update the backend, then possibly the frontend, depending on your setup. +## Backup diverging features + +As time goes on Akkoma and Pleroma added or removed different features +and reorganised the database in a different way. If you want to be able to +migrate back to Pleroma without losing any affected data, you’ll want to +make a backup before starting the migration. +If you're not interested in migrating back, skip this section +*(although it might be a good idea to temporarily keep a full DB backup +just in case something unexpected happens during migration)* + +As of 2024-02 you will want to keep a backup of: + +- the entire `chats` and `chat_message_references` tables + +The following columns are not deleted by a migration to Akkoma, but a migration +back to Pleroma or future Akkoma upgrades might affect them, so perhaps back them up as well: + +- the `birthday` of users and their `show_birthday` setting +- the `expires_at` key of in the `user_relationships` table + *(used by temporary mutes)* + +The way cached instance metadata is stored differs, but since those +will be refetched and updated anyway, there’s no need for a backup. + +Best check all newer migrations unique to Akkoma/Pleroma +to get an up-to-date picture of what needs to be kept. + ## From Source If you're running the source Akkoma install, you'll need to set the @@ -130,3 +157,4 @@ MIX_ENV=prod mix ecto.rollback --to 20210416051708 ``` Then switch back to Pleroma for updates (similar to how was done to migrate to Akkoma), and remove the front-ends. The front-ends are installed in the `frontends` folder in the [static directory](../configuration/static_dir.md). Once you are back to Pleroma, you will need to run the database migrations again. See the Pleroma documentation for this. +After this use your previous backups to restore data from diverging features. -- 2.43.0 From bff2812a939c03c099117962283f28049667ac30 Mon Sep 17 00:00:00 2001 From: Oneric Date: Mon, 19 Feb 2024 17:53:11 +0100 Subject: [PATCH 100/149] More prominently document db migrations in migrations from Pleroma By now most instance will run a version past 2022-08 but the guide only documented it for from source installs and Pleroma develop. --- docs/docs/installation/migrating_to_akkoma.md | 44 ++++++++++++++----- 1 file changed, 32 insertions(+), 12 deletions(-) diff --git a/docs/docs/installation/migrating_to_akkoma.md b/docs/docs/installation/migrating_to_akkoma.md index 40eb6cac8..bf6eeebc7 100644 --- a/docs/docs/installation/migrating_to_akkoma.md +++ b/docs/docs/installation/migrating_to_akkoma.md @@ -61,16 +61,7 @@ git pull -r # to run "git merge stable" instead (or develop if you want) ``` -### WARNING - Migrating from Pleroma Develop -If you are on pleroma develop, and have updated since 2022-08, you may have issues with database migrations. - -Please roll back the given migrations: - -```bash -MIX_ENV=prod mix ecto.rollback --migrations-path priv/repo/optional_migrations/pleroma_develop_rollbacks -n5 -``` - -Then compile, migrate and restart as usual. +And compile as usual. ## From OTP @@ -80,15 +71,44 @@ This will just be setting the update URL - find your flavour from the [mapping o export FLAVOUR=[the flavour you found above] ./bin/pleroma_ctl update --zip-url https://akkoma-updates.s3-website.fr-par.scw.cloud/stable/akkoma-$FLAVOUR.zip -./bin/pleroma_ctl migrate ``` -Then restart. When updating in the future, you canjust use +When updating in the future, you can just use ```bash ./bin/pleroma_ctl update --branch stable ``` + +## Database Migrations +### WARNING - Migrating from Pleroma past 2022-08 +If you are on Pleroma stable >= 2.5.0 or Pleroma develop, and +have updated since 2022-08, you may have issues with database migrations. + +Please first roll back the given migrations: + +=== "OTP" + ```bash + ./bin/pleroma_ctl rollback --migrations-path priv/repo/optional_migrations/pleroma_develop_rollbacks -n5 + ``` +=== "From Source" + ```bash + MIX_ENV=prod mix ecto.rollback --migrations-path priv/repo/optional_migrations/pleroma_develop_rollbacks -n5 + ``` + +### Applying Akkoma Database Migrations + +Just run + +=== "OTP" + ```bash + ./bin/pleroma_ctl migrate + ``` +=== "From Source" + ```bash + MIX_ENV=prod mix ecto.migrate + ``` + ## Frontend changes Akkoma comes with a few frontend changes as well as backend ones, -- 2.43.0 From caaf2deb2235d8af0b7af342621fec7cf9e02e89 Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 22 Oct 2023 11:53:48 +0000 Subject: [PATCH 101/149] Translated using Weblate (Polish) Currently translated at 18.1% (183 of 1006 strings) Translated using Weblate (Polish) Currently translated at 6.6% (67 of 1006 strings) Co-authored-by: Weblate Co-authored-by: subtype Translate-URL: http://translate.akkoma.dev/projects/akkoma/akkoma-backend-config-descriptions/pl/ Translation: Pleroma fe/Akkoma Backend (Config Descriptions) --- .../pl/LC_MESSAGES/config_descriptions.po | 779 +++++++++--------- 1 file changed, 391 insertions(+), 388 deletions(-) diff --git a/priv/gettext/pl/LC_MESSAGES/config_descriptions.po b/priv/gettext/pl/LC_MESSAGES/config_descriptions.po index d212a7d87..0692a61cd 100644 --- a/priv/gettext/pl/LC_MESSAGES/config_descriptions.po +++ b/priv/gettext/pl/LC_MESSAGES/config_descriptions.po @@ -8,50 +8,50 @@ ### to merge POT files into PO files. msgid "" msgstr "" -"PO-Revision-Date: 2023-08-04 14:26+0000\n" -"Last-Translator: Anonymous \n" +"PO-Revision-Date: 2023-10-22 11:53+0000\n" +"Last-Translator: subtype \n" "Language-Team: Polish \n" "Language: pl\n" "Content-Type: text/plain; charset=UTF-8\n" +"Content-Transfer-Encoding: 8bit\n" "Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " "|| n%100>=20) ? 1 : 2;\n" "X-Generator: Weblate 4.18.2\n" -"Content-Transfer-Encoding: 8bit\n" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :logger" msgid "Logger-related settings" -msgstr "Logger-related settings" +msgstr "Ustawienia związane z Loggerem" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :mime" msgid "Mime Types settings" -msgstr "Mime Types settings" +msgstr "Ustawienia typów Mime" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format 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 "" -"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)" +"Pozwala na ustawienie tokenu, który może zostać użyty do uwierzytelniania " +"zapytań z uprawnieniami administratora bez zwykłego tokenu użytkownika. " +"Dodaj parametr `admin_token` by go użyć. (Proszę przemyśl użycie " +"uwierzytelnienia HTTP Basic Auth lub OAuth jeśli to możliwe)" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma" msgid "Authenticator" -msgstr "Authenticator" +msgstr "Uwierzytelniacz" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :cors_plug" msgid "CORS plug config" -msgstr "CORS plug config" +msgstr "Konfiguracja wtyczki CORS" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format, fuzzy @@ -60,1158 +60,1161 @@ msgid "Logger" msgstr "Logger" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :mime" msgid "Mime Types" -msgstr "Mime Types" +msgstr "Typy Mime" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma" msgid "Pleroma Admin Token" -msgstr "Pleroma Admin Token" +msgstr "Token Administratora Pleromy" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma" msgid "Pleroma Authenticator" -msgstr "Pleroma Authenticator" +msgstr "Uwierzytelniacz Pleromy" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :logger-:console" msgid "Console logger settings" -msgstr "Console logger settings" +msgstr "Ustawienia logowania do konsoli" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :logger-:ex_syslogger" msgid "ExSyslogger-related settings" -msgstr "ExSyslogger-related settings" +msgstr "Ustawienia zwiazane z ExSysloggerem" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:activitypub" msgid "ActivityPub-related settings" -msgstr "ActivityPub-related settings" +msgstr "Ustawienia związane z ActivityPub" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, 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 "" -"This section configures assets to be used with various frontends. Currently " -"the only option relates to mascots on the mastodon frontend" +"Ta sekcja konfiguruje zasoby używane przez różne frontendy. Jak na razie, " +"jedyne opcje są związane z maskotkami na frontendzie Mastodon." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:auth" msgid "Authentication / authorization settings" -msgstr "Authentication / authorization settings" +msgstr "Ustawienia uwierzytelniania / autoryzacji." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:email_notifications" msgid "Email notifications settings" -msgstr "Email notifications settings" +msgstr "Ustawienia powiadomień przez e-mail" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:features" msgid "Customizable features" -msgstr "Customizable features" +msgstr "Konfigurowalne funkcjonalności" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:feed" msgid "Configure feed rendering" -msgstr "Configure feed rendering" +msgstr "Konfiguruj wyświetlanie osi czasu" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontends" msgid "Installed frontends management" -msgstr "Installed frontends management" +msgstr "Zarządzanie zainstalowanymi frontendami" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:http" msgid "HTTP settings" -msgstr "HTTP settings" +msgstr "Ustawienia HTTP" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:http_security" msgid "HTTP security settings" -msgstr "HTTP security settings" +msgstr "Ustawienia bezpieczeństwa HTTP" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instance" msgid "Instance-related settings" -msgstr "Instance-related settings" +msgstr "Ustawienia związane z intancją" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:instances_favicons" msgid "Control favicons for instances" -msgstr "Control favicons for instances" +msgstr "Kontrola favikonek dla instancji" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:ldap" msgid "Use LDAP for user authentication. When a user logs in to the Pleroma instance, the name and password will be verified by trying to authenticate (bind) to a LDAP server. If a user exists in the LDAP directory but there is no account with the same name yet on the Pleroma instance then a new Pleroma account will be created with the same name as the LDAP user name." msgstr "" -"Use LDAP for user authentication. When a user logs in to the Pleroma " -"instance, the name and password will be verified by trying to authenticate " -"(bind) to a LDAP server. If a user exists in the LDAP directory but there is " -"no account with the same name yet on the Pleroma instance then a new Pleroma " -"account will be created with the same name as the LDAP user name." +"Użyj LDAP do uwierzytelnienia użytkowników. Kiedy użytkownik zaloguje się do " +"instancji Akkomy, nazwa i hasło zostaną zweryfikowane przez próbę " +"uwierzytelnienia w serwerze LDAP. Jeśli użytkownik istnieje w katalogu LDAP " +"ale nie ma jeszcze konta z tą nazwą na instancji Akkomy, wtedy nowe konto " +"Akkomy zostanie stworzone z tą samą nazwą jak nazwa użytkownika LDAP." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:majic_pool" msgid "Majic/libmagic configuration" -msgstr "Majic/libmagic configuration" +msgstr "Konfiguracja 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 "" -"This section describe PWA manifest instance-specific values. Currently this " -"option relate only for MastoFE." +"Ta sekcja opisuje właściwe dla instancji wartości manifestu PWA. Jak na " +"razie, ta opcja jest związana tylko z MastoFE." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:media_preview_proxy" msgid "Media preview proxy" -msgstr "Media preview proxy" +msgstr "Proxy podglądu mediów" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:media_proxy" msgid "Media proxy" -msgstr "Media proxy" +msgstr "Proxy mediów" #: 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 "Custom Runtime Modules" +msgstr "Własne moduły czasu działania" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf" msgid "General MRF settings" -msgstr "General MRF settings" +msgstr "Ogólne ustawienia MRF" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_activity_expiration" msgid "Adds automatic expiration to all local activities" -msgstr "Adds automatic expiration to all local activities" +msgstr "Dodaj automatyczne wygaśnięcie do wszystkich lokalnych aktywności" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_follow_bot" msgid "Automatically follows newly discovered accounts." -msgstr "Automatically follows newly discovered accounts." +msgstr "Automatycznie śledzi nowoodnalezione konta" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_hashtag" msgid "Reject, TWKN-remove or Set-Sensitive messsages with specific hashtags (without the leading #)\n\nNote: This MRF Policy is always enabled, if you want to disable it you have to set empty lists.\n" msgstr "" -"Reject, TWKN-remove or Set-Sensitive messsages with specific hashtags (" -"without the leading #)\n" +"Odrzuć, usuń z całej znanej sieci, lub ustaw jako wrażliwe wiadomości z " +"określonymi hasztagami (bez #)\n" "\n" -"Note: This MRF Policy is always enabled, if you want to disable it you have " -"to set empty lists.\n" +"Uwaga: ta zasada MRF jest zawsze włączona. Jeśli chcesz ją wyłączyć, zostaw " +"listę pustą.\n" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_hellthread" msgid "Block messages with excessive user mentions" -msgstr "Block messages with excessive user mentions" +msgstr "Blokuj wiadomości ze zbyt dużą liczbą wspomnień" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_keyword" msgid "Reject or Word-Replace messages matching a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html)." msgstr "" -"Reject or Word-Replace messages matching a keyword or [Regex](https://hexdocs" -".pm/elixir/Regex.html)." +"Odrzuć lub zmień treść wiadomości zawierających słowo kluczowe lub " +"pasujących do [regexa](https://hexdocs.pm/elixir/Regex.html)." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_mention" msgid "Block messages which mention a specific user" -msgstr "Block messages which mention a specific user" +msgstr "Blokuj wiadomości wspominające o określonych użytkownikach" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_normalize_markup" msgid "MRF NormalizeMarkup settings. Scrub configured hypertext markup." -msgstr "MRF NormalizeMarkup settings. Scrub configured hypertext markup." +msgstr "" +"Ustawienia MRF NormalizeMarkup. Wyczyść skonfigurowany markup hipertekstu." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_object_age" msgid "Rejects or delists posts based on their timestamp deviance from your server's clock." -msgstr "" -"Rejects or delists posts based on their timestamp deviance from your " -"server's clock." +msgstr "Odrzuć lub usuń z osi czasu wpisy biorąc pod uwagę ich datę utworzenia." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_rejectnonpublic" msgid "RejectNonPublic drops posts with non-public visibility settings." -msgstr "RejectNonPublic drops posts with non-public visibility settings." +msgstr "RejectNonPublic usuwa wpisy które nie są oznaczone jako publiczne." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_simple" msgid "Simple ingress policies" -msgstr "Simple ingress policies" +msgstr "Proste zasady modyfikacji wiadomości przychodzących" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_steal_emoji" msgid "Steals emojis from selected instances when it sees them." -msgstr "Steals emojis from selected instances when it sees them." +msgstr "Kradnij emoji z określonych instancji gdy tylko je zobaczysz." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format 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 "" -"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." +"Ta taktyka przetwarza wiadomości przez alternatywny potok jeśli wiadomość " +"pasuje do określonych kryteriów. Wszystkie kryteria są skonfigurowane jako " +"mapa wyrażeń regularnych na listę modułów zasad." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:mrf_vocabulary" msgid "Filter messages which belong to certain activity vocabularies" -msgstr "Filter messages which belong to certain activity vocabularies" +msgstr "Filtruj wiadomości należących do określonego słownictwa aktywności" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:oauth2" msgid "Configure OAuth 2 provider capabilities" -msgstr "Configure OAuth 2 provider capabilities" +msgstr "Konfiguruj możliwości dostawcy OAuth 2" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:populate_hashtags_table" msgid "`populate_hashtags_table` background migration settings" -msgstr "`populate_hashtags_table` background migration settings" +msgstr "Ustawienia migracji w tle:`populate_hashtags_table`" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:rate_limit" msgid "Rate limit settings. This is an advanced feature enabled only for :authentication by default." msgstr "" -"Rate limit settings. This is an advanced feature enabled only for :" -"authentication by default." +"Ustawienia limitów prędkości. To jest zaawansowana funkcjonalność włączona " +"domyślnie tylko dla :authentication." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:rich_media" msgid "If enabled the instance will parse metadata from attached links to generate link previews" msgstr "" -"If enabled the instance will parse metadata from attached links to generate " -"link previews" +"Jeżeli włączone, ta instancja będzie przetwarzać metadane z dołączonych " +"linków by tworzyć podglądy" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:static_fe" msgid "Render profiles and posts using server-generated HTML that is viewable without using JavaScript" msgstr "" -"Render profiles and posts using server-generated HTML that is viewable " -"without using JavaScript" +"Wyświetlaj profile i posty używając tworzonego na serwerze HTML który można " +"przeglądać bez JavaScript" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:streamer" msgid "Settings for notifications streamer" -msgstr "Settings for notifications streamer" +msgstr "Ustawienia wysyłania powiadomień" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:uri_schemes" msgid "URI schemes related settings" -msgstr "URI schemes related settings" +msgstr "Ustawienia związane ze schematami URI" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, 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 "" -"The expiration time for the web responses cache. Values should be in " -"milliseconds or `nil` to disable expiration." +"Czas wygaśnięcia cache odpowiedzi sieciowych. Wartość powinna być podana w " +"milisekundach, lub `nil` by wyłączyć." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:welcome" msgid "Welcome messages settings" -msgstr "Welcome messages settings" +msgstr "Ustawienia wiadomości powitalnych" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:workers" msgid "Includes custom worker options not interpretable directly by `Oban`" -msgstr "Includes custom worker options not interpretable directly by `Oban`" +msgstr "" +"Zawiera dodatkowe opcje workerów nieinterpretowalne bezpośrednio przez `Oban`" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-ConcurrentLimiter" msgid "Limits configuration for background tasks." -msgstr "Limits configuration for background tasks." +msgstr "Konfiguracja limitów dla zadań w tle." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Oban" msgid "[Oban](https://github.com/sorentwo/oban) asynchronous job processor configuration." msgstr "" -"[Oban](https://github.com/sorentwo/oban) asynchronous job processor " -"configuration." +"Konfiguracja procesora zadań asynchronicznych [Oban](https://github.com/" +"sorentwo/oban)." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Captcha" msgid "Captcha-related settings" -msgstr "Captcha-related settings" +msgstr "Ustawienia związane z Captcha" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Captcha.Kocaptcha" msgid "Kocaptcha is a very simple captcha service with a single API endpoint, the source code is here: https://github.com/koto-bank/kocaptcha. The default endpoint (https://captcha.kotobank.ch) is hosted by the developer." msgstr "" -"Kocaptcha is a very simple captcha service with a single API endpoint, the " -"source code is here: https://github.com/koto-bank/kocaptcha. The default " -"endpoint (https://captcha.kotobank.ch) is hosted by the developer." +"Kocaptcha to bardzo prosta usługa captcha z pojedynczym endpointem API, kod " +"źródłowy jest tu: https://github.com/koto-bank/kocaptcha. Domyślny endpoint " +"(https://captcha.kotobank.ch) jest hostowany przez developera." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Emails.Mailer" msgid "Mailer-related settings" -msgstr "Mailer-related settings" +msgstr "Ustawienia związane z Mailerem" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Emails.NewUsersDigestEmail" msgid "New users admin email digest" -msgstr "New users admin email digest" +msgstr "Administracyjne podsumowanie e-maili dla nowych użytkowników" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Emails.UserEmail" msgid "Email template settings" -msgstr "Email template settings" +msgstr "Ustawienia szablonów e-maili" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Formatter" msgid "Configuration for Pleroma's link formatter which parses mentions, hashtags, and URLs." msgstr "" -"Configuration for Pleroma's link formatter which parses mentions, hashtags, " -"and URLs." +"Konfiguracja formattera linków Akkomy, który przetwarza wspomnienia, " +"hasztagi, i URL." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.ScheduledActivity" msgid "Scheduled activities settings" -msgstr "Scheduled activities settings" +msgstr "Ustawienia zaplanowanych aktywności" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Upload" msgid "Upload general settings" -msgstr "Upload general settings" +msgstr "Ustawienia wysyłania plików" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Upload.Filter.AnonymizeFilename" msgid "Filter replaces the filename of the upload" -msgstr "Filter replaces the filename of the upload" +msgstr "Filtr zmienia nazwę wysyłanego pliku" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Upload.Filter.Mogrify" msgid "Uploads mogrify filter settings" -msgstr "Uploads mogrify filter settings" +msgstr "Ustawienia mogrifera wysyłanych plików" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Uploaders.Local" msgid "Local uploader-related settings" -msgstr "Local uploader-related settings" +msgstr "Ustawnia związane z lokalnym wysyłaniem" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Uploaders.S3" msgid "S3 uploader-related settings" -msgstr "S3 uploader-related settings" +msgstr "Ustawienia związane z wysyłaniem na S3" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format, fuzzy msgctxt "config description at :pleroma-Pleroma.User.Backup" msgid "Account Backup" -msgstr "Account Backup" +msgstr "Kopia zapasowa konta" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http" msgid "HTTP invalidate settings" -msgstr "HTTP invalidate settings" +msgstr "Ustawienia inwalidacji HTTP" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Script" msgid "Invalidation script settings" -msgstr "Invalidation script settings" +msgstr "Ustawienia skryptu inwalidacyjnego" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.Metadata" msgid "Metadata-related settings" -msgstr "Metadata-related settings" +msgstr "Ustawienia związane z metadanymi" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format 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` 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" +"`Pleroma.Web.Plugs.RemoteIp` to podkładka by wywołać [`RemoteIp`](https://git" +".pleroma.social/pleroma/remote_ip) ale z konfiguracją w czasie działania.\n" +"**Jeśli twoja intancja nie jest przynajmniej za jednym odwrotnym proxy, nie " +"powinnoś włączać tej wtyczki.**\n" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Web.Preload" msgid "Preload-related settings" -msgstr "Preload-related settings" +msgstr "Ustawienia preloadu" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-Pleroma.Workers.PurgeExpiredActivity" msgid "Expired activities settings" -msgstr "Expired activities settings" +msgstr "Ustawienia wygasłych aktywności" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, 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 "" -"Web Push Notifications configuration. You can use the mix task mix " -"web_push.gen.keypair to generate it." +"Ustawienia powiadomień Push. Możesz użyć zadania mix web_push.gen.keypair by " +"je wygenerować." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :ex_aws-:s3" msgid "S3" msgstr "S3" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :logger-:console" msgid "Console Logger" -msgstr "Console Logger" +msgstr "Logger konsolowy" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :logger-:ex_syslogger" msgid "ExSyslogger" msgstr "ExSyslogger" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:activitypub" msgid "ActivityPub" msgstr "ActivityPub" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:assets" msgid "Assets" -msgstr "Assets" +msgstr "Zasoby" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:auth" msgid "Auth" -msgstr "Auth" +msgstr "Autoryzacja" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:email_notifications" msgid "Email notifications" -msgstr "Email notifications" +msgstr "Powiadomienia e-mail" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:emoji" msgid "Emoji" msgstr "Emoji" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:features" msgid "Features" -msgstr "Features" +msgstr "Funkcjonalności" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:feed" msgid "Feed" -msgstr "Feed" +msgstr "Oś czasu" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontend_configurations" msgid "Frontend configurations" -msgstr "Frontend configurations" +msgstr "Konfiguracje frontenów" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:frontends" msgid "Frontends" -msgstr "Frontends" +msgstr "Frontendy" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:http" msgid "HTTP" msgstr "HTTP" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:http_security" msgid "HTTP security" -msgstr "HTTP security" +msgstr "Bezpieczeństwo HTTP" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instance" msgid "Instance" -msgstr "Instance" +msgstr "Instancja" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:instances_favicons" msgid "Instances favicons" -msgstr "Instances favicons" +msgstr "Favikonki instancji" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:ldap" msgid "LDAP" msgstr "LDAP" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:majic_pool" msgid "Majic pool" -msgstr "Majic pool" +msgstr "Pula Majic" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:manifest" msgid "Manifest" msgstr "Manifest" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:markup" msgid "Markup Settings" -msgstr "Markup Settings" +msgstr "Ustawienia Markup" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:media_preview_proxy" msgid "Media preview proxy" -msgstr "Media preview proxy" +msgstr "Proxy podglądu mediów" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:media_proxy" msgid "Media proxy" -msgstr "Media proxy" +msgstr "Proxy mediów" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:modules" msgid "Modules" -msgstr "Modules" +msgstr "Moduły" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf" msgid "MRF" msgstr "MRF" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_activity_expiration" msgid "MRF Activity Expiration Policy" -msgstr "MRF Activity Expiration Policy" +msgstr "MRF Wygaśnięcie Aktyności" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_follow_bot" msgid "MRF FollowBot Policy" -msgstr "MRF FollowBot Policy" +msgstr "MRF Bot Śledzący" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_hashtag" msgid "MRF Hashtag" msgstr "MRF Hashtag" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_hellthread" msgid "MRF Hellthread" msgstr "MRF Hellthread" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_keyword" msgid "MRF Keyword" -msgstr "MRF Keyword" +msgstr "MRF Słowa Kluczowe" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_mention" msgid "MRF Mention" -msgstr "MRF Mention" +msgstr "MRF Wspomnienia" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_normalize_markup" msgid "MRF Normalize Markup" -msgstr "MRF Normalize Markup" +msgstr "MRF Normalizuj Markup" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_object_age" msgid "MRF Object Age" -msgstr "MRF Object Age" +msgstr "MRF Wiek Objektów" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_rejectnonpublic" msgid "MRF Reject Non Public" -msgstr "MRF Reject Non Public" +msgstr "MRF Odrzuć Niepubliczne" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_simple" msgid "MRF Simple" -msgstr "MRF Simple" +msgstr "MRF Prosty" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_steal_emoji" msgid "MRF Emojis" -msgstr "MRF Emojis" +msgstr "MRF Emoji" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_subchain" msgid "MRF Subchain" msgstr "MRF Subchain" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:mrf_vocabulary" msgid "MRF Vocabulary" -msgstr "MRF Vocabulary" +msgstr "MRF Słownictwo" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:oauth2" msgid "OAuth2" msgstr "OAuth2" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:populate_hashtags_table" msgid "Populate hashtags table" -msgstr "Populate hashtags table" +msgstr "Wypełnij tabelę hasztagów" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:rate_limit" msgid "Rate limit" -msgstr "Rate limit" +msgstr "Limit prędkości" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:restrict_unauthenticated" msgid "Restrict Unauthenticated" -msgstr "Restrict Unauthenticated" +msgstr "Ogranicz nieuwieżytelnione" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:rich_media" msgid "Rich media" -msgstr "Rich media" +msgstr "Bogate media" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:static_fe" msgid "Static FE" -msgstr "Static FE" +msgstr "Statyczny FE" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:streamer" msgid "Streamer" msgstr "Streamer" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:uri_schemes" msgid "URI Schemes" -msgstr "URI Schemes" +msgstr "Schematy URI" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:user" msgid "User" -msgstr "User" +msgstr "Użytkownik" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:web_cache_ttl" msgid "Web cache TTL" -msgstr "Web cache TTL" +msgstr "TTL cache sieciowego" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:welcome" msgid "Welcome" -msgstr "Welcome" +msgstr "Witaj" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-:workers" msgid "Workers" -msgstr "Workers" +msgstr "Workery" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-ConcurrentLimiter" msgid "ConcurrentLimiter" msgstr "ConcurrentLimiter" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Oban" msgid "Oban" msgstr "Oban" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Captcha" msgid "Pleroma.Captcha" msgstr "Pleroma.Captcha" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Captcha.Kocaptcha" msgid "Pleroma.Captcha.Kocaptcha" msgstr "Pleroma.Captcha.Kocaptcha" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.Mailer" msgid "Pleroma.Emails.Mailer" msgstr "Pleroma.Emails.Mailer" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.NewUsersDigestEmail" msgid "Pleroma.Emails.NewUsersDigestEmail" msgstr "Pleroma.Emails.NewUsersDigestEmail" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Emails.UserEmail" msgid "Pleroma.Emails.UserEmail" msgstr "Pleroma.Emails.UserEmail" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Formatter" msgid "Linkify" msgstr "Linkify" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.ScheduledActivity" msgid "Pleroma.ScheduledActivity" msgstr "Pleroma.ScheduledActivity" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Upload" msgid "Pleroma.Upload" msgstr "Pleroma.Upload" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Upload.Filter.AnonymizeFilename" msgid "Pleroma.Upload.Filter.AnonymizeFilename" msgstr "Pleroma.Upload.Filter.AnonymizeFilename" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Upload.Filter.Mogrify" msgid "Pleroma.Upload.Filter.Mogrify" msgstr "Pleroma.Upload.Filter.Mogrify" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Uploaders.Local" msgid "Pleroma.Uploaders.Local" msgstr "Pleroma.Uploaders.Local" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Uploaders.S3" msgid "Pleroma.Uploaders.S3" msgstr "Pleroma.Uploaders.S3" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.User" msgid "Pleroma.User" msgstr "Pleroma.User" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.User.Backup" msgid "Pleroma.User.Backup" msgstr "Pleroma.User.Backup" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.ApiSpec.CastAndValidate" msgid "Pleroma.Web.ApiSpec.CastAndValidate" msgstr "Pleroma.Web.ApiSpec.CastAndValidate" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Http" msgid "Pleroma.Web.MediaProxy.Invalidation.Http" msgstr "Pleroma.Web.MediaProxy.Invalidation.Http" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.MediaProxy.Invalidation.Script" msgid "Pleroma.Web.MediaProxy.Invalidation.Script" msgstr "Pleroma.Web.MediaProxy.Invalidation.Script" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.Metadata" msgid "Pleroma.Web.Metadata" msgstr "Pleroma.Web.Metadata" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.Plugs.RemoteIp" msgid "Pleroma.Web.Plugs.RemoteIp" msgstr "Pleroma.Web.Plugs.RemoteIp" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Web.Preload" msgid "Pleroma.Web.Preload" msgstr "Pleroma.Web.Preload" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :pleroma-Pleroma.Workers.PurgeExpiredActivity" msgid "Pleroma.Workers.PurgeExpiredActivity" msgstr "Pleroma.Workers.PurgeExpiredActivity" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config label at :web_push_encryption-:vapid_details" msgid "Vapid Details" -msgstr "Vapid Details" +msgstr "Detale Vapid" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :ex_aws-:s3 > :access_key_id" msgid "S3 access key ID" -msgstr "S3 access key ID" +msgstr "ID klucza dostępu S3" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :ex_aws-:s3 > :host" msgid "S3 host" -msgstr "S3 host" +msgstr "Host S3" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :ex_aws-:s3 > :region" msgid "S3 region (for AWS)" -msgstr "S3 region (for AWS)" +msgstr "Region S3 (dla AWS)" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :ex_aws-:s3 > :secret_access_key" msgid "Secret access key" -msgstr "Secret access key" +msgstr "Sekretny klucz dostępu" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, 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 "" -"Where logs will be sent, :console - send logs to stdout, { ExSyslogger, :" -"ex_syslogger } - to syslog, Quack.Logger - to Slack." +"Gdzie będą wysyłane logi, :console - wysyła na standardowe wyjście, { " +"ExSyslogger, :ex_syslogger } - do sysloga, Quack.Logger - na Slack." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :logger-:console > :format" msgid "Default: \"$date $time [$level] $levelpad$node $metadata $message\"" -msgstr "Default: \"$date $time [$level] $levelpad$node $metadata $message\"" +msgstr "Domyślne: \"$date $time [$level] $levelpad$node $metadata $message\"" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :logger-:console > :level" msgid "Log level" -msgstr "Log level" +msgstr "Poziom logowania" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :logger-:ex_syslogger > :format" msgid "Default: \"$date $time [$level] $levelpad$node $metadata $message\"" -msgstr "Default: \"$date $time [$level] $levelpad$node $metadata $message\"" +msgstr "Domyślne: \"$date $time [$level] $levelpad$node $metadata $message\"" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, 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 "" -"A string that's prepended to every message, and is typically set to the app " -"name" +"Tekst dopisywany do początku każdej wiadomości, i zazwyczaj ustawiany na " +"nazwę apki" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :logger-:ex_syslogger > :level" msgid "Log level" -msgstr "Log level" +msgstr "Poziom logowania" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma > :admin_token" msgid "Admin token" -msgstr "Admin token" +msgstr "Token admina" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:activitypub > :blockers_visible" msgid "Whether a user can see someone who has blocked them" -msgstr "Whether a user can see someone who has blocked them" +msgstr "Czy użytkownik może widzieć, kto go blokuje" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:activitypub > :follow_handshake_timeout" msgid "Following handshake timeout" -msgstr "Following handshake timeout" +msgstr "Limit czasu na hanshake prośby o śledzenie" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:activitypub > :note_replies_output_limit" msgid "The number of Note replies' URIs to be included with outgoing federation (`5` to match Mastodon hardcoded value, `0` to disable the output)" msgstr "" -"The number of Note replies' URIs to be included with outgoing federation (`5`" -" to match Mastodon hardcoded value, `0` to disable the output)" +"Limit URI do odpowiedzi na notatkę zawartych w wychodzącej federacji (`5` by " +"dopasować się do wartości ustawionej na sztywno w Mastodonie, `0` by " +"wyłączyć)" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:activitypub > :outgoing_blocks" msgid "Whether to federate blocks to other instances" -msgstr "Whether to federate blocks to other instances" +msgstr "Czy informować inne instancje o blokadach" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:activitypub > :sign_object_fetches" msgid "Sign object fetches with HTTP signatures" -msgstr "Sign object fetches with HTTP signatures" +msgstr "Podpisuj zapytania o objekty podpisami HTTP" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:activitypub > :unfollow_blocked" msgid "Whether blocks result in people getting unfollowed" -msgstr "Whether blocks result in people getting unfollowed" +msgstr "Czy blokady usuwają śledzenie" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, 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 "" -"This will be used as the default mascot on MastoFE. Default: " -"`:pleroma_fox_tan`" +msgstr "Domyślna maskotka na MastoFE. Domyślna: `:pleroma_fox_tan`" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:assets > :default_user_avatar" msgid "URL of the default user avatar" -msgstr "URL of the default user avatar" +msgstr "URL domyślnego avatara nowych użytkowników" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, 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 "" -"Keyword of mascots, each element must contain both an URL and a mime_type key" +"Słowa kluczowe maskotek, każdy element musi zawierać URL i klucz mime_type" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, 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 "" -"Authentication form template. By default it's `show.html` which corresponds " -"to `lib/pleroma/web/templates/o_auth/o_auth/show.html.ee`." +"Szablon formy uwierzytelnienia. Domyślnie jest to `show.html` które " +"odpowiada `lib/pleroma/web/templates/o_auth/o_auth/show.html.ee`." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:auth > :enforce_oauth_admin_scope_usage" msgid "OAuth admin scope requirement toggle. If enabled, admin actions explicitly demand admin OAuth scope(s) presence in OAuth token (client app must support admin scopes). If disabled and token doesn't have admin scope(s), `is_admin` user flag grants access to admin-specific actions." msgstr "" -"OAuth admin scope requirement toggle. If enabled, admin actions explicitly " -"demand admin OAuth scope(s) presence in OAuth token (client app must support " -"admin scopes). If disabled and token doesn't have admin scope(s), `is_admin` " -"user flag grants access to admin-specific actions." +"Zakres wymagań zakresu admina OAuth. Jeśli włączone, akcje admina wymagają " +"obecności zakresu admina OAuth w tokenie OAuth (aplikacja kliencka musi " +"wspierać zakresy admina). Jeśli wyłączone i token nie ma zakresów admina, " +"flaga użytkownika`is_admin` daje dostęp do zadań administracyjnych." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:auth > :oauth_consumer_strategies" msgid "The list of enabled OAuth consumer strategies. By default it's set by OAUTH_CONSUMER_STRATEGIES environment variable. Each entry in this space-delimited string should be of format \"strategy\" or \"strategy:dependency\" (e.g. twitter or keycloak:ueberauth_keycloak_strategy in case dependency is named differently than ueberauth_)." msgstr "" -"The list of enabled OAuth consumer strategies. By default it's set by " -"OAUTH_CONSUMER_STRATEGIES environment variable. Each entry in this space-" -"delimited string should be of format \"strategy\" or \"strategy:dependency\" " -"(e.g. twitter or keycloak:ueberauth_keycloak_strategy in case dependency is " -"named differently than ueberauth_)." +"Lista włączonych strategii konsumenckich OAuth. Domyślnie jest to ustawione " +"przez zmienną środowiskową OAUTH_CONSUMER_STRATEGIES. Każdy fragment tego " +"ciągu znaków oddzielonego spacjamy powinien być formatu \"strategy\" or " +"\"strategy:dependency\" (np. twitter lub keycloak:" +"ueberauth_keycloak_strategy jeśli zależność jest nazwana inaczej niż " +"ueberatuh_)." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:auth > :oauth_consumer_template" msgid "OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex`." msgstr "" -"OAuth consumer mode authentication form template. By default it's `consumer." -"html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/consumer." -"html.eex`." +"Szablon formularza uwierzytelniania w trybie konsumenckim. Domyślnie jest " +"to`consumer.html` które odpowiada`lib/pleroma/web/templates/o_auth/o_auth/" +"consumer.html.eex`." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:email_notifications > :digest" msgid "emails of \"what you've missed\" for users who have been inactive for a while" msgstr "" -"emails of \"what you've missed\" for users who have been inactive for a while" +"e-mail z \"co przegapiłoś\" dla użytkowników którzy nie logowali się przez " +"jakiś czas" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:email_notifications > :digest > :active" msgid "Globally enable or disable digest emails" -msgstr "Globally enable or disable digest emails" +msgstr "Globalnie włącz lub wyłącz e-maile z podsumowaniem" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:email_notifications > :digest > :inactivity_threshold" msgid "Minimum user inactivity threshold" -msgstr "Minimum user inactivity threshold" +msgstr "Próg minimalnej nieaktywności użytkowników" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:email_notifications > :digest > :interval" msgid "Minimum interval between digest emails to one user" -msgstr "Minimum interval between digest emails to one user" +msgstr "" +"Minimalny okres między e-mailami z podsumowaniami dla jednego użytkownika" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, 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 "" -"When to send digest email, in crontab format. \"0 0 0\" is the default, " -"meaning \"once a week at midnight on Sunday morning\"." +"Kiedy wysyłać e-mail z podsumowaniem, w formacie crontaba. Domyślne jest \"0 " +"0 0\", co znaczy \"raz na tydzień, o północy, w niedzielę wieczór\"." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, 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 "" -"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)." +"Położenie manifestu JSON. Ten manifest zawiera informacje o paczkach emoji " +"które możesz ściągnąć. Obecnie można dodać tylko jeden manifest (bez tablic)." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:emoji > :groups" msgid "Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the group name and the value is the location or array of locations. * can be used as a wildcard." msgstr "" -"Emojis are ordered in groups (tags). This is an array of key-value pairs " -"where the key is the group name and the value is the location or array of " -"locations. * can be used as a wildcard." +"Emoji są podzielone na grupy (tagi). Jest to tablica par klucz-wartość, " +"gdzie klucz jest nazwą grupy, a wartość jest położeniem lub tablicą położeń. " +"* może być użyte by dopasować dowolny ciąg znaków." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:emoji > :pack_extensions" msgid "A list of file extensions for emojis, when no emoji.txt for a pack is present" -msgstr "" -"A list of file extensions for emojis, when no emoji.txt for a pack is present" +msgstr "Lista rozszerzeń plików emoji, jeśli nie ma emoji.txt dla paczki" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, 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 "Location of custom emoji files. * can be used as a wildcard." +msgstr "" +"Położenie własnych plików emoji. * może być użyte dla dopasowania dowolnego " +"ciągu znaków." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:features > :improved_hashtag_timeline" msgid "Setting to force toggle / force disable improved hashtags timeline. `:enabled` forces hashtags to be fetched from `hashtags` table for hashtags timeline. `:disabled` forces object-embedded hashtags to be used (slower). Keep it `:auto` for automatic behaviour (it is auto-set to `:enabled` [unless overridden] when HashtagsTableMigrator completes)." msgstr "" -"Setting to force toggle / force disable improved hashtags timeline. " -"`:enabled` forces hashtags to be fetched from `hashtags` table for hashtags " -"timeline. `:disabled` forces object-embedded hashtags to be used (slower). " -"Keep it `:auto` for automatic behaviour (it is auto-set to `:enabled` [" -"unless overridden] when HashtagsTableMigrator completes)." +"Ustawienie by przełączyć / wymusić wyłączenie poprawionej osi czasów " +"hasztagów. `:enabled` wymusza by hasztagi były pobierane z tabeli `hashtags` " +"dla osi czasu hasztagów. `:disabled` wymusza użycie hasztagów zagnieżdżonych " +"w objektach (wolniejsze). Zostaw jako `:auto` dla zachowania automatycznego (" +"jest automatycznie ustawione na `:enabled` [o ile nie nadpisane] kiedy " +"zakończy pracę HashtagsTableMigrator)." #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:feed > :post_title" msgid "Configure title rendering" -msgstr "Configure title rendering" +msgstr "Konfiguruj wyświetlanie kafli" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:feed > :post_title > :max_length" msgid "Maximum number of characters before truncating title" -msgstr "Maximum number of characters before truncating title" +msgstr "Maksymalna liczba znaków przed skróceniem tytułu" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:feed > :post_title > :omission" msgid "Replacement which will be used after truncating string" -msgstr "Replacement which will be used after truncating string" +msgstr "Czego użyć po skróceniu ciągu znaków" #: lib/pleroma/docs/translator.ex:5 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgctxt "config description at :pleroma-:frontend_configurations > :pleroma_fe" msgid "Settings for Pleroma FE" -msgstr "Settings for Pleroma FE" +msgstr "Ustawienia Pleroma FE" #: lib/pleroma/docs/translator.ex:5 #, elixir-autogen, elixir-format, fuzzy -- 2.43.0 From c6dceb1802d7dfcb25d01c5e3a6c3eddbd168290 Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 22 Oct 2023 11:53:48 +0000 Subject: [PATCH 102/149] Translated using Weblate (Polish) Currently translated at 100.0% (47 of 47 strings) Co-authored-by: Weblate Co-authored-by: subtype Translate-URL: http://translate.akkoma.dev/projects/akkoma/akkoma-backend-posix-errors/pl/ Translation: Pleroma fe/Akkoma Backend (Posix Errors) --- priv/gettext/pl/LC_MESSAGES/posix_errors.po | 104 +++++++++++--------- 1 file changed, 56 insertions(+), 48 deletions(-) diff --git a/priv/gettext/pl/LC_MESSAGES/posix_errors.po b/priv/gettext/pl/LC_MESSAGES/posix_errors.po index 3fe3a2db9..442b77324 100644 --- a/priv/gettext/pl/LC_MESSAGES/posix_errors.po +++ b/priv/gettext/pl/LC_MESSAGES/posix_errors.po @@ -8,146 +8,154 @@ ### to merge POT files into PO files. msgid "" msgstr "" +"PO-Revision-Date: 2023-10-09 18:53+0000\n" +"Last-Translator: subtype \n" +"Language-Team: Polish \n" "Language: pl\n" -"Plural-Forms: nplurals=3; plural=(n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10||n%100>=20) ? 1 : 2);\n" +"Content-Type: text/plain; charset=UTF-8\n" +"Plural-Forms: nplurals=3; plural=n==1 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 " +"|| n%100>=20) ? 1 : 2;\n" +"X-Generator: Weblate 4.18.2\n" +"Content-Transfer-Encoding: 8bit\n" msgid "eperm" -msgstr "" +msgstr "Operacja niedozwolona" msgid "eacces" -msgstr "" +msgstr "Brak dostępu" msgid "eagain" -msgstr "" +msgstr "Zasoby chwilowo niedostępne" msgid "ebadf" -msgstr "" +msgstr "Błędny deskryptor pliku" msgid "ebadmsg" -msgstr "" +msgstr "Błędny komunikat" msgid "ebusy" -msgstr "" +msgstr "Urządzenie lub zasoby zajęte" msgid "edeadlk" -msgstr "" +msgstr "Uniknięto zakleszczenia zasobów" msgid "edeadlock" -msgstr "" +msgstr "Uniknięto zakleszczenia zasobów" msgid "edquot" -msgstr "" +msgstr "Przekroczony limit dyskowy" msgid "eexist" -msgstr "" +msgstr "Plik istnieje" msgid "efault" -msgstr "" +msgstr "Błędny adres" msgid "efbig" -msgstr "" +msgstr "Plik zbyt duży" msgid "eftype" -msgstr "" +msgstr "Niewłaściwy typ pliku" msgid "eintr" -msgstr "" +msgstr "Przerwane wywołanie systemowe" msgid "einval" -msgstr "" +msgstr "Zły argument" msgid "eio" -msgstr "" +msgstr "Błąd wejścia/wyjścia" msgid "eisdir" -msgstr "" +msgstr "Jest katalogiem" msgid "eloop" -msgstr "" +msgstr "Za duże zagnieżdżenie dowiązań symbolicznych" msgid "emfile" -msgstr "" +msgstr "Za dużo otwartych plików" msgid "emlink" -msgstr "" +msgstr "Za dużo dowiązań" msgid "emultihop" -msgstr "" +msgstr "Multihop attempted" msgid "enametoolong" -msgstr "" +msgstr "Za długa nazwa pliku" msgid "enfile" -msgstr "" +msgstr "Za dużo otwartych plików w systemie" msgid "enobufs" -msgstr "" +msgstr "Brak miejsca w buforze" msgid "enodev" -msgstr "" +msgstr "Nie ma takiego urządzenia" msgid "enolck" -msgstr "" +msgstr "Brak dostępnych blokad" msgid "enolink" -msgstr "" +msgstr "Połączenie zostało przerwane" msgid "enoent" -msgstr "" +msgstr "Nie ma takiego pliku ani katalogu" msgid "enomem" -msgstr "" +msgstr "Nie można przydzielić pamięci" msgid "enospc" -msgstr "" +msgstr "Brak miejsca na urządzeniu" msgid "enosr" -msgstr "" +msgstr "Brak dodatkowych strumieni" msgid "enostr" -msgstr "" +msgstr "Nie jest strumieniem" msgid "enosys" -msgstr "" +msgstr "Niezaimplementowana funkcja" msgid "enotblk" -msgstr "" +msgstr "Wymagane urządzenie blokowe" msgid "enotdir" -msgstr "" +msgstr "Nie jest katalogiem" msgid "enotsup" -msgstr "" +msgstr "Operacja nieobsługiwana" msgid "enxio" -msgstr "" +msgstr "Nie ma takiego urządzenia ani adresu" msgid "eopnotsupp" -msgstr "" +msgstr "Operacja na gnieździe nieobsługiwana" msgid "eoverflow" -msgstr "" +msgstr "Wartość za duża dla zdefiniowanego typu danych" msgid "epipe" -msgstr "" +msgstr "Przerwany potok" msgid "erange" -msgstr "" +msgstr "Za duży wynik" msgid "erofs" -msgstr "" +msgstr "System plików wyłącznie do odczytu" msgid "espipe" -msgstr "" +msgstr "Błędne przesunięcie" msgid "esrch" -msgstr "" +msgstr "Nie ma takiego procesu" msgid "estale" -msgstr "" +msgstr "Nieaktualny uchwyt pliku" msgid "etxtbsy" -msgstr "" +msgstr "Plik tekstowy zajęty" msgid "exdev" -msgstr "" +msgstr "Niepoprawne dowiązanie" -- 2.43.0 From 34ffb92db4a8cfc917ef89a829fe123e2497d461 Mon Sep 17 00:00:00 2001 From: Weblate Date: Sun, 22 Oct 2023 11:53:48 +0000 Subject: [PATCH 103/149] Update translation files Updated by "Squash Git commits" hook in Weblate. Co-authored-by: Weblate Translate-URL: http://translate.akkoma.dev/projects/akkoma/akkoma-backend-posix-errors/ Translation: Pleroma fe/Akkoma Backend (Posix Errors) --- priv/gettext/vi/LC_MESSAGES/errors.po | 34 +++++++++++++-------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/priv/gettext/vi/LC_MESSAGES/errors.po b/priv/gettext/vi/LC_MESSAGES/errors.po index 445a5ae07..8aee75e9f 100644 --- a/priv/gettext/vi/LC_MESSAGES/errors.po +++ b/priv/gettext/vi/LC_MESSAGES/errors.po @@ -3,16 +3,16 @@ msgstr "" "Project-Id-Version: PACKAGE VERSION\n" "Report-Msgid-Bugs-To: \n" "POT-Creation-Date: 2021-09-06 11:13+0000\n" -"PO-Revision-Date: 2021-09-07 16:42+0000\n" -"Last-Translator: Hồ Nhất Duy \n" -"Language-Team: Vietnamese \n" +"PO-Revision-Date: 2023-09-08 05:53+0000\n" +"Last-Translator: Nguyễn Gia Phong \n" +"Language-Team: Vietnamese \n" "Language: vi\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.6.2\n" +"X-Generator: Weblate 4.18.2\n" ## This file is a PO Template file. ## @@ -577,37 +577,37 @@ msgstr "Người này không phải quản trị viên." #, elixir-format msgid "Last export was less than a day ago" msgid_plural "Last export was less than %{days} days ago" -msgstr[0] "" +msgstr[0] "Vừa sao lưu lần cuối %{days} ngày trước" #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:399 #, elixir-autogen, elixir-format msgid "Character limit (%{limit} characters) exceeded, contains %{length} characters" -msgstr "" +msgstr "Quá dài: %{length} kí tự (tối đa %{limit})" #: lib/pleroma/web/plugs/ensure_staff_privileged_plug.ex:33 #: lib/pleroma/web/plugs/user_is_staff_plug.ex:20 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgid "User is not a staff member." -msgstr "Người này không phải quản trị viên." +msgstr "Người dùng không phải quản trị viên." #: lib/pleroma/web/o_auth/o_auth_controller.ex:391 #, elixir-autogen, elixir-format msgid "Your account is awaiting approval." -msgstr "" +msgstr "Tài khoản của bạn đang chờ được duyệt." #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:256 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:259 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:262 #, elixir-autogen, elixir-format msgid "File is too large" -msgstr "" +msgstr "Tệp quá lớn" #: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:37 #: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:48 #: lib/pleroma/web/mastodon_api/controllers/tag_controller.ex:59 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgid "Hashtag not found" -msgstr "Không tìm thấy danh sách" +msgstr "Không tìm thấy hashtag" #: lib/pleroma/web/common_api/activity_draft.ex:144 #, elixir-autogen, elixir-format @@ -627,7 +627,7 @@ msgstr "" #: lib/pleroma/web/common_api/activity_draft.ex:126 #, elixir-autogen, elixir-format msgid "You can't quote a status that doesn't exist" -msgstr "" +msgstr "Bạn không thể trích dẫn bàn viết không tồn tại" #: lib/pleroma/web/embed_controller.ex:35 #, elixir-autogen, elixir-format @@ -637,9 +637,9 @@ msgstr "" #: lib/pleroma/web/embed_controller.ex:38 #, elixir-autogen, elixir-format msgid "Not authorized to view this post" -msgstr "" +msgstr "Không có quyền xem bài viết này" #: lib/pleroma/web/embed_controller.ex:32 -#, elixir-autogen, elixir-format, fuzzy +#, elixir-autogen, elixir-format msgid "Post not found" -msgstr "Không tìm thấy danh sách" +msgstr "Không tìm thấy bài viết" -- 2.43.0 From 889b57df82b9b18e720b9254afd375bfa1c5c0ab Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sat, 24 Feb 2024 13:54:21 +0000 Subject: [PATCH 104/149] 2024.02 release --- CHANGELOG.md | 2 +- mix.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fee4d95e2..9033050a1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -## Unreleased +## 2024.02 ## Added - Full compatibility with Erlang OTP26 diff --git a/mix.exs b/mix.exs index 23f1761b5..4e8b3160d 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("3.10.3"), + version: version("3.11.0"), elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), compilers: Mix.compilers(), -- 2.43.0 From 5d467af6c5299fd249a4c7d285be6f0839c635b3 Mon Sep 17 00:00:00 2001 From: Oneric Date: Mon, 4 Mar 2024 17:50:19 +0100 Subject: [PATCH 105/149] Update notes on security exploit handling --- SECURITY.md | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/SECURITY.md b/SECURITY.md index c009d21d9..d37a8c9ca 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,16 +1,21 @@ -# Pleroma backend security policy - -## Supported versions - -Currently, Pleroma offers bugfixes and security patches only for the latest minor release. - -| Version | Support -|---------| -------- -| 2.2 | Bugfixes and security patches +# Akkoma backend security handling ## Reporting a vulnerability -Please use confidential issues (tick the "This issue is confidential and should only be visible to team members with at least Reporter access." box when submitting) at our [bugtracker](https://git.pleroma.social/pleroma/pleroma/-/issues/new) for reporting vulnerabilities. +Please send an email (preferably encrypted) or +a DM via our IRC to one of the following people: + +| Forgejo nick | IRC nick | Email | GPG | +| ------------ | ------------- | ------------- | --------------------------------------- | +| floatinghost | FloatingGhost | *see GPG key* | https://coffee-and-dreams.uk/pubkey.asc | + ## Announcements -New releases are announced at [pleroma.social](https://pleroma.social/announcements/). All security releases are tagged with ["Security"](https://pleroma.social/announcements/tags/security/). You can be notified of them by subscribing to an Atom feed at . +New releases and security issues are announced at +[meta.akkoma.dev](https://meta.akkoma.dev/c/releases) and +[@akkoma@ihatebeinga.live](https://ihatebeinga.live/akkoma). + +Both also offer RSS feeds +([meta](https://meta.akkoma.dev/c/releases/7.rss), +[fedi](https://ihatebeinga.live/users/akkoma.rss)) +so you can keep an eye on it without any accounts. -- 2.43.0 From dbb6091d0199678dd4ba887c3623d5df14c08c95 Mon Sep 17 00:00:00 2001 From: Oneric Date: Mon, 4 Mar 2024 17:50:20 +0100 Subject: [PATCH 106/149] Import copy of Plug.Static from Plug 1.15.3 The following commit will apply the needed patch --- .../web/plugs/static_no_content_type.ex | 451 ++++++++++++++++++ 1 file changed, 451 insertions(+) create mode 100644 lib/pleroma/web/plugs/static_no_content_type.ex diff --git a/lib/pleroma/web/plugs/static_no_content_type.ex b/lib/pleroma/web/plugs/static_no_content_type.ex new file mode 100644 index 000000000..285e4ff30 --- /dev/null +++ b/lib/pleroma/web/plugs/static_no_content_type.ex @@ -0,0 +1,451 @@ +# This is almost identical to Plug.Static from Plug 1.15.3 (2024-01-16) +# It being copied is a temporary measure to fix an urgent bug without +# needing to wait for merge of a suitable patch upstream +# The differences are: +# - this leading comment +# - renaming of the module from 'Plug.Static' to 'Pleroma.Web.Plugs.StaticNoCT' + +defmodule Pleroma.Web.Plugs.StaticNoCT do + @moduledoc """ + A plug for serving static assets. + + It requires two options: + + * `:at` - the request path to reach for static assets. + It must be a string. + + * `:from` - the file system path to read static assets from. + It can be either: a string containing a file system path, an + atom representing the application name (where assets will + be served from `priv/static`), a tuple containing the + application name and the directory to serve assets from (besides + `priv/static`), or an MFA tuple. + + The preferred form is to use `:from` with an atom or tuple, since + it will make your application independent from the starting directory. + For example, if you pass: + + plug Plug.Static, from: "priv/app/path" + + Plug.Static will be unable to serve assets if you build releases + or if you change the current directory. Instead do: + + plug Plug.Static, from: {:app_name, "priv/app/path"} + + If a static asset cannot be found, `Plug.Static` simply forwards + the connection to the rest of the pipeline. + + ## Cache mechanisms + + `Plug.Static` uses etags for HTTP caching. This means browsers/clients + should cache assets on the first request and validate the cache on + following requests, not downloading the static asset once again if it + has not changed. The cache-control for etags is specified by the + `cache_control_for_etags` option and defaults to `"public"`. + + However, `Plug.Static` also supports direct cache control by using + versioned query strings. If the request query string starts with + "?vsn=", `Plug.Static` assumes the application is versioning assets + and does not set the `ETag` header, meaning the cache behaviour will + be specified solely by the `cache_control_for_vsn_requests` config, + which defaults to `"public, max-age=31536000"`. + + ## Options + + * `:encodings` - list of 2-ary tuples where first value is value of + the `Accept-Encoding` header and second is extension of the file to + be served if given encoding is accepted by client. Entries will be tested + in order in list, so entries higher in list will be preferred. Defaults + to: `[]`. + + In addition to setting this value directly it supports 2 additional + options for compatibility reasons: + + + `:brotli` - will append `{"br", ".br"}` to the encodings list. + + `:gzip` - will append `{"gzip", ".gz"}` to the encodings list. + + Additional options will be added in the above order (Brotli takes + preference over Gzip) to reflect older behaviour which was set due + to fact that Brotli in general provides better compression ratio than + Gzip. + + * `:cache_control_for_etags` - sets the cache header for requests + that use etags. Defaults to `"public"`. + + * `:etag_generation` - specify a `{module, function, args}` to be used + to generate an etag. The `path` of the resource will be passed to + the function, as well as the `args`. If this option is not supplied, + etags will be generated based off of file size and modification time. + Note it is [recommended for the etag value to be quoted](https://tools.ietf.org/html/rfc7232#section-2.3), + which Plug won't do automatically. + + * `:cache_control_for_vsn_requests` - sets the cache header for + requests starting with "?vsn=" in the query string. Defaults to + `"public, max-age=31536000"`. + + * `:only` - filters which requests to serve. This is useful to avoid + file system access on every request when this plug is mounted + at `"/"`. For example, if `only: ["images", "favicon.ico"]` is + specified, only files in the "images" directory and the + "favicon.ico" file will be served by `Plug.Static`. + Note that `Plug.Static` matches these filters against request + uri and not against the filesystem. When requesting + a file with name containing non-ascii or special characters, + you should use urlencoded form. For example, you should write + `only: ["file%20name"]` instead of `only: ["file name"]`. + Defaults to `nil` (no filtering). + + * `:only_matching` - a relaxed version of `:only` that will + serve any request as long as one of the given values matches the + given path. For example, `only_matching: ["images", "favicon"]` + will match any request that starts at "images" or "favicon", + be it "/images/foo.png", "/images-high/foo.png", "/favicon.ico" + or "/favicon-high.ico". Such matches are useful when serving + digested files at the root. Defaults to `nil` (no filtering). + + * `:headers` - other headers to be set when serving static assets. Specify either + an enum of key-value pairs or a `{module, function, args}` to return an enum. The + `conn` will be passed to the function, as well as the `args`. + + * `:content_types` - custom MIME type mapping. As a map with filename as key + and content type as value. For example: + `content_types: %{"apple-app-site-association" => "application/json"}`. + + ## Examples + + This plug can be mounted in a `Plug.Builder` pipeline as follows: + + defmodule MyPlug do + use Plug.Builder + + plug Plug.Static, + at: "/public", + from: :my_app, + only: ~w(images robots.txt) + plug :not_found + + def not_found(conn, _) do + send_resp(conn, 404, "not found") + end + end + + """ + + @behaviour Plug + @allowed_methods ~w(GET HEAD) + + import Plug.Conn + alias Plug.Conn + + # In this module, the `:prim_file` Erlang module along with the `:file_info` + # record are used instead of the more common and Elixir-y `File` module and + # `File.Stat` struct, respectively. The reason behind this is performance: all + # the `File` operations pass through a single process in order to support node + # operations that we simply don't need when serving assets. + + require Record + Record.defrecordp(:file_info, Record.extract(:file_info, from_lib: "kernel/include/file.hrl")) + + defmodule InvalidPathError do + defexception message: "invalid path for static asset", plug_status: 400 + end + + @impl true + def init(opts) do + from = + case Keyword.fetch!(opts, :from) do + {_, _} = from -> from + {_, _, _} = from -> from + from when is_atom(from) -> {from, "priv/static"} + from when is_binary(from) -> from + _ -> raise ArgumentError, ":from must be an atom, a binary or a tuple" + end + + encodings = + opts + |> Keyword.get(:encodings, []) + |> maybe_add("br", ".br", Keyword.get(opts, :brotli, false)) + |> maybe_add("gzip", ".gz", Keyword.get(opts, :gzip, false)) + + %{ + encodings: encodings, + only_rules: {Keyword.get(opts, :only, []), Keyword.get(opts, :only_matching, [])}, + qs_cache: Keyword.get(opts, :cache_control_for_vsn_requests, "public, max-age=31536000"), + et_cache: Keyword.get(opts, :cache_control_for_etags, "public"), + et_generation: Keyword.get(opts, :etag_generation, nil), + headers: Keyword.get(opts, :headers, %{}), + content_types: Keyword.get(opts, :content_types, %{}), + from: from, + at: opts |> Keyword.fetch!(:at) |> Plug.Router.Utils.split() + } + end + + @impl true + def call( + conn = %Conn{method: meth}, + %{at: at, only_rules: only_rules, from: from, encodings: encodings} = options + ) + when meth in @allowed_methods do + segments = subset(at, conn.path_info) + + if allowed?(only_rules, segments) do + segments = Enum.map(segments, &uri_decode/1) + + if invalid_path?(segments) do + raise InvalidPathError, "invalid path for static asset: #{conn.request_path}" + end + + path = path(from, segments) + range = get_req_header(conn, "range") + encoding = file_encoding(conn, path, range, encodings) + serve_static(encoding, conn, segments, range, options) + else + conn + end + end + + def call(conn, _options) do + conn + end + + defp uri_decode(path) do + # TODO: Remove rescue as this can't fail from Elixir v1.13 + try do + URI.decode(path) + rescue + ArgumentError -> + raise InvalidPathError + end + end + + defp allowed?(_only_rules, []), do: false + defp allowed?({[], []}, _list), do: true + + defp allowed?({full, prefix}, [h | _]) do + h in full or (prefix != [] and match?({0, _}, :binary.match(h, prefix))) + end + + defp serve_static({content_encoding, file_info, path}, conn, segments, range, options) do + %{ + qs_cache: qs_cache, + et_cache: et_cache, + et_generation: et_generation, + headers: headers, + content_types: types + } = options + + case put_cache_header(conn, qs_cache, et_cache, et_generation, file_info, path) do + {:stale, conn} -> + filename = List.last(segments) + content_type = Map.get(types, filename) || MIME.from_path(filename) + + conn + |> put_resp_header("content-type", content_type) + |> put_resp_header("accept-ranges", "bytes") + |> maybe_add_encoding(content_encoding) + |> merge_headers(headers) + |> serve_range(file_info, path, range, options) + + {:fresh, conn} -> + conn + |> maybe_add_vary(options) + |> send_resp(304, "") + |> halt() + end + end + + defp serve_static(:error, conn, _segments, _range, _options) do + conn + end + + defp serve_range(conn, file_info, path, [range], options) do + file_info(size: file_size) = file_info + + with %{"bytes" => bytes} <- Plug.Conn.Utils.params(range), + {range_start, range_end} <- start_and_end(bytes, file_size) do + send_range(conn, path, range_start, range_end, file_size, options) + else + _ -> send_entire_file(conn, path, options) + end + end + + defp serve_range(conn, _file_info, path, _range, options) do + send_entire_file(conn, path, options) + end + + defp start_and_end("-" <> rest, file_size) do + case Integer.parse(rest) do + {last, ""} when last > 0 and last <= file_size -> {file_size - last, file_size - 1} + _ -> :error + end + end + + defp start_and_end(range, file_size) do + case Integer.parse(range) do + {first, "-"} when first >= 0 -> + {first, file_size - 1} + + {first, "-" <> rest} when first >= 0 -> + case Integer.parse(rest) do + {last, ""} when last >= first -> {first, min(last, file_size - 1)} + _ -> :error + end + + _ -> + :error + end + end + + defp send_range(conn, path, 0, range_end, file_size, options) when range_end == file_size - 1 do + send_entire_file(conn, path, options) + end + + defp send_range(conn, path, range_start, range_end, file_size, _options) do + length = range_end - range_start + 1 + + conn + |> put_resp_header("content-range", "bytes #{range_start}-#{range_end}/#{file_size}") + |> send_file(206, path, range_start, length) + |> halt() + end + + defp send_entire_file(conn, path, options) do + conn + |> maybe_add_vary(options) + |> send_file(200, path) + |> halt() + end + + defp maybe_add_encoding(conn, nil), do: conn + defp maybe_add_encoding(conn, ce), do: put_resp_header(conn, "content-encoding", ce) + + defp maybe_add_vary(conn, %{encodings: encodings}) do + # If we serve gzip or brotli at any moment, we need to set the proper vary + # header regardless of whether we are serving gzip content right now. + # See: http://www.fastly.com/blog/best-practices-for-using-the-vary-header/ + if encodings != [] do + update_in(conn.resp_headers, &[{"vary", "Accept-Encoding"} | &1]) + else + conn + end + end + + defp put_cache_header( + %Conn{query_string: "vsn=" <> _} = conn, + qs_cache, + _et_cache, + _et_generation, + _file_info, + _path + ) + when is_binary(qs_cache) do + {:stale, put_resp_header(conn, "cache-control", qs_cache)} + end + + defp put_cache_header(conn, _qs_cache, et_cache, et_generation, file_info, path) + when is_binary(et_cache) do + etag = etag_for_path(file_info, et_generation, path) + + conn = + conn + |> put_resp_header("cache-control", et_cache) + |> put_resp_header("etag", etag) + + if etag in get_req_header(conn, "if-none-match") do + {:fresh, conn} + else + {:stale, conn} + end + end + + defp put_cache_header(conn, _, _, _, _, _) do + {:stale, conn} + end + + defp etag_for_path(file_info, et_generation, path) do + case et_generation do + {module, function, args} -> + apply(module, function, [path | args]) + + nil -> + file_info(size: size, mtime: mtime) = file_info + < :erlang.phash2() |> Integer.to_string(16)::binary, ?">> + end + end + + defp file_encoding(conn, path, [_range], _encodings) do + # We do not support compression for range queries. + file_encoding(conn, path, nil, []) + end + + defp file_encoding(conn, path, _range, encodings) do + encoded = + Enum.find_value(encodings, fn {encoding, ext} -> + if file_info = accept_encoding?(conn, encoding) && regular_file_info(path <> ext) do + {encoding, file_info, path <> ext} + end + end) + + cond do + not is_nil(encoded) -> + encoded + + file_info = regular_file_info(path) -> + {nil, file_info, path} + + true -> + :error + end + end + + defp regular_file_info(path) do + case :prim_file.read_file_info(path) do + {:ok, file_info(type: :regular) = file_info} -> + file_info + + _ -> + nil + end + end + + defp accept_encoding?(conn, encoding) do + encoding? = &String.contains?(&1, [encoding, "*"]) + + Enum.any?(get_req_header(conn, "accept-encoding"), fn accept -> + accept |> Plug.Conn.Utils.list() |> Enum.any?(encoding?) + end) + end + + defp maybe_add(list, key, value, true), do: list ++ [{key, value}] + defp maybe_add(list, _key, _value, false), do: list + + defp path({module, function, arguments}, segments) + when is_atom(module) and is_atom(function) and is_list(arguments), + do: Enum.join([apply(module, function, arguments) | segments], "/") + + defp path({app, from}, segments) when is_atom(app) and is_binary(from), + do: Enum.join([Application.app_dir(app), from | segments], "/") + + defp path(from, segments), + do: Enum.join([from | segments], "/") + + defp subset([h | expected], [h | actual]), do: subset(expected, actual) + defp subset([], actual), do: actual + defp subset(_, _), do: [] + + defp invalid_path?(list) do + invalid_path?(list, :binary.compile_pattern(["/", "\\", ":", "\0"])) + end + + defp invalid_path?([h | _], _match) when h in [".", "..", ""], do: true + defp invalid_path?([h | t], match), do: String.contains?(h, match) or invalid_path?(t) + defp invalid_path?([], _match), do: false + + defp merge_headers(conn, {module, function, args}) do + merge_headers(conn, apply(module, function, [conn | args])) + end + + defp merge_headers(conn, headers) do + merge_resp_headers(conn, headers) + end +end -- 2.43.0 From 7ef93c0b6db47fa1bb76b22b66387e5a0ac891cf Mon Sep 17 00:00:00 2001 From: Oneric Date: Mon, 4 Mar 2024 17:50:20 +0100 Subject: [PATCH 107/149] Add set_content_type to Plug.StaticNoCT --- .../web/plugs/static_no_content_type.ex | 24 ++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/plugs/static_no_content_type.ex b/lib/pleroma/web/plugs/static_no_content_type.ex index 285e4ff30..ea00a2d5d 100644 --- a/lib/pleroma/web/plugs/static_no_content_type.ex +++ b/lib/pleroma/web/plugs/static_no_content_type.ex @@ -4,6 +4,7 @@ # The differences are: # - this leading comment # - renaming of the module from 'Plug.Static' to 'Pleroma.Web.Plugs.StaticNoCT' +# - additon of set_content_type option defmodule Pleroma.Web.Plugs.StaticNoCT do @moduledoc """ @@ -111,6 +112,13 @@ defmodule Pleroma.Web.Plugs.StaticNoCT do and content type as value. For example: `content_types: %{"apple-app-site-association" => "application/json"}`. + * `:set_content_type` - by default Plug.Static (re)sets the content type header + using auto-detection and the `:content_types` map. But when set to `false` + no content-type header will be inserted instead retaining the original + value or lack thereof. This can be useful when custom logic for appropiate + content types is needed which cannot be reasonably expressed as a static + filename map. + ## Examples This plug can be mounted in a `Plug.Builder` pipeline as follows: @@ -175,6 +183,7 @@ def init(opts) do et_generation: Keyword.get(opts, :etag_generation, nil), headers: Keyword.get(opts, :headers, %{}), content_types: Keyword.get(opts, :content_types, %{}), + set_content_type: Keyword.get(opts, :set_content_type, true), from: from, at: opts |> Keyword.fetch!(:at) |> Plug.Router.Utils.split() } @@ -225,22 +234,31 @@ defp allowed?({full, prefix}, [h | _]) do h in full or (prefix != [] and match?({0, _}, :binary.match(h, prefix))) end + defp maybe_put_content_type(conn, false, _, _), do: conn + + defp maybe_put_content_type(conn, _, types, filename) do + content_type = Map.get(types, filename) || MIME.from_path(filename) + + conn + |> put_resp_header("content-type", content_type) + end + defp serve_static({content_encoding, file_info, path}, conn, segments, range, options) do %{ qs_cache: qs_cache, et_cache: et_cache, et_generation: et_generation, headers: headers, - content_types: types + content_types: types, + set_content_type: set_content_type } = options case put_cache_header(conn, qs_cache, et_cache, et_generation, file_info, path) do {:stale, conn} -> filename = List.last(segments) - content_type = Map.get(types, filename) || MIME.from_path(filename) conn - |> put_resp_header("content-type", content_type) + |> maybe_put_content_type(set_content_type, types, filename) |> put_resp_header("accept-ranges", "bytes") |> maybe_add_encoding(content_encoding) |> merge_headers(headers) -- 2.43.0 From f7c9793542711bfd7bc8bdf2d8be736a4eea9d15 Mon Sep 17 00:00:00 2001 From: Oneric Date: Mon, 4 Mar 2024 17:50:21 +0100 Subject: [PATCH 108/149] Sanitise Content-Type of uploads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The lack thereof enables spoofing ActivityPub objects. A malicious user could upload fake activities as attachments and (if having access to remote search) trick local and remote fedi instances into fetching and processing it as a valid object. If uploads are hosted on the same domain as the instance itself, it is possible for anyone with upload access to impersonate(!) other users of the same instance. If uploads are exclusively hosted on a different domain, even the most basic check of domain of the object id and fetch url matching should prevent impersonation. However, it may still be possible to trick servers into accepting bogus users on the upload (sub)domain and bogus notes attributed to such users. Instances which later migrated to a different domain and have a permissive redirect rule in place can still be vulnerable. If — like Akkoma — the fetching server is overly permissive with redirects, impersonation still works. This was possible because Plug.Static also uses our custom MIME type mappings used for actually authentic AP objects. Provided external storage providers don’t somehow return ActivityStream Content-Types on their own, instances using those are also safe against their users being spoofed via uploads. Akkoma instances using the OnlyMedia upload filter cannot be exploited as a vector in this way — IF the fetching server validates the Content-Type of fetched objects (Akkoma itself does this already). However, restricting uploads to only multimedia files may be a bit too heavy-handed. Instead this commit will restrict the returned Content-Type headers for user uploaded files to a safe subset, falling back to generic 'application/octet-stream' for anything else. This will also protect against non-AP payloads as e.g. used in past frontend code injection attacks. It’s a slight regression in user comfort, if say PDFs are uploaded, but this trade-off seems fairly acceptable. (Note, just excluding our own custom types would offer no protection against non-AP payloads and bear a (perhaps small) risk of a silent regression should MIME ever decide to add a canonical extension for ActivityPub objects) Now, one might expect there to be other defence mechanisms besides Content-Type preventing counterfeits from being accepted, like e.g. validation of the queried URL and AP ID matching. Inserting a self-reference into our uploads is hard, but unfortunately *oma does not verify the id in such a way and happily accepts _anything_ from the same domain (without even considering redirects). E.g. Sharkey (and possibly other *keys) seem to attempt to guard against this by immediately refetching the object from its ID, but this is easily circumvented by just uploading two payloads with the ID of one linking to the other. Unfortunately *oma is thus _both_ a vector for spoofing and vulnerable to those spoof payloads, resulting in an easy way to impersonate our users. Similar flaws exists for emoji and media proxy. Subsequent commits will fix this by rigorously sanitising content types in more areas, hardening our checks, improving the default config and discouraging insecure config options. --- CHANGELOG.md | 11 +++++++++++ config/config.exs | 3 ++- config/description.exs | 13 +++++++++++++ lib/pleroma/web/plugs/uploaded_media.ex | 21 +++++++++++++++++++-- 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9033050a1..1108c60e8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). +## Unreleased + +## Added + +## Changed + +## Fixed +- Critical security issue allowing Akkoma to be used as a vector for + (depending on configuration) impersonation of other users or creation + of bogus users and posts on the upload domain + ## 2024.02 ## Added diff --git a/config/config.exs b/config/config.exs index 1c531344c..723d173ec 100644 --- a/config/config.exs +++ b/config/config.exs @@ -65,7 +65,8 @@ link_name: false, proxy_remote: false, filename_display_max_length: 30, - base_url: nil + base_url: nil, + allowed_mime_types: ["image", "audio", "video"] config :pleroma, Pleroma.Uploaders.Local, uploads: "uploads" diff --git a/config/description.exs b/config/description.exs index e108aaae8..3ddd874f8 100644 --- a/config/description.exs +++ b/config/description.exs @@ -105,6 +105,19 @@ "https://cdn-host.com" ] }, + %{ + key: :allowed_mime_types, + label: "Allowed MIME types", + type: {:list, :string}, + description: + "List of MIME (main) types uploads are allowed to identify themselves with. Other types may still be uploaded, but will identify as a generic binary to clients. WARNING: Loosening this over the defaults can lead to security issues. Removing types is safe, but only add to the list if you are sure you know what you are doing.", + suggestions: [ + "image", + "audio", + "video", + "font" + ] + }, %{ key: :proxy_remote, type: :boolean, diff --git a/lib/pleroma/web/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex index 300c33068..c0982b4af 100644 --- a/lib/pleroma/web/plugs/uploaded_media.ex +++ b/lib/pleroma/web/plugs/uploaded_media.ex @@ -28,7 +28,9 @@ def init(_opts) do |> Keyword.put(:at, "/__unconfigured_media_plug") |> Plug.Static.init() - %{static_plug_opts: static_plug_opts} + allowed_mime_types = Pleroma.Config.get([Pleroma.Upload, :allowed_mime_types]) + + %{static_plug_opts: static_plug_opts, allowed_mime_types: allowed_mime_types} end def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do @@ -68,13 +70,28 @@ defp media_is_banned(_, {:url, url}), do: MediaProxy.in_banned_urls(url) defp media_is_banned(_, _), do: false + defp get_safe_mime_type(%{allowed_mime_types: allowed_mime_types} = _opts, mime) do + [maintype | _] = String.split(mime, "/", parts: 2) + if maintype in allowed_mime_types, do: mime, else: "application/octet-stream" + end + + defp set_content_type(conn, opts, filepath) do + real_mime = MIME.from_path(filepath) + clean_mime = get_safe_mime_type(opts, real_mime) + put_resp_header(conn, "content-type", clean_mime) + end + defp get_media(conn, {:static_dir, directory}, opts) do static_opts = Map.get(opts, :static_plug_opts) |> Map.put(:at, [@path]) |> Map.put(:from, directory) + |> Map.put(:set_content_type, false) - conn = Plug.Static.call(conn, static_opts) + conn = + conn + |> set_content_type(opts, conn.request_path) + |> Pleroma.Web.Plugs.StaticNoCT.call(static_opts) if conn.halted do conn -- 2.43.0 From bdefbb8fd99227f084220937da954ffe7fce15ee Mon Sep 17 00:00:00 2001 From: Oneric Date: Tue, 5 Mar 2024 02:20:16 +0100 Subject: [PATCH 109/149] plug/upload_media: query config only once on init --- lib/pleroma/web/plugs/uploaded_media.ex | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/lib/pleroma/web/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex index c0982b4af..9f13d919b 100644 --- a/lib/pleroma/web/plugs/uploaded_media.ex +++ b/lib/pleroma/web/plugs/uploaded_media.ex @@ -28,12 +28,21 @@ def init(_opts) do |> Keyword.put(:at, "/__unconfigured_media_plug") |> Plug.Static.init() - allowed_mime_types = Pleroma.Config.get([Pleroma.Upload, :allowed_mime_types]) + config = Pleroma.Config.get(Pleroma.Upload) + allowed_mime_types = Keyword.fetch!(config, :allowed_mime_types) + uploader = Keyword.fetch!(config, :uploader) - %{static_plug_opts: static_plug_opts, allowed_mime_types: allowed_mime_types} + %{ + static_plug_opts: static_plug_opts, + allowed_mime_types: allowed_mime_types, + uploader: uploader + } end - def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do + def call( + %{request_path: <<"/", @path, "/", file::binary>>} = conn, + %{uploader: uploader} = opts + ) do conn = case fetch_query_params(conn) do %{query_params: %{"name" => name}} = conn -> @@ -46,10 +55,7 @@ def call(%{request_path: <<"/", @path, "/", file::binary>>} = conn, opts) do end |> merge_resp_headers([{"content-security-policy", "sandbox"}]) - config = Pleroma.Config.get(Pleroma.Upload) - - with uploader <- Keyword.fetch!(config, :uploader), - {:ok, get_method} <- uploader.get_file(file), + with {:ok, get_method} <- uploader.get_file(file), false <- media_is_banned(conn, get_method) do get_media(conn, get_method, opts) else -- 2.43.0 From fef773ca3524ed102e97014b590728b30edf9ab5 Mon Sep 17 00:00:00 2001 From: Oneric Date: Mon, 4 Mar 2024 17:50:22 +0100 Subject: [PATCH 110/149] Drop media base_url default and recommend different domain Same-domain setups enabled now at least two exploits, so they ought to be discouraged and definitely not be the default. --- CHANGELOG.md | 2 ++ docs/docs/configuration/cheatsheet.md | 3 ++- docs/docs/configuration/hardening.md | 10 ++++++++++ lib/mix/tasks/pleroma/instance.ex | 10 ++++++++++ lib/pleroma/upload.ex | 13 +++++++++++-- priv/templates/sample_config.eex | 4 +++- test/mix/tasks/pleroma/instance_test.exs | 3 +++ 7 files changed, 41 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1108c60e8..1efe1ecfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Added ## Changed +- `Pleroma.Upload, :base_url` now MUST be configured explicitly; + use of the same domain as the instance is **strongly** discouraged ## Fixed - Critical security issue allowing Akkoma to be used as a vector for diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index 3c9113f88..a04160a1d 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -598,7 +598,8 @@ the source code is here: [kocaptcha](https://github.com/koto-bank/kocaptcha). Th * `uploader`: Which one of the [uploaders](#uploaders) to use. * `filters`: List of [upload filters](#upload-filters) to use. * `link_name`: When enabled Akkoma will add a `name` parameter to the url of the upload, for example `https://instance.tld/media/corndog.png?name=corndog.png`. This is needed to provide the correct filename in Content-Disposition headers when using filters like `Pleroma.Upload.Filter.Dedupe` -* `base_url`: The base URL to access a user-uploaded file. Useful when you want to host the media files via another domain or are using a 3rd party S3 provider. +* `base_url`: The base URL to access a user-uploaded file; MUST be configured explicitly. + Using a (sub)domain distinct from the instance endpoint is **strongly** recommended. * `proxy_remote`: If you're using a remote uploader, Akkoma will proxy media requests instead of redirecting to it. * `proxy_opts`: Proxy options, see `Pleroma.ReverseProxy` documentation. * `filename_display_max_length`: Set max length of a filename to display. 0 = no limit. Default: 30. diff --git a/docs/docs/configuration/hardening.md b/docs/docs/configuration/hardening.md index 521183f7d..f8ba048dd 100644 --- a/docs/docs/configuration/hardening.md +++ b/docs/docs/configuration/hardening.md @@ -17,6 +17,16 @@ This sets the Akkoma application server to only listen to the localhost interfac This sets the `secure` flag on Akkoma’s session cookie. This makes sure, that the cookie is only accepted over encrypted HTTPs connections. This implicitly renames the cookie from `pleroma_key` to `__Host-pleroma-key` which enforces some restrictions. (see [cookie prefixes](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie#Cookie_prefixes)) +### `Pleroma.Upload, :uploader, :base_url` + +> Recommended value: *anything on a different domain than the instance endpoint; e.g. https://media.myinstance.net/* + +Uploads are user controlled and (unless you’re running a true single-user +instance) should therefore not be considered trusted. But the domain is used +as a pivilege boundary e.g. by HTTP content security policy and ActivityPub. +Having uploads on the same domain enabled several past vulnerabilities +able to be exploited by malicious users. + ### `:http_security` > Recommended value: `true` diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex index 6a7d4f0d3..b442fdb5b 100644 --- a/lib/mix/tasks/pleroma/instance.ex +++ b/lib/mix/tasks/pleroma/instance.ex @@ -20,6 +20,7 @@ def run(["gen" | rest]) do output: :string, output_psql: :string, domain: :string, + media_url: :string, instance_name: :string, admin_email: :string, notify_email: :string, @@ -64,6 +65,14 @@ def run(["gen" | rest]) do ":" ) ++ [443] + media_url = + get_option( + options, + :media_url, + "What base url will uploads use? (e.g https://media.example.com/media)\n" <> + " Generally this should NOT use the same domain as the instance " + ) + name = get_option( options, @@ -207,6 +216,7 @@ def run(["gen" | rest]) do EEx.eval_file( template_dir <> "/sample_config.eex", domain: domain, + media_url: media_url, port: port, email: email, notify_email: notify_email, diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex index 99b6b5215..974d12533 100644 --- a/lib/pleroma/upload.ex +++ b/lib/pleroma/upload.ex @@ -39,6 +39,8 @@ defmodule Pleroma.Upload do alias Pleroma.Web.ActivityPub.Utils require Logger + @mix_env Mix.env() + @type source :: Plug.Upload.t() | (data_uri_string :: String.t()) @@ -228,6 +230,13 @@ defp url_from_spec(%__MODULE__{name: name}, base_url, {:file, path}) do defp url_from_spec(_upload, _base_url, {:url, url}), do: url + if @mix_env == :test do + defp choose_base_url(prim, sec \\ nil), + do: prim || sec || Pleroma.Web.Endpoint.url() <> "/media/" + else + defp choose_base_url(prim, sec \\ nil), do: prim || sec + end + def base_url do uploader = Config.get([Pleroma.Upload, :uploader]) upload_base_url = Config.get([Pleroma.Upload, :base_url]) @@ -235,7 +244,7 @@ def base_url do case uploader do Pleroma.Uploaders.Local -> - upload_base_url || Pleroma.Web.Endpoint.url() <> "/media/" + choose_base_url(upload_base_url) Pleroma.Uploaders.S3 -> bucket = Config.get([Pleroma.Uploaders.S3, :bucket]) @@ -261,7 +270,7 @@ def base_url do end _ -> - public_endpoint || upload_base_url || Pleroma.Web.Endpoint.url() <> "/media/" + choose_base_url(public_endpoint, upload_base_url) end end end diff --git a/priv/templates/sample_config.eex b/priv/templates/sample_config.eex index 0068969ac..724c5a9d5 100644 --- a/priv/templates/sample_config.eex +++ b/priv/templates/sample_config.eex @@ -78,6 +78,8 @@ config :joken, default_signer: "<%= jwt_secret %>" config :pleroma, configurable_from_database: <%= db_configurable? %> +config :pleroma, Pleroma.Upload, <%= if Kernel.length(upload_filters) > 0 do -"config :pleroma, Pleroma.Upload, filters: #{inspect(upload_filters)}" +" filters: #{inspect(upload_filters)}," end %> + base_url: "<%= media_url %>" diff --git a/test/mix/tasks/pleroma/instance_test.exs b/test/mix/tasks/pleroma/instance_test.exs index 5a5a68053..522319371 100644 --- a/test/mix/tasks/pleroma/instance_test.exs +++ b/test/mix/tasks/pleroma/instance_test.exs @@ -39,6 +39,8 @@ test "running gen" do tmp_path() <> "setup.psql", "--domain", "test.pleroma.social", + "--media-url", + "https://media.pleroma.social/media", "--instance-name", "Pleroma", "--admin-email", @@ -92,6 +94,7 @@ test "running gen" do assert generated_config =~ "configurable_from_database: true" assert generated_config =~ "http: [ip: {127, 0, 0, 1}, port: 4000]" assert generated_config =~ "filters: [Pleroma.Upload.Filter.Exiftool]" + assert generated_config =~ "base_url: \"https://media.pleroma.social/media\"" assert File.read!(tmp_path() <> "setup.psql") == generated_setup_psql() assert File.exists?(Path.expand("./test/instance/static/robots.txt")) end -- 2.43.0 From 0ec62acb9dc1c6500033086b46c37adefb700c62 Mon Sep 17 00:00:00 2001 From: Oneric Date: Mon, 4 Mar 2024 18:39:08 +0100 Subject: [PATCH 111/149] Always insert Dedupe upload filter This actually was already intended before to eradict all future path-traversal-style exploits and to fix issues with some characters like akkoma#610 in 0b2ec0ccee. However, Dedupe and AnonymizeFilename got mixed up. The latter only anonymises the name in Content-Disposition headers GET parameters (with link_name), _not_ the upload path. Even without Dedupe, the upload path is prefixed by an UUID, so it _should_ already be hard to guess for attackers. But now we actually can be sure no path shenanigangs occur, uploads reliably work and save some disk space. While this makes the final path predictable, this prediction is not exploitable. Insertion of a back-reference to the upload itself requires pulling off a successfull preimage attack against SHA-256, which is deemed infeasible for the foreseeable futures. Dedupe was already included in the default list in config.exs since 28cfb2c37a, but this will get overridde by whatever the config generated by the "pleroma.instance gen" task chose. Upload+delete tests running in parallel using Dedupe might be flaky, but this was already true before and needs its own commit to fix eventually. --- CHANGELOG.md | 2 + config/config.exs | 2 +- docs/docs/configuration/cheatsheet.md | 17 +++--- lib/mix/tasks/pleroma/instance.ex | 21 +------ lib/pleroma/upload.ex | 2 +- test/mix/tasks/pleroma/instance_test.exs | 2 - test/mix/tasks/pleroma/uploads_test.exs | 1 - test/pleroma/object_test.exs | 55 ++++++++++--------- test/pleroma/upload_test.exs | 21 +------ .../web/activity_pub/activity_pub_test.exs | 8 --- .../controllers/mascot_controller_test.exs | 6 +- test/test_helper.exs | 4 ++ 12 files changed, 55 insertions(+), 86 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1efe1ecfc..9130a81ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Changed - `Pleroma.Upload, :base_url` now MUST be configured explicitly; use of the same domain as the instance is **strongly** discouraged +- The `Dedupe` upload filter is now always active; + `AnonymizeFilenames` is again opt-in ## Fixed - Critical security issue allowing Akkoma to be used as a vector for diff --git a/config/config.exs b/config/config.exs index 723d173ec..a366961c0 100644 --- a/config/config.exs +++ b/config/config.exs @@ -61,7 +61,7 @@ # Upload configuration config :pleroma, Pleroma.Upload, uploader: Pleroma.Uploaders.Local, - filters: [Pleroma.Upload.Filter.Dedupe], + filters: [], link_name: false, proxy_remote: false, filename_display_max_length: 30, diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index a04160a1d..9c5bb9901 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -597,7 +597,7 @@ the source code is here: [kocaptcha](https://github.com/koto-bank/kocaptcha). Th * `uploader`: Which one of the [uploaders](#uploaders) to use. * `filters`: List of [upload filters](#upload-filters) to use. -* `link_name`: When enabled Akkoma will add a `name` parameter to the url of the upload, for example `https://instance.tld/media/corndog.png?name=corndog.png`. This is needed to provide the correct filename in Content-Disposition headers when using filters like `Pleroma.Upload.Filter.Dedupe` +* `link_name`: When enabled Akkoma will add a `name` parameter to the url of the upload, for example `https://instance.tld/media/corndog.png?name=corndog.png`. This is needed to provide the correct filename in Content-Disposition headers * `base_url`: The base URL to access a user-uploaded file; MUST be configured explicitly. Using a (sub)domain distinct from the instance endpoint is **strongly** recommended. * `proxy_remote`: If you're using a remote uploader, Akkoma will proxy media requests instead of redirecting to it. @@ -639,17 +639,18 @@ config :ex_aws, :s3, ### Upload filters -#### Pleroma.Upload.Filter.AnonymizeFilename - -This filter replaces the filename (not the path) of an upload. For complete obfuscation, add -`Pleroma.Upload.Filter.Dedupe` before AnonymizeFilename. - -* `text`: Text to replace filenames in links. If empty, `{random}.extension` will be used. You can get the original filename extension by using `{extension}`, for example `custom-file-name.{extension}`. - #### Pleroma.Upload.Filter.Dedupe +**Always** active; cannot be turned off. +Renames files to their hash and prevents duplicate files filling up the disk. No specific configuration. +#### Pleroma.Upload.Filter.AnonymizeFilename + +This filter replaces the declared filename (not the path) of an upload. + +* `text`: Text to replace filenames in links. If empty, `{random}.extension` will be used. You can get the original filename extension by using `{extension}`, for example `custom-file-name.{extension}`. + #### Pleroma.Upload.Filter.Exiftool This filter only strips the GPS and location metadata with Exiftool leaving color profiles and attributes intact. diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex index b442fdb5b..44f6b6e70 100644 --- a/lib/mix/tasks/pleroma/instance.ex +++ b/lib/mix/tasks/pleroma/instance.ex @@ -36,8 +36,7 @@ def run(["gen" | rest]) do listen_ip: :string, listen_port: :string, strip_uploads: :string, - anonymize_uploads: :string, - dedupe_uploads: :string + anonymize_uploads: :string ], aliases: [ o: :output, @@ -195,14 +194,6 @@ def run(["gen" | rest]) do "n" ) === "y" - dedupe_uploads = - get_option( - options, - :dedupe_uploads, - "Do you want to deduplicate uploaded files? (y/n)", - "n" - ) === "y" - Config.put([:instance, :static_dir], static_dir) secret = :crypto.strong_rand_bytes(64) |> Base.encode64() |> binary_part(0, 64) @@ -240,8 +231,7 @@ def run(["gen" | rest]) do upload_filters: upload_filters(%{ strip: strip_uploads, - anonymize: anonymize_uploads, - dedupe: dedupe_uploads + anonymize: anonymize_uploads }) ) @@ -329,13 +319,6 @@ defp upload_filters(filters) when is_map(filters) do enabled_filters end - enabled_filters = - if filters.dedupe do - enabled_filters ++ [Pleroma.Upload.Filter.Dedupe] - else - enabled_filters - end - enabled_filters end end diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex index 974d12533..1158e9449 100644 --- a/lib/pleroma/upload.ex +++ b/lib/pleroma/upload.ex @@ -66,7 +66,7 @@ defmodule Pleroma.Upload do path: String.t() } - @always_enabled_filters [Pleroma.Upload.Filter.AnonymizeFilename] + @always_enabled_filters [Pleroma.Upload.Filter.Dedupe] defstruct [:id, :name, :tempfile, :content_type, :width, :height, :blurhash, :path] diff --git a/test/mix/tasks/pleroma/instance_test.exs b/test/mix/tasks/pleroma/instance_test.exs index 522319371..17b2e3267 100644 --- a/test/mix/tasks/pleroma/instance_test.exs +++ b/test/mix/tasks/pleroma/instance_test.exs @@ -71,8 +71,6 @@ test "running gen" do "./test/../test/instance/static/", "--strip-uploads", "y", - "--dedupe-uploads", - "n", "--anonymize-uploads", "n" ]) diff --git a/test/mix/tasks/pleroma/uploads_test.exs b/test/mix/tasks/pleroma/uploads_test.exs index 67fb642c1..d00e25a37 100644 --- a/test/mix/tasks/pleroma/uploads_test.exs +++ b/test/mix/tasks/pleroma/uploads_test.exs @@ -16,7 +16,6 @@ defmodule Mix.Tasks.Pleroma.UploadsTest do Mix.shell(Mix.Shell.IO) end) - File.mkdir_p!("test/uploads") :ok end diff --git a/test/pleroma/object_test.exs b/test/pleroma/object_test.exs index 8320660a5..4b0fec1bd 100644 --- a/test/pleroma/object_test.exs +++ b/test/pleroma/object_test.exs @@ -22,6 +22,13 @@ defmodule Pleroma.ObjectTest do :ok end + # Only works for a single attachment but that's all we need here + defp get_attachment_filepath(note, uploads_dir) do + %{data: %{"attachment" => [%{"url" => [%{"href" => href}]}]}} = note + filename = href |> Path.basename() + "#{uploads_dir}/#{filename}" + end + test "returns an object by it's AP id" do object = insert(:note) found_object = Object.get_by_ap_id(object.data["id"]) @@ -95,14 +102,13 @@ test "Disabled via config" do {:ok, %Object{} = attachment} = Pleroma.Web.ActivityPub.ActivityPub.upload(file, actor: user.ap_id) - %{data: %{"attachment" => [%{"url" => [%{"href" => href}]}]}} = - note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}}) + note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}}) uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads]) - path = href |> Path.dirname() |> Path.basename() + path = get_attachment_filepath(note, uploads_dir) - assert {:ok, ["an_image.jpg"]} == File.ls("#{uploads_dir}/#{path}") + assert File.exists?("#{path}") Object.delete(note) @@ -111,7 +117,7 @@ test "Disabled via config" do assert Object.get_by_id(note.id).data["deleted"] refute Object.get_by_id(attachment.id) == nil - assert {:ok, ["an_image.jpg"]} == File.ls("#{uploads_dir}/#{path}") + assert File.exists?("#{path}") end test "in subdirectories" do @@ -129,14 +135,13 @@ test "in subdirectories" do {:ok, %Object{} = attachment} = Pleroma.Web.ActivityPub.ActivityPub.upload(file, actor: user.ap_id) - %{data: %{"attachment" => [%{"url" => [%{"href" => href}]}]}} = - note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}}) + note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}}) uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads]) - path = href |> Path.dirname() |> Path.basename() + path = get_attachment_filepath(note, uploads_dir) - assert {:ok, ["an_image.jpg"]} == File.ls("#{uploads_dir}/#{path}") + assert File.exists?("#{path}") Object.delete(note) @@ -145,7 +150,7 @@ test "in subdirectories" do assert Object.get_by_id(note.id).data["deleted"] assert Object.get_by_id(attachment.id) == nil - assert {:ok, []} == File.ls("#{uploads_dir}/#{path}") + refute File.exists?("#{path}") end test "with dedupe enabled" do @@ -168,13 +173,11 @@ test "with dedupe enabled" do {:ok, %Object{} = attachment} = Pleroma.Web.ActivityPub.ActivityPub.upload(file, actor: user.ap_id) - %{data: %{"attachment" => [%{"url" => [%{"href" => href}]}]}} = - note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}}) + note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}}) - filename = Path.basename(href) + path = get_attachment_filepath(note, uploads_dir) - assert {:ok, files} = File.ls(uploads_dir) - assert filename in files + assert File.exists?("#{path}") Object.delete(note) @@ -182,8 +185,8 @@ test "with dedupe enabled" do assert Object.get_by_id(note.id).data["deleted"] assert Object.get_by_id(attachment.id) == nil - assert {:ok, files} = File.ls(uploads_dir) - refute filename in files + # what if another test runs concurrently using the same image file? + refute File.exists?("#{path}") end test "with objects that have legacy data.url attribute" do @@ -203,14 +206,13 @@ test "with objects that have legacy data.url attribute" do {:ok, %Object{}} = Object.create(%{url: "https://google.com", actor: user.ap_id}) - %{data: %{"attachment" => [%{"url" => [%{"href" => href}]}]}} = - note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}}) + note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}}) uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads]) - path = href |> Path.dirname() |> Path.basename() + path = get_attachment_filepath(note, uploads_dir) - assert {:ok, ["an_image.jpg"]} == File.ls("#{uploads_dir}/#{path}") + assert File.exists?("#{path}") Object.delete(note) @@ -219,7 +221,7 @@ test "with objects that have legacy data.url attribute" do assert Object.get_by_id(note.id).data["deleted"] assert Object.get_by_id(attachment.id) == nil - assert {:ok, []} == File.ls("#{uploads_dir}/#{path}") + refute File.exists?("#{path}") end test "With custom base_url" do @@ -238,14 +240,13 @@ test "With custom base_url" do {:ok, %Object{} = attachment} = Pleroma.Web.ActivityPub.ActivityPub.upload(file, actor: user.ap_id) - %{data: %{"attachment" => [%{"url" => [%{"href" => href}]}]}} = - note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}}) + note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}}) uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads]) - path = href |> Path.dirname() |> Path.basename() + path = get_attachment_filepath(note, uploads_dir) - assert {:ok, ["an_image.jpg"]} == File.ls("#{uploads_dir}/#{path}") + assert File.exists?("#{path}") Object.delete(note) @@ -254,7 +255,7 @@ test "With custom base_url" do assert Object.get_by_id(note.id).data["deleted"] assert Object.get_by_id(attachment.id) == nil - assert {:ok, []} == File.ls("#{uploads_dir}/#{path}") + refute File.exists?("#{path}") end end diff --git a/test/pleroma/upload_test.exs b/test/pleroma/upload_test.exs index ad6065b43..27a2d1b97 100644 --- a/test/pleroma/upload_test.exs +++ b/test/pleroma/upload_test.exs @@ -188,7 +188,7 @@ test "copies the file to the configured folder with anonymizing filename" do refute data["name"] == "an [image.jpg" end - test "escapes invalid characters in url" do + test "mangles the filename" do File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") file = %Plug.Upload{ @@ -200,23 +200,8 @@ test "escapes invalid characters in url" do {:ok, data} = Upload.store(file) [attachment_url | _] = data["url"] - assert Path.basename(attachment_url["href"]) == "an%E2%80%A6%20image.jpg" - end - - test "escapes reserved uri characters" do - File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") - - file = %Plug.Upload{ - content_type: "image/jpeg", - path: Path.absname("test/fixtures/image_tmp.jpg"), - filename: ":?#[]@!$&\\'()*+,;=.jpg" - } - - {:ok, data} = Upload.store(file) - [attachment_url | _] = data["url"] - - assert Path.basename(attachment_url["href"]) == - "%3A%3F%23%5B%5D%40%21%24%26%5C%27%28%29%2A%2B%2C%3B%3D.jpg" + refute Path.basename(attachment_url["href"]) == "an%E2%80%A6%20image.jpg" + refute Path.basename(attachment_url["href"]) == "an… image.jpg" end end diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index 5d5388cf5..69b4ac257 100644 --- a/test/pleroma/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -1304,14 +1304,6 @@ test "returns reblogs for users for whom reblogs have not been muted" do %{test_file: test_file} end - test "strips / from filename", %{test_file: file} do - file = %Plug.Upload{file | filename: "../../../../../nested/bad.jpg"} - {:ok, %Object{} = object} = ActivityPub.upload(file) - [%{"href" => href}] = object.data["url"] - assert Regex.match?(~r"/bad.jpg$", href) - refute Regex.match?(~r"/nested/", href) - end - test "sets a description if given", %{test_file: file} do {:ok, %Object{} = object} = ActivityPub.upload(file, description: "a cool file") assert object.data["name"] == "a cool file" diff --git a/test/pleroma/web/pleroma_api/controllers/mascot_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/mascot_controller_test.exs index 7f02bff8f..8829597eb 100644 --- a/test/pleroma/web/pleroma_api/controllers/mascot_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/mascot_controller_test.exs @@ -64,6 +64,10 @@ test "mascot retrieving" do assert json_response_and_validate_schema(ret_conn, 200) + %{"url" => uploaded_url} = Jason.decode!(ret_conn.resp_body) + + assert uploaded_url != nil and is_binary(uploaded_url) + user = User.get_cached_by_id(user.id) conn = @@ -72,6 +76,6 @@ test "mascot retrieving" do |> get("/api/v1/pleroma/mascot") assert %{"url" => url, "type" => "image"} = json_response_and_validate_schema(conn, 200) - assert url =~ "an_image" + assert url == uploaded_url end end diff --git a/test/test_helper.exs b/test/test_helper.exs index 0fc7a86b9..22a0f33ee 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -9,6 +9,10 @@ {:ok, _} = Application.ensure_all_started(:ex_machina) +# Prepare and later automatically cleanup upload dir +uploads_dir = Pleroma.Config.get([Pleroma.Uploaders.Local, :uploads], "test/uploads") +File.mkdir_p!(uploads_dir) + ExUnit.after_suite(fn _results -> uploads = Pleroma.Config.get([Pleroma.Uploaders.Local, :uploads], "test/uploads") File.rm_rf!(uploads) -- 2.43.0 From ba558c0c242fed48f0fec1cf7fe86642d07e410b Mon Sep 17 00:00:00 2001 From: Oneric Date: Thu, 7 Mar 2024 00:00:25 +0100 Subject: [PATCH 112/149] Limit instance emoji to image types Else malicious emoji packs or our EmojiStealer MRF can put payloads into the same domain as the instance itself. Sanitising the content type should prevent proper clients from acting on any potential payload. Note, this does not affect the default emoji shipped with Akkoma as they are handled by another plug. However, those are fully trusted and thus not in needed of sanitisation. --- lib/pleroma/web/plugs/instance_static.ex | 22 ++++++++++++++++++++-- lib/pleroma/web/plugs/uploaded_media.ex | 8 ++------ lib/pleroma/web/plugs/utils.ex | 14 ++++++++++++++ 3 files changed, 36 insertions(+), 8 deletions(-) create mode 100644 lib/pleroma/web/plugs/utils.ex diff --git a/lib/pleroma/web/plugs/instance_static.ex b/lib/pleroma/web/plugs/instance_static.ex index 5f9a6ee83..b72b604a1 100644 --- a/lib/pleroma/web/plugs/instance_static.ex +++ b/lib/pleroma/web/plugs/instance_static.ex @@ -3,8 +3,12 @@ # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.Plugs.InstanceStatic do + import Plug.Conn + require Pleroma.Constants + alias Pleroma.Web.Plugs.Utils + @moduledoc """ This is a shim to call `Plug.Static` but with runtime `from` configuration. @@ -43,11 +47,25 @@ def call(conn, _) do conn end - defp call_static(conn, opts, from) do + defp set_static_content_type(conn, "/emoji/" <> _ = request_path) do + real_mime = MIME.from_path(request_path) + safe_mime = Utils.get_safe_mime_type(%{allowed_mime_types: ["image"]}, real_mime) + + put_resp_header(conn, "content-type", safe_mime) + end + + defp set_static_content_type(conn, request_path) do + put_resp_header(conn, "content-type", MIME.from_path(request_path)) + end + + defp call_static(%{request_path: request_path} = conn, opts, from) do opts = opts |> Map.put(:from, from) + |> Map.put(:set_content_type, false) - Plug.Static.call(conn, opts) + conn + |> set_static_content_type(request_path) + |> Pleroma.Web.Plugs.StaticNoCT.call(opts) end end diff --git a/lib/pleroma/web/plugs/uploaded_media.ex b/lib/pleroma/web/plugs/uploaded_media.ex index 9f13d919b..746203087 100644 --- a/lib/pleroma/web/plugs/uploaded_media.ex +++ b/lib/pleroma/web/plugs/uploaded_media.ex @@ -11,6 +11,7 @@ defmodule Pleroma.Web.Plugs.UploadedMedia do require Logger alias Pleroma.Web.MediaProxy + alias Pleroma.Web.Plugs.Utils @behaviour Plug # no slashes @@ -76,14 +77,9 @@ defp media_is_banned(_, {:url, url}), do: MediaProxy.in_banned_urls(url) defp media_is_banned(_, _), do: false - defp get_safe_mime_type(%{allowed_mime_types: allowed_mime_types} = _opts, mime) do - [maintype | _] = String.split(mime, "/", parts: 2) - if maintype in allowed_mime_types, do: mime, else: "application/octet-stream" - end - defp set_content_type(conn, opts, filepath) do real_mime = MIME.from_path(filepath) - clean_mime = get_safe_mime_type(opts, real_mime) + clean_mime = Utils.get_safe_mime_type(opts, real_mime) put_resp_header(conn, "content-type", clean_mime) end diff --git a/lib/pleroma/web/plugs/utils.ex b/lib/pleroma/web/plugs/utils.ex new file mode 100644 index 000000000..770a3eeb2 --- /dev/null +++ b/lib/pleroma/web/plugs/utils.ex @@ -0,0 +1,14 @@ +# Akkoma: Magically expressive social media +# Copyright © 2024 Akkoma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Plugs.Utils do + @moduledoc """ + Some helper functions shared across several plugs + """ + + def get_safe_mime_type(%{allowed_mime_types: allowed_mime_types} = _opts, mime) do + [maintype | _] = String.split(mime, "/", parts: 2) + if maintype in allowed_mime_types, do: mime, else: "application/octet-stream" + end +end -- 2.43.0 From e88d0a28536c10c24abd9f66ad8bc552b82fe99e Mon Sep 17 00:00:00 2001 From: Oneric Date: Thu, 7 Mar 2024 00:18:00 +0100 Subject: [PATCH 113/149] Fix Content-Type of our schema Strict servers fail to process anything from us otherwise. Fixes: akkoma#716 --- lib/pleroma/web/endpoint.ex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 64593767d..6628fcaf3 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -98,6 +98,10 @@ defmodule Pleroma.Web.Endpoint do at: "/", from: :pleroma, only: Pleroma.Web.static_paths(), + # JSON-LD is accepted by some servers for AP objects and activities, + # thus only enable it here instead of a global extension mapping + # (it's our only *.jsonld file anyway) + content_types: %{"litepub-0.1.jsonld" => "application/ld+json"}, # credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength gzip: true, cache_control_for_etags: @static_cache_control, -- 2.43.0 From bcc528b2e2c5ac9a9854dd408c4d6e643863541e Mon Sep 17 00:00:00 2001 From: Oneric Date: Thu, 7 Mar 2024 01:02:32 -0100 Subject: [PATCH 114/149] Never automatically assign privileged content types By mapping all extensions related to our custom privileged types back to innocuous text/plain, our custom types will never automatically be inserted which was one of the factors making impersonation possible. Note, this does not invalidate the upload and emoji Content-Type restrictions from previous commits. Apart from counterfeit AP objects there are other payloads with standard types this protects against, e.g. *.js Javascript payloads as used in prior frontend injections. --- config/config.exs | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/config/config.exs b/config/config.exs index a366961c0..85a84208c 100644 --- a/config/config.exs +++ b/config/config.exs @@ -149,6 +149,19 @@ format: "$metadata[$level] $message", metadata: [:request_id] +# ——————————————————————————————————————————————————————————————— +# W A R N I N G +# ——————————————————————————————————————————————————————————————— +# +# Whenever adding a privileged new custom type for e.g. +# ActivityPub objects, ALWAYS map their extension back +# to "application/octet-stream". +# Else files served by us can automatically end up with +# those privileged types causing severe security hazards. +# (We need those mappings so Phoenix can assoiate its format +# (the "extension") to incoming requests of those MIME types) +# +# ——————————————————————————————————————————————————————————————— config :mime, :types, %{ "application/xml" => ["xml"], "application/xrd+xml" => ["xrd+xml"], @@ -158,9 +171,13 @@ } config :mime, :extensions, %{ - "activity+json" => "application/activity+json" + "xrd+xml" => "text/plain", + "jrd+json" => "text/plain", + "activity+json" => "text/plain" } +# ——————————————————————————————————————————————————————————————— + config :tesla, :adapter, {Tesla.Adapter.Finch, name: MyFinch} # Configures http settings, upstream proxy etc. -- 2.43.0 From 11ae8344eb62fea51b9fc26a063406f2afe253ac Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 10 Mar 2024 18:57:19 +0000 Subject: [PATCH 115/149] Sanitise Content-Type of media proxy URLs Just as with uploads and emoji before, this can otherwise be used to place counterfeit AP objects or other malicious payloads. In this case, even if we never assign a priviliged type to content, the remote server can and until now we just mimcked whatever it told us. Preview URLs already handle only specific, safe content types and redirect to the external host for all else; thus no additional sanitisiation is needed for them. Non-previews are all delegated to the modified ReverseProxy module. It already has consolidated logic for building response headers making it easy to slip in sanitisation. Although proxy urls are prefixed by a MAC built from a server secret, attackers can still achieve a perfect id match when they are able to change the contents of the pointed to URL. After sending an posts containing an attachment at a controlled destination, the proxy URL can be read back and inserted into the payload. After injection of counterfeits in the target server the content can again be changed to something innocuous lessening chance of detection. --- lib/pleroma/reverse_proxy.ex | 18 +++++++++++++ test/pleroma/reverse_proxy_test.exs | 41 +++++++++++++++++++++++++++-- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/reverse_proxy.ex b/lib/pleroma/reverse_proxy.ex index bb4f4def3..f017bf51b 100644 --- a/lib/pleroma/reverse_proxy.ex +++ b/lib/pleroma/reverse_proxy.ex @@ -17,6 +17,8 @@ defmodule Pleroma.ReverseProxy do @failed_request_ttl :timer.seconds(60) @methods ~w(GET HEAD) + @allowed_mime_types Pleroma.Config.get([Pleroma.Upload, :allowed_mime_types], []) + @cachex Pleroma.Config.get([:cachex, :provider], Cachex) def max_read_duration_default, do: @max_read_duration @@ -253,6 +255,7 @@ defp build_resp_headers(headers, opts) do headers |> Enum.filter(fn {k, _} -> k in @keep_resp_headers end) |> build_resp_cache_headers(opts) + |> sanitise_content_type() |> build_resp_content_disposition_header(opts) |> build_csp_headers() |> Keyword.merge(Keyword.get(opts, :resp_headers, [])) @@ -282,6 +285,21 @@ defp build_resp_cache_headers(headers, _opts) do end end + defp sanitise_content_type(headers) do + original_ct = get_content_type(headers) + + safe_ct = + Pleroma.Web.Plugs.Utils.get_safe_mime_type( + %{allowed_mime_types: @allowed_mime_types}, + original_ct + ) + + [ + {"content-type", safe_ct} + | Enum.filter(headers, fn {k, _v} -> k != "content-type" end) + ] + end + defp build_resp_content_disposition_header(headers, opts) do opt = Keyword.get(opts, :inline_content_types, @inline_content_types) diff --git a/test/pleroma/reverse_proxy_test.exs b/test/pleroma/reverse_proxy_test.exs index e3e2a1571..fc6ae42bc 100644 --- a/test/pleroma/reverse_proxy_test.exs +++ b/test/pleroma/reverse_proxy_test.exs @@ -75,13 +75,16 @@ test "common", %{conn: conn} do Tesla.Mock.mock(fn %{method: :head, url: "/head"} -> %Tesla.Env{ status: 200, - headers: [{"content-type", "text/html; charset=utf-8"}], + headers: [{"content-type", "image/png"}], body: "" } end) conn = ReverseProxy.call(Map.put(conn, :method, "HEAD"), "/head") - assert html_response(conn, 200) == "" + + assert conn.status == 200 + assert Conn.get_resp_header(conn, "content-type") == ["image/png"] + assert conn.resp_body == "" end end @@ -252,4 +255,38 @@ test "with content-disposition header", %{conn: conn} do assert {"content-disposition", "attachment; filename=\"filename.jpg\""} in conn.resp_headers end end + + describe "content-type sanitisation" do + test "preserves video type", %{conn: conn} do + Tesla.Mock.mock(fn %{method: :get, url: "/content"} -> + %Tesla.Env{ + status: 200, + headers: [{"content-type", "video/mp4"}], + body: "test" + } + end) + + conn = ReverseProxy.call(Map.put(conn, :method, "GET"), "/content") + + assert conn.status == 200 + assert Conn.get_resp_header(conn, "content-type") == ["video/mp4"] + assert conn.resp_body == "test" + end + + test "replaces application type", %{conn: conn} do + Tesla.Mock.mock(fn %{method: :get, url: "/content"} -> + %Tesla.Env{ + status: 200, + headers: [{"content-type", "application/activity+json"}], + body: "test" + } + end) + + conn = ReverseProxy.call(Map.put(conn, :method, "GET"), "/content") + + assert conn.status == 200 + assert Conn.get_resp_header(conn, "content-type") == ["application/octet-stream"] + assert conn.resp_body == "test" + end + end end -- 2.43.0 From fc36b04016303cec5746ec3824e5651b6a2655b1 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 10 Mar 2024 18:57:40 +0000 Subject: [PATCH 116/149] Drop media proxy same-domain default for base_url Even more than with user uploads, a same-domain proxy setup bears significant security risks due to serving untrusted content under the main domain space. A risky setup like that should never be the default. --- docs/docs/configuration/howto_mediaproxy.md | 17 +++++++++++++---- lib/pleroma/web/media_proxy.ex | 12 ++++++++++-- 2 files changed, 23 insertions(+), 6 deletions(-) diff --git a/docs/docs/configuration/howto_mediaproxy.md b/docs/docs/configuration/howto_mediaproxy.md index 8ad81bdfb..223ad7eed 100644 --- a/docs/docs/configuration/howto_mediaproxy.md +++ b/docs/docs/configuration/howto_mediaproxy.md @@ -6,7 +6,16 @@ With the `mediaproxy` function you can use nginx to cache this content, so users ## Activate it -* Edit your nginx config and add the following location: +* Edit your nginx config and add the following location to your main server block: +``` +location /proxy { + return 404; +} +``` + +* Set up a subdomain for the proxy with its nginx config on the same machine + *(the latter is not strictly required, but for simplicity we’ll assume so)* +* In this subdomain’s server block add ``` location /proxy { proxy_cache akkoma_media_cache; @@ -26,9 +35,9 @@ config :pleroma, :media_proxy, enabled: true, proxy_opts: [ redirect_on_failure: true - ] - #base_url: "https://cache.akkoma.social" + ], + base_url: "https://cache.akkoma.social" ``` -If you want to use a subdomain to serve the files, uncomment `base_url`, change the url and add a comma after `true` in the previous line. +You **really** should use a subdomain to serve proxied files; while we will fix bugs resulting from this, serving arbitrary remote content on your main domain namespace is a significant attack surface. * Restart nginx and Akkoma diff --git a/lib/pleroma/web/media_proxy.ex b/lib/pleroma/web/media_proxy.ex index c5087c42c..19411d58e 100644 --- a/lib/pleroma/web/media_proxy.ex +++ b/lib/pleroma/web/media_proxy.ex @@ -14,6 +14,8 @@ defmodule Pleroma.Web.MediaProxy do @cachex Pleroma.Config.get([:cachex, :provider], Cachex) + @mix_env Mix.env() + def cache_table, do: @cache_table @spec in_banned_urls(String.t()) :: boolean() @@ -144,8 +146,14 @@ def filename(url_or_path) do if path = URI.parse(url_or_path).path, do: Path.basename(path) end - def base_url do - Config.get([:media_proxy, :base_url], Endpoint.url()) + if @mix_env == :test do + def base_url do + Config.get([:media_proxy, :base_url], Endpoint.url()) + end + else + def base_url do + Config.get!([:media_proxy, :base_url]) + end end defp proxy_url(path, sig_base64, url_base64, filename) do -- 2.43.0 From fb54c47f0b7380f233f643699c2d14db9bb6c549 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 10 Mar 2024 19:01:17 +0000 Subject: [PATCH 117/149] Update example nginx config To account for our subdomain recommendations --- docs/docs/configuration/cheatsheet.md | 3 +- installation/nginx/akkoma.nginx | 43 +++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index 9c5bb9901..40d1319c7 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -396,7 +396,8 @@ This section describe PWA manifest instance-specific values. Currently this opti ## :media_proxy * `enabled`: Enables proxying of remote media to the instance’s proxy -* `base_url`: The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts. +* `base_url`: The base URL to access a user-uploaded file. + Using a (sub)domain distinct from the instance endpoint is **strongly** recommended. * `proxy_opts`: All options defined in `Pleroma.ReverseProxy` documentation, defaults to `[max_body_length: (25*1_048_576)]`. * `whitelist`: List of hosts with scheme to bypass the mediaproxy (e.g. `https://example.com`) * `invalidation`: options for remove media from cache after delete object: diff --git a/installation/nginx/akkoma.nginx b/installation/nginx/akkoma.nginx index 18d92f30f..1d91ce22f 100644 --- a/installation/nginx/akkoma.nginx +++ b/installation/nginx/akkoma.nginx @@ -75,9 +75,48 @@ server { proxy_set_header Host $http_host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + location ~ ^/(media|proxy) { + return 404; + } + location / { proxy_pass http://phoenix; } +} + +# Upload and MediaProxy Subdomain +# (see main domain setup for more details) +server { + server_name media.example.tld; + + listen 80; + listen [::]:80; + + location / { + return 301 https://$server_name$request_uri; + } +} + +server { + server_name media.example.tld; + + listen 443 ssl http2; + listen [::]:443 ssl http2; + + ssl_trusted_certificate /etc/letsencrypt/live/media.example.tld/chain.pem; + ssl_certificate /etc/letsencrypt/live/media.example.tld/fullchain.pem; + ssl_certificate_key /etc/letsencrypt/live/media.example.tld/privkey.pem; + # .. copy all other the ssl_* and gzip_* stuff from main domain + + # the nginx default is 1m, not enough for large media uploads + client_max_body_size 16m; + ignore_invalid_headers off; + + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_set_header Host $http_host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; location ~ ^/(media|proxy) { proxy_cache akkoma_media_cache; @@ -91,4 +130,8 @@ server { chunked_transfer_encoding on; proxy_pass http://phoenix; } + + location / { + return 404; + } } -- 2.43.0 From af041db6dc443194b37217bc107f7c8f72a9b8e5 Mon Sep 17 00:00:00 2001 From: Norm Date: Tue, 20 Feb 2024 15:11:26 -0500 Subject: [PATCH 118/149] Limit emoji stealer to alphanum, dash, or underscore characters MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As suggested in b387f4a1c1ff02573f16de0b25403cf501afc3b4, only steal emoji with alphanumerc, dash, or underscore characters. Also consolidate all validation logic into a single function. === Taken from akkoma#703 with cosmetic tweaks This matches our existing validation logic from Pleroma.Emoji, and apart from excluding the dot also POSIX’s Portable Filename Character Set making it always safe for use in filenames. Mastodon is even stricter also disallowing U+002D HYPEN-MINUS and requiring at least two characters. Given both we and Mastodon reject shortcodes excluded by this anyway, this doesn’t seem like a loss. --- .../activity_pub/mrf/steal_emoji_policy.ex | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex index 02a107c27..456fe88c5 100644 --- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex @@ -20,6 +20,19 @@ defp shortcode_matches?(shortcode, pattern) do String.match?(shortcode, pattern) end + defp reject_emoji?({shortcode, _url}, installed_emoji) do + valid_shortcode? = String.match?(shortcode, ~r/^[a-zA-Z0-9_-]+$/) + + rejected_shortcode? = + [:mrf_steal_emoji, :rejected_shortcodes] + |> Config.get([]) + |> Enum.any?(fn pattern -> shortcode_matches?(shortcode, pattern) end) + + emoji_installed? = Enum.member?(installed_emoji, shortcode) + + !valid_shortcode? or rejected_shortcode? or emoji_installed? + end + defp steal_emoji({shortcode, url}, emoji_dir_path) do url = Pleroma.Web.MediaProxy.url(url) @@ -76,18 +89,7 @@ def filter(%{"object" => %{"emoji" => foreign_emojis, "actor" => actor}} = messa new_emojis = foreign_emojis - |> Enum.reject(fn {shortcode, _url} -> shortcode in installed_emoji end) - |> Enum.reject(fn {shortcode, _url} -> - String.contains?(shortcode, ["/", "\\", ".", ":"]) - end) - |> Enum.filter(fn {shortcode, _url} -> - reject_emoji? = - [:mrf_steal_emoji, :rejected_shortcodes] - |> Config.get([]) - |> Enum.find(false, fn pattern -> shortcode_matches?(shortcode, pattern) end) - - !reject_emoji? - end) + |> Enum.reject(&reject_emoji?(&1, installed_emoji)) |> Enum.map(&steal_emoji(&1, emoji_dir_path)) |> Enum.filter(& &1) -- 2.43.0 From 111cdb0d86f51d0a3f10b77d3f785f68fc9681d4 Mon Sep 17 00:00:00 2001 From: Oneric Date: Thu, 7 Mar 2024 13:07:02 +0100 Subject: [PATCH 119/149] Split steal_emoji function for better readability --- .../activity_pub/mrf/steal_emoji_policy.ex | 44 ++++++++++--------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex index 456fe88c5..c743ac3a2 100644 --- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex @@ -33,31 +33,35 @@ defp reject_emoji?({shortcode, _url}, installed_emoji) do !valid_shortcode? or rejected_shortcode? or emoji_installed? end - defp steal_emoji({shortcode, url}, emoji_dir_path) do + defp steal_emoji(%{} = response, {shortcode, url}, emoji_dir_path) do + extension = + url + |> URI.parse() + |> Map.get(:path) + |> Path.basename() + |> Path.extname() + + shortcode = Path.basename(shortcode) + file_path = Path.join(emoji_dir_path, shortcode <> (extension || ".png")) + + case File.write(file_path, response.body) do + :ok -> + shortcode + + e -> + Logger.warning("MRF.StealEmojiPolicy: Failed to write to #{file_path}: #{inspect(e)}") + nil + end + end + + defp maybe_steal_emoji({shortcode, url}, emoji_dir_path) do url = Pleroma.Web.MediaProxy.url(url) with {:ok, %{status: status} = response} when status in 200..299 <- Pleroma.HTTP.get(url) do size_limit = Config.get([:mrf_steal_emoji, :size_limit], 50_000) if byte_size(response.body) <= size_limit do - extension = - url - |> URI.parse() - |> Map.get(:path) - |> Path.basename() - |> Path.extname() - - shortcode = Path.basename(shortcode) - file_path = Path.join(emoji_dir_path, shortcode <> (extension || ".png")) - - case File.write(file_path, response.body) do - :ok -> - shortcode - - e -> - Logger.warning("MRF.StealEmojiPolicy: Failed to write to #{file_path}: #{inspect(e)}") - nil - end + steal_emoji(response, {shortcode, url}, emoji_dir_path) else Logger.debug( "MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{size_limit} B)" @@ -90,7 +94,7 @@ def filter(%{"object" => %{"emoji" => foreign_emojis, "actor" => actor}} = messa new_emojis = foreign_emojis |> Enum.reject(&reject_emoji?(&1, installed_emoji)) - |> Enum.map(&steal_emoji(&1, emoji_dir_path)) + |> Enum.map(&maybe_steal_emoji(&1, emoji_dir_path)) |> Enum.filter(& &1) if !Enum.empty?(new_emojis) do -- 2.43.0 From a8c6c780b4e0d0db9a393ff6bb6a3b904ad94269 Mon Sep 17 00:00:00 2001 From: Oneric Date: Thu, 7 Mar 2024 23:35:05 +0100 Subject: [PATCH 120/149] StealEmoji: use Content-Type and reject non-images MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit E.g. *key’s emoji URLs typically don’t have file extensions, but until now we just slapped ".png" at its end hoping for the best. Furthermore, this gives us a chance to actually reject non-images, which before was not feasible exatly due to those extension-less URLs --- .../activity_pub/mrf/steal_emoji_policy.ex | 26 ++++++---- .../mrf/steal_emoji_policy_test.exs | 51 +++++++++++++++++-- 2 files changed, 62 insertions(+), 15 deletions(-) diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex index c743ac3a2..9b2fa3020 100644 --- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex @@ -33,16 +33,9 @@ defp reject_emoji?({shortcode, _url}, installed_emoji) do !valid_shortcode? or rejected_shortcode? or emoji_installed? end - defp steal_emoji(%{} = response, {shortcode, url}, emoji_dir_path) do - extension = - url - |> URI.parse() - |> Map.get(:path) - |> Path.basename() - |> Path.extname() - + defp steal_emoji(%{} = response, {shortcode, extension}, emoji_dir_path) do shortcode = Path.basename(shortcode) - file_path = Path.join(emoji_dir_path, shortcode <> (extension || ".png")) + file_path = Path.join(emoji_dir_path, shortcode <> "." <> extension) case File.write(file_path, response.body) do :ok -> @@ -54,14 +47,25 @@ defp steal_emoji(%{} = response, {shortcode, url}, emoji_dir_path) do end end + defp get_extension_if_safe(response) do + content_type = + :proplists.get_value("content-type", response.headers, MIME.from_path(response.url)) + + case content_type do + "image/" <> _ -> List.first(MIME.extensions(content_type)) + _ -> nil + end + end + defp maybe_steal_emoji({shortcode, url}, emoji_dir_path) do url = Pleroma.Web.MediaProxy.url(url) with {:ok, %{status: status} = response} when status in 200..299 <- Pleroma.HTTP.get(url) do size_limit = Config.get([:mrf_steal_emoji, :size_limit], 50_000) + extension = get_extension_if_safe(response) - if byte_size(response.body) <= size_limit do - steal_emoji(response, {shortcode, url}, emoji_dir_path) + if byte_size(response.body) <= size_limit and extension do + steal_emoji(response, {shortcode, extension}, emoji_dir_path) else Logger.debug( "MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{size_limit} B)" diff --git a/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs index 59baa3a43..b0f6d351c 100644 --- a/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs @@ -45,7 +45,11 @@ test "Steals emoji on unknown shortcode from allowed remote host", %{ refute File.exists?(path) Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox.png"} -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")} + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/image.jpg"), + url: "https://example.org/emoji/firedfox.png" + } end) clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468) @@ -72,7 +76,11 @@ test "rejects invalid shortcodes", %{path: path} do fullpath = Path.join(path, "fired/fox.png") Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox"} -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")} + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/image.jpg"), + url: "https://example.org/emoji/firedfox.png" + } end) clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468) @@ -86,6 +94,36 @@ test "rejects invalid shortcodes", %{path: path} do refute File.exists?(fullpath) end + test "prefers content-type header for extension", %{path: path} do + message = %{ + "type" => "Create", + "object" => %{ + "emoji" => [{"firedfox", "https://example.org/emoji/firedfox.fud"}], + "actor" => "https://example.org/users/admin" + } + } + + Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox.fud"} -> + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/image.jpg"), + url: "https://example.org/emoji/firedfox.wevp", + headers: [{"content-type", "image/gif"}] + } + end) + + clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468) + + assert {:ok, _message} = StealEmojiPolicy.filter(message) + + assert "firedfox" in installed() + assert File.exists?(path) + + assert path + |> Path.join("firedfox.gif") + |> File.exists?() + end + test "reject regex shortcode", %{message: message} do refute "firedfox" in installed() @@ -118,7 +156,11 @@ test "reject if size is above the limit", %{message: message} do refute "firedfox" in installed() Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox.png"} -> - %Tesla.Env{status: 200, body: File.read!("test/fixtures/image.jpg")} + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/image.jpg"), + url: "https://example.org/emoji/firedfox.png" + } end) clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 50_000) @@ -132,7 +174,8 @@ test "reject if host returns error", %{message: message} do refute "firedfox" in installed() Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox.png"} -> - {:ok, %Tesla.Env{status: 404, body: "Not found"}} + {:ok, + %Tesla.Env{status: 404, body: "Not found", url: "https://example.org/emoji/firedfox.png"}} end) clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468) -- 2.43.0 From 5b126567bb6cd25b8299ee953042710ef645555f Mon Sep 17 00:00:00 2001 From: Oneric Date: Thu, 7 Mar 2024 23:39:00 +0100 Subject: [PATCH 121/149] StealEmoji: drop superfluous basename Since 3 commits ago we restrict shortcodes to a subset of the POSIX Portable Filename Character Set, therefore this can never have a directory component. --- lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex index 9b2fa3020..da6b8275d 100644 --- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex @@ -34,7 +34,6 @@ defp reject_emoji?({shortcode, _url}, installed_emoji) do end defp steal_emoji(%{} = response, {shortcode, extension}, emoji_dir_path) do - shortcode = Path.basename(shortcode) file_path = Path.join(emoji_dir_path, shortcode <> "." <> extension) case File.write(file_path, response.body) do -- 2.43.0 From fa98b44acf9f04a7c50f591aeaa4a666b3967984 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sat, 9 Mar 2024 22:18:00 +0100 Subject: [PATCH 122/149] Fill out path for newly created packs Before this was only filled on loading the pack again, preventing the created pack from being used directly. --- lib/pleroma/emoji/pack.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index 9049d9097..30ed5a8bd 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -31,7 +31,10 @@ def create(name) do with :ok <- validate_not_empty([name]), dir <- Path.join(emoji_path(), name), :ok <- File.mkdir(dir) do - save_pack(%__MODULE__{pack_file: Path.join(dir, "pack.json")}) + save_pack(%__MODULE__{ + path: dir, + pack_file: Path.join(dir, "pack.json") + }) end end -- 2.43.0 From d1c4d0740467455b4fd158c9c3d1b79e500a79f9 Mon Sep 17 00:00:00 2001 From: Oneric Date: Fri, 8 Mar 2024 03:06:40 +0100 Subject: [PATCH 123/149] Convert StealEmoji to pack.json This will decouple filenames from shortcodes and allow more image formats to work instead of only those included in the auto-load glob. (Albeit we still saved other formats to disk, wasting space) Furthermore, this will allow us to make final URL paths infeasible to predict. --- lib/pleroma/emoji/pack.ex | 16 ++++- .../activity_pub/mrf/steal_emoji_policy.ex | 58 +++++++++++++------ 2 files changed, 56 insertions(+), 18 deletions(-) diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index 30ed5a8bd..f007cde65 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -92,7 +92,7 @@ defp unpack_zip_emojies(zip_files) do end) end - @spec add_file(t(), String.t(), Path.t(), Plug.Upload.t()) :: + @spec add_file(t(), String.t(), Path.t(), Plug.Upload.t() | binary()) :: {:ok, t()} | {:error, File.posix() | atom()} def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"} = file) do @@ -140,6 +140,14 @@ def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"} end def add_file(%Pack{} = pack, shortcode, filename, %Plug.Upload{} = file) do + try_add_file(pack, shortcode, filename, file) + end + + def add_file(%Pack{} = pack, shortcode, filename, filedata) when is_binary(filedata) do + try_add_file(pack, shortcode, filename, filedata) + end + + defp try_add_file(%Pack{} = pack, shortcode, filename, file) do with :ok <- validate_not_empty([shortcode, filename]), :ok <- validate_emoji_not_exists(shortcode), {:ok, updated_pack} <- do_add_file(pack, shortcode, filename, file) do @@ -485,6 +493,12 @@ defp save_file(%Plug.Upload{path: upload_path}, pack, filename) do end end + defp save_file(file_data, pack, filename) when is_binary(file_data) do + file_path = Path.join(pack.path, filename) + create_subdirs(file_path) + File.write(file_path, file_data, [:binary]) + end + defp put_emoji(pack, shortcode, filename) do files = Map.put(pack.files, shortcode, filename) %{pack | files: files, files_count: length(Map.keys(files))} diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex index da6b8275d..ed421d93e 100644 --- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex @@ -6,10 +6,41 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do require Logger alias Pleroma.Config + alias Pleroma.Emoji.Pack @moduledoc "Detect new emojis by their shortcode and steals them" @behaviour Pleroma.Web.ActivityPub.MRF.Policy + @pack_name "stolen" + + defp create_pack() do + with {:ok, pack} = Pack.create(@pack_name) do + Pack.save_metadata( + %{ + "description" => "Collection of emoji auto-stolen from other instances", + "homepage" => Pleroma.Web.Endpoint.url(), + "can-download" => false, + "share-files" => false + }, + pack + ) + end + end + + defp load_or_create_pack() do + case Pack.load_pack(@pack_name) do + {:ok, pack} -> {:ok, pack} + {:error, :enoent} -> create_pack() + e -> e + end + end + + defp add_emoji(shortcode, extension, filedata) do + {:ok, pack} = load_or_create_pack() + filename = shortcode <> "." <> extension + Pack.add_file(pack, shortcode, filename, filedata) + end + defp accept_host?(host), do: host in Config.get([:mrf_steal_emoji, :hosts], []) defp shortcode_matches?(shortcode, pattern) when is_binary(pattern) do @@ -33,15 +64,16 @@ defp reject_emoji?({shortcode, _url}, installed_emoji) do !valid_shortcode? or rejected_shortcode? or emoji_installed? end - defp steal_emoji(%{} = response, {shortcode, extension}, emoji_dir_path) do - file_path = Path.join(emoji_dir_path, shortcode <> "." <> extension) - - case File.write(file_path, response.body) do - :ok -> + defp steal_emoji(%{} = response, {shortcode, extension}) do + case add_emoji(shortcode, extension, response.body) do + {:ok, _} -> shortcode e -> - Logger.warning("MRF.StealEmojiPolicy: Failed to write to #{file_path}: #{inspect(e)}") + Logger.warning( + "MRF.StealEmojiPolicy: Failed to add #{shortcode}.#{extension}: #{inspect(e)}" + ) + nil end end @@ -56,7 +88,7 @@ defp get_extension_if_safe(response) do end end - defp maybe_steal_emoji({shortcode, url}, emoji_dir_path) do + defp maybe_steal_emoji({shortcode, url}) do url = Pleroma.Web.MediaProxy.url(url) with {:ok, %{status: status} = response} when status in 200..299 <- Pleroma.HTTP.get(url) do @@ -64,7 +96,7 @@ defp maybe_steal_emoji({shortcode, url}, emoji_dir_path) do extension = get_extension_if_safe(response) if byte_size(response.body) <= size_limit and extension do - steal_emoji(response, {shortcode, extension}, emoji_dir_path) + steal_emoji(response, {shortcode, extension}) else Logger.debug( "MRF.StealEmojiPolicy: :#{shortcode}: at #{url} (#{byte_size(response.body)} B) over size limit (#{size_limit} B)" @@ -86,18 +118,10 @@ def filter(%{"object" => %{"emoji" => foreign_emojis, "actor" => actor}} = messa if host != Pleroma.Web.Endpoint.host() and accept_host?(host) do installed_emoji = Pleroma.Emoji.get_all() |> Enum.map(fn {k, _} -> k end) - emoji_dir_path = - Config.get( - [:mrf_steal_emoji, :path], - Path.join(Config.get([:instance, :static_dir]), "emoji/stolen") - ) - - File.mkdir_p(emoji_dir_path) - new_emojis = foreign_emojis |> Enum.reject(&reject_emoji?(&1, installed_emoji)) - |> Enum.map(&maybe_steal_emoji(&1, emoji_dir_path)) + |> Enum.map(&maybe_steal_emoji(&1)) |> Enum.filter(& &1) if !Enum.empty?(new_emojis) do -- 2.43.0 From ee5ce87825d598409e4d409a8256677f2f52fbc0 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sat, 9 Mar 2024 21:39:25 +0000 Subject: [PATCH 124/149] test: use pack functions to check for emoji The hardocded path and filenames assumptions will be broken with the next commit. --- .../mrf/steal_emoji_policy_test.exs | 44 +++++++++++-------- 1 file changed, 25 insertions(+), 19 deletions(-) diff --git a/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs index b0f6d351c..43de902c7 100644 --- a/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs @@ -7,8 +7,23 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicyTest do alias Pleroma.Config alias Pleroma.Emoji + alias Pleroma.Emoji.Pack alias Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy + defp has_pack?() do + case Pack.load_pack("stolen") do + {:ok, _pack} -> true + {:error, :enoent} -> false + end + end + + defp has_emoji?(shortcode) do + case Pack.load_pack("stolen") do + {:ok, pack} -> Map.has_key?(pack.files, shortcode) + {:error, :enoent} -> false + end + end + setup do emoji_path = [:instance, :static_dir] |> Config.get() |> Path.join("emoji/stolen") @@ -26,7 +41,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicyTest do File.rm_rf!(emoji_path) end) - [message: message, path: emoji_path] + [message: message] end test "does nothing by default", %{message: message} do @@ -38,11 +53,10 @@ test "does nothing by default", %{message: message} do end test "Steals emoji on unknown shortcode from allowed remote host", %{ - message: message, - path: path + message: message } do refute "firedfox" in installed() - refute File.exists?(path) + refute has_pack?() Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox.png"} -> %Tesla.Env{ @@ -57,14 +71,12 @@ test "Steals emoji on unknown shortcode from allowed remote host", %{ assert {:ok, _message} = StealEmojiPolicy.filter(message) assert "firedfox" in installed() - assert File.exists?(path) + assert has_pack?() - assert path - |> Path.join("firedfox.png") - |> File.exists?() + assert has_emoji?("firedfox") end - test "rejects invalid shortcodes", %{path: path} do + test "rejects invalid shortcodes" do message = %{ "type" => "Create", "object" => %{ @@ -73,8 +85,6 @@ test "rejects invalid shortcodes", %{path: path} do } } - fullpath = Path.join(path, "fired/fox.png") - Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox"} -> %Tesla.Env{ status: 200, @@ -86,15 +96,15 @@ test "rejects invalid shortcodes", %{path: path} do clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468) refute "firedfox" in installed() - refute File.exists?(path) + refute has_pack?() assert {:ok, _message} = StealEmojiPolicy.filter(message) refute "fired/fox" in installed() - refute File.exists?(fullpath) + refute has_emoji?("fired/fox") end - test "prefers content-type header for extension", %{path: path} do + test "prefers content-type header for extension" do message = %{ "type" => "Create", "object" => %{ @@ -117,11 +127,7 @@ test "prefers content-type header for extension", %{path: path} do assert {:ok, _message} = StealEmojiPolicy.filter(message) assert "firedfox" in installed() - assert File.exists?(path) - - assert path - |> Path.join("firedfox.gif") - |> File.exists?() + assert has_emoji?("firedfox") end test "reject regex shortcode", %{message: message} do -- 2.43.0 From a4fa2ec9af769ac160f3e1a60c3273a6f991b9fc Mon Sep 17 00:00:00 2001 From: Oneric Date: Sat, 9 Mar 2024 22:41:26 +0100 Subject: [PATCH 125/149] StealEmoji: make final paths infeasible to predict MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Certain attacks rely on predictable paths for their payloads. If we weren’t so overly lax in our (id, URL) check, the current counterfeit activity exploit would be one of those. It seems plausible for future attacks to hinge on or being made easier by predictable paths too. In general, letting remote actors place arbitrary data at a path within our domain of their choosing (sans prefix) just doesn’t seem like a good idea. Using fully random filenames would have worked as well, but this is less friendly for admins checking emoji dirs. The generated suffix should still be more than enough; an attacker needs on average 140 trillion attempts to correctly guess the final path. --- .../web/activity_pub/mrf/steal_emoji_policy.ex | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex index ed421d93e..3a6eae3f2 100644 --- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex @@ -37,7 +37,16 @@ defp load_or_create_pack() do defp add_emoji(shortcode, extension, filedata) do {:ok, pack} = load_or_create_pack() - filename = shortcode <> "." <> extension + # Make final path infeasible to predict to thwart certain kinds of attacks + # (48 bits is slighty more than 8 base62 chars, thus 9 chars) + salt = + :crypto.strong_rand_bytes(6) + |> :crypto.bytes_to_integer() + |> Base62.encode() + |> String.pad_leading(9, "0") + + filename = shortcode <> "-" <> salt <> "." <> extension + Pack.add_file(pack, shortcode, filename, filedata) end @@ -71,7 +80,7 @@ defp steal_emoji(%{} = response, {shortcode, extension}) do e -> Logger.warning( - "MRF.StealEmojiPolicy: Failed to add #{shortcode}.#{extension}: #{inspect(e)}" + "MRF.StealEmojiPolicy: Failed to add #{shortcode} as #{extension}: #{inspect(e)}" ) nil -- 2.43.0 From d1ce5fd911542ccd4894150d75fe288146b4e16f Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 10 Mar 2024 00:44:12 +0100 Subject: [PATCH 126/149] test/steal_emoji: reduce code duplication with mock macro --- .../mrf/steal_emoji_policy_test.exs | 57 ++++++++----------- 1 file changed, 24 insertions(+), 33 deletions(-) diff --git a/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs index 43de902c7..b34269aa5 100644 --- a/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs @@ -24,6 +24,25 @@ defp has_emoji?(shortcode) do end end + defmacro mock_tesla( + url \\ "https://example.org/emoji/firedfox.png", + status \\ 200, + headers \\ [], + get_body \\ File.read!("test/fixtures/image.jpg") + ) do + quote do + Tesla.Mock.mock(fn + %{method: :get, url: unquote(url)} -> + %Tesla.Env{ + status: unquote(status), + body: unquote(get_body), + url: unquote(url), + headers: unquote(headers) + } + end) + end + end + setup do emoji_path = [:instance, :static_dir] |> Config.get() |> Path.join("emoji/stolen") @@ -58,13 +77,7 @@ test "Steals emoji on unknown shortcode from allowed remote host", %{ refute "firedfox" in installed() refute has_pack?() - Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox.png"} -> - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/image.jpg"), - url: "https://example.org/emoji/firedfox.png" - } - end) + mock_tesla() clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468) @@ -85,13 +98,7 @@ test "rejects invalid shortcodes" do } } - Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox"} -> - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/image.jpg"), - url: "https://example.org/emoji/firedfox.png" - } - end) + mock_tesla() clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468) @@ -113,14 +120,7 @@ test "prefers content-type header for extension" do } } - Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox.fud"} -> - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/image.jpg"), - url: "https://example.org/emoji/firedfox.wevp", - headers: [{"content-type", "image/gif"}] - } - end) + mock_tesla("https://example.org/emoji/firedfox.fud", 200, [{"content-type", "image/gif"}]) clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468) @@ -161,13 +161,7 @@ test "reject string shortcode", %{message: message} do test "reject if size is above the limit", %{message: message} do refute "firedfox" in installed() - Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox.png"} -> - %Tesla.Env{ - status: 200, - body: File.read!("test/fixtures/image.jpg"), - url: "https://example.org/emoji/firedfox.png" - } - end) + mock_tesla() clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 50_000) @@ -179,10 +173,7 @@ test "reject if size is above the limit", %{message: message} do test "reject if host returns error", %{message: message} do refute "firedfox" in installed() - Tesla.Mock.mock(fn %{method: :get, url: "https://example.org/emoji/firedfox.png"} -> - {:ok, - %Tesla.Env{status: 404, body: "Not found", url: "https://example.org/emoji/firedfox.png"}} - end) + mock_tesla("https://example.org/emoji/firedfox.png", 404, [], "Not found") clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468) -- 2.43.0 From 6d003e1acdf4d92cbf02ccba7b627d1fb8f3db6f Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 10 Mar 2024 01:14:51 +0100 Subject: [PATCH 127/149] test/steal_emoji: consolidate configuration setup --- .../mrf/steal_emoji_policy_test.exs | 28 ++++++------------- 1 file changed, 9 insertions(+), 19 deletions(-) diff --git a/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs index b34269aa5..2103e8539 100644 --- a/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs @@ -44,6 +44,11 @@ defmacro mock_tesla( end setup do + clear_config(:mrf_steal_emoji, + hosts: ["example.org"], + size_limit: 284_468 + ) + emoji_path = [:instance, :static_dir] |> Config.get() |> Path.join("emoji/stolen") Emoji.reload() @@ -66,6 +71,7 @@ defmacro mock_tesla( test "does nothing by default", %{message: message} do refute "firedfox" in installed() + clear_config(:mrf_steal_emoji, []) assert {:ok, _message} = StealEmojiPolicy.filter(message) refute "firedfox" in installed() @@ -79,8 +85,6 @@ test "Steals emoji on unknown shortcode from allowed remote host", %{ mock_tesla() - clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468) - assert {:ok, _message} = StealEmojiPolicy.filter(message) assert "firedfox" in installed() @@ -100,8 +104,6 @@ test "rejects invalid shortcodes" do mock_tesla() - clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468) - refute "firedfox" in installed() refute has_pack?() @@ -122,8 +124,6 @@ test "prefers content-type header for extension" do mock_tesla("https://example.org/emoji/firedfox.fud", 200, [{"content-type", "image/gif"}]) - clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468) - assert {:ok, _message} = StealEmojiPolicy.filter(message) assert "firedfox" in installed() @@ -133,11 +133,7 @@ test "prefers content-type header for extension" do test "reject regex shortcode", %{message: message} do refute "firedfox" in installed() - clear_config(:mrf_steal_emoji, - hosts: ["example.org"], - size_limit: 284_468, - rejected_shortcodes: [~r/firedfox/] - ) + clear_config([:mrf_steal_emoji, :rejected_shortcodes], [~r/firedfox/]) assert {:ok, _message} = StealEmojiPolicy.filter(message) @@ -147,11 +143,7 @@ test "reject regex shortcode", %{message: message} do test "reject string shortcode", %{message: message} do refute "firedfox" in installed() - clear_config(:mrf_steal_emoji, - hosts: ["example.org"], - size_limit: 284_468, - rejected_shortcodes: ["firedfox"] - ) + clear_config([:mrf_steal_emoji, :rejected_shortcodes], ["firedfox"]) assert {:ok, _message} = StealEmojiPolicy.filter(message) @@ -163,7 +155,7 @@ test "reject if size is above the limit", %{message: message} do mock_tesla() - clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 50_000) + clear_config([:mrf_steal_emoji, :size_limit], 50_000) assert {:ok, _message} = StealEmojiPolicy.filter(message) @@ -175,8 +167,6 @@ test "reject if host returns error", %{message: message} do mock_tesla("https://example.org/emoji/firedfox.png", 404, [], "Not found") - clear_config(:mrf_steal_emoji, hosts: ["example.org"], size_limit: 284_468) - ExUnit.CaptureLog.capture_log(fn -> assert {:ok, _message} = StealEmojiPolicy.filter(message) end) =~ "MRF.StealEmojiPolicy: Failed to fetch https://example.org/emoji/firedfox.png" -- 2.43.0 From d6d838cbe83e8caf3e1fc67a81c3943e880ab290 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 10 Mar 2024 01:35:35 +0100 Subject: [PATCH 128/149] StealEmoji: check remote size before downloading To save on bandwith and avoid OOMs with large files. Ofc, this relies on the remote server (a) sending a content-length header and (b) being honest about the size. Common fedi servers seem to provide the header and (b) at least raises the required privilege of an malicious actor to a server infrastructure admin of an explicitly allowed host. A more complete defense which still works when faced with a malicious server requires changes in upstream Finch; see https://github.com/sneako/finch/issues/224 --- docs/docs/configuration/cheatsheet.md | 4 +- .../activity_pub/mrf/steal_emoji_policy.ex | 25 +++++++++- .../mrf/steal_emoji_policy_test.exs | 50 ++++++++++++++++++- 3 files changed, 75 insertions(+), 4 deletions(-) diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index 40d1319c7..c4259c6cf 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -236,7 +236,9 @@ config :pleroma, :mrf_user_allowlist, %{ #### :mrf_steal_emoji * `hosts`: List of hosts to steal emojis from * `rejected_shortcodes`: Regex-list of shortcodes to reject -* `size_limit`: File size limit (in bytes), checked before an emoji is saved to the disk +* `size_limit`: File size limit (in bytes), checked before download if possible (and remote server honest), + otherwise or again checked before saving emoji to the disk +* `download_unknown_size`: whether to download an emoji when the remote server doesn’t report its size in advance #### :mrf_activity_expiration diff --git a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex index 3a6eae3f2..26d3dc592 100644 --- a/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/steal_emoji_policy.ex @@ -13,6 +13,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.StealEmojiPolicy do @pack_name "stolen" + # Config defaults + @size_limit 50_000 + @download_unknown_size false + defp create_pack() do with {:ok, pack} = Pack.create(@pack_name) do Pack.save_metadata( @@ -97,11 +101,28 @@ defp get_extension_if_safe(response) do end end + defp is_remote_size_within_limit?(url) do + with {:ok, %{status: status, headers: headers} = _response} when status in 200..299 <- + Pleroma.HTTP.request(:head, url, nil, [], []) do + content_length = :proplists.get_value("content-length", headers, nil) + size_limit = Config.get([:mrf_steal_emoji, :size_limit], @size_limit) + + accept_unknown = + Config.get([:mrf_steal_emoji, :download_unknown_size], @download_unknown_size) + + content_length <= size_limit or + (content_length == nil and accept_unknown) + else + _ -> false + end + end + defp maybe_steal_emoji({shortcode, url}) do url = Pleroma.Web.MediaProxy.url(url) - with {:ok, %{status: status} = response} when status in 200..299 <- Pleroma.HTTP.get(url) do - size_limit = Config.get([:mrf_steal_emoji, :size_limit], 50_000) + with {:remote_size, true} <- {:remote_size, is_remote_size_within_limit?(url)}, + {:ok, %{status: status} = response} when status in 200..299 <- Pleroma.HTTP.get(url) do + size_limit = Config.get([:mrf_steal_emoji, :size_limit], @size_limit) extension = get_extension_if_safe(response) if byte_size(response.body) <= size_limit and extension do diff --git a/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs index 2103e8539..ba5087f1b 100644 --- a/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/steal_emoji_policy_test.exs @@ -32,6 +32,14 @@ defmacro mock_tesla( ) do quote do Tesla.Mock.mock(fn + %{method: :head, url: unquote(url)} -> + %Tesla.Env{ + status: unquote(status), + body: nil, + url: unquote(url), + headers: unquote(headers) + } + %{method: :get, url: unquote(url)} -> %Tesla.Env{ status: unquote(status), @@ -46,7 +54,8 @@ defmacro mock_tesla( setup do clear_config(:mrf_steal_emoji, hosts: ["example.org"], - size_limit: 284_468 + size_limit: 284_468, + download_unknown_size: true ) emoji_path = [:instance, :static_dir] |> Config.get() |> Path.join("emoji/stolen") @@ -174,5 +183,44 @@ test "reject if host returns error", %{message: message} do refute "firedfox" in installed() end + test "reject unknown size", %{message: message} do + clear_config([:mrf_steal_emoji, :download_unknown_size], false) + mock_tesla() + + refute "firedfox" in installed() + + ExUnit.CaptureLog.capture_log(fn -> + assert {:ok, _message} = StealEmojiPolicy.filter(message) + end) =~ + "MRF.StealEmojiPolicy: Failed to fetch https://example.org/emoji/firedfox.png: {:remote_size, false}" + + refute "firedfox" in installed() + end + + test "reject too large content-size before download", %{message: message} do + clear_config([:mrf_steal_emoji, :download_unknown_size], false) + mock_tesla("https://example.org/emoji/firedfox.png", 200, [{"content-length", 2 ** 30}]) + + refute "firedfox" in installed() + + ExUnit.CaptureLog.capture_log(fn -> + assert {:ok, _message} = StealEmojiPolicy.filter(message) + end) =~ + "MRF.StealEmojiPolicy: Failed to fetch https://example.org/emoji/firedfox.png: {:remote_size, false}" + + refute "firedfox" in installed() + end + + test "accepts content-size below limit", %{message: message} do + clear_config([:mrf_steal_emoji, :download_unknown_size], false) + mock_tesla("https://example.org/emoji/firedfox.png", 200, [{"content-length", 2}]) + + refute "firedfox" in installed() + + assert {:ok, _message} = StealEmojiPolicy.filter(message) + + assert "firedfox" in installed() + end + defp installed, do: Emoji.get_all() |> Enum.map(fn {k, _} -> k end) end -- 2.43.0 From ddd79ff22d7e8ba1931b6aa40996637e3b73a5ac Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 10 Mar 2024 07:15:26 +0100 Subject: [PATCH 129/149] Proactively harden emoji pack against path traversal No new path traversal attacks are known. But given the many entrypoints and code flow complexity inside pack.ex, it unfortunately seems possible a future refactor or addition might reintroduce one. Furthermore, some old packs might still contain traversing path entries which could trigger undesireable actions on rename or delete. To ensure this can never happen, assert safety during path construction. Path.safe_relative was introduced in Elixir 1.14, but fortunately, we already require at least 1.14 anyway. --- lib/pleroma/emoji/pack.ex | 55 ++++++++++++++++++++++++-------- test/pleroma/emoji/pack_test.exs | 6 ++-- 2 files changed, 46 insertions(+), 15 deletions(-) diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index f007cde65..142208854 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -26,10 +26,32 @@ defmodule Pleroma.Emoji.Pack do alias Pleroma.Emoji.Pack alias Pleroma.Utils + # Invalid/Malicious names are supposed to be filtered out before path joining, + # but there are many entrypoints to affected functions so as the code changes + # we might accidentally let an unsanitised name slip through. + # To make sure, use the below which crash the process otherwise. + + # ALWAYS use this when constructing paths from external name! + # (name meaning it must be only a single path component) + defp path_join_name_safe(dir, name) do + if to_string(name) != Path.basename(name) or name in ["..", ".", ""] do + raise "Invalid or malicious pack name: #{name}" + else + Path.join(dir, name) + end + end + + # ALWAYS use this to join external paths + # (which are allowed to have several components) + defp path_join_safe(dir, path) do + {:ok, safe_path} = Path.safe_relative(path) + Path.join(dir, safe_path) + end + @spec create(String.t()) :: {:ok, t()} | {:error, File.posix()} | {:error, :empty_values} def create(name) do with :ok <- validate_not_empty([name]), - dir <- Path.join(emoji_path(), name), + dir <- path_join_name_safe(emoji_path(), name), :ok <- File.mkdir(dir) do save_pack(%__MODULE__{ path: dir, @@ -68,7 +90,7 @@ def show(opts) do {:ok, [binary()]} | {:error, File.posix(), binary()} | {:error, :empty_values} def delete(name) do with :ok <- validate_not_empty([name]), - pack_path <- Path.join(emoji_path(), name) do + pack_path <- path_join_name_safe(emoji_path(), name) do File.rm_rf(pack_path) end end @@ -110,7 +132,7 @@ def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"} Enum.map_reduce(emojies, pack, fn item, emoji_pack -> emoji_file = %Plug.Upload{ filename: item[:filename], - path: Path.join(tmp_dir, item[:path]) + path: path_join_safe(tmp_dir, item[:path]) } {:ok, updated_pack} = @@ -200,6 +222,7 @@ def import_from_filesystem do {:ok, results} <- File.ls(emoji_path) do names = results + # items come from File.ls, thus safe |> Enum.map(&Path.join(emoji_path, &1)) |> Enum.reject(fn path -> File.dir?(path) and File.exists?(Path.join(path, "pack.json")) @@ -298,8 +321,8 @@ def update_metadata(name, data) do @spec load_pack(String.t()) :: {:ok, t()} | {:error, :file.posix()} def load_pack(name) do - name = Path.basename(name) - pack_file = Path.join([emoji_path(), name, "pack.json"]) + pack_dir = path_join_name_safe(emoji_path(), name) + pack_file = Path.join(pack_dir, "pack.json") with {:ok, _} <- File.stat(pack_file), {:ok, pack_data} <- File.read(pack_file) do @@ -423,7 +446,13 @@ defp downloadable?(pack) do end defp create_archive_and_cache(pack, hash) do - files = [~c"pack.json" | Enum.map(pack.files, fn {_, file} -> to_charlist(file) end)] + files = [ + ~c"pack.json" + | Enum.map(pack.files, fn {_, file} -> + {:ok, file} = Path.safe_relative(file) + to_charlist(file) + end) + ] {:ok, {_, result}} = :zip.zip(~c"#{pack.name}.zip", files, [:memory, cwd: to_charlist(pack.path)]) @@ -485,7 +514,7 @@ defp validate_not_empty(list) do end defp save_file(%Plug.Upload{path: upload_path}, pack, filename) do - file_path = Path.join(pack.path, filename) + file_path = path_join_safe(pack.path, filename) create_subdirs(file_path) with {:ok, _} <- File.copy(upload_path, file_path) do @@ -494,7 +523,7 @@ defp save_file(%Plug.Upload{path: upload_path}, pack, filename) do end defp save_file(file_data, pack, filename) when is_binary(file_data) do - file_path = Path.join(pack.path, filename) + file_path = path_join_safe(pack.path, filename) create_subdirs(file_path) File.write(file_path, file_data, [:binary]) end @@ -510,8 +539,8 @@ defp delete_emoji(pack, shortcode) do end defp rename_file(pack, filename, new_filename) do - old_path = Path.join(pack.path, filename) - new_path = Path.join(pack.path, new_filename) + old_path = path_join_safe(pack.path, filename) + new_path = path_join_safe(pack.path, new_filename) create_subdirs(new_path) with :ok <- File.rename(old_path, new_path) do @@ -529,7 +558,7 @@ defp create_subdirs(file_path) do defp remove_file(pack, shortcode) do with {:ok, filename} <- get_filename(pack, shortcode), - emoji <- Path.join(pack.path, filename), + emoji <- path_join_safe(pack.path, filename), :ok <- File.rm(emoji) do remove_dir_if_empty(emoji, filename) end @@ -547,7 +576,7 @@ defp remove_dir_if_empty(emoji, filename) do defp get_filename(pack, shortcode) do with %{^shortcode => filename} when is_binary(filename) <- pack.files, - file_path <- Path.join(pack.path, filename), + file_path <- path_join_safe(pack.path, filename), {:ok, _} <- File.stat(file_path) do {:ok, filename} else @@ -585,7 +614,7 @@ defp validate_downloadable(pack) do end defp copy_as(remote_pack, local_name) do - path = Path.join(emoji_path(), local_name) + path = path_join_name_safe(emoji_path(), local_name) %__MODULE__{ name: local_name, diff --git a/test/pleroma/emoji/pack_test.exs b/test/pleroma/emoji/pack_test.exs index 4d769789d..f5d2e2eef 100644 --- a/test/pleroma/emoji/pack_test.exs +++ b/test/pleroma/emoji/pack_test.exs @@ -93,7 +93,9 @@ test "add emoji file", %{pack: pack} do assert updated_pack.files_count == 1 end - test "load_pack/1 ignores path traversal in a forged pack name", %{pack: pack} do - assert {:ok, ^pack} = Pack.load_pack("../../../../../dump_pack") + test "load_pack/1 panics on path traversal in a forged pack name" do + assert_raise(RuntimeError, "Invalid or malicious pack name: ../../../../../dump_pack", fn -> + Pack.load_pack("../../../../../dump_pack") + end) end end -- 2.43.0 From c806adbfdbea3c30d6d9ecb92cceaefa240438c4 Mon Sep 17 00:00:00 2001 From: Oneric Date: Mon, 11 Mar 2024 22:52:46 +0100 Subject: [PATCH 130/149] Refactor Fetcher.get_object for readability Apart from slightly different error reasons wrt content-type, this does not change functionality in any way. --- lib/pleroma/object/fetcher.ex | 46 ++++++++++++++++++----------------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 9e62ca69f..47f5f9169 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -275,37 +275,39 @@ def get_object(id) do |> maybe_date_fetch(date) |> sign_fetch(id, date) - case HTTP.get(id, headers) do - {:ok, %{body: body, status: code, headers: headers}} when code in 200..299 -> - case List.keyfind(headers, "content-type", 0) do - {_, content_type} -> - case Plug.Conn.Utils.media_type(content_type) do - {:ok, "application", "activity+json", _} -> - {:ok, body} + with {:ok, %{body: body, status: code, headers: headers}} when code in 200..299 <- + HTTP.get(id, headers), + {:has_content_type, {_, content_type}} <- + {:has_content_type, List.keyfind(headers, "content-type", 0)}, + {:parse_content_type, {:ok, "application", subtype, type_params}} <- + {:parse_content_type, Plug.Conn.Utils.media_type(content_type)} do + case {subtype, type_params} do + {"activity+json", _} -> + {:ok, body} - {:ok, "application", "ld+json", - %{"profile" => "https://www.w3.org/ns/activitystreams"}} -> - {:ok, body} + {"ld+json", %{"profile" => "https://www.w3.org/ns/activitystreams"}} -> + {:ok, body} - # pixelfed sometimes (and only sometimes) responds with http instead of https - {:ok, "application", "ld+json", - %{"profile" => "http://www.w3.org/ns/activitystreams"}} -> - {:ok, body} - - _ -> - {:error, {:content_type, content_type}} - end - - _ -> - {:error, {:content_type, nil}} - end + # pixelfed sometimes (and only sometimes) responds with http instead of https + {"ld+json", %{"profile" => "http://www.w3.org/ns/activitystreams"}} -> + {:ok, body} + _ -> + {:error, {:content_type, content_type}} + end + else {:ok, %{status: code}} when code in [404, 410] -> {:error, {"Object has been deleted", id, code}} {:error, e} -> {:error, e} + {:has_content_type, _} -> + {:error, {:content_type, nil}} + + {:parse_content_type, e} -> + {:error, {:content_type, e}} + e -> {:error, e} end -- 2.43.0 From 93ab6a018e96fe300e677ae35b83e8989a11a09a Mon Sep 17 00:00:00 2001 From: Oneric Date: Wed, 13 Mar 2024 19:41:14 -0100 Subject: [PATCH 131/149] mix: fix docs task --- mix.exs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mix.exs b/mix.exs index 4e8b3160d..98d65484a 100644 --- a/mix.exs +++ b/mix.exs @@ -21,13 +21,13 @@ def project do source_url: "https://akkoma.dev/AkkomaGang/akkoma", docs: [ source_url_pattern: "https://akkoma.dev/AkkomaGang/akkoma/blob/develop/%{path}#L%{line}", - logo: "priv/static/images/logo.png", - extras: ["README.md", "CHANGELOG.md"] ++ Path.wildcard("docs/**/*.md"), + logo: "priv/static/logo-512.png", + extras: ["README.md", "CHANGELOG.md"] ++ Path.wildcard("docs/docs/**/*.md"), groups_for_extras: [ - "Installation manuals": Path.wildcard("docs/installation/*.md"), - Configuration: Path.wildcard("docs/config/*.md"), - Administration: Path.wildcard("docs/admin/*.md"), - "Pleroma's APIs and Mastodon API extensions": Path.wildcard("docs/api/*.md") + "Installation manuals": Path.wildcard("docs/docs/installation/*.md"), + Configuration: Path.wildcard("docs/docs/config/*.md"), + Administration: Path.wildcard("docs/docs/admin/*.md"), + "Pleroma's APIs and Mastodon API extensions": Path.wildcard("docs/docs/api/*.md") ], main: "readme", output: "priv/static/doc" -- 2.43.0 From 2bcf633dc2863cea68f6528320817f80b8e0f14c Mon Sep 17 00:00:00 2001 From: Oneric Date: Wed, 13 Mar 2024 19:42:51 -0100 Subject: [PATCH 132/149] Document Pleroma.Object.Fetcher --- lib/pleroma/object/containment.ex | 3 +++ lib/pleroma/object/fetcher.ex | 19 ++++++++++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex index 040537acf..219bc3892 100644 --- a/lib/pleroma/object/containment.ex +++ b/lib/pleroma/object/containment.ex @@ -64,6 +64,9 @@ def contain_origin(id, %{"attributedTo" => actor} = params), def contain_origin(_id, _data), do: :error + @doc """ + Check whether the object id is from the same host as another id + """ def contain_origin_from_id(id, %{"id" => other_id} = _params) when is_binary(other_id) do id_uri = URI.parse(id) other_uri = URI.parse(other_id) diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 47f5f9169..c3aaf7a03 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -18,6 +18,14 @@ defmodule Pleroma.Object.Fetcher do require Logger require Pleroma.Constants + @moduledoc """ + This module deals with correctly fetching Acitivity Pub objects in a safe way. + + The core function is `fetch_and_contain_remote_object_from_id/1` which performs + the actual fetch and common safety and authenticity checks. Other `fetch_*` + function use the former and perform some additional tasks + """ + defp touch_changeset(changeset) do updated_at = NaiveDateTime.utc_now() @@ -103,6 +111,7 @@ defp reinject_object(%Object{} = object, new_data) do end end + @doc "Assumes object already is in our database and refetches from remote to update (e.g. for polls)" def refetch_object(%Object{data: %{"id" => id}} = object) do with {:local, false} <- {:local, Object.local?(object)}, {:ok, new_data} <- fetch_and_contain_remote_object_from_id(id), @@ -114,7 +123,12 @@ def refetch_object(%Object{data: %{"id" => id}} = object) do end end - # Note: will create a Create activity, which we need internally at the moment. + @doc """ + Fetches a new object and puts it through the processing pipeline for inbound objects + + Note: will also insert a fake Create activity, since atm we internally + need everything to be traced back to a Create activity. + """ def fetch_object_from_id(id, options \\ []) do with %URI{} = uri <- URI.parse(id), # let's check the URI is even vaguely valid first @@ -185,6 +199,7 @@ defp prepare_activity_params(data) do |> Maps.put_if_present("bcc", data["bcc"]) end + @doc "Identical to `fetch_object_from_id/2` but just directly returns the object or on error `nil`" def fetch_object_from_id!(id, options \\ []) do with {:ok, object} <- fetch_object_from_id(id, options) do object @@ -235,6 +250,7 @@ defp maybe_date_fetch(headers, date) do end end + @doc "Fetches arbitrary remote object and performs basic safety and authenticity checks" def fetch_and_contain_remote_object_from_id(id) def fetch_and_contain_remote_object_from_id(%{"id" => id}), @@ -267,6 +283,7 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do def fetch_and_contain_remote_object_from_id(_id), do: {:error, "id must be a string"} + @doc "Do NOT use; only public for use in tests" def get_object(id) do date = Pleroma.Signature.signed_date() -- 2.43.0 From baaeffdebcf4efeeacd69ff8311fa6276c6b979d Mon Sep 17 00:00:00 2001 From: Oneric Date: Wed, 13 Mar 2024 20:04:31 -0100 Subject: [PATCH 133/149] Update spoofed activity test Turns out we already had a test for activities spoofed via upload due to an exploit several years. Back then *oma did not verify content-type at all and doing so was the only adopted countermeasure. Even the added test sample though suffered from a mismatching id, yet nobody seems to have thought it a good idea to tighten id checks, huh Since we will add stricter id checks later, make id and URL match and also add a testcase for no content type at all. The new section will be expanded in subsequent commits. --- test/pleroma/object/fetcher_test.exs | 53 ++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 10 deletions(-) diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs index 8cf0bce48..b2da0a757 100644 --- a/test/pleroma/object/fetcher_test.exs +++ b/test/pleroma/object/fetcher_test.exs @@ -14,6 +14,17 @@ defmodule Pleroma.Object.FetcherTest do import Mock import Tesla.Mock + defp spoofed_object_with_ids( + id \\ "https://patch.cx/objects/spoof", + actor_id \\ "https://patch.cx/users/rin" + ) do + File.read!("test/fixtures/spoofed-object.json") + |> Jason.decode!() + |> Map.put("id", id) + |> Map.put("actor", actor_id) + |> Jason.encode!() + end + setup do mock(fn %{method: :get, url: "https://mastodon.example.org/users/userisgone"} -> @@ -22,15 +33,28 @@ defmodule Pleroma.Object.FetcherTest do %{method: :get, url: "https://mastodon.example.org/users/userisgone404"} -> %Tesla.Env{status: 404} + # Spoof: wrong Content-Type %{ method: :get, - url: - "https://patch.cx/media/03ca3c8b4ac3ddd08bf0f84be7885f2f88de0f709112131a22d83650819e36c2.json" + url: "https://patch.cx/objects/spoof_content_type.json" } -> %Tesla.Env{ status: 200, + url: "https://patch.cx/objects/spoof_content_type.json", headers: [{"content-type", "application/json"}], - body: File.read!("test/fixtures/spoofed-object.json") + body: spoofed_object_with_ids("https://patch.cx/objects/spoof_content_type.json") + } + + # Spoof: no Content-Type + %{ + method: :get, + url: "https://patch.cx/objects/spoof_content_type" + } -> + %Tesla.Env{ + status: 200, + url: "https://patch.cx/objects/spoof_content_type", + headers: [], + body: spoofed_object_with_ids("https://patch.cx/objects/spoof_content_type") } env -> @@ -129,6 +153,22 @@ test "it rejects objects when attributedTo is wrong (variant 2)" do end end + describe "fetcher security and auth checks" do + test "it does not fetch a spoofed object without content type" do + assert {:error, {:content_type, nil}} = + Fetcher.fetch_and_contain_remote_object_from_id( + "https://patch.cx/objects/spoof_content_type" + ) + end + + test "it does not fetch a spoofed object with wrong content type" do + assert {:error, {:content_type, _}} = + Fetcher.fetch_and_contain_remote_object_from_id( + "https://patch.cx/objects/spoof_content_type.json" + ) + end + end + describe "fetching an object" do test "it fetches an object" do {:ok, object} = @@ -155,13 +195,6 @@ test "Return MRF reason when fetched status is rejected by one" do ) end - test "it does not fetch a spoofed object uploaded on an instance as an attachment" do - assert {:error, _} = - Fetcher.fetch_object_from_id( - "https://patch.cx/media/03ca3c8b4ac3ddd08bf0f84be7885f2f88de0f709112131a22d83650819e36c2.json" - ) - end - test "does not fetch anything from a rejected instance" do clear_config([:mrf_simple, :reject], [{"evil.example.org", "i said so"}]) -- 2.43.0 From c4cf4d7f0bb1d1082f74aea8fb54ed50160ab29e Mon Sep 17 00:00:00 2001 From: Oneric Date: Wed, 13 Mar 2024 20:12:17 -0100 Subject: [PATCH 134/149] Reject cross-domain redirects when fetching AP objects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Such redirects on AP queries seem most likely to be a spoofing attempt. If the object is legit, the id should match the final domain anyway and users can directly use the canonical URL. The lack of such a check (and use of the initially queried domain’s authority instead of the final domain) was enabling the current exploit to even affect instances which already migrated away from a same-domain upload/proxy setup in the past, but retained a redirect to not break old attachments. (In theory this redirect could, with some effort, have been limited to only old files, but common guides employed a catch-all redirect, which allows even future uploads to be reachable via an initial query to the main domain) Same-domain redirects are valid and also used by ourselves, e.g. for redirecting /notice/XXX to /objects/YYY. --- lib/pleroma/object/fetcher.ex | 25 +++++++++++++- test/pleroma/object/fetcher_test.exs | 50 ++++++++++++++++++++++++++++ 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index c3aaf7a03..9c0bf7124 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -26,6 +26,8 @@ defmodule Pleroma.Object.Fetcher do function use the former and perform some additional tasks """ + @mix_env Mix.env() + defp touch_changeset(changeset) do updated_at = NaiveDateTime.utc_now() @@ -283,6 +285,22 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do def fetch_and_contain_remote_object_from_id(_id), do: {:error, "id must be a string"} + defp check_crossdomain_redirect(final_host, original_url) + + # HOPEFULLY TEMPORARY + # Basically none of our Tesla mocks in tests set the (supposed to + # exist for Tesla proper) url parameter for their responses + # causing almost every fetch in test to fail otherwise + if @mix_env == :test do + defp check_crossdomain_redirect(nil, _) do + {:cross_domain_redirect, false} + end + end + + defp check_crossdomain_redirect(final_host, original_url) do + {:cross_domain_redirect, final_host != URI.parse(original_url).host} + end + @doc "Do NOT use; only public for use in tests" def get_object(id) do date = Pleroma.Signature.signed_date() @@ -292,8 +310,13 @@ def get_object(id) do |> maybe_date_fetch(date) |> sign_fetch(id, date) - with {:ok, %{body: body, status: code, headers: headers}} when code in 200..299 <- + with {:ok, %{body: body, status: code, headers: headers, url: final_url}} + when code in 200..299 <- HTTP.get(id, headers), + remote_host <- + URI.parse(final_url).host, + {:cross_domain_redirect, false} <- + check_crossdomain_redirect(remote_host, id), {:has_content_type, {_, content_type}} <- {:has_content_type, List.keyfind(headers, "content-type", 0)}, {:parse_content_type, {:ok, "application", subtype, type_params}} <- diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs index b2da0a757..7f6f9c031 100644 --- a/test/pleroma/object/fetcher_test.exs +++ b/test/pleroma/object/fetcher_test.exs @@ -57,6 +57,33 @@ defp spoofed_object_with_ids( body: spoofed_object_with_ids("https://patch.cx/objects/spoof_content_type") } + # Spoof: cross-domain redirect with original domain id + %{method: :get, url: "https://patch.cx/objects/spoof_media_redirect1"} -> + %Tesla.Env{ + status: 200, + url: "https://media.patch.cx/objects/spoof", + headers: [{"content-type", "application/activity+json"}], + body: spoofed_object_with_ids("https://patch.cx/objects/spoof_media_redirect1") + } + + # Spoof: cross-domain redirect with final domain id + %{method: :get, url: "https://patch.cx/objects/spoof_media_redirect2"} -> + %Tesla.Env{ + status: 200, + url: "https://media.patch.cx/objects/spoof_media_redirect2", + headers: [{"content-type", "application/activity+json"}], + body: spoofed_object_with_ids("https://media.patch.cx/objects/spoof_media_redirect2") + } + + # No-Spoof: same domain redirect + %{method: :get, url: "https://patch.cx/objects/spoof_redirect"} -> + %Tesla.Env{ + status: 200, + url: "https://patch.cx/objects/spoof", + headers: [{"content-type", "application/activity+json"}], + body: spoofed_object_with_ids("https://patch.cx/objects/spoof_redirect") + } + env -> apply(HttpRequestMock, :request, [env]) end) @@ -167,6 +194,29 @@ test "it does not fetch a spoofed object with wrong content type" do "https://patch.cx/objects/spoof_content_type.json" ) end + + test "it does not fetch an object via cross-domain redirects (initial id)" do + assert {:error, {:cross_domain_redirect, true}} = + Fetcher.fetch_and_contain_remote_object_from_id( + "https://patch.cx/objects/spoof_media_redirect1" + ) + end + + test "it does not fetch an object via cross-domain redirects (final id)" do + assert {:error, {:cross_domain_redirect, true}} = + Fetcher.fetch_and_contain_remote_object_from_id( + "https://patch.cx/objects/spoof_media_redirect2" + ) + end + + test "it accepts same-domain redirects" do + assert {:ok, %{"id" => id} = _object} = + Fetcher.fetch_and_contain_remote_object_from_id( + "https://patch.cx/objects/spoof_redirect" + ) + + assert id == "https://patch.cx/objects/spoof_redirect" + end end describe "fetching an object" do -- 2.43.0 From fee57eb376a446ee394812762df148d7b4d19b39 Mon Sep 17 00:00:00 2001 From: Oneric Date: Wed, 13 Mar 2024 20:21:19 -0100 Subject: [PATCH 135/149] Move actor check into fetch_and_contain_remote_object_from_id MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This brings it in line with its name and closes an, in practice harmless, verification hole. This was/is the only user of contain_origin making it safe to change the behaviour on actor-less objects. Until now refetched objects did not ensure the new actor matches the domain of the object. We refetch polls occasionally to retrieve up-to-date vote counts. A malicious AP server could have switched out the poll after initial posting with a completely different post attribute to an actor from another server. While we indeed fell for this spoof before the commit, it fortunately seems to have had no ill effect in practice, since the asociated Create activity is not changed. When exposing the actor via our REST API, we read this info from the activity not the object. This at first thought still keeps one avenue for exploit open though: the updated actor can be from our own domain and a third server be instructed to fetch the object from us. However this is foiled by an id mismatch. By necessity of being fetchable and our longstanding same-domain check, the id must still be from the attacker’s server. Even the most barebone authenticity check is able to sus this out. --- lib/pleroma/object/containment.ex | 2 +- lib/pleroma/object/fetcher.ex | 10 ++--- test/pleroma/object/containment_test.exs | 50 ++++++++++++++++++++++-- test/pleroma/object/fetcher_test.exs | 20 ++++++++++ 4 files changed, 72 insertions(+), 10 deletions(-) diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex index 219bc3892..85b777179 100644 --- a/lib/pleroma/object/containment.ex +++ b/lib/pleroma/object/containment.ex @@ -62,7 +62,7 @@ def contain_origin(id, %{"actor" => _actor} = params) do def contain_origin(id, %{"attributedTo" => actor} = params), do: contain_origin(id, Map.put(params, "actor", actor)) - def contain_origin(_id, _data), do: :error + def contain_origin(_id, _data), do: :ok @doc """ Check whether the object id is from the same host as another id diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 9c0bf7124..16d8194e7 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -143,7 +143,6 @@ def fetch_object_from_id(id, options \\ []) do {_, {:ok, data}} <- {:fetch, fetch_and_contain_remote_object_from_id(id)}, {_, nil} <- {:normalize, Object.normalize(data, fetch: false)}, params <- prepare_activity_params(data), - {_, :ok} <- {:containment, Containment.contain_origin(id, params)}, {_, {:ok, activity}} <- {:transmogrifier, Transmogrifier.handle_incoming(params, options)}, {_, _data, %Object{} = object} <- @@ -156,9 +155,6 @@ def fetch_object_from_id(id, options \\ []) do {:scheme, false} -> {:error, "URI Scheme Invalid"} - {:containment, _} -> - {:error, "Object containment failed."} - {:transmogrifier, {:error, {:reject, e}}} -> {:reject, e} @@ -264,7 +260,8 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")}, {:ok, body} <- get_object(id), {:ok, data} <- safe_json_decode(body), - :ok <- Containment.contain_origin_from_id(id, data) do + {_, :ok} <- {:containment, Containment.contain_origin_from_id(id, data)}, + {_, :ok} <- {:containment, Containment.contain_origin(id, data)} do unless Instances.reachable?(id) do Instances.set_reachable(id) end @@ -274,6 +271,9 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do {:scheme, _} -> {:error, "Unsupported URI scheme"} + {:containment, _} -> + {:error, "Object containment failed."} + {:error, e} -> {:error, e} diff --git a/test/pleroma/object/containment_test.exs b/test/pleroma/object/containment_test.exs index fb2fb7d49..ad1660069 100644 --- a/test/pleroma/object/containment_test.exs +++ b/test/pleroma/object/containment_test.exs @@ -17,16 +17,58 @@ defmodule Pleroma.Object.ContainmentTest do end describe "general origin containment" do - test "works for completely actorless posts" do - assert :error == - Containment.contain_origin("https://glaceon.social/users/monorail", %{ + test "handles completly actorless objects gracefully" do + assert :ok == + Containment.contain_origin("https://glaceon.social/statuses/123", %{ "deleted" => "2019-10-30T05:48:50.249606Z", "formerType" => "Note", - "id" => "https://glaceon.social/users/monorail/statuses/103049757364029187", + "id" => "https://glaceon.social/statuses/123", "type" => "Tombstone" }) end + test "errors for spoofed actors" do + assert :error == + Containment.contain_origin("https://glaceon.social/statuses/123", %{ + "actor" => "https://otp.akkoma.dev/users/you", + "id" => "https://glaceon.social/statuses/123", + "type" => "Note" + }) + end + + test "errors for spoofed attributedTo" do + assert :error == + Containment.contain_origin("https://glaceon.social/statuses/123", %{ + "attributedTo" => "https://otp.akkoma.dev/users/you", + "id" => "https://glaceon.social/statuses/123", + "type" => "Note" + }) + end + + test "accepts valid actors" do + assert :ok == + Containment.contain_origin("https://glaceon.social/statuses/123", %{ + "actor" => "https://glaceon.social/users/monorail", + "attributedTo" => "https://glaceon.social/users/monorail", + "id" => "https://glaceon.social/statuses/123", + "type" => "Note" + }) + + assert :ok == + Containment.contain_origin("https://glaceon.social/statuses/123", %{ + "actor" => "https://glaceon.social/users/monorail", + "id" => "https://glaceon.social/statuses/123", + "type" => "Note" + }) + + assert :ok == + Containment.contain_origin("https://glaceon.social/statuses/123", %{ + "attributedTo" => "https://glaceon.social/users/monorail", + "id" => "https://glaceon.social/statuses/123", + "type" => "Note" + }) + end + test "contain_origin_from_id() catches obvious spoofing attempts" do data = %{ "id" => "http://example.com/~alyssa/activities/1234.json" diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs index 7f6f9c031..b289e869d 100644 --- a/test/pleroma/object/fetcher_test.exs +++ b/test/pleroma/object/fetcher_test.exs @@ -84,6 +84,19 @@ defp spoofed_object_with_ids( body: spoofed_object_with_ids("https://patch.cx/objects/spoof_redirect") } + # Spoof: Actor from another domain + %{method: :get, url: "https://patch.cx/objects/spoof_foreign_actor"} -> + %Tesla.Env{ + status: 200, + url: "https://patch.cx/objects/spoof_foreign_actor", + headers: [{"content-type", "application/activity+json"}], + body: + spoofed_object_with_ids( + "https://patch.cx/objects/spoof_foreign_actor", + "https://not.patch.cx/users/rin" + ) + } + env -> apply(HttpRequestMock, :request, [env]) end) @@ -217,6 +230,13 @@ test "it accepts same-domain redirects" do assert id == "https://patch.cx/objects/spoof_redirect" end + + test "it does not fetch a spoofed object with a foreign actor" do + assert {:error, "Object containment failed."} = + Fetcher.fetch_and_contain_remote_object_from_id( + "https://patch.cx/objects/spoof_foreign_actor" + ) + end end describe "fetching an object" do -- 2.43.0 From 59a142e0b0e6760e116443567802ef2ab7483178 Mon Sep 17 00:00:00 2001 From: Oneric Date: Wed, 13 Mar 2024 21:00:23 -0100 Subject: [PATCH 136/149] Never fetch resource from ourselves MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit If it’s not already in the database, it must be counterfeit (or just not exists at all) Changed test URLs were only ever used from "local: false" users anyway. --- lib/pleroma/object/containment.ex | 13 +++++++ lib/pleroma/object/fetcher.ex | 4 +++ .../users_mock/friendica_followers.json | 2 +- .../users_mock/friendica_following.json | 2 +- .../users_mock/masto_closed_followers.json | 4 +-- .../masto_closed_followers_page.json | 2 +- .../users_mock/masto_closed_following.json | 4 +-- .../masto_closed_following_page.json | 2 +- .../users_mock/pleroma_followers.json | 8 ++--- .../users_mock/pleroma_following.json | 8 ++--- test/pleroma/object/fetcher_test.exs | 7 ++++ test/pleroma/user_test.exs | 22 ++++++------ .../web/activity_pub/activity_pub_test.exs | 36 +++++++++---------- test/support/http_request_mock.ex | 16 ++++----- 14 files changed, 77 insertions(+), 53 deletions(-) diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex index 85b777179..48e62a917 100644 --- a/lib/pleroma/object/containment.ex +++ b/lib/pleroma/object/containment.ex @@ -47,6 +47,19 @@ def get_object(_) do defp compare_uris(%URI{host: host} = _id_uri, %URI{host: host} = _other_uri), do: :ok defp compare_uris(_id_uri, _other_uri), do: :error + @doc """ + Checks whether an URL to fetch from is from the local server. + + We never want to fetch from ourselves; if it’s not in the database + it can’t be authentic and must be a counterfeit. + """ + def contain_local_fetch(id) do + case compare_uris(URI.parse(id), Pleroma.Web.Endpoint.struct_url()) do + :ok -> :error + _ -> :ok + end + end + @doc """ Checks that an imported AP object's actor matches the host it came from. """ diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 16d8194e7..f256bbf77 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -258,6 +258,7 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do Logger.debug("Fetching object #{id} via AP") with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")}, + {_, :ok} <- {:local_fetch, Containment.contain_local_fetch(id)}, {:ok, body} <- get_object(id), {:ok, data} <- safe_json_decode(body), {_, :ok} <- {:containment, Containment.contain_origin_from_id(id, data)}, @@ -271,6 +272,9 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do {:scheme, _} -> {:error, "Unsupported URI scheme"} + {:local_fetch, _} -> + {:error, "Trying to fetch local resource"} + {:containment, _} -> {:error, "Object containment failed."} diff --git a/test/fixtures/users_mock/friendica_followers.json b/test/fixtures/users_mock/friendica_followers.json index 7b86b5fe2..02b287e23 100644 --- a/test/fixtures/users_mock/friendica_followers.json +++ b/test/fixtures/users_mock/friendica_followers.json @@ -13,7 +13,7 @@ "directMessage": "litepub:directMessage" } ], - "id": "http://localhost:8080/followers/fuser3", + "id": "http://remote.org/followers/fuser3", "type": "OrderedCollection", "totalItems": 296 } diff --git a/test/fixtures/users_mock/friendica_following.json b/test/fixtures/users_mock/friendica_following.json index 7c526befc..0908e78f0 100644 --- a/test/fixtures/users_mock/friendica_following.json +++ b/test/fixtures/users_mock/friendica_following.json @@ -13,7 +13,7 @@ "directMessage": "litepub:directMessage" } ], - "id": "http://localhost:8080/following/fuser3", + "id": "http://remote.org/following/fuser3", "type": "OrderedCollection", "totalItems": 32 } diff --git a/test/fixtures/users_mock/masto_closed_followers.json b/test/fixtures/users_mock/masto_closed_followers.json index da296892d..ccc32d15e 100644 --- a/test/fixtures/users_mock/masto_closed_followers.json +++ b/test/fixtures/users_mock/masto_closed_followers.json @@ -1,7 +1,7 @@ { "@context": "https://www.w3.org/ns/activitystreams", - "id": "http://localhost:4001/users/masto_closed/followers", + "id": "http://remote.org/users/masto_closed/followers", "type": "OrderedCollection", "totalItems": 437, - "first": "http://localhost:4001/users/masto_closed/followers?page=1" + "first": "http://remote.org/users/masto_closed/followers?page=1" } diff --git a/test/fixtures/users_mock/masto_closed_followers_page.json b/test/fixtures/users_mock/masto_closed_followers_page.json index 04ab0c4d3..e4f1b3ac0 100644 --- a/test/fixtures/users_mock/masto_closed_followers_page.json +++ b/test/fixtures/users_mock/masto_closed_followers_page.json @@ -1 +1 @@ -{"@context":"https://www.w3.org/ns/activitystreams","id":"http://localhost:4001/users/masto_closed/followers?page=1","type":"OrderedCollectionPage","totalItems":437,"next":"http://localhost:4001/users/masto_closed/followers?page=2","partOf":"http://localhost:4001/users/masto_closed/followers","orderedItems":["https://testing.uguu.ltd/users/rin","https://patch.cx/users/rin","https://letsalllovela.in/users/xoxo","https://pleroma.site/users/crushv","https://aria.company/users/boris","https://kawen.space/users/crushv","https://freespeech.host/users/cvcvcv","https://pleroma.site/users/picpub","https://pixelfed.social/users/nosleep","https://boopsnoot.gq/users/5c1896d162f7d337f90492a3","https://pikachu.rocks/users/waifu","https://royal.crablettesare.life/users/crablettes"]} +{"@context":"https://www.w3.org/ns/activitystreams","id":"http://remote.org/users/masto_closed/followers?page=1","type":"OrderedCollectionPage","totalItems":437,"next":"http://remote.org/users/masto_closed/followers?page=2","partOf":"http://remote.org/users/masto_closed/followers","orderedItems":["https://testing.uguu.ltd/users/rin","https://patch.cx/users/rin","https://letsalllovela.in/users/xoxo","https://pleroma.site/users/crushv","https://aria.company/users/boris","https://kawen.space/users/crushv","https://freespeech.host/users/cvcvcv","https://pleroma.site/users/picpub","https://pixelfed.social/users/nosleep","https://boopsnoot.gq/users/5c1896d162f7d337f90492a3","https://pikachu.rocks/users/waifu","https://royal.crablettesare.life/users/crablettes"]} diff --git a/test/fixtures/users_mock/masto_closed_following.json b/test/fixtures/users_mock/masto_closed_following.json index 146d49f9c..34e9e9fe8 100644 --- a/test/fixtures/users_mock/masto_closed_following.json +++ b/test/fixtures/users_mock/masto_closed_following.json @@ -1,7 +1,7 @@ { "@context": "https://www.w3.org/ns/activitystreams", - "id": "http://localhost:4001/users/masto_closed/following", + "id": "http://remote.org/users/masto_closed/following", "type": "OrderedCollection", "totalItems": 152, - "first": "http://localhost:4001/users/masto_closed/following?page=1" + "first": "http://remote.org/users/masto_closed/following?page=1" } diff --git a/test/fixtures/users_mock/masto_closed_following_page.json b/test/fixtures/users_mock/masto_closed_following_page.json index 8d8324699..d398ae3cf 100644 --- a/test/fixtures/users_mock/masto_closed_following_page.json +++ b/test/fixtures/users_mock/masto_closed_following_page.json @@ -1 +1 @@ -{"@context":"https://www.w3.org/ns/activitystreams","id":"http://localhost:4001/users/masto_closed/following?page=1","type":"OrderedCollectionPage","totalItems":152,"next":"http://localhost:4001/users/masto_closed/following?page=2","partOf":"http://localhost:4001/users/masto_closed/following","orderedItems":["https://testing.uguu.ltd/users/rin","https://patch.cx/users/rin","https://letsalllovela.in/users/xoxo","https://pleroma.site/users/crushv","https://aria.company/users/boris","https://kawen.space/users/crushv","https://freespeech.host/users/cvcvcv","https://pleroma.site/users/picpub","https://pixelfed.social/users/nosleep","https://boopsnoot.gq/users/5c1896d162f7d337f90492a3","https://pikachu.rocks/users/waifu","https://royal.crablettesare.life/users/crablettes"]} +{"@context":"https://www.w3.org/ns/activitystreams","id":"http://remote.org/users/masto_closed/following?page=1","type":"OrderedCollectionPage","totalItems":152,"next":"http://remote.org/users/masto_closed/following?page=2","partOf":"http://remote.org/users/masto_closed/following","orderedItems":["https://testing.uguu.ltd/users/rin","https://patch.cx/users/rin","https://letsalllovela.in/users/xoxo","https://pleroma.site/users/crushv","https://aria.company/users/boris","https://kawen.space/users/crushv","https://freespeech.host/users/cvcvcv","https://pleroma.site/users/picpub","https://pixelfed.social/users/nosleep","https://boopsnoot.gq/users/5c1896d162f7d337f90492a3","https://pikachu.rocks/users/waifu","https://royal.crablettesare.life/users/crablettes"]} diff --git a/test/fixtures/users_mock/pleroma_followers.json b/test/fixtures/users_mock/pleroma_followers.json index db71d084b..9611990ee 100644 --- a/test/fixtures/users_mock/pleroma_followers.json +++ b/test/fixtures/users_mock/pleroma_followers.json @@ -1,14 +1,14 @@ { "type": "OrderedCollection", "totalItems": 527, - "id": "http://localhost:4001/users/fuser2/followers", + "id": "http://remote.org/users/fuser2/followers", "first": { "type": "OrderedCollectionPage", "totalItems": 527, - "partOf": "http://localhost:4001/users/fuser2/followers", + "partOf": "http://remote.org/users/fuser2/followers", "orderedItems": [], - "next": "http://localhost:4001/users/fuser2/followers?page=2", - "id": "http://localhost:4001/users/fuser2/followers?page=1" + "next": "http://remote.org/users/fuser2/followers?page=2", + "id": "http://remote.org/users/fuser2/followers?page=1" }, "@context": [ "https://www.w3.org/ns/activitystreams", diff --git a/test/fixtures/users_mock/pleroma_following.json b/test/fixtures/users_mock/pleroma_following.json index 33d087703..27fadbc94 100644 --- a/test/fixtures/users_mock/pleroma_following.json +++ b/test/fixtures/users_mock/pleroma_following.json @@ -1,14 +1,14 @@ { "type": "OrderedCollection", "totalItems": 267, - "id": "http://localhost:4001/users/fuser2/following", + "id": "http://remote.org/users/fuser2/following", "first": { "type": "OrderedCollectionPage", "totalItems": 267, - "partOf": "http://localhost:4001/users/fuser2/following", + "partOf": "http://remote.org/users/fuser2/following", "orderedItems": [], - "next": "http://localhost:4001/users/fuser2/following?page=2", - "id": "http://localhost:4001/users/fuser2/following?page=1" + "next": "http://remote.org/users/fuser2/following?page=2", + "id": "http://remote.org/users/fuser2/following?page=1" }, "@context": [ "https://www.w3.org/ns/activitystreams", diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs index b289e869d..d1f3c070e 100644 --- a/test/pleroma/object/fetcher_test.exs +++ b/test/pleroma/object/fetcher_test.exs @@ -237,6 +237,13 @@ test "it does not fetch a spoofed object with a foreign actor" do "https://patch.cx/objects/spoof_foreign_actor" ) end + + test "it does not fetch from localhost" do + assert {:error, "Trying to fetch local resource"} = + Fetcher.fetch_and_contain_remote_object_from_id( + Pleroma.Web.Endpoint.url() <> "/spoof_local" + ) + end end describe "fetching an object" do diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index 5a0d77cab..96ca8d0fd 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -326,9 +326,9 @@ test "unfollow with synchronizes external user" do insert(:user, %{ local: false, nickname: "fuser2", - ap_id: "http://localhost:4001/users/fuser2", - follower_address: "http://localhost:4001/users/fuser2/followers", - following_address: "http://localhost:4001/users/fuser2/following" + ap_id: "http://remote.org/users/fuser2", + follower_address: "http://remote.org/users/fuser2/followers", + following_address: "http://remote.org/users/fuser2/following" }) {:ok, user, followed} = User.follow(user, followed, :follow_accept) @@ -2177,8 +2177,8 @@ test "it returns a list of AP ids for a given set of nicknames" do describe "sync followers count" do setup do - user1 = insert(:user, local: false, ap_id: "http://localhost:4001/users/masto_closed") - user2 = insert(:user, local: false, ap_id: "http://localhost:4001/users/fuser2") + user1 = insert(:user, local: false, ap_id: "http://remote.org/users/masto_closed") + user2 = insert(:user, local: false, ap_id: "http://remote.org/users/fuser2") insert(:user, local: true) insert(:user, local: false, is_active: false) {:ok, user1: user1, user2: user2} @@ -2272,8 +2272,8 @@ test "updates the counters normally on following/getting a follow when disabled" other_user = insert(:user, local: false, - follower_address: "http://localhost:4001/users/masto_closed/followers", - following_address: "http://localhost:4001/users/masto_closed/following", + follower_address: "http://remote.org/users/masto_closed/followers", + following_address: "http://remote.org/users/masto_closed/following", ap_enabled: true ) @@ -2294,8 +2294,8 @@ test "synchronizes the counters with the remote instance for the followed when e other_user = insert(:user, local: false, - follower_address: "http://localhost:4001/users/masto_closed/followers", - following_address: "http://localhost:4001/users/masto_closed/following", + follower_address: "http://remote.org/users/masto_closed/followers", + following_address: "http://remote.org/users/masto_closed/following", ap_enabled: true ) @@ -2316,8 +2316,8 @@ test "synchronizes the counters with the remote instance for the follower when e other_user = insert(:user, local: false, - follower_address: "http://localhost:4001/users/masto_closed/followers", - following_address: "http://localhost:4001/users/masto_closed/following", + follower_address: "http://remote.org/users/masto_closed/followers", + following_address: "http://remote.org/users/masto_closed/following", ap_enabled: true ) diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index 69b4ac257..c6543ec83 100644 --- a/test/pleroma/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -1643,8 +1643,8 @@ test "synchronizes following/followers counters" do user = insert(:user, local: false, - follower_address: "http://localhost:4001/users/fuser2/followers", - following_address: "http://localhost:4001/users/fuser2/following" + follower_address: "http://remote.org/users/fuser2/followers", + following_address: "http://remote.org/users/fuser2/following" ) {:ok, info} = ActivityPub.fetch_follow_information_for_user(user) @@ -1655,7 +1655,7 @@ test "synchronizes following/followers counters" do test "detects hidden followers" do mock(fn env -> case env.url do - "http://localhost:4001/users/masto_closed/followers?page=1" -> + "http://remote.org/users/masto_closed/followers?page=1" -> %Tesla.Env{status: 403, body: ""} _ -> @@ -1666,8 +1666,8 @@ test "detects hidden followers" do user = insert(:user, local: false, - follower_address: "http://localhost:4001/users/masto_closed/followers", - following_address: "http://localhost:4001/users/masto_closed/following" + follower_address: "http://remote.org/users/masto_closed/followers", + following_address: "http://remote.org/users/masto_closed/following" ) {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user) @@ -1678,7 +1678,7 @@ test "detects hidden followers" do test "detects hidden follows" do mock(fn env -> case env.url do - "http://localhost:4001/users/masto_closed/following?page=1" -> + "http://remote.org/users/masto_closed/following?page=1" -> %Tesla.Env{status: 403, body: ""} _ -> @@ -1689,8 +1689,8 @@ test "detects hidden follows" do user = insert(:user, local: false, - follower_address: "http://localhost:4001/users/masto_closed/followers", - following_address: "http://localhost:4001/users/masto_closed/following" + follower_address: "http://remote.org/users/masto_closed/followers", + following_address: "http://remote.org/users/masto_closed/following" ) {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user) @@ -1702,8 +1702,8 @@ test "detects hidden follows/followers for friendica" do user = insert(:user, local: false, - follower_address: "http://localhost:8080/followers/fuser3", - following_address: "http://localhost:8080/following/fuser3" + follower_address: "http://remote.org/followers/fuser3", + following_address: "http://remote.org/following/fuser3" ) {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user) @@ -1716,28 +1716,28 @@ test "detects hidden follows/followers for friendica" do test "doesn't crash when follower and following counters are hidden" do mock(fn env -> case env.url do - "http://localhost:4001/users/masto_hidden_counters/following" -> + "http://remote.org/users/masto_hidden_counters/following" -> json( %{ "@context" => "https://www.w3.org/ns/activitystreams", - "id" => "http://localhost:4001/users/masto_hidden_counters/followers" + "id" => "http://remote.org/users/masto_hidden_counters/followers" }, headers: HttpRequestMock.activitypub_object_headers() ) - "http://localhost:4001/users/masto_hidden_counters/following?page=1" -> + "http://remote.org/users/masto_hidden_counters/following?page=1" -> %Tesla.Env{status: 403, body: ""} - "http://localhost:4001/users/masto_hidden_counters/followers" -> + "http://remote.org/users/masto_hidden_counters/followers" -> json( %{ "@context" => "https://www.w3.org/ns/activitystreams", - "id" => "http://localhost:4001/users/masto_hidden_counters/following" + "id" => "http://remote.org/users/masto_hidden_counters/following" }, headers: HttpRequestMock.activitypub_object_headers() ) - "http://localhost:4001/users/masto_hidden_counters/followers?page=1" -> + "http://remote.org/users/masto_hidden_counters/followers?page=1" -> %Tesla.Env{status: 403, body: ""} end end) @@ -1745,8 +1745,8 @@ test "doesn't crash when follower and following counters are hidden" do user = insert(:user, local: false, - follower_address: "http://localhost:4001/users/masto_hidden_counters/followers", - following_address: "http://localhost:4001/users/masto_hidden_counters/following" + follower_address: "http://remote.org/users/masto_hidden_counters/followers", + following_address: "http://remote.org/users/masto_hidden_counters/following" ) {:ok, follow_info} = ActivityPub.fetch_follow_information_for_user(user) diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index 6772a7421..e831f43f7 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -964,7 +964,7 @@ def get("https://pleroma.local/notice/9kCP7V", _, _, _) do {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/rich_media/ogp.html")}} end - def get("http://localhost:4001/users/masto_closed/followers", _, _, _) do + def get("http://remote.org/users/masto_closed/followers", _, _, _) do {:ok, %Tesla.Env{ status: 200, @@ -973,7 +973,7 @@ def get("http://localhost:4001/users/masto_closed/followers", _, _, _) do }} end - def get("http://localhost:4001/users/masto_closed/followers?page=1", _, _, _) do + def get("http://remote.org/users/masto_closed/followers?page=1", _, _, _) do {:ok, %Tesla.Env{ status: 200, @@ -982,7 +982,7 @@ def get("http://localhost:4001/users/masto_closed/followers?page=1", _, _, _) do }} end - def get("http://localhost:4001/users/masto_closed/following", _, _, _) do + def get("http://remote.org/users/masto_closed/following", _, _, _) do {:ok, %Tesla.Env{ status: 200, @@ -991,7 +991,7 @@ def get("http://localhost:4001/users/masto_closed/following", _, _, _) do }} end - def get("http://localhost:4001/users/masto_closed/following?page=1", _, _, _) do + def get("http://remote.org/users/masto_closed/following?page=1", _, _, _) do {:ok, %Tesla.Env{ status: 200, @@ -1000,7 +1000,7 @@ def get("http://localhost:4001/users/masto_closed/following?page=1", _, _, _) do }} end - def get("http://localhost:8080/followers/fuser3", _, _, _) do + def get("http://remote.org/followers/fuser3", _, _, _) do {:ok, %Tesla.Env{ status: 200, @@ -1009,7 +1009,7 @@ def get("http://localhost:8080/followers/fuser3", _, _, _) do }} end - def get("http://localhost:8080/following/fuser3", _, _, _) do + def get("http://remote.org/following/fuser3", _, _, _) do {:ok, %Tesla.Env{ status: 200, @@ -1018,7 +1018,7 @@ def get("http://localhost:8080/following/fuser3", _, _, _) do }} end - def get("http://localhost:4001/users/fuser2/followers", _, _, _) do + def get("http://remote.org/users/fuser2/followers", _, _, _) do {:ok, %Tesla.Env{ status: 200, @@ -1027,7 +1027,7 @@ def get("http://localhost:4001/users/fuser2/followers", _, _, _) do }} end - def get("http://localhost:4001/users/fuser2/following", _, _, _) do + def get("http://remote.org/users/fuser2/following", _, _, _) do {:ok, %Tesla.Env{ status: 200, -- 2.43.0 From f07eb4cb55ebcc68bdf7ed502fcbffbdee7dc819 Mon Sep 17 00:00:00 2001 From: Oneric Date: Fri, 15 Mar 2024 20:31:45 -0100 Subject: [PATCH 137/149] Sanity check fetched user data In order to properly process incoming notes we need to be able to map the key id back to an actor. Also, check collections actually belong to the same server. Key ids of Hubzilla and Bridgy samples were updated to what modern versions of those output. If anything still uses the old format, we would not be able to verify their posts anyway. --- lib/pleroma/object/containment.ex | 8 ++ lib/pleroma/web/activity_pub/activity_pub.ex | 6 ++ .../object_validators/user_validator.ex | 92 +++++++++++++++++++ test/fixtures/bridgy/actor.json | 2 +- ...ps___osada.macgirvin.com_channel_mike.json | 2 +- .../kaniini@hubzilla.example.org.json | 2 +- 6 files changed, 109 insertions(+), 3 deletions(-) create mode 100644 lib/pleroma/web/activity_pub/object_validators/user_validator.ex diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex index 48e62a917..a312f69e8 100644 --- a/lib/pleroma/object/containment.ex +++ b/lib/pleroma/object/containment.ex @@ -101,4 +101,12 @@ def contain_child(%{"object" => %{"id" => id, "attributedTo" => _} = object}), do: contain_origin(id, object) def contain_child(_), do: :ok + + @doc "Checks whether two URIs belong to the same domain" + def same_origin(id1, id2) do + uri1 = URI.parse(id1) + uri2 = URI.parse(id2) + + compare_uris(uri1, uri2) + end end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 4a8ce2d3d..deca69613 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -22,6 +22,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do alias Pleroma.Upload alias Pleroma.User alias Pleroma.Web.ActivityPub.MRF + alias Pleroma.Web.ActivityPub.ObjectValidators.UserValidator alias Pleroma.Web.ActivityPub.Transmogrifier alias Pleroma.Web.Streamer alias Pleroma.Web.WebFinger @@ -1722,6 +1723,7 @@ def user_data_from_user_object(data, additional \\ []) do def fetch_and_prepare_user_from_ap_id(ap_id, additional \\ []) do with {:ok, data} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id), + {:valid, {:ok, _, _}} <- {:valid, UserValidator.validate(data, [])}, {:ok, data} <- user_data_from_user_object(data, additional) do {:ok, maybe_update_follow_information(data)} else @@ -1734,6 +1736,10 @@ def fetch_and_prepare_user_from_ap_id(ap_id, additional \\ []) do Logger.debug("Rejected user #{ap_id}: #{inspect(reason)}") {:error, e} + {:valid, reason} -> + Logger.debug("Data is not a valid user #{ap_id}: #{inspect(reason)}") + {:error, "Not a user"} + {:error, e} -> Logger.error("Could not decode user at fetch #{ap_id}, #{inspect(e)}") {:error, e} diff --git a/lib/pleroma/web/activity_pub/object_validators/user_validator.ex b/lib/pleroma/web/activity_pub/object_validators/user_validator.ex new file mode 100644 index 000000000..90b5404f3 --- /dev/null +++ b/lib/pleroma/web/activity_pub/object_validators/user_validator.ex @@ -0,0 +1,92 @@ +# Akkoma: Magically expressive social media +# Copyright © 2024 Akkoma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ActivityPub.ObjectValidators.UserValidator do + @moduledoc """ + Checks whether ActivityPub data represents a valid user + + Users don't go through the same ingest pipeline like activities or other objects. + To ensure this can only match a user and no users match in the other pipeline, + this is a separate from the generic ObjectValidator. + """ + + @behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating + + alias Pleroma.Object.Containment + alias Pleroma.Signature + + @impl true + def validate(object, meta) + + def validate(%{"type" => type, "id" => _id} = data, meta) + when type in ["Person", "Organization", "Group", "Application"] do + with :ok <- validate_pubkey(data), + :ok <- validate_inbox(data), + :ok <- contain_collection_origin(data) do + {:ok, data, meta} + else + {:error, e} -> {:error, e} + e -> {:error, e} + end + end + + def validate(_, _), do: {:error, "Not a user object"} + + defp mabye_validate_owner(nil, _actor), do: :ok + defp mabye_validate_owner(actor, actor), do: :ok + defp mabye_validate_owner(_owner, _actor), do: :error + + defp validate_pubkey( + %{"id" => id, "publicKey" => %{"id" => pk_id, "publicKeyPem" => _key}} = data + ) + when id != nil do + with {_, {:ok, kactor}} <- {:key, Signature.key_id_to_actor_id(pk_id)}, + true <- id == kactor, + :ok <- mabye_validate_owner(Map.get(data, "owner"), id) do + :ok + else + {:key, _} -> + {:error, "Unable to determine actor id from key id"} + + false -> + {:error, "Key id does not relate to user id"} + + _ -> + {:error, "Actor does not own its public key"} + end + end + + # pubkey is optional atm + defp validate_pubkey(_data), do: :ok + + defp validate_inbox(%{"id" => id, "inbox" => inbox}) do + case Containment.same_origin(id, inbox) do + :ok -> :ok + :error -> {:error, "Inbox on different doamin"} + end + end + + defp validate_inbox(_), do: {:error, "No inbox"} + + defp check_field_value(%{"id" => id} = _data, value) do + Containment.same_origin(id, value) + end + + defp maybe_check_field(data, field) do + with val when val != nil <- data[field], + :ok <- check_field_value(data, val) do + :ok + else + nil -> :ok + _ -> {:error, "#{field} on different domain"} + end + end + + defp contain_collection_origin(data) do + Enum.reduce(["followers", "following", "featured"], :ok, fn + field, :ok -> maybe_check_field(data, field) + _, error -> error + end) + end +end diff --git a/test/fixtures/bridgy/actor.json b/test/fixtures/bridgy/actor.json index 5b2d8982b..b4e859a82 100644 --- a/test/fixtures/bridgy/actor.json +++ b/test/fixtures/bridgy/actor.json @@ -70,7 +70,7 @@ "preferredUsername": "jk.nipponalba.scot", "summary": "", "publicKey": { - "id": "jk.nipponalba.scot", + "id": "https://fed.brid.gy/jk.nipponalba.scot#key", "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdarxwzxnNbJ2hneWOYHkYJowk\npyigQtxlUd0VjgSQHwxU9kWqfbrHBVADyTtcqi/4dAzQd3UnCI1TPNnn4LPZY9PW\noiWd3Zl1/EfLFxO7LU9GS7fcSLQkyj5JNhSlN3I8QPudZbybrgRDVZYooDe1D+52\n5KLGqC2ajrIVOiDRTQIDAQAB\n-----END PUBLIC KEY-----" }, "inbox": "https://fed.brid.gy/jk.nipponalba.scot/inbox", diff --git a/test/fixtures/tesla_mock/https___osada.macgirvin.com_channel_mike.json b/test/fixtures/tesla_mock/https___osada.macgirvin.com_channel_mike.json index ca76d6e17..70d750e5d 100644 --- a/test/fixtures/tesla_mock/https___osada.macgirvin.com_channel_mike.json +++ b/test/fixtures/tesla_mock/https___osada.macgirvin.com_channel_mike.json @@ -37,7 +37,7 @@ "sharedInbox": "https://osada.macgirvin.com/inbox" }, "publicKey": { - "id": "https://osada.macgirvin.com/channel/mike/public_key_pem", + "id": "https://osada.macgirvin.com/channel/mike", "owner": "https://osada.macgirvin.com/channel/mike", "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAskSyK2VwBNKbzZl9XNJk\nvxU5AAilmRArMmmKSzphdHaVBHakeafUfixvqNrQ/oX2srJvJKcghNmEMrJ6MJ7r\npeEndVOo7pcP4PwVjtnC06p3J711q5tBehqM25BfCLCrB2YqWF6c8zk3CPN3Na21\n8k5s4cO95N/rGN+Po0XFAX/HjKjlpgNpKRDrpxmXxTU8NZfAqeQGJ5oiMBZI9vVB\n+eU7t1L6F5/XWuUCeP4OMrG8oZX822AREba8rknS6DpkWGES0Rx2eNOyYTf6ue75\nI6Ek6rlO+da5wMWr+3BvYMq4JMIwTHzAO+ZqqJPFpzKSiVuAWb2DOX/MDFecVWJE\ntF/R60lONxe4e/00MPCoDdqkLKdwROsk1yGL7z4Zk6jOWFEhIcWy/d2Ya5CpPvS3\nu4wNN4jkYAjra+8TiloRELhV4gpcEk8nkyNwLXOhYm7zQ5sIc5rfXoIrFzALB86W\nG05Nnqg+77zZIaTZpD9qekYlaEt+0OVtt9TTIeTiudQ983l6mfKwZYymrzymH1dL\nVgxBRYo+Z53QOSLiSKELfTBZxEoP1pBw6RiOHXydmJ/39hGgc2YAY/5ADwW2F2yb\nJ7+gxG6bPJ3ikDLYcD4CB5iJQdnTcDsFt3jyHAT6wOCzFAYPbHUqtzHfUM30dZBn\nnJhQF8udPLcXLaj6GW75JacCAwEAAQ==\n-----END PUBLIC KEY-----\n" }, diff --git a/test/fixtures/tesla_mock/kaniini@hubzilla.example.org.json b/test/fixtures/tesla_mock/kaniini@hubzilla.example.org.json index 11c79e11e..c354747cc 100644 --- a/test/fixtures/tesla_mock/kaniini@hubzilla.example.org.json +++ b/test/fixtures/tesla_mock/kaniini@hubzilla.example.org.json @@ -1 +1 @@ -{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1","https://hubzilla.example.org/apschema/v1.2"],"type":"Person","id":"https://hubzilla.example.org/channel/kaniini","preferredUsername":"kaniini","name":"kaniini","icon":{"type":"Image","mediaType":"image/jpeg","url":"https://hubzilla.example.org/photo/profile/l/281","height":300,"width":300},"url":{"type":"Link","mediaType":"text/html","href":"https://hubzilla.example.org/channel/kaniini"},"inbox":"https://hubzilla.example.org/inbox/kaniini","outbox":"https://hubzilla.example.org/outbox/kaniini","followers":"https://hubzilla.example.org/followers/kaniini","following":"https://hubzilla.example.org/following/kaniini","endpoints":{"sharedInbox":"https://hubzilla.example.org/inbox"},"publicKey":{"id":"https://hubzilla.example.org/channel/kaniini/public_key_pem","owner":"https://hubzilla.example.org/channel/kaniini","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvXCDkQPw+1N8B2CUd5s2\nbYvjHt+t7soMNfUiRy0qGbgW46S45k5lCq1KpbFIX3sgGZ4OWjnXVbvjCJi4kl5M\nfm5DBXzpuu05AmjVl8hqk4GejajiE/1Nq0uWHPiOSFWispUjCzzCu65V+IsiE5JU\nvcL6WEf/pYNRq7gYqyT693F7+cO5/rVv9OScx5UOxbIuU1VXYhdHCqAMDJWadC89\nhePrcD3HOQKl06W2tDxHcWk6QjrdsUQGbNOgK/QIN9gSxA+rCFEvH5O0HAhI0aXq\ncOB+vysJUFLeQOAqmAKvKS5V6RqE1GqqT0pDWHack4EmQi0gkgVzo+45xoP6wfDl\nWwG88w21LNxGvGHuN4I8mg6cEoApqKQBSOj086UtfDfSlPC1B+PRD2phE5etucHd\nF/RIWN3SxVzU9BKIiaDm2gwOpvI8QuorQb6HDtZFO5NsSN3PnMnSywPe7kXl/469\nuQRYXrseqyOVIi6WjhvXkyWVKVE5CBz+S8wXHfKph+9YOyUcJeAVMijp9wrjBlMc\noSzOGu79oM7tpMSq/Xo6ePJ/glNOwZR+OKrg92Qp9BGTKDNwGrxuxP/9KwWtGLNf\nOMTtIkxtC3ubhxL3lBxOd7l+Bmum0UJV2f8ogkCgvTpIz05jMoyU8qWl6kkWNQlY\nDropXWaOfy7Lac+G4qlfSgsCAwEAAQ==\n-----END PUBLIC KEY-----\n"},"nomadicLocations":[{"id":"https://hubzilla.example.org/locs/kaniini","type":"nomadicLocation","locationAddress":"acct:kaniini@hubzilla.example.org","locationPrimary":true,"locationDeleted":false}],"signature":{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1"],"type":"RsaSignature2017","nonce":"6b981a2f3bdcffc20252e3b131d4a4569fd2dea9fac543e5196136302f492694","creator":"https://hubzilla.example.org/channel/kaniini/public_key_pem","created":"2018-05-19T08:19:13Z","signatureValue":"ezpT4iCIUzJSeJa/Jsf4EkgbX9enWZG/0eliLXZcvkeCX9mZabaX9LMQRViP2GSlAJBHJu+UqK5LWaoWw9pYkQQHUL+43w2DeBxQicEcPqpT46j6pHuWptfwB8YHTC2/Pb56Y/jseU37j+FW8xVmcGZk4cPqJRLQNojwJlQiFOpBEd4Cel6081W12Pep578+6xBL+h92RJsWznA1gE/NV9dkCqoAoNdiORJg68sVTm0yYxPit2D/DLwXUFeBhC47EZtY3DtAOf7rADGwbquXKug/wtEI47R4p9dJvMWERSVW9O2FmDk8deUjRR3qO1iYGce8O+uMnnBHmuTcToRUHH7mxfMdqjfbcZ9DGBjKtLPSOyVPT9rENeyX8fsksmX0XhfHsNSWkmeDaU5/Au3IY75gDewiGzmzLOpRc6GUnHHro7lMpyMuo3lLZKjNVsFZbx+sXCYwORz5GAMuwIt/iCUdrsQsF5aycqfUAZrFBPguH6DVjbMUqyLvS78sDKiWqgWVhq9VDKse+WuQaJLGBDJNF9APoA6NDMjjIBZfmkGf2mV7ubIYihoOncUjahFqxU5306cNxAcdj2uNcwkgX4BCnBe/L2YsvMHhZrupzDewWWy4fxhktyoZ7VhLSl1I7fMPytjOpb9EIvng4DHGX2t+hKfon2rCGfECPavwiTM="}} +{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1","https://hubzilla.example.org/apschema/v1.2"],"type":"Person","id":"https://hubzilla.example.org/channel/kaniini","preferredUsername":"kaniini","name":"kaniini","icon":{"type":"Image","mediaType":"image/jpeg","url":"https://hubzilla.example.org/photo/profile/l/281","height":300,"width":300},"url":{"type":"Link","mediaType":"text/html","href":"https://hubzilla.example.org/channel/kaniini"},"inbox":"https://hubzilla.example.org/inbox/kaniini","outbox":"https://hubzilla.example.org/outbox/kaniini","followers":"https://hubzilla.example.org/followers/kaniini","following":"https://hubzilla.example.org/following/kaniini","endpoints":{"sharedInbox":"https://hubzilla.example.org/inbox"},"publicKey":{"id":"https://hubzilla.example.org/channel/kaniini","owner":"https://hubzilla.example.org/channel/kaniini","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAvXCDkQPw+1N8B2CUd5s2\nbYvjHt+t7soMNfUiRy0qGbgW46S45k5lCq1KpbFIX3sgGZ4OWjnXVbvjCJi4kl5M\nfm5DBXzpuu05AmjVl8hqk4GejajiE/1Nq0uWHPiOSFWispUjCzzCu65V+IsiE5JU\nvcL6WEf/pYNRq7gYqyT693F7+cO5/rVv9OScx5UOxbIuU1VXYhdHCqAMDJWadC89\nhePrcD3HOQKl06W2tDxHcWk6QjrdsUQGbNOgK/QIN9gSxA+rCFEvH5O0HAhI0aXq\ncOB+vysJUFLeQOAqmAKvKS5V6RqE1GqqT0pDWHack4EmQi0gkgVzo+45xoP6wfDl\nWwG88w21LNxGvGHuN4I8mg6cEoApqKQBSOj086UtfDfSlPC1B+PRD2phE5etucHd\nF/RIWN3SxVzU9BKIiaDm2gwOpvI8QuorQb6HDtZFO5NsSN3PnMnSywPe7kXl/469\nuQRYXrseqyOVIi6WjhvXkyWVKVE5CBz+S8wXHfKph+9YOyUcJeAVMijp9wrjBlMc\noSzOGu79oM7tpMSq/Xo6ePJ/glNOwZR+OKrg92Qp9BGTKDNwGrxuxP/9KwWtGLNf\nOMTtIkxtC3ubhxL3lBxOd7l+Bmum0UJV2f8ogkCgvTpIz05jMoyU8qWl6kkWNQlY\nDropXWaOfy7Lac+G4qlfSgsCAwEAAQ==\n-----END PUBLIC KEY-----\n"},"nomadicLocations":[{"id":"https://hubzilla.example.org/locs/kaniini","type":"nomadicLocation","locationAddress":"acct:kaniini@hubzilla.example.org","locationPrimary":true,"locationDeleted":false}],"signature":{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1"],"type":"RsaSignature2017","nonce":"6b981a2f3bdcffc20252e3b131d4a4569fd2dea9fac543e5196136302f492694","creator":"https://hubzilla.example.org/channel","created":"2018-05-19T08:19:13Z","signatureValue":"ezpT4iCIUzJSeJa/Jsf4EkgbX9enWZG/0eliLXZcvkeCX9mZabaX9LMQRViP2GSlAJBHJu+UqK5LWaoWw9pYkQQHUL+43w2DeBxQicEcPqpT46j6pHuWptfwB8YHTC2/Pb56Y/jseU37j+FW8xVmcGZk4cPqJRLQNojwJlQiFOpBEd4Cel6081W12Pep578+6xBL+h92RJsWznA1gE/NV9dkCqoAoNdiORJg68sVTm0yYxPit2D/DLwXUFeBhC47EZtY3DtAOf7rADGwbquXKug/wtEI47R4p9dJvMWERSVW9O2FmDk8deUjRR3qO1iYGce8O+uMnnBHmuTcToRUHH7mxfMdqjfbcZ9DGBjKtLPSOyVPT9rENeyX8fsksmX0XhfHsNSWkmeDaU5/Au3IY75gDewiGzmzLOpRc6GUnHHro7lMpyMuo3lLZKjNVsFZbx+sXCYwORz5GAMuwIt/iCUdrsQsF5aycqfUAZrFBPguH6DVjbMUqyLvS78sDKiWqgWVhq9VDKse+WuQaJLGBDJNF9APoA6NDMjjIBZfmkGf2mV7ubIYihoOncUjahFqxU5306cNxAcdj2uNcwkgX4BCnBe/L2YsvMHhZrupzDewWWy4fxhktyoZ7VhLSl1I7fMPytjOpb9EIvng4DHGX2t+hKfon2rCGfECPavwiTM="}} -- 2.43.0 From 3e134b07fa4e382f1f4cfdbe90e74f8e73336a4e Mon Sep 17 00:00:00 2001 From: Oneric Date: Fri, 15 Mar 2024 18:57:09 -0100 Subject: [PATCH 138/149] fetcher: return final URL after redirects from get_object MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Since we reject cross-domain redirects, this doesn’t yet make a difference, but it’s requried for stricter checking subsequent commits will introduce. To make sure (and in case we ever decide to reallow cross-domain redirects) also use the final location for containment and reachability checks. --- lib/pleroma/object/fetcher.ex | 27 +++++++++++++++++++-------- test/pleroma/object/fetcher_test.exs | 27 ++++++++++++++++++++++++--- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index f256bbf77..263f9a4fa 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -259,12 +259,12 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do with {:scheme, true} <- {:scheme, String.starts_with?(id, "http")}, {_, :ok} <- {:local_fetch, Containment.contain_local_fetch(id)}, - {:ok, body} <- get_object(id), + {:ok, final_id, body} <- get_object(id), {:ok, data} <- safe_json_decode(body), - {_, :ok} <- {:containment, Containment.contain_origin_from_id(id, data)}, - {_, :ok} <- {:containment, Containment.contain_origin(id, data)} do - unless Instances.reachable?(id) do - Instances.set_reachable(id) + {_, :ok} <- {:containment, Containment.contain_origin_from_id(final_id, data)}, + {_, :ok} <- {:containment, Containment.contain_origin(final_id, data)} do + unless Instances.reachable?(final_id) do + Instances.set_reachable(final_id) end {:ok, data} @@ -305,6 +305,15 @@ defp check_crossdomain_redirect(final_host, original_url) do {:cross_domain_redirect, final_host != URI.parse(original_url).host} end + if @mix_env == :test do + defp get_final_id(nil, initial_url), do: initial_url + defp get_final_id("", initial_url), do: initial_url + end + + defp get_final_id(final_url, _intial_url) do + final_url + end + @doc "Do NOT use; only public for use in tests" def get_object(id) do date = Pleroma.Signature.signed_date() @@ -325,16 +334,18 @@ def get_object(id) do {:has_content_type, List.keyfind(headers, "content-type", 0)}, {:parse_content_type, {:ok, "application", subtype, type_params}} <- {:parse_content_type, Plug.Conn.Utils.media_type(content_type)} do + final_id = get_final_id(final_url, id) + case {subtype, type_params} do {"activity+json", _} -> - {:ok, body} + {:ok, final_id, body} {"ld+json", %{"profile" => "https://www.w3.org/ns/activitystreams"}} -> - {:ok, body} + {:ok, final_id, body} # pixelfed sometimes (and only sometimes) responds with http instead of https {"ld+json", %{"profile" => "http://www.w3.org/ns/activitystreams"}} -> - {:ok, body} + {:ok, final_id, body} _ -> {:error, {:content_type, content_type}} diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs index d1f3c070e..a761578f9 100644 --- a/test/pleroma/object/fetcher_test.exs +++ b/test/pleroma/object/fetcher_test.exs @@ -693,12 +693,13 @@ test "should return ok if the content type is application/activity+json" do } -> %Tesla.Env{ status: 200, + url: "https://mastodon.social/2", headers: [{"content-type", "application/activity+json"}], body: "{}" } end) - assert {:ok, "{}"} = Fetcher.get_object("https://mastodon.social/2") + assert {:ok, _, "{}"} = Fetcher.get_object("https://mastodon.social/2") end test "should return ok if the content type is application/ld+json with a profile" do @@ -709,6 +710,7 @@ test "should return ok if the content type is application/ld+json with a profile } -> %Tesla.Env{ status: 200, + url: "https://mastodon.social/2", headers: [ {"content-type", "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""} @@ -717,7 +719,7 @@ test "should return ok if the content type is application/ld+json with a profile } end) - assert {:ok, "{}"} = Fetcher.get_object("https://mastodon.social/2") + assert {:ok, _, "{}"} = Fetcher.get_object("https://mastodon.social/2") Tesla.Mock.mock(fn %{ @@ -734,7 +736,7 @@ test "should return ok if the content type is application/ld+json with a profile } end) - assert {:ok, "{}"} = Fetcher.get_object("https://mastodon.social/2") + assert {:ok, _, "{}"} = Fetcher.get_object("https://mastodon.social/2") end test "should not return ok with other content types" do @@ -745,6 +747,7 @@ test "should not return ok with other content types" do } -> %Tesla.Env{ status: 200, + url: "https://mastodon.social/2", headers: [{"content-type", "application/json"}], body: "{}" } @@ -753,5 +756,23 @@ test "should not return ok with other content types" do assert {:error, {:content_type, "application/json"}} = Fetcher.get_object("https://mastodon.social/2") end + + test "returns the url after redirects" do + Tesla.Mock.mock(fn + %{ + method: :get, + url: "https://mastodon.social/5" + } -> + %Tesla.Env{ + status: 200, + url: "https://mastodon.social/7", + headers: [{"content-type", "application/activity+json"}], + body: "{}" + } + end) + + assert {:ok, "https://mastodon.social/7", "{}"} = + Fetcher.get_object("https://mastodon.social/5") + end end end -- 2.43.0 From 9061d148bedb2685c460b098bebae13b05587f84 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 24 Mar 2024 17:32:28 -0100 Subject: [PATCH 139/149] =?UTF-8?q?Ensure=20object=20id=20doesn=E2=80=99t?= =?UTF-8?q?=20change=20on=20refetch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pleroma/object/fetcher.ex | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 263f9a4fa..e51262de4 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -117,10 +117,12 @@ defp reinject_object(%Object{} = object, new_data) do def refetch_object(%Object{data: %{"id" => id}} = object) do with {:local, false} <- {:local, Object.local?(object)}, {:ok, new_data} <- fetch_and_contain_remote_object_from_id(id), + {:id, true} <- {:id, new_data["id"] == id}, {:ok, object} <- reinject_object(object, new_data) do {:ok, object} else {:local, true} -> {:ok, object} + {:id, false} -> {:error, "Object id changed on refetch"} e -> {:error, e} end end -- 2.43.0 From 48b3a357930b75bf2dcbe74b63a103172c7508e1 Mon Sep 17 00:00:00 2001 From: Oneric Date: Wed, 20 Mar 2024 19:03:39 -0100 Subject: [PATCH 140/149] Update user reference after fetch Since we always followed redirects (and until recently allowed fuzzy id matches), the ap_id of the received object might differ from the iniital fetch url. This lead to us mistakenly trying to insert a new user with the same nickname, ap_id, etc as an existing user (which will fail due to uniqueness constraints) instead of updating the existing one. --- lib/pleroma/web/activity_pub/activity_pub.ex | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index deca69613..1e06bc809 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1840,6 +1840,13 @@ def make_user_from_ap_id(ap_id, additional \\ []) do with {:ok, data} <- fetch_and_prepare_user_from_ap_id(ap_id, additional) do {:ok, _pid} = Task.start(fn -> pinned_fetch_task(data) end) + user = + if data.ap_id != ap_id do + User.get_cached_by_ap_id(data.ap_id) + else + user + end + if user do user |> User.remote_user_changeset(data) -- 2.43.0 From 8684964c5d03f6c70f73730b3f1ad26784ffb004 Mon Sep 17 00:00:00 2001 From: Oneric Date: Fri, 15 Mar 2024 23:00:19 -0100 Subject: [PATCH 141/149] Only allow exact id matches MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This protects us from falling for obvious spoofs as from the current upload exploit (unfortunately we can’t reasonably do anything about spoofs with exact matches as was possible via emoji and proxy). Such objects being invalid is supported by the spec, sepcifically sections 3.1 and 3.2: https://www.w3.org/TR/activitypub/#obj-id Anonymous objects are not relevant here (they can only exists within parent objects iiuc) and neither is client-to-server or transient objects (as those cannot be fetched in the first place). This leaves us with the requirement for `id` to (a) exist and (b) be a publicly dereferencable URI from the originating server. This alone does not yet demand strict equivalence, but the spec then further explains objects ought to be fetchable _via their ID_. Meaning an object not retrievable via its ID, is invalid. This reading is supported by the fact, e.g. GoToSocial (recently) and Mastodon (for 6+ years) do already implement such strict ID checks, additionally proving this doesn’t cause federation issues in practice. However, apart from canonical IDs there can also be additional display URLs. *omas first redirect those to their canonical location, but *keys and Mastodon directly serve the AP representation without redirects. Mastodon and GTS deal with this in two different ways, but both constitute an effective countermeasure: - Mastodon: Unless it already is a known AP id, two fetches occur. The first fetch just reads the `id` property and then refetches from the id. The last fetch requires the returned id to exactly match the URL the content was fetched from. (This can be optimised by skipping the second fetch if it already matches) https://github.com/mastodon/mastodon/blob/05eda8d19330a9c27c0cf07de19a87edff269057/app/helpers/jsonld_helper.rb#L168 https://github.com/mastodon/mastodon/commit/63f097979990bf5ba9db848b8a253056bad781af - GTS: Only does a single fetch and then checks if _either_ the id _or_ url property (which can be an object) match the original fetch URL. This relies on implementations always including their display URL as "url" if differing from the id. For actors this is true for all investigated implementations, for posts only Mastodon includes an "url", but it is also the only one with a differing display URL. https://github.com/superseriousbusiness/gotosocial/commit/2bafd7daf542d985ee76d9079a30a602cb7be827#diff-943bbb02c8ac74ac5dc5d20807e561dcdfaebdc3b62b10730f643a20ac23c24fR222 Albeit Mastodon’s refetch offers higher compatibility with theoretical implmentations using either multiple different display URL or not denoting any of them as "url" at all, for now we chose to adopt a GTS-like refetch-free approach to avoid additional implementation concerns wrt to whether redirects should be allowed when fetching a canonical AP id and potential for accidentally loosening some checks (e.g. cross-domain refetches) for one of the fetches. This may be reconsidered in the future. --- lib/pleroma/object/containment.ex | 35 ++++++++++++ lib/pleroma/object/fetcher.ex | 5 +- .../https__info.pleroma.site_activity3.json | 2 +- .../tesla_mock/relay@mastdon.example.org.json | 6 +- .../collections/collections_fetcher_test.exs | 7 ++- test/pleroma/object/containment_test.exs | 50 +++++++++++++++++ test/pleroma/object/fetcher_test.exs | 55 ++++++++++++++++++- .../web/activity_pub/activity_pub_test.exs | 8 +-- test/support/http_request_mock.ex | 18 ++++++ 9 files changed, 174 insertions(+), 12 deletions(-) diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex index a312f69e8..37bc20e4d 100644 --- a/lib/pleroma/object/containment.ex +++ b/lib/pleroma/object/containment.ex @@ -11,6 +11,9 @@ defmodule Pleroma.Object.Containment do Object containment is an important step in validating remote objects to prevent spoofing, therefore removal of object containment functions is NOT recommended. """ + + alias Pleroma.Web.ActivityPub.Transmogrifier + def get_actor(%{"actor" => actor}) when is_binary(actor) do actor end @@ -47,6 +50,18 @@ def get_object(_) do defp compare_uris(%URI{host: host} = _id_uri, %URI{host: host} = _other_uri), do: :ok defp compare_uris(_id_uri, _other_uri), do: :error + defp compare_uris_exact(uri, uri), do: :ok + + defp compare_uris_exact(%URI{} = id, %URI{} = other), + do: compare_uris_exact(URI.to_string(id), URI.to_string(other)) + + defp compare_uris_exact(id_uri, other_uri) + when is_binary(id_uri) and is_binary(other_uri) do + norm_id = String.replace_suffix(id_uri, "/", "") + norm_other = String.replace_suffix(other_uri, "/", "") + if norm_id == norm_other, do: :ok, else: :error + end + @doc """ Checks whether an URL to fetch from is from the local server. @@ -77,6 +92,26 @@ def contain_origin(id, %{"attributedTo" => actor} = params), def contain_origin(_id, _data), do: :ok + @doc """ + Check whether the fetch URL (after redirects) exactly (sans tralining slash) matches either + the canonical ActivityPub id or the objects url field (for display URLs from *key and Mastodon) + + Since this is meant to be used for fetches, anonymous or transient objects are not accepted here. + """ + def contain_id_to_fetch(url, %{"id" => id} = data) when is_binary(id) do + with {:id, :error} <- {:id, compare_uris_exact(id, url)}, + # "url" can be a "Link" object and this is checked before full normalisation + display_url <- Transmogrifier.fix_url(data)["url"], + true <- display_url != nil do + compare_uris_exact(display_url, url) + else + {:id, :ok} -> :ok + _ -> :error + end + end + + def contain_id_to_fetch(_url, _data), do: :error + @doc """ Check whether the object id is from the same host as another id """ diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index e51262de4..618fb278e 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -263,7 +263,7 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do {_, :ok} <- {:local_fetch, Containment.contain_local_fetch(id)}, {:ok, final_id, body} <- get_object(id), {:ok, data} <- safe_json_decode(body), - {_, :ok} <- {:containment, Containment.contain_origin_from_id(final_id, data)}, + {_, :ok} <- {:strict_id, Containment.contain_id_to_fetch(final_id, data)}, {_, :ok} <- {:containment, Containment.contain_origin(final_id, data)} do unless Instances.reachable?(final_id) do Instances.set_reachable(final_id) @@ -271,6 +271,9 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do {:ok, data} else + {:strict_id, _} -> + {:error, "Object's ActivityPub id/url does not match final fetch URL"} + {:scheme, _} -> {:error, "Unsupported URI scheme"} diff --git a/test/fixtures/tesla_mock/https__info.pleroma.site_activity3.json b/test/fixtures/tesla_mock/https__info.pleroma.site_activity3.json index 1df73f2c5..dbf74dfe1 100644 --- a/test/fixtures/tesla_mock/https__info.pleroma.site_activity3.json +++ b/test/fixtures/tesla_mock/https__info.pleroma.site_activity3.json @@ -3,7 +3,7 @@ "attributedTo": "http://mastodon.example.org/users/admin", "attachment": [], "content": "

    this post was not actually written by Haelwenn

    ", - "id": "https://info.pleroma.site/activity2.json", + "id": "https://info.pleroma.site/activity3.json", "published": "2018-09-01T22:15:00Z", "tag": [], "to": [ diff --git a/test/fixtures/tesla_mock/relay@mastdon.example.org.json b/test/fixtures/tesla_mock/relay@mastdon.example.org.json index c1fab7d3b..21dd405c8 100644 --- a/test/fixtures/tesla_mock/relay@mastdon.example.org.json +++ b/test/fixtures/tesla_mock/relay@mastdon.example.org.json @@ -11,7 +11,7 @@ "toot": "http://joinmastodon.org/ns#", "Emoji": "toot:Emoji" }], - "id": "http://mastodon.example.org/users/admin", + "id": "http://mastodon.example.org/users/relay", "type": "Application", "invisible": true, "following": "http://mastodon.example.org/users/admin/following", @@ -24,8 +24,8 @@ "url": "http://mastodon.example.org/@admin", "manuallyApprovesFollowers": false, "publicKey": { - "id": "http://mastodon.example.org/users/admin#main-key", - "owner": "http://mastodon.example.org/users/admin", + "id": "http://mastodon.example.org/users/relay#main-key", + "owner": "http://mastodon.example.org/users/relay", "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtc4Tir+3ADhSNF6VKrtW\nOU32T01w7V0yshmQei38YyiVwVvFu8XOP6ACchkdxbJ+C9mZud8qWaRJKVbFTMUG\nNX4+6Q+FobyuKrwN7CEwhDALZtaN2IPbaPd6uG1B7QhWorrY+yFa8f2TBM3BxnUy\nI4T+bMIZIEYG7KtljCBoQXuTQmGtuffO0UwJksidg2ffCF5Q+K//JfQagJ3UzrR+\nZXbKMJdAw4bCVJYs4Z5EhHYBwQWiXCyMGTd7BGlmMkY6Av7ZqHKC/owp3/0EWDNz\nNqF09Wcpr3y3e8nA10X40MJqp/wR+1xtxp+YGbq/Cj5hZGBG7etFOmIpVBrDOhry\nBwIDAQAB\n-----END PUBLIC KEY-----\n" }, "attachment": [{ diff --git a/test/pleroma/collections/collections_fetcher_test.exs b/test/pleroma/collections/collections_fetcher_test.exs index 7a582a3d7..ff1aa84db 100644 --- a/test/pleroma/collections/collections_fetcher_test.exs +++ b/test/pleroma/collections/collections_fetcher_test.exs @@ -12,11 +12,14 @@ defmodule Akkoma.Collections.FetcherTest do end test "it should extract items from an embedded array in a Collection" do + ap_id = "https://example.com/collection/ordered_array" + unordered_collection = "test/fixtures/collections/unordered_array.json" |> File.read!() - - ap_id = "https://example.com/collection/ordered_array" + |> Jason.decode!() + |> Map.put("id", ap_id) + |> Jason.encode!(pretty: true) Tesla.Mock.mock(fn %{ diff --git a/test/pleroma/object/containment_test.exs b/test/pleroma/object/containment_test.exs index ad1660069..f8f40a3ac 100644 --- a/test/pleroma/object/containment_test.exs +++ b/test/pleroma/object/containment_test.exs @@ -105,6 +105,56 @@ test "contain_origin_from_id() allows matching IDs" do ) end + test "contain_id_to_fetch() refuses alternate IDs within the same origin domain" do + data = %{ + "id" => "http://example.com/~alyssa/activities/1234.json", + "url" => "http://example.com/@alyssa/status/1234" + } + + :error = + Containment.contain_id_to_fetch( + "http://example.com/~alyssa/activities/1234", + data + ) + end + + test "contain_id_to_fetch() allows matching IDs" do + data = %{ + "id" => "http://example.com/~alyssa/activities/1234.json/" + } + + :ok = + Containment.contain_id_to_fetch( + "http://example.com/~alyssa/activities/1234.json/", + data + ) + + :ok = + Containment.contain_id_to_fetch( + "http://example.com/~alyssa/activities/1234.json", + data + ) + end + + test "contain_id_to_fetch() allows display URLs" do + data = %{ + "id" => "http://example.com/~alyssa/activities/1234.json", + "url" => "http://example.com/@alyssa/status/1234" + } + + :ok = + Containment.contain_id_to_fetch( + "http://example.com/@alyssa/status/1234", + data + ) + + :ok = + Containment.contain_id_to_fetch( + "http://example.com/@alyssa/status/1234/", + data + ) + end + test "users cannot be collided through fake direction spoofing attempts" do _user = insert(:user, %{ diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs index a761578f9..c59d77f0f 100644 --- a/test/pleroma/object/fetcher_test.exs +++ b/test/pleroma/object/fetcher_test.exs @@ -57,6 +57,46 @@ defp spoofed_object_with_ids( body: spoofed_object_with_ids("https://patch.cx/objects/spoof_content_type") } + # Spoof: mismatching ids + # Variant 1: Non-exisitng fake id + %{ + method: :get, + url: + "https://patch.cx/media/03ca3c8b4ac3ddd08bf0f84be7885f2f88de0f709112131a22d83650819e36c2.json" + } -> + %Tesla.Env{ + status: 200, + url: + "https://patch.cx/media/03ca3c8b4ac3ddd08bf0f84be7885f2f88de0f709112131a22d83650819e36c2.json", + headers: [{"content-type", "application/activity+json"}], + body: spoofed_object_with_ids() + } + + %{method: :get, url: "https://patch.cx/objects/spoof"} -> + %Tesla.Env{ + status: 404, + url: "https://patch.cx/objects/spoof", + headers: [], + body: "Not found" + } + + # Varaint 2: two-stage payload + %{method: :get, url: "https://patch.cx/media/spoof_stage1.json"} -> + %Tesla.Env{ + status: 200, + url: "https://patch.cx/media/spoof_stage1.json", + headers: [{"content-type", "application/activity+json"}], + body: spoofed_object_with_ids("https://patch.cx/media/spoof_stage2.json") + } + + %{method: :get, url: "https://patch.cx/media/spoof_stage2.json"} -> + %Tesla.Env{ + status: 200, + url: "https://patch.cx/media/spoof_stage2.json", + headers: [{"content-type", "application/activity+json"}], + body: spoofed_object_with_ids("https://patch.cx/media/unpredictable.json") + } + # Spoof: cross-domain redirect with original domain id %{method: :get, url: "https://patch.cx/objects/spoof_media_redirect1"} -> %Tesla.Env{ @@ -79,7 +119,7 @@ defp spoofed_object_with_ids( %{method: :get, url: "https://patch.cx/objects/spoof_redirect"} -> %Tesla.Env{ status: 200, - url: "https://patch.cx/objects/spoof", + url: "https://patch.cx/objects/spoof_redirect", headers: [{"content-type", "application/activity+json"}], body: spoofed_object_with_ids("https://patch.cx/objects/spoof_redirect") } @@ -110,6 +150,7 @@ defp spoofed_object_with_ids( %{method: :get, url: "https://social.sakamoto.gq/notice/9wTkLEnuq47B25EehM"} -> %Tesla.Env{ status: 200, + url: "https://social.sakamoto.gq/objects/f20f2497-66d9-4a52-a2e1-1be2a39c32c1", body: File.read!("test/fixtures/fetch_mocks/9wTkLEnuq47B25EehM.json"), headers: HttpRequestMock.activitypub_object_headers() } @@ -208,6 +249,18 @@ test "it does not fetch a spoofed object with wrong content type" do ) end + test "it does not fetch a spoofed object with id different from URL" do + assert {:error, "Object's ActivityPub id/url does not match final fetch URL"} = + Fetcher.fetch_and_contain_remote_object_from_id( + "https://patch.cx/media/03ca3c8b4ac3ddd08bf0f84be7885f2f88de0f709112131a22d83650819e36c2.json" + ) + + assert {:error, "Object's ActivityPub id/url does not match final fetch URL"} = + Fetcher.fetch_and_contain_remote_object_from_id( + "https://patch.cx/media/spoof_stage1.json" + ) + end + test "it does not fetch an object via cross-domain redirects (initial id)" do assert {:error, {:cross_domain_redirect, true}} = Fetcher.fetch_and_contain_remote_object_from_id( diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index c6543ec83..5ad6d4716 100644 --- a/test/pleroma/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -312,7 +312,7 @@ test "fetches user featured collection" do end test "fetches user featured collection using the first property" do - featured_url = "https://friendica.example.com/raha/collections/featured" + featured_url = "https://friendica.example.com/featured/raha" first_url = "https://friendica.example.com/featured/raha?page=1" featured_data = @@ -350,7 +350,7 @@ test "fetches user featured collection using the first property" do end test "fetches user featured when it has string IDs" do - featured_url = "https://example.com/alisaie/collections/featured" + featured_url = "https://example.com/users/alisaie/collections/featured" dead_url = "https://example.com/users/alisaie/statuses/108311386746229284" featured_data = @@ -1720,7 +1720,7 @@ test "doesn't crash when follower and following counters are hidden" do json( %{ "@context" => "https://www.w3.org/ns/activitystreams", - "id" => "http://remote.org/users/masto_hidden_counters/followers" + "id" => "http://remote.org/users/masto_hidden_counters/following" }, headers: HttpRequestMock.activitypub_object_headers() ) @@ -1732,7 +1732,7 @@ test "doesn't crash when follower and following counters are hidden" do json( %{ "@context" => "https://www.w3.org/ns/activitystreams", - "id" => "http://remote.org/users/masto_hidden_counters/following" + "id" => "http://remote.org/users/masto_hidden_counters/followers" }, headers: HttpRequestMock.activitypub_object_headers() ) diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index e831f43f7..cc0e22af1 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -572,6 +572,7 @@ def get("https://social.stopwatchingus-heidelberg.de/.well-known/host-meta", _, }} end + # Mastodon status via display URL def get( "http://mastodon.example.org/@admin/99541947525187367", _, @@ -581,6 +582,23 @@ def get( {:ok, %Tesla.Env{ status: 200, + url: "http://mastodon.example.org/@admin/99541947525187367", + body: File.read!("test/fixtures/mastodon-note-object.json"), + headers: activitypub_object_headers() + }} + end + + # same status via its canonical ActivityPub id + def get( + "http://mastodon.example.org/users/admin/statuses/99541947525187367", + _, + _, + _ + ) do + {:ok, + %Tesla.Env{ + status: 200, + url: "http://mastodon.example.org/users/admin/statuses/99541947525187367", body: File.read!("test/fixtures/mastodon-note-object.json"), headers: activitypub_object_headers() }} -- 2.43.0 From 61ec592d668956aeb075585391414284016ecba9 Mon Sep 17 00:00:00 2001 From: Oneric Date: Tue, 26 Mar 2024 15:11:06 -0100 Subject: [PATCH 142/149] Drop obsolete pixelfed workaround This pixelfed issue was fixed in 2022-12 in https://github.com/pixelfed/pixelfed/pull/3932 Co-authored-by: FloatingGhost --- lib/pleroma/object/fetcher.ex | 4 ---- test/pleroma/object/fetcher_test.exs | 17 ----------------- 2 files changed, 21 deletions(-) diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 618fb278e..6609b8c1a 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -348,10 +348,6 @@ def get_object(id) do {"ld+json", %{"profile" => "https://www.w3.org/ns/activitystreams"}} -> {:ok, final_id, body} - # pixelfed sometimes (and only sometimes) responds with http instead of https - {"ld+json", %{"profile" => "http://www.w3.org/ns/activitystreams"}} -> - {:ok, final_id, body} - _ -> {:error, {:content_type, content_type}} end diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs index c59d77f0f..4c4831af3 100644 --- a/test/pleroma/object/fetcher_test.exs +++ b/test/pleroma/object/fetcher_test.exs @@ -773,23 +773,6 @@ test "should return ok if the content type is application/ld+json with a profile end) assert {:ok, _, "{}"} = Fetcher.get_object("https://mastodon.social/2") - - Tesla.Mock.mock(fn - %{ - method: :get, - url: "https://mastodon.social/2" - } -> - %Tesla.Env{ - status: 200, - headers: [ - {"content-type", - "application/ld+json; profile=\"http://www.w3.org/ns/activitystreams\""} - ], - body: "{}" - } - end) - - assert {:ok, _, "{}"} = Fetcher.get_object("https://mastodon.social/2") end test "should not return ok with other content types" do -- 2.43.0 From 31f90bbb52db472f45d38aa90fc1bae43b65d7ff Mon Sep 17 00:00:00 2001 From: Oneric Date: Tue, 26 Mar 2024 15:44:44 -0100 Subject: [PATCH 143/149] Register APNG MIME type MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The newest git HEAD of MIME already knows about APNG, but this hasn’t been released yet. Without this, APNG attachments from remote posts won’t display as images in frontends. Fixes: akkoma#657 --- config/config.exs | 5 ++++- .../attachment_validator_test.exs | 17 +++++++++++++++++ 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/config/config.exs b/config/config.exs index 85a84208c..e0a5eccb1 100644 --- a/config/config.exs +++ b/config/config.exs @@ -167,7 +167,10 @@ "application/xrd+xml" => ["xrd+xml"], "application/jrd+json" => ["jrd+json"], "application/activity+json" => ["activity+json"], - "application/ld+json" => ["activity+json"] + "application/ld+json" => ["activity+json"], + # Can be removed when bumping MIME past 2.0.5 + # see https://akkoma.dev/AkkomaGang/akkoma/issues/657 + "image/apng" => ["apng"] } config :mime, :extensions, %{ diff --git a/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs b/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs index 9150b8d41..f8dec09d3 100644 --- a/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/attachment_validator_test.exs @@ -11,6 +11,23 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidatorTest do import Pleroma.Factory describe "attachments" do + test "works with apng" do + attachment = + %{ + "mediaType" => "image/apng", + "name" => "", + "type" => "Document", + "url" => + "https://media.misskeyusercontent.com/io/2859c26e-cd43-4550-848b-b6243bc3fe28.apng" + } + + assert {:ok, attachment} = + AttachmentValidator.cast_and_validate(attachment) + |> Ecto.Changeset.apply_action(:insert) + + assert attachment.mediaType == "image/apng" + end + test "works with honkerific attachments" do attachment = %{ "mediaType" => "", -- 2.43.0 From d4411012007333821e254a7487d75bc295d78ed0 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 17 Mar 2024 15:29:23 -0100 Subject: [PATCH 144/149] Add mix task to detect uploaded spoof payloads --- .../docs/administration/CLI_tasks/security.md | 32 +++ lib/mix/tasks/pleroma/security.ex | 209 ++++++++++++++++++ 2 files changed, 241 insertions(+) create mode 100644 docs/docs/administration/CLI_tasks/security.md create mode 100644 lib/mix/tasks/pleroma/security.ex diff --git a/docs/docs/administration/CLI_tasks/security.md b/docs/docs/administration/CLI_tasks/security.md new file mode 100644 index 000000000..99b84264c --- /dev/null +++ b/docs/docs/administration/CLI_tasks/security.md @@ -0,0 +1,32 @@ +# Security-related tasks + +{! administration/CLI_tasks/general_cli_task_info.include !} + +!!! danger + Many of these tasks were written in response to a patched exploit. + It is recommended to run those very soon after installing its respective security update. + Over time with db migrations they might become less accurate or be removed altogether. + If you never ran an affected version, there’s no point in running them. + +## Spoofed AcitivityPub objects exploit (2024-03, fixed in 3.11.1) + +### Search for uploaded spoofing payloads + +Scans local uploads for spoofing payloads. +If the instance is not using the local uploader it was not affected. +Attachments wil be scanned anyway in case local uploader was used in the past. + +!!! note + This cannot reliably detect payloads attached to deleted posts. + +=== "OTP" + + ```sh + ./bin/pleroma_ctl security spoof-uploaded + ``` + +=== "From Source" + + ```sh + mix pleroma.security spoof-uploaded + ``` diff --git a/lib/mix/tasks/pleroma/security.ex b/lib/mix/tasks/pleroma/security.ex new file mode 100644 index 000000000..354f227bd --- /dev/null +++ b/lib/mix/tasks/pleroma/security.ex @@ -0,0 +1,209 @@ +# Akkoma: Magically expressive social media +# Copyright © 2024 Akkoma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Mix.Tasks.Pleroma.Security do + use Mix.Task + import Mix.Pleroma + + alias Pleroma.Config + + require Logger + + @shortdoc """ + Security-related tasks, like e.g. checking for signs past exploits were abused. + """ + + # Constants etc + defp local_id_prefix(), do: Pleroma.Web.Endpoint.url() <> "/" + + defp local_id_pattern(), do: local_id_prefix() <> "%" + + @activity_exts ["activity+json", "activity%2Bjson"] + + defp activity_ext_url_patterns() do + for e <- @activity_exts do + for suf <- ["", "?%"] do + # Escape literal % for use in SQL patterns + ee = String.replace(e, "%", "\\%") + "%.#{ee}#{suf}" + end + end + |> List.flatten() + end + + # Search for malicious uploads exploiting the lack of Content-Type sanitisation from before 2024-03 + def run(["spoof-uploaded"]) do + Logger.put_process_level(self(), :notice) + start_pleroma() + + IO.puts(""" + +------------------------+ + | SPOOF SEARCH UPLOADS | + +------------------------+ + Checking if any uploads are using privileged types. + NOTE if attachment deletion is enabled, payloads used + in the past may no longer exist. + """) + + do_spoof_uploaded() + end + + # +-----------------------------+ + # | S P O O F - U P L O A D E D | + # +-----------------------------+ + defp do_spoof_uploaded() do + files = + case Config.get!([Pleroma.Upload, :uploader]) do + Pleroma.Uploaders.Local -> + uploads_search_spoofs_local_dir(Config.get!([Pleroma.Uploaders.Local, :uploads])) + + _ -> + IO.puts(""" + NOTE: + Not using local uploader; thus not affected by this exploit. + It's impossible to check for files, but in case local uploader was used before + or to check if anyone futilely attempted a spoof, notes will still be scanned. + """) + + [] + end + + emoji = uploads_search_spoofs_local_dir(Config.get!([:instance, :static_dir])) + + post_attachs = uploads_search_spoofs_notes() + + not_orphaned_urls = + post_attachs + |> Enum.map(fn {_u, _a, url} -> url end) + |> MapSet.new() + + orphaned_attachs = upload_search_orphaned_attachments(not_orphaned_urls) + + IO.puts("\nSearch concluded; here are the results:") + pretty_print_list_with_title(emoji, "Emoji") + pretty_print_list_with_title(files, "Uploaded Files") + pretty_print_list_with_title(post_attachs, "(Not Deleted) Post Attachments") + pretty_print_list_with_title(orphaned_attachs, "Orphaned Uploads") + + IO.puts(""" + In total found + #{length(emoji)} emoji + #{length(files)} uploads + #{length(post_attachs)} not deleted posts + #{length(orphaned_attachs)} orphaned attachments + """) + end + + defp uploads_search_spoofs_local_dir(dir) do + local_dir = String.replace_suffix(dir, "/", "") + + IO.puts("Searching for suspicious files in #{local_dir}...") + + glob_ext = "{" <> Enum.join(@activity_exts, ",") <> "}" + + Path.wildcard(local_dir <> "/**/*." <> glob_ext, match_dot: true) + |> Enum.map(fn path -> + String.replace_prefix(path, local_dir <> "/", "") + end) + |> Enum.sort() + end + + defp uploads_search_spoofs_notes() do + IO.puts("Now querying DB for posts with spoofing attachments. This might take a while...") + + patterns = [local_id_pattern() | activity_ext_url_patterns()] + + # if jsonb_array_elemsts in FROM can be used with normal Ecto functions, idk how + """ + SELECT DISTINCT a.data->>'actor', a.id, url->>'href' + FROM public.objects AS o JOIN public.activities AS a + ON o.data->>'id' = a.data->>'object', + jsonb_array_elements(o.data->'attachment') AS attachs, + jsonb_array_elements(attachs->'url') AS url + WHERE o.data->>'type' = 'Note' AND + o.data->>'id' LIKE $1::text AND ( + url->>'href' LIKE $2::text OR + url->>'href' LIKE $3::text OR + url->>'href' LIKE $4::text OR + url->>'href' LIKE $5::text + ) + ORDER BY a.data->>'actor', a.id, url->>'href'; + """ + |> Pleroma.Repo.query!(patterns, timeout: :infinity) + |> map_raw_id_apid_tuple() + end + + defp upload_search_orphaned_attachments(not_orphaned_urls) do + IO.puts(""" + Now querying DB for orphaned spoofing attachment (i.e. their post was deleted, + but if :cleanup_attachments was not enabled traces remain in the database) + This might take a bit... + """) + + patterns = activity_ext_url_patterns() + + """ + SELECT DISTINCT attach.id, url->>'href' + FROM public.objects AS attach, + jsonb_array_elements(attach.data->'url') AS url + WHERE (attach.data->>'type' = 'Image' OR + attach.data->>'type' = 'Document') + AND ( + url->>'href' LIKE $1::text OR + url->>'href' LIKE $2::text OR + url->>'href' LIKE $3::text OR + url->>'href' LIKE $4::text + ) + ORDER BY attach.id, url->>'href'; + """ + |> Pleroma.Repo.query!(patterns, timeout: :infinity) + |> then(fn res -> Enum.map(res.rows, fn [id, url] -> {id, url} end) end) + |> Enum.filter(fn {_, url} -> !(url in not_orphaned_urls) end) + end + + # +-----------------------------------+ + # | module-specific utility functions | + # +-----------------------------------+ + defp pretty_print_list_with_title(list, title) do + title_len = String.length(title) + title_underline = String.duplicate("=", title_len) + IO.puts(title) + IO.puts(title_underline) + pretty_print_list(list) + end + + defp pretty_print_list([]), do: IO.puts("") + + defp pretty_print_list([{a, o} | rest]) + when (is_binary(a) or is_number(a)) and is_binary(o) do + IO.puts(" {#{a}, #{o}}") + pretty_print_list(rest) + end + + defp pretty_print_list([{u, a, o} | rest]) + when is_binary(a) and is_binary(u) and is_binary(o) do + IO.puts(" {#{u}, #{a}, #{o}}") + pretty_print_list(rest) + end + + defp pretty_print_list([e | rest]) when is_binary(e) do + IO.puts(" #{e}") + pretty_print_list(rest) + end + + defp pretty_print_list([e | rest]), do: pretty_print_list([inspect(e) | rest]) + + defp map_raw_id_apid_tuple(res) do + user_prefix = local_id_prefix() <> "users/" + + Enum.map(res.rows, fn + [uid, aid, oid] -> + { + String.replace_prefix(uid, user_prefix, ""), + FlakeId.to_string(aid), + oid + } + end) + end +end -- 2.43.0 From 0648d9ebaa5714647a420881361668a7e3a1c7c8 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 17 Mar 2024 20:07:16 -0100 Subject: [PATCH 145/149] Add mix tasks to detect spoofed posts and users At least as far as we can --- .../docs/administration/CLI_tasks/security.md | 24 ++++ lib/mix/tasks/pleroma/security.ex | 121 ++++++++++++++++++ 2 files changed, 145 insertions(+) diff --git a/docs/docs/administration/CLI_tasks/security.md b/docs/docs/administration/CLI_tasks/security.md index 99b84264c..a0208c4e5 100644 --- a/docs/docs/administration/CLI_tasks/security.md +++ b/docs/docs/administration/CLI_tasks/security.md @@ -30,3 +30,27 @@ Attachments wil be scanned anyway in case local uploader was used in the past. ```sh mix pleroma.security spoof-uploaded ``` + +### Search for counterfeit posts in database + +Scans all notes in the database for signs of being spoofed. + +!!! note + Spoofs targeting local accounts can be detected rather reliably + (with some restrictions documented in the task’s logs). + Counterfeit posts from remote users cannot. A best-effort attempt is made, but + a thorough attacker can avoid this and it may yield a small amount of false positives. + + Should you find counterfeit posts of local users, let other admins know so they can delete the too. + +=== "OTP" + + ```sh + ./bin/pleroma_ctl security spoof-inserted + ``` + +=== "From Source" + + ```sh + mix pleroma.security spoof-inserted + ``` diff --git a/lib/mix/tasks/pleroma/security.ex b/lib/mix/tasks/pleroma/security.ex index 354f227bd..dc1f667d7 100644 --- a/lib/mix/tasks/pleroma/security.ex +++ b/lib/mix/tasks/pleroma/security.ex @@ -4,6 +4,7 @@ defmodule Mix.Tasks.Pleroma.Security do use Mix.Task + import Ecto.Query import Mix.Pleroma alias Pleroma.Config @@ -49,6 +50,23 @@ def run(["spoof-uploaded"]) do do_spoof_uploaded() end + # Fuzzy search for potentially counterfeit activities in the database resulting from the same exploit + def run(["spoof-inserted"]) do + Logger.put_process_level(self(), :notice) + start_pleroma() + + IO.puts(""" + +----------------------+ + | SPOOF SEARCH NOTES | + +----------------------+ + Starting fuzzy search for counterfeit activities. + NOTE this can not guarantee detecting all counterfeits + and may yield a small percentage of false positives. + """) + + do_spoof_inserted() + end + # +-----------------------------+ # | S P O O F - U P L O A D E D | # +-----------------------------+ @@ -162,6 +180,109 @@ defp upload_search_orphaned_attachments(not_orphaned_urls) do |> Enum.filter(fn {_, url} -> !(url in not_orphaned_urls) end) end + # +-----------------------------+ + # | S P O O F - I N S E R T E D | + # +-----------------------------+ + defp do_spoof_inserted() do + IO.puts(""" + Searching for local posts whose Create activity has no ActivityPub id... + This is a pretty good indicator, but only for spoofs of local actors + and only if the spoofing happened after around late 2021. + """) + + idless_create = + search_local_notes_without_create_id() + |> Enum.sort() + + IO.puts("Done.\n") + + IO.puts(""" + Now trying to weed out other poorly hidden spoofs. + This can't detect all and may have some false positives. + """) + + likely_spoofed_posts_set = MapSet.new(idless_create) + + sus_pattern_posts = + search_sus_notes_by_id_patterns() + |> Enum.filter(fn r -> !(r in likely_spoofed_posts_set) end) + + IO.puts("Done.\n") + + IO.puts(""" + Finally, searching for spoofed, local user accounts. + (It's impossible to detect spoofed remote users) + """) + + spoofed_users = search_bogus_local_users() + + pretty_print_list_with_title(sus_pattern_posts, "Maybe Spoofed Posts") + pretty_print_list_with_title(idless_create, "Likely Spoofed Posts") + pretty_print_list_with_title(spoofed_users, "Spoofed local user accounts") + + IO.puts(""" + In total found: + #{length(spoofed_users)} bogus users + #{length(idless_create)} likely spoofed posts + #{length(sus_pattern_posts)} maybe spoofed posts + """) + end + + defp search_local_notes_without_create_id() do + Pleroma.Object + |> where([o], fragment("?->>'id' LIKE ?", o.data, ^local_id_pattern())) + |> join(:inner, [o], a in Pleroma.Activity, + on: fragment("?->>'object' = ?->>'id'", a.data, o.data) + ) + |> where([o, a], fragment("NOT (? \\? 'id') OR ?->>'id' IS NULL", a.data, a.data)) + |> select([o, a], {a.id, fragment("?->>'id'", o.data)}) + |> order_by([o, a], a.id) + |> Pleroma.Repo.all() + end + + defp search_sus_notes_by_id_patterns() do + [ep1, ep2, ep3, ep4] = activity_ext_url_patterns() + + Pleroma.Object + |> where( + [o], + # for local objects we know exactly how a genuine id looks like + # (though a thorough attacker can emulate this) + # for remote posts, use some best-effort patterns + fragment( + """ + (?->>'id' LIKE ? AND ?->>'id' NOT SIMILAR TO + ? || 'objects/[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}') + """, + o.data, + ^local_id_pattern(), + o.data, + ^local_id_prefix() + ) or + fragment("?->>'id' LIKE ?", o.data, "%/emoji/%") or + fragment("?->>'id' LIKE ?", o.data, "%/media/%") or + fragment("?->>'id' LIKE ?", o.data, "%/proxy/%") or + fragment("?->>'id' LIKE ?", o.data, ^ep1) or + fragment("?->>'id' LIKE ?", o.data, ^ep2) or + fragment("?->>'id' LIKE ?", o.data, ^ep3) or + fragment("?->>'id' LIKE ?", o.data, ^ep4) + ) + |> join(:inner, [o], a in Pleroma.Activity, + on: fragment("?->>'object' = ?->>'id'", a.data, o.data) + ) + |> select([o, a], {a.id, fragment("?->>'id'", o.data)}) + |> order_by([o, a], a.id) + |> Pleroma.Repo.all() + end + + defp search_bogus_local_users() do + Pleroma.User.Query.build(%{}) + |> where([u], u.local == false and like(u.ap_id, ^local_id_pattern())) + |> order_by([u], u.ap_id) + |> select([u], u.ap_id) + |> Pleroma.Repo.all() + end + # +-----------------------------------+ # | module-specific utility functions | # +-----------------------------------+ -- 2.43.0 From ee7d98b093651b8e1f2050cfe38533a9b6bd7b00 Mon Sep 17 00:00:00 2001 From: Oneric Date: Thu, 28 Mar 2024 20:24:02 -0100 Subject: [PATCH 146/149] Update Changelog --- CHANGELOG.md | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9130a81ae..c46e84fa8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,17 +7,37 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased ## Added +- CLI tasks best-effort checking for past abuse of the recent spoofing exploit +- new `:mrf_steal_emoji, :download_unknown_size` option; defaults to `false` ## Changed -- `Pleroma.Upload, :base_url` now MUST be configured explicitly; +- `Pleroma.Upload, :base_url` now MUST be configured explicitly if used; use of the same domain as the instance is **strongly** discouraged +- `:media_proxy, :base_url` now MUST be configured explicitly if used; + use of the same domain as the instance is **strongly** discouraged +- StealEmoji: + - now uses the pack.json format; + existing users must migrate with an out-of-band script (check release notes) + - only steals shortcodes recognised as valid + - URLs of stolen emoji is no longer predictable - The `Dedupe` upload filter is now always active; `AnonymizeFilenames` is again opt-in +- received AP data is sanity checked before we attempt to parse it as a user +- Uploads, emoji and media proxy now restrict Content-Type headers to a safe subset +- Akkoma will no longer fetch and parse objects hosted on the same domain ## Fixed - Critical security issue allowing Akkoma to be used as a vector for (depending on configuration) impersonation of other users or creation of bogus users and posts on the upload domain +- Critical security issue letting Akkoma fall for the above impersonation + payloads due to lack of strict id checking +- Critical security issue allowing domains redirect to to pose as the initial domain + (e.g. with media proxy's fallback redirects) +- refetched objects can no longer attribute themselves to third-party actors + (this had no externally visible effect since actor info is read from the Create activity) +- our litepub JSON-LD schema is now served with the correct content type +- remote APNG attachments are now recognised as images ## 2024.02 -- 2.43.0 From 3650bb03709bf68be2fd7ebcb16b5fc7189c36da Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sat, 30 Mar 2024 11:44:34 +0000 Subject: [PATCH 147/149] Changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c46e84fa8..3d15caf51 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -## Unreleased +## 2024.03 ## Added - CLI tasks best-effort checking for past abuse of the recent spoofing exploit -- 2.43.0 From 087d88f78778e10965bfe1e641603c31fd66b6ec Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sat, 30 Mar 2024 11:45:07 +0000 Subject: [PATCH 148/149] bump version --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 98d65484a..f501eec58 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("3.11.0"), + version: version("3.12.0"), elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), compilers: Mix.compilers(), -- 2.43.0 From 2d439034ca801b704536cb05483e012d62c2d52e Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sat, 30 Mar 2024 12:55:22 +0000 Subject: [PATCH 149/149] Ensure that spoof-inserted does not time out --- lib/mix/tasks/pleroma/security.ex | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/mix/tasks/pleroma/security.ex b/lib/mix/tasks/pleroma/security.ex index dc1f667d7..f039e0980 100644 --- a/lib/mix/tasks/pleroma/security.ex +++ b/lib/mix/tasks/pleroma/security.ex @@ -237,7 +237,7 @@ defp search_local_notes_without_create_id() do |> where([o, a], fragment("NOT (? \\? 'id') OR ?->>'id' IS NULL", a.data, a.data)) |> select([o, a], {a.id, fragment("?->>'id'", o.data)}) |> order_by([o, a], a.id) - |> Pleroma.Repo.all() + |> Pleroma.Repo.all(timeout: :infinity) end defp search_sus_notes_by_id_patterns() do @@ -272,7 +272,7 @@ defp search_sus_notes_by_id_patterns() do ) |> select([o, a], {a.id, fragment("?->>'id'", o.data)}) |> order_by([o, a], a.id) - |> Pleroma.Repo.all() + |> Pleroma.Repo.all(timeout: :infinity) end defp search_bogus_local_users() do @@ -280,7 +280,7 @@ defp search_bogus_local_users() do |> where([u], u.local == false and like(u.ap_id, ^local_id_pattern())) |> order_by([u], u.ap_id) |> select([u], u.ap_id) - |> Pleroma.Repo.all() + |> Pleroma.Repo.all(timeout: :infinity) end # +-----------------------------------+ -- 2.43.0