From 97037c0b53472f61f17263f318166f986a25baa5 Mon Sep 17 00:00:00 2001 From: Bryan Fink Date: Fri, 7 Jul 2023 11:35:01 -0500 Subject: [PATCH 01/70] do not fetch if limit_to_local_content is enabled Prior to this change, anyone, authenticated or not, could submit a search query for an activity by URL, and cause the fetcher to go fetch it. That shouldn't happen if `limit_to_local_content` is set to `:all` or if it's set to `:unauthenticated` and the query came from an unauthenticated source. --- lib/pleroma/search/database_search.ex | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/lib/pleroma/search/database_search.ex b/lib/pleroma/search/database_search.ex index 3735a5fab..8f6bf30b4 100644 --- a/lib/pleroma/search/database_search.ex +++ b/lib/pleroma/search/database_search.ex @@ -132,21 +132,29 @@ defp query_with(q, :rum, search_query, :websearch) do ) end - def maybe_restrict_local(q, user) do + def should_restrict_local(user) do limit = Pleroma.Config.get([:instance, :limit_to_local_content], :unauthenticated) case {limit, user} do - {:all, _} -> restrict_local(q) - {:unauthenticated, %User{}} -> q - {:unauthenticated, _} -> restrict_local(q) - {false, _} -> q + {:all, _} -> true + {:unauthenticated, %User{}} -> false + {:unauthenticated, _} -> true + {false, _} -> false + end + end + + def maybe_restrict_local(q, user) do + case should_restrict_local(user) do + true -> restrict_local(q) + false -> q end end defp restrict_local(q), do: where(q, local: true) def maybe_fetch(activities, user, search_query) do - with true <- Regex.match?(~r/https?:/, search_query), + with false <- should_restrict_local(user), + true <- Regex.match?(~r/https?:/, search_query), {:ok, object} <- Fetcher.fetch_object_from_id(search_query), %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]), true <- Visibility.visible_for_user?(activity, user) do From 4ff52930936699161223b12c4396d5a5ad6736d4 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 23 Jun 2024 20:46:58 +0200 Subject: [PATCH 02/70] Federate emoji as anonymous objects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Usually an id should point to another AP object and the image file isn’t an AP object. We currently do not provide standalone AP objects for emoji and don't keep track of remote emoji at all. Thus just federate them as anonymous objects, i.e. objects only existing within a parent context and using an explicit null id. IceShrimp.NET previously adopted anonymous objects for remote emoji without any apparent issues. See: https://iceshrimp.dev/iceshrimp/Iceshrimp.NET/commit/333611f65eb2a65b2779ece0435b5ba84bf60e99 Fixes: https://akkoma.dev/AkkomaGang/akkoma/issues/694 --- CHANGELOG.md | 2 ++ lib/pleroma/web/activity_pub/transmogrifier.ex | 2 +- .../web/activity_pub/transmogrifier/note_handling_test.exs | 2 +- test/pleroma/web/activity_pub/views/user_view_test.exs | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c743e5bd..c04ccec32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Fixed - Meilisearch: order of results returned from our REST API now actually matches how Meilisearch ranks results +- Emoji are now federated as anonymous objects, fixing issues with + some strict servers e.g. rejecting e.g. remote emoji reactions ## Changed - Refactored Rich Media to cache the content in the database. Fetching operations that could block status rendering have been eliminated. diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index ca5e85f2e..75c1f0f0c 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -951,7 +951,7 @@ defp build_emoji_tag({name, url}) do "name" => ":" <> name <> ":", "type" => "Emoji", "updated" => "1970-01-01T00:00:00Z", - "id" => url + "id" => nil } end diff --git a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs index 16ee31483..c8aa2a1ed 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs @@ -700,7 +700,7 @@ test "take_emoji_tags/1" do assert Transmogrifier.take_emoji_tags(user) == [ %{ "icon" => %{"type" => "Image", "url" => "https://example.org/firefox.png"}, - "id" => "https://example.org/firefox.png", + "id" => nil, "name" => ":firefox:", "type" => "Emoji", "updated" => "1970-01-01T00:00:00Z" 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 abe91cdea..2a367b680 100644 --- a/test/pleroma/web/activity_pub/views/user_view_test.exs +++ b/test/pleroma/web/activity_pub/views/user_view_test.exs @@ -43,7 +43,7 @@ test "Renders with emoji tags" do "tag" => [ %{ "icon" => %{"type" => "Image", "url" => "/test"}, - "id" => "/test", + "id" => nil, "name" => ":bib:", "type" => "Emoji", "updated" => "1970-01-01T00:00:00Z" From 80a4e30be7f41142e2f64868d2af2c468d0bdf41 Mon Sep 17 00:00:00 2001 From: Oneric Date: Thu, 27 Jun 2024 18:27:18 +0200 Subject: [PATCH 03/70] Upgrade captach dep Fixes a deprecation warning showing up each mix call when using elixir 1.17 --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 7ffc450e2..b147f2681 100644 --- a/mix.exs +++ b/mix.exs @@ -180,7 +180,7 @@ defp deps do {:remote_ip, "~> 1.1.0"}, {:captcha, git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git", - ref: "90f6ce7672f70f56708792a98d98bd05176c9176"}, + ref: "6630c42aaaab124e697b4e513190c89d8b64e410"}, {:restarter, path: "./restarter"}, {:majic, git: "https://akkoma.dev/AkkomaGang/majic.git", From 7cd395415210f5a981d45e26bc6e21e078ee96cd Mon Sep 17 00:00:00 2001 From: Oneric Date: Thu, 20 Jun 2024 19:52:31 +0200 Subject: [PATCH 04/70] Remove superfluous actor key suffix Fragments are already always stripped anyway so listing one specific fragment here is unnecessary and potentially confusing. This effectively reverts 4457928e325cf370a0d2e028232dbd0a542547e0 but keeps the added bridgy testcase. --- 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 c4ac2c87e..3d33fcd62 100644 --- a/lib/pleroma/signature.ex +++ b/lib/pleroma/signature.ex @@ -10,7 +10,7 @@ defmodule Pleroma.Signature do alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub - @known_suffixes ["/publickey", "/main-key", "#key"] + @known_suffixes ["/publickey", "/main-key"] def key_id_to_actor_id(key_id) do uri = From 95ed4931f8660df352a4f80f9b4767dba94f1eea Mon Sep 17 00:00:00 2001 From: Oneric Date: Thu, 20 Jun 2024 19:52:34 +0200 Subject: [PATCH 05/70] docs: note frontend tasks need to be run as akkoma user Fixes: https://akkoma.dev/AkkomaGang/akkoma/issues/748 --- docs/docs/installation/frontends.include | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/docs/installation/frontends.include b/docs/docs/installation/frontends.include index 6da4018a9..094cfe4bd 100644 --- a/docs/docs/installation/frontends.include +++ b/docs/docs/installation/frontends.include @@ -6,7 +6,9 @@ probably install frontends. These are no longer bundled with the distribution and need an extra command to install. -For most installations, the following will suffice: +You **must** run frontend management tasks as the akkoma user, +the same way you downloaded the build or cloned the git repo before. +But otherwise, for most installations, the following will suffice: === "OTP" ```sh @@ -28,4 +30,3 @@ For most installations, the following will suffice: ``` For more customised installations, refer to [Frontend Management](../../configuration/frontend_management) - From 0ab2f2ab457d16894c3a4affb77cd66a33901437 Mon Sep 17 00:00:00 2001 From: Oneric Date: Thu, 20 Jun 2024 19:52:39 +0200 Subject: [PATCH 06/70] ci: retry failed tasks once We have a bunch of mysterious sporadic failures which usually disappear when rerunning failed jobs only. Ideally we should locate and fix the cause of those psoradic failures, but until we figure this out retrying once makes CI status less useless. --- .woodpecker/test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.woodpecker/test.yml b/.woodpecker/test.yml index 81c779b50..45cf7f93d 100644 --- a/.woodpecker/test.yml +++ b/.woodpecker/test.yml @@ -87,5 +87,5 @@ steps: - 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 test --preload-modules --exclude erratic --exclude federated --exclude mocked || mix test --failed + - mix test --preload-modules --only mocked || mix test --failed From 07539f78257c366536341063c547a63eee6ef160 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 18 Jun 2024 10:50:31 -0400 Subject: [PATCH 07/70] Hide logs during test unless a test fails Currently `mix test` prints a slew of logs in the terminal with messages from different tests intermsparsed. Globally enabling capture log hides log messages unless a test fails reducing noise and making it easier to anylse the important (from failed tests) messages. Compiler warnings and a few messages not printed via Logger still show up but its much more readable than before. Ported from: https://git.pleroma.social/pleroma/pleroma/-/commit/3aed111a42472018f01899714305d4f3a5cbc911 --- test/mix/tasks/pleroma/config_test.exs | 1 - test/pleroma/object/fetcher_test.exs | 1 - test/pleroma/user_test.exs | 2 -- .../web/activity_pub/activity_pub_controller_test.exs | 4 ---- test/pleroma/web/activity_pub/relay_test.exs | 1 - .../activity_pub/transmogrifier/announce_handling_test.exs | 2 -- .../activity_pub/transmogrifier/delete_handling_test.exs | 1 - .../web/activity_pub/transmogrifier/note_handling_test.exs | 2 -- test/pleroma/web/activity_pub/transmogrifier_test.exs | 1 - .../web/mastodon_api/controllers/search_controller_test.exs | 1 - .../mastodon_api/controllers/timeline_controller_test.exs | 2 -- test/pleroma/web/push/impl_test.exs | 2 -- test/pleroma/web/web_finger_test.exs | 1 - test/test_helper.exs | 6 +++++- 14 files changed, 5 insertions(+), 22 deletions(-) diff --git a/test/mix/tasks/pleroma/config_test.exs b/test/mix/tasks/pleroma/config_test.exs index 3b09f656b..75c67efb4 100644 --- a/test/mix/tasks/pleroma/config_test.exs +++ b/test/mix/tasks/pleroma/config_test.exs @@ -51,7 +51,6 @@ test "error if file with custom settings doesn't exist" do clear_config(:configurable_from_database, true) end - @tag capture_log: true test "config migration refused when deprecated settings are found" do clear_config([:media_proxy, :whitelist], ["domain_without_scheme.com"]) assert config_records() == [] diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs index 12154cb05..74f2cd31f 100644 --- a/test/pleroma/object/fetcher_test.exs +++ b/test/pleroma/object/fetcher_test.exs @@ -189,7 +189,6 @@ defp spoofed_object_with_ids( :ok end - @tag capture_log: true test "it works when fetching the OP actor errors out" do # Here we simulate a case where the author of the OP can't be read assert {:ok, _} = diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index de71f4b95..90cee1c65 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -814,7 +814,6 @@ test "gets an existing user by fully qualified nickname, case insensitive" do assert user == fetched_user end - @tag capture_log: true test "returns nil if no user could be fetched" do {:error, fetched_user} = User.get_or_fetch_by_nickname("nonexistant@social.heldscal.la") assert fetched_user == "not found nonexistant@social.heldscal.la" @@ -871,7 +870,6 @@ test "if nicknames clash, the old user gets a prefix with the old id to the nick assert orig_user.nickname == "#{orig_user.id}.admin@mastodon.example.org" end - @tag capture_log: true test "it returns the old user if stale, but unfetchable" do a_week_ago = NaiveDateTime.add(NaiveDateTime.utc_now(), -604_800) 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 b325bcb9a..4c134c8f2 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -573,7 +573,6 @@ test "it inserts an incoming activity into the database", %{conn: conn} do assert Activity.get_by_ap_id(data["id"]) end - @tag capture_log: true test "it inserts an incoming activity into the database" <> "even if we can't fetch the user but have it in our db", %{conn: conn} do @@ -1074,7 +1073,6 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn, da assert Instances.reachable?(sender_host) end - @tag capture_log: true test "it removes all follower collections but actor's", %{conn: conn} do [actor, recipient] = insert_pair(:user) @@ -1138,7 +1136,6 @@ test "it requires authentication", %{conn: conn} do assert json_response(ret_conn, 200) end - @tag capture_log: true test "forwarded report", %{conn: conn} do admin = insert(:user, is_admin: true) actor = insert(:user, local: false) @@ -1215,7 +1212,6 @@ test "forwarded report", %{conn: conn} do ) end - @tag capture_log: true test "forwarded report from mastodon", %{conn: conn} do admin = insert(:user, is_admin: true) actor = insert(:user, local: false) diff --git a/test/pleroma/web/activity_pub/relay_test.exs b/test/pleroma/web/activity_pub/relay_test.exs index 920aef9da..99cc2071e 100644 --- a/test/pleroma/web/activity_pub/relay_test.exs +++ b/test/pleroma/web/activity_pub/relay_test.exs @@ -114,7 +114,6 @@ test "returns error when activity not `Create` type" do assert Relay.publish(activity) == {:error, "Not implemented"} end - @tag capture_log: true test "returns error when activity not public" do activity = insert(:direct_note_activity) assert Relay.publish(activity) == {:error, false} diff --git a/test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs index 524acddaf..88f42cada 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/announce_handling_test.exs @@ -83,7 +83,6 @@ test "it works for incoming announces, fetching the announced object" do assert(Activity.get_create_by_object_ap_id(data["object"])) end - @tag capture_log: true test "it works for incoming announces with an existing activity" do user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{status: "hey"}) @@ -136,7 +135,6 @@ test "it works for incoming announces with an inlined activity" do assert object.data["content"] == "this is a private toot" end - @tag capture_log: true test "it rejects incoming announces with an inlined activity from another origin" do Tesla.Mock.mock(fn %{method: :get} -> %Tesla.Env{status: 404, body: ""} diff --git a/test/pleroma/web/activity_pub/transmogrifier/delete_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/delete_handling_test.exs index b7160bf58..e5d6f940a 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/delete_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/delete_handling_test.exs @@ -86,7 +86,6 @@ test "it fails for incoming deletes with spoofed origin" do assert match?({:error, _}, Transmogrifier.handle_incoming(data)) end - @tag capture_log: true test "it works for incoming user deletes" do %{ap_id: ap_id} = insert(:user, ap_id: "http://mastodon.example.org/users/admin") diff --git a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs index 16ee31483..6a7bd3a43 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs @@ -57,7 +57,6 @@ test "it ignores an incoming notice if we already have it" do assert activity == returned_activity end - @tag capture_log: true test "it fetches reply-to activities if we don't have them" do data = File.read!("test/fixtures/mastodon-post-activity.json") @@ -537,7 +536,6 @@ test "returns object with inReplyTo when denied incoming reply", %{data: data} d assert modified_object["inReplyTo"] == [] end - @tag capture_log: true test "returns modified object when allowed incoming reply", %{data: data} do object_with_reply = Map.put( diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs index dd7977593..1be69317c 100644 --- a/test/pleroma/web/activity_pub/transmogrifier_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs @@ -561,7 +561,6 @@ test "returns nil when cannot normalize object" do end) =~ ":valid_uri_scheme" end - @tag capture_log: true test "returns {:ok, %Object{}} for success case" do assert {:ok, %Object{}} = Transmogrifier.get_obj_helper( diff --git a/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs index 6a0d25058..64364cfb3 100644 --- a/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/search_controller_test.exs @@ -79,7 +79,6 @@ test "search", %{conn: conn} do assert status["id"] == to_string(activity.id) end - @tag capture_log: true test "constructs hashtags from search query", %{conn: conn} do results = conn diff --git a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs index 3e7b6730c..ccaf17fe4 100644 --- a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs @@ -152,7 +152,6 @@ test "filtering", %{conn: conn, user: user} do end describe "public" do - @tag capture_log: true test "the public timeline", %{conn: conn} do user = insert(:user) @@ -810,7 +809,6 @@ test "filtering", %{user: user, conn: conn} do describe "hashtag" do setup do: oauth_access(["n/a"]) - @tag capture_log: true test "hashtag timeline", %{conn: conn} do following = insert(:user) diff --git a/test/pleroma/web/push/impl_test.exs b/test/pleroma/web/push/impl_test.exs index a40c4ff13..f9936095f 100644 --- a/test/pleroma/web/push/impl_test.exs +++ b/test/pleroma/web/push/impl_test.exs @@ -67,7 +67,6 @@ test "performs sending notifications" do assert Impl.perform(notif) == {:ok, [:ok, :ok]} end - @tag capture_log: true test "returns error if notif does not match " do assert Impl.perform(%{}) == {:error, :unknown_type} end @@ -76,7 +75,6 @@ test "successful message sending" do assert Impl.push_message(@message, @sub, @api_key, %Subscription{}) == :ok end - @tag capture_log: true test "fail message sending" do assert Impl.push_message( @message, diff --git a/test/pleroma/web/web_finger_test.exs b/test/pleroma/web/web_finger_test.exs index 2af084090..4fc2a8ffa 100644 --- a/test/pleroma/web/web_finger_test.exs +++ b/test/pleroma/web/web_finger_test.exs @@ -190,7 +190,6 @@ test "prevents spoofing" do end end - @tag capture_log: true test "prevents forgeries" do Tesla.Mock.mock(fn %{url: "https://bad.com/.well-known/webfinger?resource=acct:meanie@bad.com"} -> diff --git a/test/test_helper.exs b/test/test_helper.exs index dafa45099..6dcb87ff6 100644 --- a/test/test_helper.exs +++ b/test/test_helper.exs @@ -3,7 +3,11 @@ # SPDX-License-Identifier: AGPL-3.0-only os_exclude = if :os.type() == {:unix, :darwin}, do: [skip_on_mac: true], else: [] -ExUnit.start(exclude: [:federated, :erratic] ++ os_exclude) + +ExUnit.start( + capture_log: true, + exclude: [:federated, :erratic] ++ os_exclude +) Ecto.Adapters.SQL.Sandbox.mode(Pleroma.Repo, :manual) From cf19d4901f688016835e4b4f17a998a6b0816d56 Mon Sep 17 00:00:00 2001 From: Mark Felder Date: Tue, 18 Jun 2024 15:25:17 +0000 Subject: [PATCH 08/70] Disable Ecto query logging in tests The debug logs are very noisy and can be enabled during analysis of a specific error believed to be SQL-related -- Before log capturing those debug messages were still hidden, but with log capturing they show up in the output of failed tests unless disabled. Cherry-picked-from: https://git.pleroma.social/pleroma/pleroma/-/commit/e628d00a81ce18eaa54c6efb1e48a31d57216368 --- config/test.exs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/test.exs b/config/test.exs index 1df360ec7..556b9e78c 100644 --- a/config/test.exs +++ b/config/test.exs @@ -51,7 +51,8 @@ hostname: System.get_env("DB_HOST") || "localhost", pool: Ecto.Adapters.SQL.Sandbox, pool_size: 50, - queue_target: 5000 + queue_target: 5000, + log: false config :pleroma, :dangerzone, override_repo_pool_size: true From 495a1a71e89f3a3b67f9948f5e241a8b195f5ecf Mon Sep 17 00:00:00 2001 From: Oneric Date: Fri, 21 Jun 2024 18:30:47 +0200 Subject: [PATCH 09/70] strip_metadata: skip BMP files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Not _yet_ supported as of exiftool 12.87, though at first glance it seems like standard BMP files can't store any metadata besides colour profiles Fixes the specific case from https://akkoma.dev/AkkomaGang/akkoma-fe/issues/396 although the frontend shouldn’t get bricked regardless. --- lib/pleroma/upload/filter/exiftool/strip_metadata.ex | 1 + .../upload/filter/exiftool/strip_location_test.exs | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/lib/pleroma/upload/filter/exiftool/strip_metadata.ex b/lib/pleroma/upload/filter/exiftool/strip_metadata.ex index 912ff6a92..a2604a682 100644 --- a/lib/pleroma/upload/filter/exiftool/strip_metadata.ex +++ b/lib/pleroma/upload/filter/exiftool/strip_metadata.ex @@ -18,6 +18,7 @@ defmodule Pleroma.Upload.Filter.Exiftool.StripMetadata do # Formats not compatible with exiftool at this time def filter(%Pleroma.Upload{content_type: "image/webp"}), do: {:ok, :noop} + def filter(%Pleroma.Upload{content_type: "image/bmp"}), do: {:ok, :noop} def filter(%Pleroma.Upload{content_type: "image/svg+xml"}), do: {:ok, :noop} def filter(%Pleroma.Upload{tempfile: file, content_type: "image" <> _}) do diff --git a/test/pleroma/upload/filter/exiftool/strip_location_test.exs b/test/pleroma/upload/filter/exiftool/strip_location_test.exs index 2e017cd7e..1f798556b 100644 --- a/test/pleroma/upload/filter/exiftool/strip_location_test.exs +++ b/test/pleroma/upload/filter/exiftool/strip_location_test.exs @@ -115,6 +115,15 @@ test "verify webp files are skipped" do assert Filter.Exiftool.StripMetadata.filter(upload) == {:ok, :noop} end + test "verify bmp files are skipped" do + upload = %Pleroma.Upload{ + name: "sample.bmp", + content_type: "image/bmp" + } + + assert Filter.Exiftool.StripMetadata.filter(upload) == {:ok, :noop} + end + test "verify svg files are skipped" do upload = %Pleroma.Upload{ name: "sample.svg", From ca182a0ae7cde1838b6f1c63b7348af7eb6b45b1 Mon Sep 17 00:00:00 2001 From: Oneric Date: Thu, 20 Jun 2024 19:52:43 +0200 Subject: [PATCH 10/70] Correctly parse content types with multiple profiles Multiple profiles can be specified as a space-separated list and the possibility of additional profiles is explicitly brought up in ActivityStream spec --- CHANGELOG.md | 1 + lib/pleroma/object/fetcher.ex | 8 ++++++-- test/pleroma/object/fetcher_test.exs | 22 +++++++++++++++++++++- 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3c743e5bd..d1e3a42c0 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 - Meilisearch: order of results returned from our REST API now actually matches how Meilisearch ranks results +- AP objects with additional JSON-LD profiles beyond ActivityStreams can now be fetched ## Changed - Refactored Rich Media to cache the content in the database. Fetching operations that could block status rendering have been eliminated. diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 937026e04..54ea9e74e 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -369,8 +369,12 @@ def get_object(id) do {"activity+json", _} -> {:ok, final_id, body} - {"ld+json", %{"profile" => "https://www.w3.org/ns/activitystreams"}} -> - {:ok, final_id, body} + {"ld+json", %{"profile" => profiles}} -> + if "https://www.w3.org/ns/activitystreams" in String.split(profiles) do + {:ok, final_id, body} + else + {:error, {:content_type, content_type}} + end _ -> {:error, {:content_type, content_type}} diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs index 74f2cd31f..62dd64625 100644 --- a/test/pleroma/object/fetcher_test.exs +++ b/test/pleroma/object/fetcher_test.exs @@ -754,7 +754,7 @@ test "should return ok if the content type is application/activity+json" do 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 + test "should return ok if the content type is application/ld+json with the ActivityStream profile" do Tesla.Mock.mock(fn %{ method: :get, @@ -774,6 +774,26 @@ test "should return ok if the content type is application/ld+json with a profile assert {:ok, _, "{}"} = Fetcher.get_object("https://mastodon.social/2") end + test "should return ok if the content type is application/ld+json with several profiles" do + Tesla.Mock.mock(fn + %{ + method: :get, + url: "https://mastodon.social/2" + } -> + %Tesla.Env{ + status: 200, + url: "https://mastodon.social/2", + headers: [ + {"content-type", + "application/ld+json; profile=\"https://example.org/ns/superduperspec https://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 Tesla.Mock.mock(fn %{ From d488cf476ea2ea662c5ec6acfa319331c3490d27 Mon Sep 17 00:00:00 2001 From: Oneric Date: Thu, 20 Jun 2024 19:52:47 +0200 Subject: [PATCH 11/70] Fix voters count field MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mastodon API demands this be null unless it’s a multi-selection poll. Not abiding by this can mess up display in some clients. Fixes: https://akkoma.dev/AkkomaGang/akkoma/issues/190 --- CHANGELOG.md | 2 ++ lib/pleroma/web/api_spec/schemas/poll.ex | 4 +++- lib/pleroma/web/mastodon_api/views/poll_view.ex | 14 +++++++++++--- .../web/mastodon_api/views/poll_view_test.exs | 2 +- 4 files changed, 17 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d1e3a42c0..0d451dca8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Fixed - Meilisearch: order of results returned from our REST API now actually matches how Meilisearch ranks results - AP objects with additional JSON-LD profiles beyond ActivityStreams can now be fetched +- Single-selection polls no longer expose the voter_count; MastoAPI demands it be null + and this confused some clients leading to vote distributions >100% ## Changed - Refactored Rich Media to cache the content in the database. Fetching operations that could block status rendering have been eliminated. diff --git a/lib/pleroma/web/api_spec/schemas/poll.ex b/lib/pleroma/web/api_spec/schemas/poll.ex index 943ad8bd4..13a5e8be2 100644 --- a/lib/pleroma/web/api_spec/schemas/poll.ex +++ b/lib/pleroma/web/api_spec/schemas/poll.ex @@ -32,7 +32,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do }, voters_count: %Schema{ type: :integer, - description: "How many unique accounts have voted. Number." + nullable: true, + description: + "How many unique accounts have voted for a multi-selection poll. Number, or null if single-selection poll." }, voted: %Schema{ type: :boolean, diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex index aa6443754..411cbd15a 100644 --- a/lib/pleroma/web/mastodon_api/views/poll_view.ex +++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex @@ -19,7 +19,7 @@ def render("show.json", %{object: object, multiple: multiple, options: options} expired: expired, multiple: multiple, votes_count: votes_count, - voters_count: voters_count(object), + voters_count: voters_count(multiple, object), options: options, emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"]) } @@ -68,11 +68,19 @@ defp options_and_votes_count(options) do end) end - defp voters_count(%{data: %{"voters" => voters}}) when is_list(voters) do + defp voters_count(false, _poll_data) do + # Mastodon always sets voter count to "null" unless multiple options were selectable + # Some clients may rely on this to detect multiple selection polls and it can mess + # up percentages for some clients if we never got a correct remote voter count and + # only count local voters here; see https://akkoma.dev/AkkomaGang/akkoma/issues/190 + nil + end + + defp voters_count(_multiple, %{data: %{"voters" => voters}}) when is_list(voters) do length(voters) end - defp voters_count(_), do: 0 + defp voters_count(_, _), do: 0 defp voted_and_own_votes(%{object: object} = params, options) do if params[:for] do diff --git a/test/pleroma/web/mastodon_api/views/poll_view_test.exs b/test/pleroma/web/mastodon_api/views/poll_view_test.exs index 224b26cb9..91d95f229 100644 --- a/test/pleroma/web/mastodon_api/views/poll_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/poll_view_test.exs @@ -43,7 +43,7 @@ test "renders a poll" do %{title: "why are you even asking?", votes_count: 0} ], votes_count: 0, - voters_count: 0 + voters_count: nil } result = PollView.render("show.json", %{object: object}) From a3101a435bae8bba7da77bba52d80e06b85593a4 Mon Sep 17 00:00:00 2001 From: Oneric Date: Thu, 20 Jun 2024 19:53:03 +0200 Subject: [PATCH 12/70] Fix swagger-ui Ever since the browser frontend switcher was introduced in de64c6c54aaacc4123031f2e3d5bfb9fc9c517fe /akkoma counts as an API prefix and thus gets skipped by frontend plugs breaking the old swagger ui path of /akkoma/swagger-ui. Do the simple thing and change the frontend path to /pleroma/swaggerui which isn't an API path and can't collide with frontend user paths given pleroma is areserved nickname. Reported in https://meta.akkoma.dev/t/view-all-endpoints/269/7 https://meta.akkoma.dev/t/swagger-ui-not-loading/728 --- CHANGELOG.md | 1 + docs/docs/configuration/cheatsheet.md | 2 +- docs/docs/configuration/frontend_management.md | 2 +- lib/pleroma/web/endpoint.ex | 4 ++-- 4 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0d451dca8..d2fcd752c 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/). ## BREAKING - Minimum PostgreSQL version is raised to 12 +- Swagger UI moved from `/akkoma/swaggerui/` to `/pleroma/swaggerui/` ## Added - Implement [FEP-67ff](https://codeberg.org/fediverse/fep/src/branch/main/fep/67ff/fep-67ff.md) (federation documentation) diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index 80f5c3577..916e1cc0c 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -338,7 +338,7 @@ config :pleroma, :frontends, * `:primary` - The frontend that will be served at `/` * `:admin` - The frontend that will be served at `/pleroma/admin` -* `:swagger` - Config for developers to act as an API reference to be served at `/akkoma/swaggerui/` (trailing slash _needed_). Disabled by default. +* `:swagger` - Config for developers to act as an API reference to be served at `/pleroma/swaggerui/` (trailing slash _needed_). Disabled by default. * `:mastodon` - The mastodon-fe configuration. This shouldn't need to be changed. This is served at `/web` when installed. ### :static\_fe diff --git a/docs/docs/configuration/frontend_management.md b/docs/docs/configuration/frontend_management.md index bc5344826..8875ee279 100644 --- a/docs/docs/configuration/frontend_management.md +++ b/docs/docs/configuration/frontend_management.md @@ -60,4 +60,4 @@ config :pleroma, :frontends, Then run the [pleroma.frontend cli task](../../administration/CLI_tasks/frontend) with the name of `swagger-ui` to install the distribution files. -You will now be able to view documentation at `/akkoma/swaggerui` +You will now be able to view documentation at `/pleroma/swaggerui` diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index 6628fcaf3..3c9fcb48c 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -66,10 +66,10 @@ defmodule Pleroma.Web.Endpoint do } ) - plug(Plug.Static.IndexHtml, at: "/akkoma/swaggerui") + plug(Plug.Static.IndexHtml, at: "/pleroma/swaggerui/") plug(Pleroma.Web.Plugs.FrontendStatic, - at: "/akkoma/swaggerui", + at: "/pleroma/swaggerui", frontend_type: :swagger, gzip: true, if: &Pleroma.Web.Swagger.ui_enabled?/0, From 940792f8ba8eae33a2cb48b313986b3674aa3a20 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sat, 13 Jul 2024 06:54:37 +0200 Subject: [PATCH 13/70] Refetch on AP ID mismatch MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit As hinted at in the commit message when strict checking was added in 8684964c5d03f6c70f73730b3f1ad26784ffb004, refetching is more robust than display URL comparison but in exchange is harder to implement correctly. A similar refetch approach is also employed by e.g. Mastodon, IceShrimp and FireFish. To make sure no checks can be bypassed by forcing a refetch, id checking is placed at the very end. This will fix: - Peertube display URL arrays our transmogrifier fails to normalise - non-canonical display URLs from alternative frontends (theoretical; we didnt’t get any actual reports about this) It will also be helpful in the planned key handling overhaul. The modified user collision test was introduced in https://git.pleroma.social/pleroma/pleroma/-/merge_requests/461 and unfortunately the issues this fixes aren’t public. Afaict it was just meant to guard against someone serving faked data belonging to an unrelated domain. Since we now refetch and the id actually is mocked, lookup now succeeds but will use the real data from the authorative server making it unproblematic. Instead modify the fake data further and make sure we don’t end up using the spoofed version. --- lib/pleroma/object/containment.ex | 57 +++++++++++++++--------- lib/pleroma/object/fetcher.ex | 42 +++++++++++------ test/pleroma/object/containment_test.exs | 35 +++++++-------- test/pleroma/object/fetcher_test.exs | 2 +- test/support/http_request_mock.ex | 7 ++- 5 files changed, 87 insertions(+), 56 deletions(-) diff --git a/lib/pleroma/object/containment.ex b/lib/pleroma/object/containment.ex index 37bc20e4d..7b1cc37bd 100644 --- a/lib/pleroma/object/containment.ex +++ b/lib/pleroma/object/containment.ex @@ -12,8 +12,6 @@ defmodule Pleroma.Object.Containment do 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 @@ -50,16 +48,39 @@ 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 uri_strip_slash(%URI{path: path} = uri) when is_binary(path), + do: %{uri | path: String.replace_suffix(path, "/", "")} - defp compare_uris_exact(%URI{} = id, %URI{} = other), - do: compare_uris_exact(URI.to_string(id), URI.to_string(other)) + defp uri_strip_slash(uri), do: uri - 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 + # domain names are case-insensitive per spec (other parts of URIs aren’t necessarily) + defp uri_normalise_host(%URI{host: host} = uri) when is_binary(host), + do: %{uri | host: String.downcase(host, :ascii)} + + defp uri_normalise_host(uri), do: uri + + defp compare_uri_identities(uri, uri), do: :ok + + defp compare_uri_identities(id_uri, other_uri) when is_binary(id_uri) and is_binary(other_uri), + do: compare_uri_identities(URI.parse(id_uri), URI.parse(other_uri)) + + defp compare_uri_identities(%URI{} = id, %URI{} = other) do + normid = + %{id | fragment: nil} + |> uri_strip_slash() + |> uri_normalise_host() + + normother = + %{other | fragment: nil} + |> uri_strip_slash() + |> uri_normalise_host() + + # Conversion back to binary avoids issues from non-normalised deprecated authority field + if URI.to_string(normid) == URI.to_string(normother) do + :ok + else + :error + end end @doc """ @@ -93,21 +114,13 @@ 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) + Check whether the fetch URL (after redirects) is the + same location the canonical ActivityPub id points to. 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 + def contain_id_to_fetch(url, %{"id" => id}) when is_binary(id) do + compare_uri_identities(url, id) end def contain_id_to_fetch(_url, _data), do: :error diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 937026e04..7f9a922aa 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -116,7 +116,7 @@ defp reinject_object(%Object{} = object, new_data) do @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), + {:ok, new_data} <- fetch_and_contain_remote_object_from_id(id, true), {:id, true} <- {:id, new_data["id"] == id}, {:ok, object} <- reinject_object(object, new_data) do {:ok, object} @@ -253,14 +253,17 @@ 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) + @doc """ + Fetches arbitrary remote object and performs basic safety and authenticity checks. + When the fetch URL is known to already be a canonical AP id, checks are stricter. + """ + def fetch_and_contain_remote_object_from_id(id, is_ap_id \\ false) - def fetch_and_contain_remote_object_from_id(%{"id" => id}), - do: fetch_and_contain_remote_object_from_id(id) + def fetch_and_contain_remote_object_from_id(%{"id" => id}, is_ap_id), + do: fetch_and_contain_remote_object_from_id(id, is_ap_id) - def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do - Logger.debug("Fetching object #{id} via AP") + def fetch_and_contain_remote_object_from_id(id, is_ap_id) when is_binary(id) do + Logger.debug("Fetching object #{id} via AP [ap_id=#{is_ap_id}]") with {:valid_uri_scheme, true} <- {:valid_uri_scheme, String.starts_with?(id, "http")}, %URI{} = uri <- URI.parse(id), @@ -270,18 +273,31 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do {:mrf_accept_check, Pleroma.Web.ActivityPub.MRF.SimplePolicy.check_accept(uri)}, {:local_fetch, :ok} <- {:local_fetch, Containment.contain_local_fetch(id)}, {:ok, final_id, body} <- get_object(id), + # a canonical ID shouldn't be a redirect + true <- !is_ap_id || final_id == id, {:ok, data} <- safe_json_decode(body), - {_, :ok} <- {:strict_id, Containment.contain_id_to_fetch(final_id, data)}, - {_, :ok} <- {:containment, Containment.contain_origin(final_id, data)} do + {_, :ok} <- {:containment, Containment.contain_origin(final_id, data)}, + {_, _, :ok} <- {:strict_id, data["id"], Containment.contain_id_to_fetch(final_id, data)} do unless Instances.reachable?(final_id) do Instances.set_reachable(final_id) end {:ok, data} else - {:strict_id, _} = e -> - log_fetch_error(id, e) - {:error, :id_mismatch} + # E.g. Mastodon and *key serve the AP object directly under their display URLs without + # redirecting to their canonical location first, thus ids will expectedly differ. + # Similarly keys, either use a fragment ID and are a subobjects or a distinct ID + # but for compatibility are still a subobject presenting their owning actors ID at the toplevel. + # Refetching _once_ from the listed id, should yield a strict match afterwards. + {:strict_id, ap_id, _} = e -> + case is_ap_id do + false -> + fetch_and_contain_remote_object_from_id(ap_id, true) + + true -> + log_fetch_error(id, e) + {:error, :id_mismatch} + end {:mrf_reject_check, _} = e -> log_fetch_error(id, e) @@ -311,7 +327,7 @@ def fetch_and_contain_remote_object_from_id(id) when is_binary(id) do end end - def fetch_and_contain_remote_object_from_id(_id), + def fetch_and_contain_remote_object_from_id(_id, _is_ap_id), do: {:error, :invalid_id} defp check_crossdomain_redirect(final_host, original_url) diff --git a/test/pleroma/object/containment_test.exs b/test/pleroma/object/containment_test.exs index f8f40a3ac..1a1d01473 100644 --- a/test/pleroma/object/containment_test.exs +++ b/test/pleroma/object/containment_test.exs @@ -9,7 +9,6 @@ defmodule Pleroma.Object.ContainmentTest do alias Pleroma.User import Pleroma.Factory - import ExUnit.CaptureLog setup_all do Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -136,23 +135,17 @@ test "contain_id_to_fetch() allows matching IDs" do ) end - test "contain_id_to_fetch() allows display URLs" do + test "contain_id_to_fetch() allows fragments and normalises domain casing" do data = %{ - "id" => "http://example.com/~alyssa/activities/1234.json", - "url" => "http://example.com/@alyssa/status/1234" + "id" => "http://example.com/users/capybara", + "url" => "http://example.com/@capybara" } - :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 - ) + assert :ok == + Containment.contain_id_to_fetch( + "http://EXAMPLE.com/users/capybara#key", + data + ) end test "users cannot be collided through fake direction spoofing attempts" do @@ -164,10 +157,14 @@ test "users cannot be collided through fake direction spoofing attempts" do follower_address: User.ap_followers(%User{nickname: "rye@niu.moe"}) }) - assert capture_log(fn -> - {:error, _} = User.get_or_fetch_by_ap_id("https://n1u.moe/users/rye") - end) =~ - "[error] Could not decode user at fetch https://n1u.moe/users/rye" + # Fetch from an attempted spoof id will suceed, but automatically retrieve + # the real data from the homeserver instead of naïvely using the spoof + {:ok, fetched_user} = User.get_or_fetch_by_ap_id("https://n1u.moe/users/rye") + + refute fetched_user.name == "evil rye" + refute fetched_user.raw_bio == "boooo!" + assert fetched_user.name == "♡ rye ♡" + assert fetched_user.nickname == "rye@niu.moe" end test "contain_origin_from_id() gracefully handles cases where no ID is present" do diff --git a/test/pleroma/object/fetcher_test.exs b/test/pleroma/object/fetcher_test.exs index 12154cb05..d2de0ccc5 100644 --- a/test/pleroma/object/fetcher_test.exs +++ b/test/pleroma/object/fetcher_test.exs @@ -252,7 +252,7 @@ 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, :id_mismatch} = + assert {:error, :not_found} = Fetcher.fetch_and_contain_remote_object_from_id( "https://patch.cx/media/03ca3c8b4ac3ddd08bf0f84be7885f2f88de0f709112131a22d83650819e36c2.json" ) diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index 6a01393e3..ea06c4966 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -263,7 +263,12 @@ def get("https://n1u.moe/users/rye", _, _, @activitypub_accept_headers) do {:ok, %Tesla.Env{ status: 200, - body: File.read!("test/fixtures/tesla_mock/rye.json"), + body: + File.read!("test/fixtures/tesla_mock/rye.json") + |> Jason.decode!() + |> Map.put("name", "evil rye") + |> Map.put("bio", "boooo!") + |> Jason.encode!(), headers: activitypub_object_headers() }} end From d5b0720596b70240bb559a2084d3c3cdc76fe7b2 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sat, 13 Jul 2024 06:54:45 +0200 Subject: [PATCH 14/70] Allow cross-domain redirects on AP requests Since we now remember the final location redirects lead to and use it for all further checks since 3e134b07fa4e382f1f4cfdbe90e74f8e73336a4e, these redirects can no longer be exploited to serve counterfeit objects. This fixes: - display URLs from independent webapp clients redirecting to the canonical domain - Peertube display URLs for remote content (acting like the above) --- lib/pleroma/object/fetcher.ex | 18 +--------------- test/pleroma/object/fetcher_test.exs | 32 ++++++++++++++++++++++++---- 2 files changed, 29 insertions(+), 21 deletions(-) diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 7f9a922aa..2cecfec67 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -317,7 +317,7 @@ def fetch_and_contain_remote_object_from_id(id, is_ap_id) when is_binary(id) do {:containment, reason} -> log_fetch_error(id, reason) - {:error, reason} + {:error, {:containment, reason}} {:error, e} -> {:error, e} @@ -330,22 +330,10 @@ def fetch_and_contain_remote_object_from_id(id, is_ap_id) when is_binary(id) do def fetch_and_contain_remote_object_from_id(_id, _is_ap_id), do: {:error, :invalid_id} - 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 - if @mix_env == :test do defp get_final_id(nil, initial_url), do: initial_url defp get_final_id("", initial_url), do: initial_url @@ -371,10 +359,6 @@ def get_object(id) do with {:ok, %{body: body, status: code, headers: headers, url: final_url}} when code in 200..299 <- HTTP.Backoff.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 d2de0ccc5..84bf0aa05 100644 --- a/test/pleroma/object/fetcher_test.exs +++ b/test/pleroma/object/fetcher_test.exs @@ -22,6 +22,7 @@ defp spoofed_object_with_ids( |> Jason.decode!() |> Map.put("id", id) |> Map.put("actor", actor_id) + |> Map.put("attributedTo", actor_id) |> Jason.encode!() end @@ -109,7 +110,7 @@ defp spoofed_object_with_ids( body: spoofed_object_with_ids("https://patch.cx/objects/spoof_media_redirect1") } - # Spoof: cross-domain redirect with final domain id + # Spoof: cross-domain redirect with final domain id, but original id actor %{method: :get, url: "https://patch.cx/objects/spoof_media_redirect2"} -> %Tesla.Env{ status: 200, @@ -118,6 +119,19 @@ defp spoofed_object_with_ids( body: spoofed_object_with_ids("https://media.patch.cx/objects/spoof_media_redirect2") } + # No-Spoof: cross-domain redirect with id and actor from final domain + %{method: :get, url: "https://patch.cx/objects/spoof_media_redirect3"} -> + %Tesla.Env{ + status: 200, + url: "https://media.patch.cx/objects/spoof_media_redirect3", + headers: [{"content-type", "application/activity+json"}], + body: + spoofed_object_with_ids( + "https://media.patch.cx/objects/spoof_media_redirect3", + "https://media.patch.cx/users/rin" + ) + } + # No-Spoof: same domain redirect %{method: :get, url: "https://patch.cx/objects/spoof_redirect"} -> %Tesla.Env{ @@ -264,19 +278,29 @@ test "it does not fetch a spoofed object with id different from URL" do end test "it does not fetch an object via cross-domain redirects (initial id)" do - assert {:error, {:cross_domain_redirect, true}} = + assert {:error, {:containment, _}} = 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}} = + test "it does not fetch an object via cross-domain redirect if the actor is from the original domain" do + assert {:error, {:containment, :error}} = Fetcher.fetch_and_contain_remote_object_from_id( "https://patch.cx/objects/spoof_media_redirect2" ) end + test "it allows cross-domain redirects when id and author are from final domain" do + assert {:ok, %{"id" => id, "attributedTo" => author}} = + Fetcher.fetch_and_contain_remote_object_from_id( + "https://patch.cx/objects/spoof_media_redirect3" + ) + + assert URI.parse(id).host == "media.patch.cx" + assert URI.parse(author).host == "media.patch.cx" + end + test "it accepts same-domain redirects" do assert {:ok, %{"id" => id} = _object} = Fetcher.fetch_and_contain_remote_object_from_id( From 8b5aca96191e6741cd10fff594d7923d3ea7e9bb Mon Sep 17 00:00:00 2001 From: TudbuT Date: Fri, 18 Oct 2024 14:50:28 +0200 Subject: [PATCH 15/70] fix fs error while unpacking frontends --- lib/pleroma/frontend.ex | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/pleroma/frontend.ex b/lib/pleroma/frontend.ex index dc9d55646..100490c70 100644 --- a/lib/pleroma/frontend.ex +++ b/lib/pleroma/frontend.ex @@ -79,6 +79,10 @@ def unzip(zip, dest) do new_file_path = Path.join(dest, path) + new_file_path + |> Path.dirname() + |> File.rm() + new_file_path |> Path.dirname() |> File.mkdir_p!() From 661b7fedb635eb23b106eca4730a89b2320d1e9f Mon Sep 17 00:00:00 2001 From: TudbuT Date: Fri, 18 Oct 2024 14:57:31 +0200 Subject: [PATCH 16/70] fix wrong type when importing emojis --- lib/mix/tasks/pleroma/emoji.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex index 12918dfff..aa8131254 100644 --- a/lib/mix/tasks/pleroma/emoji.ex +++ b/lib/mix/tasks/pleroma/emoji.ex @@ -111,7 +111,7 @@ def run(["get-packs" | args]) do {:ok, _} = :zip.unzip(binary_archive, - cwd: pack_path, + cwd: to_charlist(pack_path), file_list: files_to_unzip ) From e4332d06b5cb94c619ceb9844123f45f08369c7e Mon Sep 17 00:00:00 2001 From: Norm Date: Thu, 17 Oct 2024 02:33:42 -0400 Subject: [PATCH 17/70] update nsfwCensorImage suggestion in config/description.exs Turns out this is also used to set the default values in adminfe. However, this URL may break with newer Akkoma-FE versions. Instead, set this to blank so that it falls back to the default NSFW cover image set at build time on Akkoma-FE. --- config/description.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/description.exs b/config/description.exs index b69478fdb..7f97740b4 100644 --- a/config/description.exs +++ b/config/description.exs @@ -1184,7 +1184,7 @@ logoMask: true, minimalScopesMode: false, noAttachmentLinks: false, - nsfwCensorImage: "/static/img/nsfw.74818f9.png", + nsfwCensorImage: "", postContentType: "text/plain", redirectRootLogin: "/main/friends", redirectRootNoLogin: "/main/all", @@ -1285,7 +1285,7 @@ type: {:string, :image}, description: "URL of the image to use for hiding NSFW media attachments in the timeline", - suggestions: ["/static/img/nsfw.74818f9.png"] + suggestions: [""] }, %{ key: :postContentType, From a2e397a79d05af35211f313a12ccb7e5ad800571 Mon Sep 17 00:00:00 2001 From: Norm Date: Tue, 22 Oct 2024 15:56:22 -0400 Subject: [PATCH 18/70] Update asdf install docs in Debian install guide Instead of trying to update the version of asdf being used, just point users to the guide on their website. Ideally we'd do this for Elixir and Erlang as well, but new versions of those packages may sometimes have compatibility issues with Akkoma. For now, update those to the latest OTP and Elixir versions known to be comaptible with Akkoma. --- docs/docs/installation/debian_based_en.md | 26 ++++++++--------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/docs/docs/installation/debian_based_en.md b/docs/docs/installation/debian_based_en.md index 5dddabe7f..442849e69 100644 --- a/docs/docs/installation/debian_based_en.md +++ b/docs/docs/installation/debian_based_en.md @@ -35,32 +35,24 @@ sudo useradd -r -s /bin/false -m -d /var/lib/akkoma -U akkoma ### Install Elixir and Erlang +#### Using `apt` If your distribution packages a recent enough version of Elixir, you can install it directly from the distro repositories and skip to the next section of the guide: ```shell sudo apt install elixir erlang-dev erlang-nox ``` -Otherwise use [asdf](https://github.com/asdf-vm/asdf) to install the latest versions of Elixir and Erlang. +#### Using `asdf` +If your distribution does not have a recent version of Elxir in their repositories, you can use [asdf](https://asdf-vm.com/) to install a newer version of Elixir and Erlang. First, install some dependencies needed to build Elixir and Erlang: ```shell sudo apt install curl unzip build-essential autoconf m4 libncurses5-dev libssh-dev unixodbc-dev xsltproc libxml2-utils libncurses-dev ``` -Then login to the `akkoma` user and install asdf: -```shell -git clone https://github.com/asdf-vm/asdf.git ~/.asdf --branch v0.11.3 -``` +Then login to the `akkoma` user. -Add the following lines to `~/.bashrc`: -```shell -. "$HOME/.asdf/asdf.sh" -# asdf completions -. "$HOME/.asdf/completions/asdf.bash" -``` - -Restart the shell: +Install asdf by following steps 1 to 3 on [their website](https://asdf-vm.com/guide/getting-started.html), then restart the shell to load asdf: ```shell exec $SHELL ``` @@ -69,15 +61,15 @@ Next install Erlang: ```shell asdf plugin add erlang https://github.com/asdf-vm/asdf-erlang.git export KERL_CONFIGURE_OPTIONS="--disable-debug --without-javac" -asdf install erlang 25.3.2.5 -asdf global erlang 25.3.2.5 +asdf install erlang 26.2.5.4 +asdf global erlang 26.2.5.4 ``` Now install Elixir: ```shell asdf plugin-add elixir https://github.com/asdf-vm/asdf-elixir.git -asdf install elixir 1.15.4-otp-25 -asdf global elixir 1.15.4-otp-25 +asdf install elixir 1.17.3-otp-26 +asdf global elixir 1.17.3-otp-26 ``` Confirm that Elixir is installed correctly by checking the version: From 40da4e88ea24e85ffbd805fdc8d81921a9893cee Mon Sep 17 00:00:00 2001 From: Norm Date: Fri, 25 Oct 2024 11:09:20 -0400 Subject: [PATCH 19/70] Update hashtag prune to account for followed hashtags Currently pruning hashtags with the prune_objects task only accounts for whether that hashtag is associated with an object, but this may lead to a foreign key constraint violation if that hashtag has no objects but is followed by a local user. This adds an additional check to see if that hashtag has any followers before proceeding to delete it. --- lib/mix/tasks/pleroma/database.ex | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index 87ccfdff1..f85fe5bea 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -346,7 +346,10 @@ def run(["prune_objects" | args]) do DELETE FROM hashtags AS ht WHERE NOT EXISTS ( SELECT 1 FROM hashtags_objects hto - WHERE ht.id = hto.hashtag_id) + WHERE ht.id = hto.hashtag_id + UNION + SELECT 1 FROM user_follows_hashtag ufht + WHERE ht.id = ufht.hashtag_id) """ |> Repo.query!() From 88a8086ad38d7d0fc0992c06aa5dff71597fbb0b Mon Sep 17 00:00:00 2001 From: Norm Date: Fri, 25 Oct 2024 12:25:18 -0400 Subject: [PATCH 20/70] Use LEFT JOIN instead of UNION for hashtag pruning --- lib/mix/tasks/pleroma/database.ex | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex index f85fe5bea..0a09a1c4a 100644 --- a/lib/mix/tasks/pleroma/database.ex +++ b/lib/mix/tasks/pleroma/database.ex @@ -343,13 +343,16 @@ def run(["prune_objects" | args]) do %{:num_rows => del_hashtags} = """ - DELETE FROM hashtags AS ht - WHERE NOT EXISTS ( - SELECT 1 FROM hashtags_objects hto - WHERE ht.id = hto.hashtag_id - UNION - SELECT 1 FROM user_follows_hashtag ufht - WHERE ht.id = ufht.hashtag_id) + DELETE FROM hashtags + USING hashtags AS ht + LEFT JOIN hashtags_objects hto + ON ht.id = hto.hashtag_id + LEFT JOIN user_follows_hashtag ufht + ON ht.id = ufht.hashtag_id + WHERE + hashtags.id = ht.id + AND hto.hashtag_id is NULL + AND ufht.hashtag_id is NULL """ |> Repo.query!() From cbd236aeb53b8e73adb220a242c2481efe76541f Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Sat, 26 Oct 2024 05:04:20 +0100 Subject: [PATCH 21/70] mix format --- lib/pleroma/frontend.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/frontend.ex b/lib/pleroma/frontend.ex index 100490c70..a309d8467 100644 --- a/lib/pleroma/frontend.ex +++ b/lib/pleroma/frontend.ex @@ -82,7 +82,7 @@ def unzip(zip, dest) do new_file_path |> Path.dirname() |> File.rm() - + new_file_path |> Path.dirname() |> File.mkdir_p!() From fc99c694e65730bf6b391ce340670356857f363b Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Thu, 27 Jun 2024 05:05:56 +0100 Subject: [PATCH 22/70] Add signing key modules --- lib/pleroma/user/signing_key.ex | 190 ++++++++++++++++++ ...0240625213637_create_signing_key_table.exs | 15 ++ .../20240625220752_move_signing_keys.exs | 35 ++++ 3 files changed, 240 insertions(+) create mode 100644 lib/pleroma/user/signing_key.ex create mode 100644 priv/repo/migrations/20240625213637_create_signing_key_table.exs create mode 100644 priv/repo/migrations/20240625220752_move_signing_keys.exs diff --git a/lib/pleroma/user/signing_key.ex b/lib/pleroma/user/signing_key.ex new file mode 100644 index 000000000..92ce7ae5d --- /dev/null +++ b/lib/pleroma/user/signing_key.ex @@ -0,0 +1,190 @@ +defmodule Pleroma.User.SigningKey do + use Ecto.Schema + import Ecto.Query + import Ecto.Changeset + alias Pleroma.User + alias Pleroma.Repo + alias Pleroma.HTTP + + require Logger + + @primary_key false + schema "signing_keys" do + belongs_to(:user, Pleroma.User, type: FlakeId.Ecto.CompatType) + field :public_key, :string + field :private_key, :string + # This is an arbitrary field given by the remote instance + field :key_id, :string, primary_key: true + timestamps() + end + + def key_id_of_local_user(%User{local: true, signing_key: %__MODULE__{key_id: key_id}}), + do: key_id + + @spec remote_changeset(__MODULE__, map) :: Changeset.t() + def remote_changeset(%__MODULE__{} = signing_key, attrs) do + signing_key + |> cast(attrs, [:public_key, :key_id]) + |> validate_required([:public_key, :key_id]) + end + + @spec key_id_to_user_id(String.t()) :: String.t() | nil + @doc """ + Given a key ID, return the user ID associated with that key. + Returns nil if the key ID is not found. + """ + def key_id_to_user_id(key_id) do + from(sk in __MODULE__, where: sk.key_id == ^key_id) + |> select([sk], sk.user_id) + |> Repo.one() + end + + @spec key_id_to_ap_id(String.t()) :: String.t() | nil + @doc """ + Given a key ID, return the AP ID associated with that key. + Returns nil if the key ID is not found. + """ + def key_id_to_ap_id(key_id) do + Logger.debug("Looking up key ID: #{key_id}") + + result = + from(sk in __MODULE__, where: sk.key_id == ^key_id) + |> join(:inner, [sk], u in User, on: sk.user_id == u.id) + |> select([sk, u], %{user: u}) + |> Repo.one() + + case result do + %{user: %User{ap_id: ap_id}} -> ap_id + _ -> nil + end + end + + @spec generate_rsa_pem() :: {:ok, binary()} + @doc """ + Generate a new RSA private key and return it as a PEM-encoded string. + """ + def generate_rsa_pem do + key = :public_key.generate_key({:rsa, 2048, 65_537}) + entry = :public_key.pem_entry_encode(:RSAPrivateKey, key) + pem = :public_key.pem_encode([entry]) |> String.trim_trailing() + {:ok, pem} + end + + @spec generate_local_keys(String.t()) :: {:ok, Changeset.t()} | {:error, String.t()} + @doc """ + Generate a new RSA key pair and create a changeset for it + """ + def generate_local_keys(ap_id) do + {:ok, private_pem} = generate_rsa_pem() + {:ok, local_pem} = private_pem_to_public_pem(private_pem) + + %__MODULE__{} + |> change() + |> put_change(:public_key, local_pem) + |> put_change(:private_key, private_pem) + |> put_change(:key_id, ap_id <> "#main-key") + end + + @spec private_pem_to_public_pem(binary) :: {:ok, binary()} | {:error, String.t()} + @doc """ + Given a private key in PEM format, return the corresponding public key in PEM format. + """ + def private_pem_to_public_pem(private_pem) do + [private_key_code] = :public_key.pem_decode(private_pem) + private_key = :public_key.pem_entry_decode(private_key_code) + {:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} = private_key + public_key = {:RSAPublicKey, modulus, exponent} + public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) + {:ok, :public_key.pem_encode([public_key])} + end + + @spec public_key(User.t()) :: {:ok, binary()} | {:error, String.t()} + @doc """ + Given a user, return the public key for that user in binary format. + """ + def public_key(%User{signing_key: %__MODULE__{public_key: public_key_pem}}) do + key = + public_key_pem + |> :public_key.pem_decode() + |> hd() + |> :public_key.pem_entry_decode() + + {:ok, key} + end + + def public_key(_), do: {:error, "key not found"} + + def public_key_pem(%User{signing_key: %__MODULE__{public_key: public_key_pem}}), + do: public_key_pem + + @spec private_key(User.t()) :: {:ok, binary()} | {:error, String.t()} + @doc """ + Given a user, return the private key for that user in binary format. + """ + def private_key(%User{signing_key: %__MODULE__{private_key: private_key_pem}}) do + key = + private_key_pem + |> :public_key.pem_decode() + |> hd() + |> :public_key.pem_entry_decode() + + {:ok, key} + end + + @spec fetch_remote_key(String.t()) :: {:ok, __MODULE__} | {:error, String.t()} + @doc """ + Fetch a remote key by key ID. + Will send a request to the remote instance to get the key ID. + This request should, at the very least, return a user ID and a public key object. + Though bear in mind that some implementations (looking at you, pleroma) may require a signature for this request. + This has the potential to create an infinite loop if the remote instance requires a signature to fetch the key... + So if we're rejected, we should probably just give up. + """ + def fetch_remote_key(key_id) do + resp = HTTP.Backoff.get(key_id) + + case handle_signature_response(resp) do + {:ok, ap_id, public_key_pem} -> + # fetch the user + user = User.get_or_fetch_by_ap_id(ap_id) + # store the key + key = %__MODULE__{ + user_id: user.id, + public_key: public_key_pem, + key_id: key_id + } + + Repo.insert(key) + + _ -> + {:error, "Could not fetch key"} + end + end + + # Take the response from the remote instance and extract the key details + # will check if the key ID matches the owner of the key, if not, error + defp extract_key_details(%{"id" => ap_id, "publicKey" => public_key}) do + if ap_id !== public_key["owner"] do + {:error, "Key ID does not match owner"} + else + %{"publicKeyPem" => public_key_pem} = public_key + {:ok, ap_id, public_key_pem} + end + end + + defp handle_signature_response({:ok, %{status: status, body: body}}) when status in 200..299 do + case Jason.decode(body) do + {:ok, %{"id" => _user_id, "publicKey" => _public_key} = body} -> + extract_key_details(body) + + {:ok, %{"error" => error}} -> + {:error, error} + + {:error, _} -> + {:error, "Could not parse key"} + end + end + + defp handle_signature_response({:error, e}), do: {:error, e} + defp handle_signature_response(other), do: {:error, "Could not fetch key: #{inspect(other)}"} +end diff --git a/priv/repo/migrations/20240625213637_create_signing_key_table.exs b/priv/repo/migrations/20240625213637_create_signing_key_table.exs new file mode 100644 index 000000000..34169f78c --- /dev/null +++ b/priv/repo/migrations/20240625213637_create_signing_key_table.exs @@ -0,0 +1,15 @@ +defmodule Pleroma.Repo.Migrations.CreateSigningKeyTable do + use Ecto.Migration + + def change do + create table(:signing_keys, primary_key: false) do + add :user_id, references(:users, type: :uuid, on_delete: :delete_all) + add :key_id, :text, primary_key: true + add :public_key, :text + add :private_key, :text + timestamps() + end + + create index(:signing_keys, [:key_id]) + end +end diff --git a/priv/repo/migrations/20240625220752_move_signing_keys.exs b/priv/repo/migrations/20240625220752_move_signing_keys.exs new file mode 100644 index 000000000..d9ef1acab --- /dev/null +++ b/priv/repo/migrations/20240625220752_move_signing_keys.exs @@ -0,0 +1,35 @@ +defmodule Pleroma.Repo.Migrations.MoveSigningKeys do + use Ecto.Migration + alias Pleroma.User + alias Pleroma.Repo + import Ecto.Query + + def up do + # we do not handle remote users here! + # because we want to store a key id -> user id mapping, and we don't + # currently store key ids for remote users... + query = + from(u in User) + |> where(local: true) + + Repo.stream(query, timeout: :infinity) + |> Enum.each(fn + %User{id: user_id, keys: private_key, local: true} -> + # we can precompute the public key here... + # we do use it on every user view which makes it a bit of a dos attack vector + # so we should probably cache it + {:ok, public_key} = User.SigningKey.private_pem_to_public_pem(private_key) + + key = %User.SigningKey{ + user_id: user_id, + public_key: public_key, + private_key: private_key + } + + {:ok, _} = Repo.insert(key) + end) + end + + # no need to rollback + def down, do: :ok +end From b0f7da9ce0a35fd08dc4d2101ecda0f4cded50dd Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Thu, 27 Jun 2024 05:06:04 +0100 Subject: [PATCH 23/70] remove now-unused Keys module --- lib/pleroma/keys.ex | 46 --------------------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 lib/pleroma/keys.ex diff --git a/lib/pleroma/keys.ex b/lib/pleroma/keys.ex deleted file mode 100644 index 413861b15..000000000 --- a/lib/pleroma/keys.ex +++ /dev/null @@ -1,46 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Keys do - # Native generation of RSA keys is only available since OTP 20+ and in default build conditions - # We try at compile time to generate natively an RSA key otherwise we fallback on the old way. - try do - _ = :public_key.generate_key({:rsa, 2048, 65_537}) - - def generate_rsa_pem do - key = :public_key.generate_key({:rsa, 2048, 65_537}) - entry = :public_key.pem_entry_encode(:RSAPrivateKey, key) - pem = :public_key.pem_encode([entry]) |> String.trim_trailing() - {:ok, pem} - end - rescue - _ -> - def generate_rsa_pem do - port = Port.open({:spawn, "openssl genrsa"}, [:binary]) - - {:ok, pem} = - receive do - {^port, {:data, pem}} -> {:ok, pem} - end - - Port.close(port) - - if Regex.match?(~r/RSA PRIVATE KEY/, pem) do - {:ok, pem} - else - :error - end - end - end - - def keys_from_pem(pem) do - with [private_key_code] <- :public_key.pem_decode(pem), - private_key <- :public_key.pem_entry_decode(private_key_code), - {:RSAPrivateKey, _, modulus, exponent, _, _, _, _, _, _, _} <- private_key do - {:ok, private_key, {:RSAPublicKey, modulus, exponent}} - else - error -> {:error, error} - end - end -end From 9728e2f8f71e3a33f9f6ae60da79145c09b08e06 Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Thu, 27 Jun 2024 05:06:27 +0100 Subject: [PATCH 24/70] adjust logic to use relation :signing_key --- lib/pleroma/object/fetcher.ex | 1 - lib/pleroma/signature.ex | 48 +++++++---------- lib/pleroma/user.ex | 33 ++++++------ lib/pleroma/web/activity_pub/activity_pub.ex | 26 ++++++++-- .../object_validators/user_validator.ex | 31 +---------- .../web/activity_pub/views/user_view.ex | 23 ++++++--- .../mapped_signature_to_identity_plug.ex | 30 +++++++---- mix.lock | 14 ++--- test/pleroma/keys_test.exs | 24 --------- test/pleroma/user_test.exs | 10 ++-- ...mapped_signature_to_identity_plug_test.exs | 51 +++++++++++-------- test/support/factory.ex | 18 +++++-- 12 files changed, 150 insertions(+), 159 deletions(-) delete mode 100644 test/pleroma/keys_test.exs diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 2cecfec67..897146bce 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -343,7 +343,6 @@ 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() diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex index c4ac2c87e..1374ac068 100644 --- a/lib/pleroma/signature.ex +++ b/lib/pleroma/signature.ex @@ -5,45 +5,33 @@ defmodule Pleroma.Signature do @behaviour HTTPSignatures.Adapter - alias Pleroma.EctoType.ActivityPub.ObjectValidators - alias Pleroma.Keys alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub - - @known_suffixes ["/publickey", "/main-key", "#key"] + alias Pleroma.User.SigningKey def key_id_to_actor_id(key_id) do - uri = - key_id - |> URI.parse() - |> Map.put(:fragment, nil) - |> Map.put(:query, nil) - |> remove_suffix(@known_suffixes) + # Given the key ID, first attempt to look it up in the signing keys table. + # If it's not found, then attempt to look it up via request to the remote instance. + case SigningKey.key_id_to_ap_id(key_id) do + nil -> + # this requires us to look up the url! + request_key_id_from_remote_instance(key_id) - maybe_ap_id = URI.to_string(uri) - - case ObjectValidators.ObjectID.cast(maybe_ap_id) do - {:ok, ap_id} -> - {:ok, ap_id} - - _ -> - case Pleroma.Web.WebFinger.finger(maybe_ap_id) do - {:ok, %{"ap_id" => ap_id}} -> {:ok, ap_id} - _ -> {:error, maybe_ap_id} - end + key -> + {:ok, key} end end - defp remove_suffix(uri, [test | rest]) do - if not is_nil(uri.path) and String.ends_with?(uri.path, test) do - Map.put(uri, :path, String.replace(uri.path, test, "")) - else - remove_suffix(uri, rest) + def request_key_id_from_remote_instance(key_id) do + case SigningKey.fetch_remote_key(key_id) do + {:ok, key_id} -> + {:ok, key_id} + + {:error, _} -> + {:error, "Key ID not found"} end end - defp remove_suffix(uri, []), do: uri - def fetch_public_key(conn) do with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn), {:ok, actor_id} <- key_id_to_actor_id(kid), @@ -67,8 +55,8 @@ def refetch_public_key(conn) do end end - def sign(%User{keys: keys} = user, headers) do - with {:ok, private_key, _} <- Keys.keys_from_pem(keys) do + def sign(%User{} = user, headers) do + with {:ok, private_key} <- SigningKey.private_key(user) do HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers) end end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 2bc3e9ace..593173288 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -25,7 +25,6 @@ defmodule Pleroma.User do alias Pleroma.Hashtag alias Pleroma.User.HashtagFollow alias Pleroma.HTML - alias Pleroma.Keys alias Pleroma.MFA alias Pleroma.Notification alias Pleroma.Object @@ -43,6 +42,7 @@ defmodule Pleroma.User do alias Pleroma.Web.OAuth alias Pleroma.Web.RelMe alias Pleroma.Workers.BackgroundWorker + alias Pleroma.User.SigningKey use Pleroma.Web, :verified_routes @@ -222,6 +222,10 @@ defmodule Pleroma.User do on_replace: :delete ) + # FOR THE FUTURE: We might want to make this a one-to-many relationship + # it's entirely possible right now, but we don't have a use case for it + has_one(:signing_key, SigningKey, foreign_key: :user_id) + timestamps() end @@ -457,6 +461,7 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do |> fix_follower_address() struct + |> Repo.preload(:signing_key) |> cast( params, [ @@ -495,6 +500,7 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do |> validate_required([:ap_id]) |> validate_required([:name], trim: false) |> unique_constraint(:nickname) + |> cast_assoc(:signing_key, with: &SigningKey.remote_changeset/2, required: false) |> validate_format(:nickname, @email_regex) |> validate_length(:bio, max: bio_limit) |> validate_length(:name, max: name_limit) @@ -570,6 +576,7 @@ def update_changeset(struct, params \\ %{}) do :pleroma_settings_store, &{:ok, Map.merge(struct.pleroma_settings_store, &1)} ) + |> cast_assoc(:signing_key, with: &SigningKey.remote_changeset/2, requred: false) |> validate_fields(false, struct) end @@ -828,8 +835,10 @@ def put_following_and_follower_and_featured_address(changeset) do end defp put_private_key(changeset) do - {:ok, pem} = Keys.generate_rsa_pem() - put_change(changeset, :keys, pem) + ap_id = get_field(changeset, :ap_id) + + changeset + |> put_assoc(:signing_key, SigningKey.generate_local_keys(ap_id)) end defp autofollow_users(user) do @@ -2051,24 +2060,16 @@ defp create_service_actor(uri, nickname) do |> set_cache() end - def public_key(%{public_key: public_key_pem}) when is_binary(public_key_pem) do - key = - public_key_pem - |> :public_key.pem_decode() - |> hd() - |> :public_key.pem_entry_decode() - - {:ok, key} - end - - def public_key(_), do: {:error, "key not found"} + defdelegate public_key(user), to: SigningKey def get_public_key_for_ap_id(ap_id) do with {:ok, %User{} = user} <- get_or_fetch_by_ap_id(ap_id), - {:ok, public_key} <- public_key(user) do + {:ok, public_key} <- SigningKey.public_key(user) do {:ok, public_key} else - _ -> :error + e -> + Logger.error("Could not get public key for #{ap_id}.\n#{inspect(e)}") + {:error, e} end end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index c87072300..9b28e64d9 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1547,6 +1547,17 @@ defp normalize_attachment(%{} = attachment), do: [attachment] defp normalize_attachment(attachment) when is_list(attachment), do: attachment defp normalize_attachment(_), do: [] + defp maybe_make_public_key_object(data) do + if is_map(data["publicKey"]) && is_binary(data["publicKey"]["publicKeyPem"]) do + %{ + public_key: data["publicKey"]["publicKeyPem"], + key_id: data["publicKey"]["id"] + } + else + nil + end + end + defp object_to_user_data(data, additional) do fields = data @@ -1578,9 +1589,16 @@ defp object_to_user_data(data, additional) do featured_address = data["featured"] {:ok, pinned_objects} = fetch_and_prepare_featured_from_ap_id(featured_address) - public_key = - if is_map(data["publicKey"]) && is_binary(data["publicKey"]["publicKeyPem"]) do - data["publicKey"]["publicKeyPem"] + # first, check that the owner is correct + signing_key = + if data["id"] !== data["publicKey"]["owner"] do + Logger.error( + "Owner of the public key is not the same as the actor - not saving the public key." + ) + + nil + else + maybe_make_public_key_object(data) end shared_inbox = @@ -1624,7 +1642,7 @@ defp object_to_user_data(data, additional) do bio: data["summary"] || "", actor_type: actor_type, also_known_as: also_known_as, - public_key: public_key, + signing_key: signing_key, inbox: data["inbox"], shared_inbox: shared_inbox, pinned_objects: pinned_objects, diff --git a/lib/pleroma/web/activity_pub/object_validators/user_validator.ex b/lib/pleroma/web/activity_pub/object_validators/user_validator.ex index adb291a55..b80068e37 100644 --- a/lib/pleroma/web/activity_pub/object_validators/user_validator.ex +++ b/lib/pleroma/web/activity_pub/object_validators/user_validator.ex @@ -14,7 +14,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.UserValidator do @behaviour Pleroma.Web.ActivityPub.ObjectValidator.Validating alias Pleroma.Object.Containment - alias Pleroma.Signature require Pleroma.Constants @@ -23,8 +22,7 @@ def validate(object, meta) def validate(%{"type" => type, "id" => _id} = data, meta) when type in Pleroma.Constants.actor_types() do - with :ok <- validate_pubkey(data), - :ok <- validate_inbox(data), + with :ok <- validate_inbox(data), :ok <- contain_collection_origin(data) do {:ok, data, meta} else @@ -35,33 +33,6 @@ def validate(%{"type" => type, "id" => _id} = data, meta) 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 diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 47b8e37e5..d855e1e40 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -5,7 +5,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do use Pleroma.Web, :view - alias Pleroma.Keys alias Pleroma.Object alias Pleroma.Repo alias Pleroma.User @@ -33,9 +32,7 @@ def render("endpoints.json", %{user: %User{local: true} = _user}) do def render("endpoints.json", _), do: %{} def render("service.json", %{user: user}) do - {:ok, _, public_key} = Keys.keys_from_pem(user.keys) - public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) - public_key = :public_key.pem_encode([public_key]) + public_key = User.SigningKey.public_key_pem(user) endpoints = render("endpoints.json", %{user: user}) @@ -70,9 +67,7 @@ def render("user.json", %{user: %User{nickname: "internal." <> _} = user}), do: render("service.json", %{user: user}) |> Map.put("preferredUsername", user.nickname) def render("user.json", %{user: user}) do - {:ok, _, public_key} = Keys.keys_from_pem(user.keys) - public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) - public_key = :public_key.pem_encode([public_key]) + public_key = User.SigningKey.public_key_pem(user) user = User.sanitize_html(user) endpoints = render("endpoints.json", %{user: user}) @@ -116,6 +111,20 @@ def render("user.json", %{user: user}) do |> Map.merge(Utils.make_json_ld_header()) end + def render("keys.json", %{user: user}) do + public_key = User.SigningKey.public_key_pem(user) + + %{ + "id" => user.ap_id, + "publicKey" => %{ + "id" => User.SigningKey.key_id_of_local_user(user), + "owner" => user.ap_id, + "publicKeyPem" => public_key + } + } + |> Map.merge(Utils.make_json_ld_header()) + end + def render("following.json", %{user: user, page: page} = opts) do showing_items = (opts[:for] && opts[:for] == user) || !user.hide_follows showing_count = showing_items || !user.hide_follows_count diff --git a/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex index a73def682..93d269187 100644 --- a/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex +++ b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex @@ -4,7 +4,6 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlug do alias Pleroma.Helpers.AuthHelper - alias Pleroma.Signature alias Pleroma.User alias Pleroma.Web.ActivityPub.Utils @@ -33,7 +32,7 @@ def call(%{assigns: %{valid_signature: true}, params: %{"actor" => actor}} = con |> assign(:valid_signature, false) # remove me once testsuite uses mapped capabilities instead of what we do now - {:user, nil} -> + {:user, _} -> Logger.debug("Failed to map identity from signature (lookup failure)") Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}, actor=#{actor}") @@ -93,22 +92,33 @@ defp only_permit_user_routes(conn) do end defp key_id_from_conn(conn) do - with %{"keyId" => key_id} <- HTTPSignatures.signature_for_conn(conn), - {:ok, ap_id} <- Signature.key_id_to_actor_id(key_id) do - ap_id - else + case HTTPSignatures.signature_for_conn(conn) do + %{"keyId" => key_id} when is_binary(key_id) -> + key_id + _ -> nil end end defp user_from_key_id(conn) do - with key_actor_id when is_binary(key_actor_id) <- key_id_from_conn(conn), - {:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(key_actor_id) do + with {:key_id, key_id} when is_binary(key_id) <- {:key_id, key_id_from_conn(conn)}, + {:mapped_ap_id, ap_id} when is_binary(ap_id) <- + {:mapped_ap_id, User.SigningKey.key_id_to_ap_id(key_id)}, + {:user_fetch, {:ok, %User{} = user}} <- {:user_fetch, User.get_or_fetch_by_ap_id(ap_id)} do user else - _ -> - nil + {:key_id, nil} -> + Logger.debug("Failed to map identity from signature (no key ID)") + {:key_id, nil} + + {:mapped_ap_id, nil} -> + Logger.debug("Failed to map identity from signature (could not map key ID to AP ID)") + {:mapped_ap_id, nil} + + {:user_fetch, {:error, _}} -> + Logger.debug("Failed to map identity from signature (lookup failure)") + {:user_fetch, nil} end end diff --git a/mix.lock b/mix.lock index 09997f29d..92a704add 100644 --- a/mix.lock +++ b/mix.lock @@ -34,14 +34,14 @@ "elasticsearch": {:git, "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", "6cd946f75f6ab9042521a009d1d32d29a90113ca", [ref: "main"]}, "elixir_make": {:hex, :elixir_make, "0.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"}, "elixir_xml_to_map": {:hex, :elixir_xml_to_map, "3.1.0", "4d6260486a8cce59e4bf3575fe2dd2a24766546ceeef9f93fcec6f7c62a2827a", [:mix], [{:erlsom, "~> 1.4", [hex: :erlsom, repo: "hexpm", optional: false]}], "hexpm", "8fe5f2e75f90bab07ee2161120c2dc038ebcae8135554f5582990f1c8c21f911"}, - "erlex": {:hex, :erlex, "0.2.6", "c7987d15e899c7a2f34f5420d2a2ea0d659682c06ac607572df55a43753aa12e", [:mix], [], "hexpm", "2ed2e25711feb44d52b17d2780eabf998452f6efda104877a3881c2f8c0c0c75"}, + "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "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.5.4", "86c5bb870a49e0ab6f5aa5dd58cf505f09d2624ebe17530db3c1b61c88a673af", [: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", "e82bd0091bb9a5bb190139599f922ff3fc7aebcca4374d65c99c4e23aa6d1625"}, "ex_aws_s3": {:hex, :ex_aws_s3, "2.5.3", "422468e5c3e1a4da5298e66c3468b465cfd354b842e512cb1f6fbbe4e2f5bdaf", [: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", "4f09dd372cc386550e484808c5ac5027766c8d0cd8271ccc578b82ee6ef4f3b8"}, "ex_const": {:hex, :ex_const, "0.3.0", "9d79516679991baf540ef445438eef1455ca91cf1a3c2680d8fb9e5bea2fe4de", [:mix], [], "hexpm", "76546322abb9e40ee4a2f454cf1c8a5b25c3672fa79bed1ea52c31e0d2428ca9"}, - "ex_doc": {:hex, :ex_doc, "0.34.0", "ab95e0775db3df71d30cf8d78728dd9261c355c81382bcd4cefdc74610bef13e", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "60734fb4c1353f270c3286df4a0d51e65a2c1d9fba66af3940847cc65a8066d7"}, - "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_doc": {:hex, :ex_doc, "0.34.1", "9751a0419bc15bc7580c73fde506b17b07f6402a1e5243be9e0f05a68c723368", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d441f1a86a235f59088978eff870de2e815e290e44a8bd976fe5d64470a4c9d2"}, + "ex_machina": {:hex, :ex_machina, "2.8.0", "a0e847b5712065055ec3255840e2c78ef9366634d62390839d4880483be38abe", [: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", "79fe1a9c64c0c1c1fab6c4fa5d871682cb90de5885320c187d117004627a7729"}, "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"}, "expo": {:hex, :expo, "0.4.1", "1c61d18a5df197dfda38861673d392e642649a9cef7694d2f97a587b2cfb319b", [:mix], [], "hexpm", "2ff7ba7a798c8c543c12550fa0e2cbc81b95d4974c65855d8d15ba7b37a1ce47"}, @@ -83,10 +83,10 @@ "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, - "oban": {:hex, :oban, "2.17.10", "c3e5bd739b5c3fdc38eba1d43ab270a8c6ca4463bb779b7705c69400b0d87678", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4afd027b8e2bc3c399b54318b4f46ee8c40251fb55a285cb4e38b5363f0ee7c4"}, + "oban": {:hex, :oban, "2.17.11", "7a641f9f737b626030c3e2209b53df6db83740ac5537208bac7d3b9871c2d5e7", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c445c488151939d64265a5efea51973fa0b42ee4ebbb31aa83fac26543b8ac6d"}, "open_api_spex": {:hex, :open_api_spex, "3.19.1", "65ccb5d06e3d664d1eec7c5ea2af2289bd2f37897094a74d7219fb03fc2b5994", [: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 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "392895827ce2984a3459c91a484e70708132d8c2c6c5363972b4b91d6bbac3dd"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, - "phoenix": {:hex, :phoenix, "1.7.12", "1cc589e0eab99f593a8aa38ec45f15d25297dd6187ee801c8de8947090b5a9d3", [: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.7", [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", "d646192fbade9f485b01bc9920c139bfdd19d0f8df3d73fd8eaf2dfbe0d2837c"}, + "phoenix": {:hex, :phoenix, "1.7.14", "a7d0b3f1bc95987044ddada111e77bd7f75646a08518942c72a8440278ae7825", [: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.7", [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", "c7859bc56cc5dfef19ecfc240775dae358cbaa530231118a9e014df392ace61a"}, "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.1", "96798325fab2fed5a824ca204e877b81f9afd2e480f581e81f7b4b64a5a477f2", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.17", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "0ae544ff99f3c482b0807c5cec2c8289e810ecacabc04959d82c3337f4703391"}, "phoenix_html": {:hex, :phoenix_html, "3.3.4", "42a09fc443bbc1da37e372a5c8e6755d046f22b9b11343bf885067357da21cb3", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0249d3abec3714aff3415e7ee3d9786cb325be3151e6c4b3021502c585bf53fb"}, "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"}, @@ -95,7 +95,7 @@ "phoenix_swoosh": {:hex, :phoenix_swoosh, "1.2.1", "b74ccaa8046fbc388a62134360ee7d9742d5a8ae74063f34eb050279de7a99e1", [: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 or ~> 4.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", "4000eeba3f9d7d1a6bf56d2bd56733d5cadf41a7f0d8ffe5bb67e7d667e204a2"}, "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [: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", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"}, - "plug": {:hex, :plug, "1.16.0", "1d07d50cb9bb05097fdf187b31cf087c7297aafc3fed8299aac79c128a707e47", [: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", "cbf53aa1f5c4d758a7559c0bd6d59e286c2be0c6a1fac8cc3eee2f638243b93e"}, + "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [: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", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, "plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [: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", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"}, "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, "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"}, @@ -120,7 +120,7 @@ "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.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"}, "temple": {:git, "https://akkoma.dev/AkkomaGang/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]}, - "tesla": {:hex, :tesla, "1.9.0", "8c22db6a826e56a087eeb8cdef56889731287f53feeb3f361dec5d4c8efb6f14", [: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", "7c240c67e855f7e63e795bf16d6b3f5115a81d1f44b7fe4eadbf656bae0fef8a"}, + "tesla": {:hex, :tesla, "1.11.0", "81b2b10213dddb27105ec6102d9eb0cc93d7097a918a0b1594f2dfd1a4601190", [: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", "b83ab5d4c2d202e1ea2b7e17a49f788d49a699513d7c4f08f2aef2c281be69db"}, "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"}, diff --git a/test/pleroma/keys_test.exs b/test/pleroma/keys_test.exs deleted file mode 100644 index 9a15bf06e..000000000 --- a/test/pleroma/keys_test.exs +++ /dev/null @@ -1,24 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2021 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.KeysTest do - use Pleroma.DataCase, async: true - - alias Pleroma.Keys - - test "generates an RSA private key pem" do - {:ok, key} = Keys.generate_rsa_pem() - - assert is_binary(key) - assert Regex.match?(~r/RSA/, key) - end - - test "returns a public and private key from a pem" do - pem = File.read!("test/fixtures/private_key.pem") - {:ok, private, public} = Keys.keys_from_pem(pem) - - assert elem(private, 0) == :RSAPrivateKey - assert elem(public, 0) == :RSAPublicKey - end -end diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index e3f0bb415..207e38993 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -639,11 +639,12 @@ test "it sets the password_hash, ap_id, private key and followers collection add changeset = User.register_changeset(%User{}, @full_user_data) assert changeset.valid? - assert is_binary(changeset.changes[:password_hash]) - assert is_binary(changeset.changes[:keys]) assert changeset.changes[:ap_id] == User.ap_id(%User{nickname: @full_user_data.nickname}) - assert is_binary(changeset.changes[:keys]) + assert changeset.changes[:signing_key] + assert changeset.changes[:signing_key].valid? + assert is_binary(changeset.changes[:signing_key].changes.private_key) + assert is_binary(changeset.changes[:signing_key].changes.public_key) assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers" end @@ -1665,7 +1666,6 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do name: "qqqqqqq", password_hash: "pdfk2$1b3n159001", keys: "RSA begin buplic key", - public_key: "--PRIVATE KEYE--", avatar: %{"a" => "b"}, tags: ["qqqqq"], banner: %{"a" => "b"}, @@ -1704,8 +1704,6 @@ test "delete/1 purges a user when they wouldn't be fully deleted" do email: nil, name: nil, password_hash: nil, - keys: "RSA begin buplic key", - public_key: "--PRIVATE KEYE--", avatar: %{}, tags: [], last_refreshed_at: nil, diff --git a/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs b/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs index 086a27885..cfcccbf9e 100644 --- a/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs +++ b/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs @@ -8,52 +8,58 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlugTest do import Tesla.Mock import Plug.Conn + import Pleroma.Factory import Pleroma.Tests.Helpers, only: [clear_config: 2] setup do mock(fn env -> apply(HttpRequestMock, :request, [env]) end) - :ok + user = insert(:user) + {:ok, %{user: user}} end - defp set_signature(conn, key_id) do + defp set_signature(conn, ap_id) do conn - |> put_req_header("signature", "keyId=\"#{key_id}\"") + |> put_req_header("signature", "keyId=\"#{ap_id}#main-key\"") |> assign(:valid_signature, true) end - test "it successfully maps a valid identity with a valid signature" do + test "it successfully maps a valid identity with a valid signature", %{user: user} do conn = build_conn(:get, "/doesntmattter") - |> set_signature("http://mastodon.example.org/users/admin") + |> set_signature(user.ap_id) |> MappedSignatureToIdentityPlug.call(%{}) refute is_nil(conn.assigns.user) end - test "it successfully maps a valid identity with a valid signature with payload" do + test "it successfully maps a valid identity with a valid signature with payload", %{user: user} do conn = - build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"}) - |> set_signature("http://mastodon.example.org/users/admin") + build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id}) + |> set_signature(user.ap_id) |> MappedSignatureToIdentityPlug.call(%{}) refute is_nil(conn.assigns.user) end - test "it considers a mapped identity to be invalid when it mismatches a payload" do + test "it considers a mapped identity to be invalid when it mismatches a payload", %{user: user} do conn = - build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"}) + build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id}) |> set_signature("https://niu.moe/users/rye") |> MappedSignatureToIdentityPlug.call(%{}) assert %{valid_signature: false} == conn.assigns end - test "it considers a mapped identity to be invalid when the associated instance is blocked" do + test "it considers a mapped identity to be invalid when the associated instance is blocked", %{ + user: user + } do clear_config([:activitypub, :authorized_fetch_mode], true) + # extract domain from user.ap_id + url = URI.parse(user.ap_id) clear_config([:mrf_simple, :reject], [ - {"mastodon.example.org", "anime is banned"} + {url.host, "anime is banned"} ]) on_exit(fn -> @@ -62,18 +68,20 @@ test "it considers a mapped identity to be invalid when the associated instance end) conn = - build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"}) - |> set_signature("http://mastodon.example.org/users/admin") + build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id}) + |> set_signature(user.ap_id) |> MappedSignatureToIdentityPlug.call(%{}) assert %{valid_signature: false} == conn.assigns end - test "allowlist federation: it considers a mapped identity to be valid when the associated instance is allowed" do + test "allowlist federation: it considers a mapped identity to be valid when the associated instance is allowed", + %{user: user} do clear_config([:activitypub, :authorized_fetch_mode], true) + url = URI.parse(user.ap_id) clear_config([:mrf_simple, :accept], [ - {"mastodon.example.org", "anime is allowed"} + {url.host, "anime is allowed"} ]) on_exit(fn -> @@ -82,15 +90,16 @@ test "allowlist federation: it considers a mapped identity to be valid when the end) conn = - build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"}) - |> set_signature("http://mastodon.example.org/users/admin") + build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id}) + |> set_signature(user.ap_id) |> MappedSignatureToIdentityPlug.call(%{}) assert conn.assigns[:valid_signature] refute is_nil(conn.assigns.user) end - test "allowlist federation: it considers a mapped identity to be invalid when the associated instance is not allowed" do + test "allowlist federation: it considers a mapped identity to be invalid when the associated instance is not allowed", + %{user: user} do clear_config([:activitypub, :authorized_fetch_mode], true) clear_config([:mrf_simple, :accept], [ @@ -103,8 +112,8 @@ test "allowlist federation: it considers a mapped identity to be invalid when th end) conn = - build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"}) - |> set_signature("http://mastodon.example.org/users/admin") + build_conn(:post, "/doesntmattter", %{"actor" => user.ap_id}) + |> set_signature(user.ap_id) |> MappedSignatureToIdentityPlug.call(%{}) assert %{valid_signature: false} == conn.assigns diff --git a/test/support/factory.ex b/test/support/factory.ex index 2a73a4ae6..f648179d2 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -47,7 +47,6 @@ def instance_factory(attrs \\ %{}) do end def user_factory(attrs \\ %{}) do - pem = Enum.random(@rsa_keys) # Argon2.hash_pwd_salt("test") # it really eats CPU time, so we use a precomputed hash password_hash = @@ -64,8 +63,7 @@ def user_factory(attrs \\ %{}) do last_refreshed_at: NaiveDateTime.utc_now(), notification_settings: %Pleroma.User.NotificationSetting{}, multi_factor_authentication_settings: %Pleroma.MFA.Settings{}, - ap_enabled: true, - keys: pem + ap_enabled: true } urls = @@ -90,13 +88,27 @@ def user_factory(attrs \\ %{}) do end attrs = Map.delete(attrs, :domain) + signing_key = insert(:signing_key, %{key_id: urls[:ap_id] <> "#main-key"}) user |> Map.put(:raw_bio, user.bio) + |> Map.put(:signing_key, signing_key) |> Map.merge(urls) |> merge_attributes(attrs) end + def signing_key_factory(attrs \\ %{}) do + pem = Enum.random(@rsa_keys) + {:ok, public_key} = Pleroma.User.SigningKey.private_pem_to_public_pem(pem) + + %Pleroma.User.SigningKey{ + public_key: public_key, + private_key: pem, + key_id: attrs[:key_id] || "https://example.com/key" + } + |> merge_attributes(attrs) + end + def user_relationship_factory(attrs \\ %{}) do source = attrs[:source] || insert(:user) target = attrs[:target] || insert(:user) From 9c876cea21373e9d17742abc04f4657f73cd359e Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Thu, 27 Jun 2024 06:58:05 +0100 Subject: [PATCH 25/70] Fix some tests --- lib/pleroma/user.ex | 3 ++- lib/pleroma/user/signing_key.ex | 5 ++++ mix.exs | 2 +- .../activity_pub_controller_test.exs | 25 +++++++++++++------ ...mapped_signature_to_identity_plug_test.exs | 2 ++ test/support/factory.ex | 15 ++++++----- 6 files changed, 36 insertions(+), 16 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 593173288..05d3011cb 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1155,7 +1155,8 @@ def update_and_set_cache(%{data: %Pleroma.User{} = user} = changeset) do was_superuser_before_update = User.superuser?(user) with {:ok, user} <- Repo.update(changeset, stale_error_field: :id) do - set_cache(user) + user + |> set_cache() end |> maybe_remove_report_notifications(was_superuser_before_update) end diff --git a/lib/pleroma/user/signing_key.ex b/lib/pleroma/user/signing_key.ex index 92ce7ae5d..3b5f4c35d 100644 --- a/lib/pleroma/user/signing_key.ex +++ b/lib/pleroma/user/signing_key.ex @@ -18,6 +18,11 @@ defmodule Pleroma.User.SigningKey do timestamps() end + def load_key(%User{} = user) do + user + |> Repo.preload(:signing_key) + end + def key_id_of_local_user(%User{local: true, signing_key: %__MODULE__{key_id: key_id}}), do: key_id diff --git a/mix.exs b/mix.exs index 7ffc450e2..418db47a1 100644 --- a/mix.exs +++ b/mix.exs @@ -200,7 +200,7 @@ defp deps do ## dev & test {:ex_doc, "~> 0.30", only: :dev, runtime: false}, - {:ex_machina, "~> 2.7", only: :test}, + {:ex_machina, "~> 2.8", only: :test}, {:credo, "~> 1.7", only: [:dev, :test], runtime: false}, {:mock, "~> 0.3.8", only: :test}, {:excoveralls, "0.16.1", only: :test}, 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 b325bcb9a..26b6f8e47 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -584,6 +584,7 @@ test "it inserts an incoming activity into the database" <> local: false, last_refreshed_at: nil ) + |> with_signing_key() data = File.read!("test/fixtures/mastodon-post-activity.json") @@ -594,7 +595,7 @@ test "it inserts an incoming activity into the database" <> conn = conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{user.ap_id}/main-key\"") + |> put_req_header("signature", "keyId=\"#{user.signing_key.key_id}\"") |> put_req_header("content-type", "application/activity+json") |> post("/inbox", data) @@ -609,6 +610,7 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn} do sender_url = data["actor"] sender = insert(:user, ap_id: data["actor"]) + |> with_signing_key() Instances.set_consistently_unreachable(sender_url) refute Instances.reachable?(sender_url) @@ -616,7 +618,7 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn} do conn = conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{sender.ap_id}/main-key\"") + |> put_req_header("signature", "keyId=\"#{sender.signing_key.key_id}\"") |> put_req_header("content-type", "application/activity+json") |> post("/inbox", data) @@ -784,6 +786,7 @@ test "mastodon pin/unpin", %{conn: conn} do ap_id: actor, featured_address: "https://example.com/users/lain/collections/featured" ) + |> with_signing_key() Tesla.Mock.mock(fn %{ @@ -839,7 +842,7 @@ test "mastodon pin/unpin", %{conn: conn} do assert "ok" == conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{sender.ap_id}/main-key\"") + |> put_req_header("signature", "keyId=\"#{sender.signing_key.key_id}\"") |> put_req_header("content-type", "application/activity+json") |> post("/inbox", data) |> json_response(200) @@ -974,6 +977,7 @@ test "it accepts announces with to as string instead of array", %{conn: conn} do {:ok, post} = CommonAPI.post(user, %{status: "hey"}) announcer = insert(:user, local: false) + |> with_signing_key() data = %{ "@context" => "https://www.w3.org/ns/activitystreams", @@ -988,7 +992,7 @@ test "it accepts announces with to as string instead of array", %{conn: conn} do conn = conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{announcer.ap_id}/main-key\"") + |> put_req_header("signature", "keyId=\"#{announcer.signing_key.key_id}\"") |> put_req_header("content-type", "application/activity+json") |> post("/users/#{user.nickname}/inbox", data) @@ -1004,6 +1008,7 @@ test "it accepts messages from actors that are followed by the user", %{ } do recipient = insert(:user) actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"}) + |> with_signing_key() {:ok, recipient, actor} = User.follow(recipient, actor) @@ -1019,7 +1024,7 @@ test "it accepts messages from actors that are followed by the user", %{ conn = conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{actor.ap_id}/main-key\"") + |> put_req_header("signature", "keyId=\"#{actor.signing_key.key_id}\"") |> put_req_header("content-type", "application/activity+json") |> post("/users/#{recipient.nickname}/inbox", data) @@ -1057,6 +1062,7 @@ test "it returns a note activity in a collection", %{conn: conn} do test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do user = insert(:user) + |> with_signing_key() data = Map.put(data, "bcc", [user.ap_id]) sender_host = URI.parse(data["actor"]).host @@ -1066,7 +1072,7 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn, da conn = conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{data["actor"]}/main-key\"") + |> put_req_header("signature", "keyId=\"#{user.signing_key.key_id}\"") |> put_req_header("content-type", "application/activity+json") |> post("/users/#{user.nickname}/inbox", data) @@ -1077,6 +1083,8 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn, da @tag capture_log: true test "it removes all follower collections but actor's", %{conn: conn} do [actor, recipient] = insert_pair(:user) + actor = with_signing_key(actor) + to = [ recipient.ap_id, @@ -1105,7 +1113,7 @@ test "it removes all follower collections but actor's", %{conn: conn} do conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{actor.ap_id}/main-key\"") + |> put_req_header("signature", "keyId=\"#{actor.signing_key.key_id}\"") |> put_req_header("content-type", "application/activity+json") |> post("/users/#{recipient.nickname}/inbox", data) |> json_response(200) @@ -1142,6 +1150,7 @@ test "it requires authentication", %{conn: conn} do test "forwarded report", %{conn: conn} do admin = insert(:user, is_admin: true) actor = insert(:user, local: false) + |> with_signing_key() remote_domain = URI.parse(actor.ap_id).host reported_user = insert(:user) @@ -1198,7 +1207,7 @@ test "forwarded report", %{conn: conn} do conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{actor.ap_id}/main-key\"") + |> put_req_header("signature", "keyId=\"#{actor.signing_key.key_id}\"") |> put_req_header("content-type", "application/activity+json") |> post("/users/#{reported_user.nickname}/inbox", data) |> json_response(200) diff --git a/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs b/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs index cfcccbf9e..77b14bbd2 100644 --- a/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs +++ b/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs @@ -58,6 +58,7 @@ test "it considers a mapped identity to be invalid when the associated instance # extract domain from user.ap_id url = URI.parse(user.ap_id) + clear_config([:mrf_simple, :reject], [ {url.host, "anime is banned"} ]) @@ -80,6 +81,7 @@ test "allowlist federation: it considers a mapped identity to be valid when the clear_config([:activitypub, :authorized_fetch_mode], true) url = URI.parse(user.ap_id) + clear_config([:mrf_simple, :accept], [ {url.host, "anime is allowed"} ]) diff --git a/test/support/factory.ex b/test/support/factory.ex index f648179d2..6689cc5d4 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -88,25 +88,28 @@ def user_factory(attrs \\ %{}) do end attrs = Map.delete(attrs, :domain) - signing_key = insert(:signing_key, %{key_id: urls[:ap_id] <> "#main-key"}) - user |> Map.put(:raw_bio, user.bio) - |> Map.put(:signing_key, signing_key) |> Map.merge(urls) |> merge_attributes(attrs) end + def with_signing_key(%User{} = user) do + signing_key = build(:signing_key, %{user: user, key_id: "#{user.ap_id}#main-key"}) + insert(signing_key) + %{user | signing_key: signing_key} + end + def signing_key_factory(attrs \\ %{}) do pem = Enum.random(@rsa_keys) + user = attrs[:user] || insert(:user) {:ok, public_key} = Pleroma.User.SigningKey.private_pem_to_public_pem(pem) - %Pleroma.User.SigningKey{ + user_id: user.id, public_key: public_key, private_key: pem, - key_id: attrs[:key_id] || "https://example.com/key" + key_id: attrs[:key_id] } - |> merge_attributes(attrs) end def user_relationship_factory(attrs \\ %{}) do From 8f322456a0eb3416fb8f5cf9c720c618c2c16f72 Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Fri, 28 Jun 2024 03:00:10 +0100 Subject: [PATCH 26/70] Allow unsigned fetches of a user's public key --- lib/pleroma/user/signing_key.ex | 22 +++++++++--- .../activity_pub/activity_pub_controller.ex | 34 ++++++++++++++++++- .../web/activity_pub/views/user_view.ex | 6 ++-- .../web/plugs/ensure_user_public_key_plug.ex | 33 ++++++++++++++++++ lib/pleroma/web/router.ex | 9 ++++- 5 files changed, 94 insertions(+), 10 deletions(-) create mode 100644 lib/pleroma/web/plugs/ensure_user_public_key_plug.ex diff --git a/lib/pleroma/user/signing_key.ex b/lib/pleroma/user/signing_key.ex index 3b5f4c35d..1568b5487 100644 --- a/lib/pleroma/user/signing_key.ex +++ b/lib/pleroma/user/signing_key.ex @@ -23,8 +23,12 @@ def load_key(%User{} = user) do |> Repo.preload(:signing_key) end - def key_id_of_local_user(%User{local: true, signing_key: %__MODULE__{key_id: key_id}}), - do: key_id + def key_id_of_local_user(%User{local: true} = user) do + case Repo.preload(user, :signing_key) do + %User{signing_key: %__MODULE__{key_id: key_id}} -> key_id + _ -> nil + end + end @spec remote_changeset(__MODULE__, map) :: Changeset.t() def remote_changeset(%__MODULE__{} = signing_key, attrs) do @@ -119,9 +123,16 @@ def public_key(%User{signing_key: %__MODULE__{public_key: public_key_pem}}) do def public_key(_), do: {:error, "key not found"} - def public_key_pem(%User{signing_key: %__MODULE__{public_key: public_key_pem}}), - do: public_key_pem + def public_key_pem(%User{} = user) do + case Repo.preload(user, :signing_key) do + %User{signing_key: %__MODULE__{public_key: public_key_pem}} -> {:ok, public_key_pem} + _ -> {:error, "key not found"} + end + end + def public_key_pem(e) do + {:error, "key not found"} + end @spec private_key(User.t()) :: {:ok, binary()} | {:error, String.t()} @doc """ Given a user, return the private key for that user in binary format. @@ -146,7 +157,8 @@ def private_key(%User{signing_key: %__MODULE__{private_key: private_key_pem}}) d So if we're rejected, we should probably just give up. """ def fetch_remote_key(key_id) do - resp = HTTP.Backoff.get(key_id) + # we should probably sign this, just in case + resp = Pleroma.Object.Fetcher.get_object(key_id) case handle_signature_response(resp) do {:ok, ap_id, public_key_pem} -> diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 6642f7771..dff3e1f8e 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -60,7 +60,27 @@ defp relay_active?(conn, _) do end end - def user(conn, %{"nickname" => nickname}) do + + @doc """ + Render the user's AP data + WARNING: we cannot actually check if the request has a fragment! so let's play defensively + - IF we have a valid signature, serve full user + - IF we do not, and authorized_fetch_mode is enabled, serve the key only + - OTHERWISE, serve the full actor (since we don't need to worry about the signature) + """ + def user(%{assigns: %{valid_signature: true}} = conn, params) do + render_full_user(conn, params) + end + + def user(conn, params) do + if Pleroma.Config.get([:activitypub, :authorized_fetch_mode], false) do + render_key_only_user(conn, params) + else + render_full_user(conn, params) + end + end + + defp render_full_user(conn, %{"nickname" => nickname}) do with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do conn |> put_resp_content_type("application/activity+json") @@ -72,6 +92,18 @@ def user(conn, %{"nickname" => nickname}) do end end + def render_key_only_user(conn, %{"nickname" => nickname}) do + with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do + conn + |> put_resp_content_type("application/activity+json") + |> put_view(UserView) + |> render("keys.json", %{user: user}) + else + nil -> {:error, :not_found} + %{local: false} -> {:error, :not_found} + end + end + def object(%{assigns: assigns} = conn, _) do with ap_id <- Endpoint.url() <> conn.request_path, %Object{} = object <- Object.get_cached_by_ap_id(ap_id), diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index d855e1e40..f91810fb8 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -32,7 +32,7 @@ def render("endpoints.json", %{user: %User{local: true} = _user}) do def render("endpoints.json", _), do: %{} def render("service.json", %{user: user}) do - public_key = User.SigningKey.public_key_pem(user) + {:ok, public_key} = User.SigningKey.public_key_pem(user) endpoints = render("endpoints.json", %{user: user}) @@ -67,7 +67,7 @@ def render("user.json", %{user: %User{nickname: "internal." <> _} = user}), do: render("service.json", %{user: user}) |> Map.put("preferredUsername", user.nickname) def render("user.json", %{user: user}) do - public_key = User.SigningKey.public_key_pem(user) + {:ok, public_key} = User.SigningKey.public_key_pem(user) user = User.sanitize_html(user) endpoints = render("endpoints.json", %{user: user}) @@ -112,7 +112,7 @@ def render("user.json", %{user: user}) do end def render("keys.json", %{user: user}) do - public_key = User.SigningKey.public_key_pem(user) + {:ok, public_key} = User.SigningKey.public_key_pem(user) %{ "id" => user.ap_id, diff --git a/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex b/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex new file mode 100644 index 000000000..baebcec29 --- /dev/null +++ b/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex @@ -0,0 +1,33 @@ +defmodule Pleroma.Web.Plugs.EnsureUserPublicKeyPlug do + @moduledoc """ + This plug will attempt to pull in a user's public key if we do not have it. + We _should_ be able to request the URL from the key URL... + """ + + import Plug.Conn + + alias Pleroma.User + + def init(options), do: options + + def call(conn, _opts) do + key_id = key_id_from_conn(conn) + unless is_nil(key_id) do + SigningKey.fetch_remote_key(key_id) + # now we SHOULD have the user that owns the key locally. maybe. + # if we don't, we'll error out when we try to validate. + end + + conn + end + + defp key_id_from_conn(conn) do + case HTTPSignatures.signature_for_conn(conn) do + %{"keyId" => key_id} when is_binary(key_id) -> + key_id + + _ -> + nil + end + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 49ab3540b..c227d0d4a 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -144,7 +144,14 @@ defmodule Pleroma.Web.Router do }) end + pipeline :optional_http_signature do + plug(Pleroma.Web.Plugs.EnsureUserPublicKeyPlug) + plug(Pleroma.Web.Plugs.HTTPSignaturePlug) + plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug) + end + pipeline :http_signature do + plug(Pleroma.Web.Plugs.EnsureUserPublicKeyPlug) plug(Pleroma.Web.Plugs.HTTPSignaturePlug) plug(Pleroma.Web.Plugs.MappedSignatureToIdentityPlug) plug(Pleroma.Web.Plugs.EnsureHTTPSignaturePlug) @@ -745,7 +752,7 @@ defmodule Pleroma.Web.Router do scope "/", Pleroma.Web do # Note: html format is supported only if static FE is enabled # Note: http signature is only considered for json requests (no auth for non-json requests) - pipe_through([:accepts_html_xml_json, :http_signature, :static_fe]) + pipe_through([:accepts_html_xml_json, :optional_http_signature, :static_fe]) # Note: returns user _profile_ for json requests, redirects to user _feed_ for non-json ones get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed) From 6da783b84d628a4cc71408e26e6834d029d7299d Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Fri, 28 Jun 2024 03:22:11 +0100 Subject: [PATCH 27/70] Fix http signature plug tests --- lib/pleroma/user/signing_key.ex | 21 ++++++++---- .../activity_pub/activity_pub_controller.ex | 1 - .../web/plugs/ensure_user_public_key_plug.ex | 1 + lib/pleroma/web/plugs/http_signature_plug.ex | 13 +++++--- .../activity_pub_controller_test.exs | 32 ++++++++++++------- .../web/plugs/http_signature_plug_test.exs | 15 +++++++-- test/support/factory.ex | 7 +++- 7 files changed, 63 insertions(+), 27 deletions(-) diff --git a/lib/pleroma/user/signing_key.ex b/lib/pleroma/user/signing_key.ex index 1568b5487..3d9b476f0 100644 --- a/lib/pleroma/user/signing_key.ex +++ b/lib/pleroma/user/signing_key.ex @@ -111,14 +111,20 @@ def private_pem_to_public_pem(private_pem) do @doc """ Given a user, return the public key for that user in binary format. """ - def public_key(%User{signing_key: %__MODULE__{public_key: public_key_pem}}) do - key = - public_key_pem - |> :public_key.pem_decode() - |> hd() - |> :public_key.pem_entry_decode() + def public_key(%User{} = user) do + case Repo.preload(user, :signing_key) do + %User{signing_key: %__MODULE__{public_key: public_key_pem}} -> + key = + public_key_pem + |> :public_key.pem_decode() + |> hd() + |> :public_key.pem_entry_decode() - {:ok, key} + {:ok, key} + + _ -> + {:error, "key not found"} + end end def public_key(_), do: {:error, "key not found"} @@ -133,6 +139,7 @@ def public_key_pem(%User{} = user) do def public_key_pem(e) do {:error, "key not found"} end + @spec private_key(User.t()) :: {:ok, binary()} | {:error, String.t()} @doc """ Given a user, return the private key for that user in binary format. diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index dff3e1f8e..9c6b3655d 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -60,7 +60,6 @@ defp relay_active?(conn, _) do end end - @doc """ Render the user's AP data WARNING: we cannot actually check if the request has a fragment! so let's play defensively diff --git a/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex b/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex index baebcec29..c59053854 100644 --- a/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex +++ b/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex @@ -12,6 +12,7 @@ def init(options), do: options def call(conn, _opts) do key_id = key_id_from_conn(conn) + unless is_nil(key_id) do SigningKey.fetch_remote_key(key_id) # now we SHOULD have the user that owns the key locally. maybe. diff --git a/lib/pleroma/web/plugs/http_signature_plug.ex b/lib/pleroma/web/plugs/http_signature_plug.ex index e3ae99636..06527cada 100644 --- a/lib/pleroma/web/plugs/http_signature_plug.ex +++ b/lib/pleroma/web/plugs/http_signature_plug.ex @@ -139,12 +139,17 @@ defp maybe_require_signature( defp maybe_require_signature(conn), do: conn defp signature_host(conn) do - with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn), - {:ok, actor_id} <- Signature.key_id_to_actor_id(kid) do + with {:key_id, %{"keyId" => kid}} <- {:key_id, HTTPSignatures.signature_for_conn(conn)}, + {:actor_id, {:ok, actor_id}} <- {:actor_id, Signature.key_id_to_actor_id(kid)} do actor_id else - e -> - {:error, e} + {:key_id, e} -> + Logger.error("Failed to extract key_id from signature: #{inspect(e)}") + nil + + {:actor_id, e} -> + Logger.error("Failed to extract actor_id from signature: #{inspect(e)}") + nil end end end diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs index 26b6f8e47..d67b59ef2 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -609,8 +609,10 @@ test "it clears `unreachable` federation status of the sender", %{conn: conn} do data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!() sender_url = data["actor"] - sender = insert(:user, ap_id: data["actor"]) - |> with_signing_key() + + sender = + insert(:user, ap_id: data["actor"]) + |> with_signing_key() Instances.set_consistently_unreachable(sender_url) refute Instances.reachable?(sender_url) @@ -976,8 +978,10 @@ test "it accepts announces with to as string instead of array", %{conn: conn} do user = insert(:user) {:ok, post} = CommonAPI.post(user, %{status: "hey"}) - announcer = insert(:user, local: false) - |> with_signing_key() + + announcer = + insert(:user, local: false) + |> with_signing_key() data = %{ "@context" => "https://www.w3.org/ns/activitystreams", @@ -1007,8 +1011,10 @@ test "it accepts messages from actors that are followed by the user", %{ data: data } do recipient = insert(:user) - actor = insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"}) - |> with_signing_key() + + actor = + insert(:user, %{ap_id: "http://mastodon.example.org/users/actor"}) + |> with_signing_key() {:ok, recipient, actor} = User.follow(recipient, actor) @@ -1061,8 +1067,10 @@ test "it returns a note activity in a collection", %{conn: conn} do end test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do - user = insert(:user) - |> with_signing_key() + user = + insert(:user) + |> with_signing_key() + data = Map.put(data, "bcc", [user.ap_id]) sender_host = URI.parse(data["actor"]).host @@ -1085,7 +1093,6 @@ test "it removes all follower collections but actor's", %{conn: conn} do [actor, recipient] = insert_pair(:user) actor = with_signing_key(actor) - to = [ recipient.ap_id, recipient.follower_address, @@ -1149,8 +1156,11 @@ test "it requires authentication", %{conn: conn} do @tag capture_log: true test "forwarded report", %{conn: conn} do admin = insert(:user, is_admin: true) - actor = insert(:user, local: false) - |> with_signing_key() + + actor = + insert(:user, local: false) + |> with_signing_key() + remote_domain = URI.parse(actor.ap_id).host reported_user = insert(:user) diff --git a/test/pleroma/web/plugs/http_signature_plug_test.exs b/test/pleroma/web/plugs/http_signature_plug_test.exs index 0a602424d..7f623bfca 100644 --- a/test/pleroma/web/plugs/http_signature_plug_test.exs +++ b/test/pleroma/web/plugs/http_signature_plug_test.exs @@ -14,6 +14,15 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do import Phoenix.Controller, only: [put_format: 2] import Mock + setup do + user = + :user + |> insert(%{ ap_id: "http://mastodon.example.org/users/admin" }) + |> with_signing_key(%{ key_id: "http://mastodon.example.org/users/admin#main-key" }) + + {:ok, %{user: user}} + end + setup_with_mocks([ {HTTPSignatures, [], [ @@ -46,15 +55,15 @@ defp submit_to_plug(host, method, path) do |> HTTPSignaturePlug.call(%{}) end - test "it call HTTPSignatures to check validity if the actor signed it" do - params = %{"actor" => "http://mastodon.example.org/users/admin"} + test "it call HTTPSignatures to check validity if the actor signed it", %{user: user} do + params = %{"actor" => user.ap_id} conn = build_conn(:get, "/doesntmattter", params) conn = conn |> put_req_header( "signature", - "keyId=\"http://mastodon.example.org/users/admin#main-key" + "keyId=\"#{user.signing_key.key_id}\"" ) |> put_format("activity+json") |> HTTPSignaturePlug.call(%{}) diff --git a/test/support/factory.ex b/test/support/factory.ex index 6689cc5d4..1f54c5b7a 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -88,14 +88,18 @@ def user_factory(attrs \\ %{}) do end attrs = Map.delete(attrs, :domain) + user |> Map.put(:raw_bio, user.bio) |> Map.merge(urls) |> merge_attributes(attrs) end - def with_signing_key(%User{} = user) do + def with_signing_key(%User{} = user, attrs \\ %{}) do + signing_key = build(:signing_key, %{user: user, key_id: "#{user.ap_id}#main-key"}) + |> merge_attributes(attrs) + insert(signing_key) %{user | signing_key: signing_key} end @@ -104,6 +108,7 @@ def signing_key_factory(attrs \\ %{}) do pem = Enum.random(@rsa_keys) user = attrs[:user] || insert(:user) {:ok, public_key} = Pleroma.User.SigningKey.private_pem_to_public_pem(pem) + %Pleroma.User.SigningKey{ user_id: user.id, public_key: public_key, From ccf1007883e24b490608abdbdd4f13cba8921498 Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Sun, 30 Jun 2024 04:25:55 +0100 Subject: [PATCH 28/70] Fix about a million tests --- lib/pleroma/signature.ex | 20 ++--- lib/pleroma/user/signing_key.ex | 49 +++++++++--- .../web/activity_pub/views/user_view.ex | 7 +- .../web/plugs/ensure_user_public_key_plug.ex | 2 +- ...0240625213637_create_signing_key_table.exs | 2 +- test/mix/tasks/pleroma/database_test.exs | 3 +- test/pleroma/signature_test.exs | 75 ++++--------------- test/pleroma/user_search_test.exs | 2 +- .../activity_pub_controller_test.exs | 27 ++++++- .../web/activity_pub/publisher_test.exs | 10 +++ .../web/plugs/http_signature_plug_test.exs | 4 +- ...mapped_signature_to_identity_plug_test.exs | 6 +- test/support/factory.ex | 10 +-- test/support/helpers.ex | 1 + test/support/http_request_mock.ex | 27 +++++++ test/support/matching_helpers.ex | 9 +++ 16 files changed, 154 insertions(+), 100 deletions(-) create mode 100644 test/support/matching_helpers.ex diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex index 1374ac068..cedfee2e5 100644 --- a/lib/pleroma/signature.ex +++ b/lib/pleroma/signature.ex @@ -8,32 +8,24 @@ defmodule Pleroma.Signature do alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.User.SigningKey + require Logger def key_id_to_actor_id(key_id) do # Given the key ID, first attempt to look it up in the signing keys table. - # If it's not found, then attempt to look it up via request to the remote instance. case SigningKey.key_id_to_ap_id(key_id) do nil -> - # this requires us to look up the url! - request_key_id_from_remote_instance(key_id) + # hm, we SHOULD have gotten this in the pipeline before we hit here! + Logger.error("Could not figure out who owns the key #{key_id}") + {:error, :key_owner_not_found} key -> {:ok, key} end end - def request_key_id_from_remote_instance(key_id) do - case SigningKey.fetch_remote_key(key_id) do - {:ok, key_id} -> - {:ok, key_id} - - {:error, _} -> - {:error, "Key ID not found"} - end - end - def fetch_public_key(conn) do with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn), + {:ok, %SigningKey{}} <- SigningKey.get_or_fetch_by_key_id(kid), {:ok, actor_id} <- key_id_to_actor_id(kid), {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do {:ok, public_key} @@ -45,8 +37,8 @@ def fetch_public_key(conn) do def refetch_public_key(conn) do with %{"keyId" => kid} <- HTTPSignatures.signature_for_conn(conn), + {:ok, %SigningKey{}} <- SigningKey.get_or_fetch_by_key_id(kid), {:ok, actor_id} <- key_id_to_actor_id(kid), - {:ok, _user} <- ActivityPub.make_user_from_ap_id(actor_id), {:ok, public_key} <- User.get_public_key_for_ap_id(actor_id) do {:ok, public_key} else diff --git a/lib/pleroma/user/signing_key.ex b/lib/pleroma/user/signing_key.ex index 3d9b476f0..1fa574ae2 100644 --- a/lib/pleroma/user/signing_key.ex +++ b/lib/pleroma/user/signing_key.ex @@ -154,6 +154,21 @@ def private_key(%User{signing_key: %__MODULE__{private_key: private_key_pem}}) d {:ok, key} end + @spec get_or_fetch_by_key_id(String.t()) :: {:ok, __MODULE__} | {:error, String.t()} + @doc """ + Given a key ID, return the signing key associated with that key. + Will either return the key if it exists locally, or fetch it from the remote instance. + """ + def get_or_fetch_by_key_id(key_id) do + case key_id_to_user_id(key_id) do + nil -> + fetch_remote_key(key_id) + + user_id -> + {:ok, Repo.get_by(__MODULE__, user_id: user_id)} + end + end + @spec fetch_remote_key(String.t()) :: {:ok, __MODULE__} | {:error, String.t()} @doc """ Fetch a remote key by key ID. @@ -164,23 +179,33 @@ def private_key(%User{signing_key: %__MODULE__{private_key: private_key_pem}}) d So if we're rejected, we should probably just give up. """ def fetch_remote_key(key_id) do + Logger.debug("Fetching remote key: #{key_id}") # we should probably sign this, just in case resp = Pleroma.Object.Fetcher.get_object(key_id) - case handle_signature_response(resp) do - {:ok, ap_id, public_key_pem} -> - # fetch the user - user = User.get_or_fetch_by_ap_id(ap_id) - # store the key - key = %__MODULE__{ - user_id: user.id, - public_key: public_key_pem, - key_id: key_id - } + case resp do + {:ok, _original_url, body} -> + case handle_signature_response(resp) do + {:ok, ap_id, public_key_pem} -> + Logger.debug("Fetched remote key: #{ap_id}") + # fetch the user + {:ok, user} = User.get_or_fetch_by_ap_id(ap_id) + # store the key + key = %__MODULE__{ + user_id: user.id, + public_key: public_key_pem, + key_id: key_id + } - Repo.insert(key) + Repo.insert(key, on_conflict: :replace_all, conflict_target: :key_id) + + e -> + Logger.debug("Failed to fetch remote key: #{inspect(e)}") + {:error, "Could not fetch key"} + end _ -> + Logger.debug("Failed to fetch remote key: #{inspect(resp)}") {:error, "Could not fetch key"} end end @@ -196,7 +221,7 @@ defp extract_key_details(%{"id" => ap_id, "publicKey" => public_key}) do end end - defp handle_signature_response({:ok, %{status: status, body: body}}) when status in 200..299 do + defp handle_signature_response({:ok, _original_url, body}) do case Jason.decode(body) do {:ok, %{"id" => _user_id, "publicKey" => _public_key} = body} -> extract_key_details(body) diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index f91810fb8..ad6aeaff2 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -67,7 +67,12 @@ def render("user.json", %{user: %User{nickname: "internal." <> _} = user}), do: render("service.json", %{user: user}) |> Map.put("preferredUsername", user.nickname) def render("user.json", %{user: user}) do - {:ok, public_key} = User.SigningKey.public_key_pem(user) + public_key = + case User.SigningKey.public_key_pem(user) do + {:ok, public_key} -> public_key + _ -> nil + end + user = User.sanitize_html(user) endpoints = render("endpoints.json", %{user: user}) diff --git a/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex b/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex index c59053854..4da9215a3 100644 --- a/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex +++ b/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex @@ -14,7 +14,7 @@ def call(conn, _opts) do key_id = key_id_from_conn(conn) unless is_nil(key_id) do - SigningKey.fetch_remote_key(key_id) + User.SigningKey.fetch_remote_key(key_id) # now we SHOULD have the user that owns the key locally. maybe. # if we don't, we'll error out when we try to validate. end diff --git a/priv/repo/migrations/20240625213637_create_signing_key_table.exs b/priv/repo/migrations/20240625213637_create_signing_key_table.exs index 34169f78c..5bd9bbcf2 100644 --- a/priv/repo/migrations/20240625213637_create_signing_key_table.exs +++ b/priv/repo/migrations/20240625213637_create_signing_key_table.exs @@ -10,6 +10,6 @@ def change do timestamps() end - create index(:signing_keys, [:key_id]) + create unique_index(:signing_keys, [:key_id]) end end diff --git a/test/mix/tasks/pleroma/database_test.exs b/test/mix/tasks/pleroma/database_test.exs index 20f113e6e..4f97a978a 100644 --- a/test/mix/tasks/pleroma/database_test.exs +++ b/test/mix/tasks/pleroma/database_test.exs @@ -353,7 +353,7 @@ test "with the --keep-threads option it keeps old threads with bookmarked posts" test "We don't have unexpected tables which may contain objects that are referenced by activities" do # We can delete orphaned activities. For that we look for the objects they reference in the 'objects', 'activities', and 'users' table. - # If someone adds another table with objects (idk, maybe with separate relations, or collections or w/e), then we need to make sure we + # If someone adds another table with objects (idk, maybe with separate relations, or collections or w/e), then we need to make sure we # add logic for that in the 'prune_objects' task so that we don't wrongly delete their corresponding activities. # So when someone adds (or removes) a table, this test will fail. # Either the table contains objects which can be referenced from the activities table @@ -401,6 +401,7 @@ test "We don't have unexpected tables which may contain objects that are referen ["rich_media_card"], ["scheduled_activities"], ["schema_migrations"], + ["signing_keys"], ["thread_mutes"], ["user_follows_hashtag"], ["user_frontend_setting_profiles"], diff --git a/test/pleroma/signature_test.exs b/test/pleroma/signature_test.exs index 768c78f21..0e360d58d 100644 --- a/test/pleroma/signature_test.exs +++ b/test/pleroma/signature_test.exs @@ -35,25 +35,23 @@ defp make_fake_conn(key_id), do: %Plug.Conn{req_headers: %{"signature" => make_fake_signature(key_id <> "#main-key")}} describe "fetch_public_key/1" do - test "it returns key" do + test "it returns the key" do expected_result = {:ok, @rsa_public_key} - user = insert(:user, public_key: @public_key) + user = + insert(:user) + |> with_signing_key(public_key: @public_key) assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) == expected_result end - test "it returns error when not found user" do - assert capture_log(fn -> - assert Signature.fetch_public_key(make_fake_conn("https://test-ap-id")) == - {:error, :error} - end) =~ "[error] Could not decode user" - end - test "it returns error if public key is nil" do - user = insert(:user, public_key: nil) + # this actually needs the URL to be valid + user = insert(:user) + key_id = user.ap_id <> "#main-key" + Tesla.Mock.mock(fn %{url: ^key_id} -> {:ok, %{status: 404}} end) - assert Signature.fetch_public_key(make_fake_conn(user.ap_id)) == {:error, :error} + assert {:error, _} = Signature.fetch_public_key(make_fake_conn(user.ap_id)) end end @@ -63,12 +61,6 @@ test "it returns key" do assert Signature.refetch_public_key(make_fake_conn(ap_id)) == {:ok, @rsa_public_key} end - - test "it returns error when not found user" do - assert capture_log(fn -> - {:error, _} = Signature.refetch_public_key(make_fake_conn("https://test-ap_id")) - end) =~ "[error] Could not decode user" - end end defp split_signature(sig) do @@ -104,9 +96,9 @@ defp assert_part_equal(part_a, part_b) do test "it returns signature headers" do user = insert(:user, %{ - ap_id: "https://mastodon.social/users/lambadalambda", - keys: @private_key + ap_id: "https://mastodon.social/users/lambadalambda" }) + |> with_signing_key(private_key: @private_key) headers = %{ host: "test.test", @@ -121,50 +113,15 @@ test "it returns signature headers" do "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 - user = insert(:user, %{ap_id: "https://mastodon.social/users/lambadalambda", keys: ""}) - - assert Signature.sign( - user, - %{host: "test.test", "content-length": "100"} - ) == {:error, []} - end end describe "key_id_to_actor_id/1" do - test "it properly deduces the actor id for misskey" do - assert Signature.key_id_to_actor_id("https://example.com/users/1234/publickey") == - {:ok, "https://example.com/users/1234"} - end + test "it reverses the key id to actor id" do + user = + insert(:user) + |> with_signing_key() - test "it properly deduces the actor id for mastodon and pleroma" do - assert Signature.key_id_to_actor_id("https://example.com/users/1234#main-key") == - {:ok, "https://example.com/users/1234"} - end - - test "it deduces the actor id for gotoSocial" do - assert Signature.key_id_to_actor_id("https://example.com/users/1234/main-key") == - {:ok, "https://example.com/users/1234"} - end - - test "it deduces the actor ID for streams" do - assert Signature.key_id_to_actor_id("https://example.com/users/1234?operation=getkey") == - {:ok, "https://example.com/users/1234"} - end - - test "it deduces the actor ID for bridgy" do - assert Signature.key_id_to_actor_id("https://example.com/1234#key") == - {:ok, "https://example.com/1234"} - end - - test "it calls webfinger for 'acct:' accounts" do - with_mock(Pleroma.Web.WebFinger, - finger: fn _ -> {:ok, %{"ap_id" => "https://gensokyo.2hu/users/raymoo"}} end - ) do - assert Signature.key_id_to_actor_id("acct:raymoo@gensokyo.2hu") == - {:ok, "https://gensokyo.2hu/users/raymoo"} - end + assert Signature.key_id_to_actor_id(user.signing_key.key_id) == {:ok, user.ap_id} end end diff --git a/test/pleroma/user_search_test.exs b/test/pleroma/user_search_test.exs index 2af19b6de..5d018a301 100644 --- a/test/pleroma/user_search_test.exs +++ b/test/pleroma/user_search_test.exs @@ -259,7 +259,7 @@ test "works with URIs" do |> Map.put(:multi_factor_authentication_settings, nil) |> Map.put(:notification_settings, nil) - assert user == expected + assert_user_match(user, expected) end test "excludes a blocked users from search result" 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 d67b59ef2..120fb6d1b 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -645,7 +645,7 @@ test "accept follow activity", %{conn: conn} do assert "ok" == conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{followed_relay.ap_id}/main-key\"") + |> put_req_header("signature", "keyId=\"#{followed_relay.ap_id}#main-key\"") |> put_req_header("content-type", "application/activity+json") |> post("/inbox", accept) |> json_response(200) @@ -682,6 +682,7 @@ test "accepts Add/Remove activities", %{conn: conn} do |> String.replace("{{nickname}}", "lain") actor = "https://example.com/users/lain" + key_id = "#{actor}/main-key" insert(:user, ap_id: actor, @@ -709,6 +710,16 @@ test "accepts Add/Remove activities", %{conn: conn} do headers: [{"content-type", "application/activity+json"}] } + %{ + method: :get, + url: ^key_id + } -> + %Tesla.Env{ + status: 200, + body: user, + headers: [{"content-type", "application/activity+json"}] + } + %{method: :get, url: "https://example.com/users/lain/collections/featured"} -> %Tesla.Env{ status: 200, @@ -782,6 +793,7 @@ test "mastodon pin/unpin", %{conn: conn} do |> String.replace("{{nickname}}", "lain") actor = "https://example.com/users/lain" + key_id = "#{actor}/main-key" sender = insert(:user, @@ -811,6 +823,15 @@ test "mastodon pin/unpin", %{conn: conn} do headers: [{"content-type", "application/activity+json"}] } + %{ + method: :get, + url: ^key_id + } -> + %Tesla.Env{ + status: 200, + body: user, + headers: [{"content-type", "application/activity+json"}] + } %{method: :get, url: "https://example.com/users/lain/collections/featured"} -> %Tesla.Env{ status: 200, @@ -907,6 +928,7 @@ test "it inserts an incoming activity into the database", %{conn: conn, data: da test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do user = insert(:user) + |> with_signing_key() data = data @@ -952,6 +974,7 @@ test "it accepts messages with cc as string instead of array", %{conn: conn, dat test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do user = insert(:user) + |> with_signing_key() data = data @@ -1273,7 +1296,7 @@ test "forwarded report from mastodon", %{conn: conn} do conn |> assign(:valid_signature, true) - |> put_req_header("signature", "keyId=\"#{remote_actor}/main-key\"") + |> put_req_header("signature", "keyId=\"#{remote_actor}#main-key\"") |> put_req_header("content-type", "application/activity+json") |> post("/users/#{reported_user.nickname}/inbox", data) |> json_response(200) diff --git a/test/pleroma/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs index 87930b7b1..726d85958 100644 --- a/test/pleroma/web/activity_pub/publisher_test.exs +++ b/test/pleroma/web/activity_pub/publisher_test.exs @@ -141,6 +141,7 @@ test "publish to url with with different ports" do end) actor = insert(:user) + |> with_signing_key() assert {:ok, %{body: "port 42"}} = Publisher.publish_one(%{ @@ -166,6 +167,7 @@ test "publish to url with with different ports" do [:passthrough], [] do actor = insert(:user) + |> with_signing_key() inbox = "http://200.site/users/nick1/inbox" assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) @@ -177,6 +179,7 @@ test "publish to url with with different ports" do [:passthrough], [] do actor = insert(:user) + |> with_signing_key() inbox = "http://200.site/users/nick1/inbox" assert {:ok, _} = @@ -196,6 +199,7 @@ test "publish to url with with different ports" do [:passthrough], [] do actor = insert(:user) + |> with_signing_key() inbox = "http://200.site/users/nick1/inbox" assert {:ok, _} = @@ -215,6 +219,7 @@ test "publish to url with with different ports" do [:passthrough], [] do actor = insert(:user) + |> with_signing_key() inbox = "http://404.site/users/nick1/inbox" assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) @@ -227,6 +232,7 @@ test "publish to url with with different ports" do [:passthrough], [] do actor = insert(:user) + |> with_signing_key() inbox = "http://connrefused.site/users/nick1/inbox" assert capture_log(fn -> @@ -242,6 +248,7 @@ test "publish to url with with different ports" do [:passthrough], [] do actor = insert(:user) + |> with_signing_key() inbox = "http://200.site/users/nick1/inbox" assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) @@ -254,6 +261,7 @@ test "publish to url with with different ports" do [:passthrough], [] do actor = insert(:user) + |> with_signing_key() inbox = "http://connrefused.site/users/nick1/inbox" assert capture_log(fn -> @@ -295,6 +303,7 @@ test "publish to url with with different ports" do }) actor = insert(:user, follower_address: follower.ap_id) + |> with_signing_key() {:ok, follower, actor} = Pleroma.User.follow(follower, actor) {:ok, _another_follower, actor} = Pleroma.User.follow(another_follower, actor) @@ -366,6 +375,7 @@ test "publish to url with with different ports" do }) actor = insert(:user, follower_address: follower.ap_id) + |> with_signing_key() {:ok, follower, actor} = Pleroma.User.follow(follower, actor) actor = refresh_record(actor) diff --git a/test/pleroma/web/plugs/http_signature_plug_test.exs b/test/pleroma/web/plugs/http_signature_plug_test.exs index 7f623bfca..5236b519e 100644 --- a/test/pleroma/web/plugs/http_signature_plug_test.exs +++ b/test/pleroma/web/plugs/http_signature_plug_test.exs @@ -17,8 +17,8 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlugTest do setup do user = :user - |> insert(%{ ap_id: "http://mastodon.example.org/users/admin" }) - |> with_signing_key(%{ key_id: "http://mastodon.example.org/users/admin#main-key" }) + |> insert(%{ap_id: "http://mastodon.example.org/users/admin"}) + |> with_signing_key(%{key_id: "http://mastodon.example.org/users/admin#main-key"}) {:ok, %{user: user}} end diff --git a/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs b/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs index 77b14bbd2..5789cd756 100644 --- a/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs +++ b/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs @@ -14,7 +14,11 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlugTest do setup do mock(fn env -> apply(HttpRequestMock, :request, [env]) end) - user = insert(:user) + + user = + insert(:user) + |> with_signing_key() + {:ok, %{user: user}} end diff --git a/test/support/factory.ex b/test/support/factory.ex index 1f54c5b7a..54e5f91b7 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -96,9 +96,9 @@ def user_factory(attrs \\ %{}) do end def with_signing_key(%User{} = user, attrs \\ %{}) do - - signing_key = build(:signing_key, %{user: user, key_id: "#{user.ap_id}#main-key"}) - |> merge_attributes(attrs) + signing_key = + build(:signing_key, %{user: user, key_id: "#{user.ap_id}#main-key"}) + |> merge_attributes(attrs) insert(signing_key) %{user | signing_key: signing_key} @@ -111,8 +111,8 @@ def signing_key_factory(attrs \\ %{}) do %Pleroma.User.SigningKey{ user_id: user.id, - public_key: public_key, - private_key: pem, + public_key: attrs[:public_key] || public_key, + private_key: attrs[:private_key] || pem, key_id: attrs[:key_id] } end diff --git a/test/support/helpers.ex b/test/support/helpers.ex index 2dfff70a2..8d481b5f5 100644 --- a/test/support/helpers.ex +++ b/test/support/helpers.ex @@ -65,6 +65,7 @@ defmacro __using__(_opts) do clear_config: 1, clear_config: 2 ] + import Pleroma.Test.MatchingHelpers def time_travel(entity, seconds) do new_time = NaiveDateTime.add(entity.inserted_at, seconds) diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index ea06c4966..d14434333 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -424,6 +424,15 @@ def get("http://mastodon.example.org/users/admin", _, _, _) do }} end + def get("http://mastodon.example.org/users/admin/main-key", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/tesla_mock/admin@mastdon.example.org.json"), + headers: activitypub_object_headers() + }} + end + def get( "http://mastodon.example.org/users/admin/statuses/99512778738411822/replies?min_id=99512778738411824&page=true", _, @@ -958,6 +967,15 @@ def get("https://mastodon.social/users/lambadalambda", _, _, _) do }} end + def get("https://mastodon.social/users/lambadalambda#main-key", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/lambadalambda.json"), + headers: activitypub_object_headers() + }} + end + def get("https://mastodon.social/users/lambadalambda/collections/featured", _, _, _) do {:ok, %Tesla.Env{ @@ -1403,6 +1421,15 @@ def get("https://relay.mastodon.host/actor", _, _, _) do }} end + def get("https://relay.mastodon.host/actor#main-key", _, _, _) do + {:ok, + %Tesla.Env{ + status: 200, + body: File.read!("test/fixtures/relay/relay.json"), + headers: activitypub_object_headers() + }} + end + def get("http://localhost:4001/", _, "", [{"accept", "text/html"}]) do {:ok, %Tesla.Env{status: 200, body: File.read!("test/fixtures/tesla_mock/7369654.html")}} end diff --git a/test/support/matching_helpers.ex b/test/support/matching_helpers.ex new file mode 100644 index 000000000..39963171f --- /dev/null +++ b/test/support/matching_helpers.ex @@ -0,0 +1,9 @@ +defmodule Pleroma.Test.MatchingHelpers do + import ExUnit.Assertions + @assoc_fields [ + :signing_key + ] + def assert_user_match(actor1, actor2) do + assert Ecto.reset_fields(actor1, @assoc_fields) == Ecto.reset_fields(actor2, @assoc_fields) + end +end From 430b376dede4847f0f63cc64be40995648e52dda Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Tue, 20 Aug 2024 11:05:17 +0100 Subject: [PATCH 29/70] mix format --- lib/pleroma/user/signing_key.ex | 20 ++++--- test/pleroma/user_search_test.exs | 2 +- .../activity_pub_controller_test.exs | 47 ++++++++------- .../web/activity_pub/publisher_test.exs | 57 ++++++++++++------- test/support/helpers.ex | 1 + test/support/matching_helpers.ex | 1 + 6 files changed, 78 insertions(+), 50 deletions(-) diff --git a/lib/pleroma/user/signing_key.ex b/lib/pleroma/user/signing_key.ex index 1fa574ae2..11786bbb0 100644 --- a/lib/pleroma/user/signing_key.ex +++ b/lib/pleroma/user/signing_key.ex @@ -144,14 +144,20 @@ def public_key_pem(e) do @doc """ Given a user, return the private key for that user in binary format. """ - def private_key(%User{signing_key: %__MODULE__{private_key: private_key_pem}}) do - key = - private_key_pem - |> :public_key.pem_decode() - |> hd() - |> :public_key.pem_entry_decode() + def private_key(%User{} = user) do + case Repo.preload(user, :signing_key) do + %{signing_key: %__MODULE__{private_key: private_key_pem}} -> + key = + private_key_pem + |> :public_key.pem_decode() + |> hd() + |> :public_key.pem_entry_decode() - {:ok, key} + {:ok, key} + + _ -> + {:error, "key not found"} + end end @spec get_or_fetch_by_key_id(String.t()) :: {:ok, __MODULE__} | {:error, String.t()} diff --git a/test/pleroma/user_search_test.exs b/test/pleroma/user_search_test.exs index 5d018a301..b499629c8 100644 --- a/test/pleroma/user_search_test.exs +++ b/test/pleroma/user_search_test.exs @@ -259,7 +259,7 @@ test "works with URIs" do |> Map.put(:multi_factor_authentication_settings, nil) |> Map.put(:notification_settings, nil) - assert_user_match(user, expected) + assert_user_match(user, expected) end test "excludes a blocked users from search result" 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 120fb6d1b..183818159 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -710,15 +710,15 @@ test "accepts Add/Remove activities", %{conn: conn} do headers: [{"content-type", "application/activity+json"}] } - %{ - method: :get, - url: ^key_id - } -> - %Tesla.Env{ - status: 200, - body: user, - headers: [{"content-type", "application/activity+json"}] - } + %{ + method: :get, + url: ^key_id + } -> + %Tesla.Env{ + status: 200, + body: user, + headers: [{"content-type", "application/activity+json"}] + } %{method: :get, url: "https://example.com/users/lain/collections/featured"} -> %Tesla.Env{ @@ -823,15 +823,16 @@ test "mastodon pin/unpin", %{conn: conn} do headers: [{"content-type", "application/activity+json"}] } - %{ - method: :get, - url: ^key_id - } -> - %Tesla.Env{ - status: 200, - body: user, - headers: [{"content-type", "application/activity+json"}] - } + %{ + method: :get, + url: ^key_id + } -> + %Tesla.Env{ + status: 200, + body: user, + headers: [{"content-type", "application/activity+json"}] + } + %{method: :get, url: "https://example.com/users/lain/collections/featured"} -> %Tesla.Env{ status: 200, @@ -927,8 +928,9 @@ test "it inserts an incoming activity into the database", %{conn: conn, data: da end test "it accepts messages with to as string instead of array", %{conn: conn, data: data} do - user = insert(:user) - |> with_signing_key() + user = + insert(:user) + |> with_signing_key() data = data @@ -973,8 +975,9 @@ test "it accepts messages with cc as string instead of array", %{conn: conn, dat end test "it accepts messages with bcc as string instead of array", %{conn: conn, data: data} do - user = insert(:user) - |> with_signing_key() + user = + insert(:user) + |> with_signing_key() data = data diff --git a/test/pleroma/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs index 726d85958..eeec59cfb 100644 --- a/test/pleroma/web/activity_pub/publisher_test.exs +++ b/test/pleroma/web/activity_pub/publisher_test.exs @@ -140,8 +140,9 @@ test "publish to url with with different ports" do {:ok, %Tesla.Env{status: 200, body: "port 80"}} end) - actor = insert(:user) - |> with_signing_key() + actor = + insert(:user) + |> with_signing_key() assert {:ok, %{body: "port 42"}} = Publisher.publish_one(%{ @@ -166,8 +167,10 @@ test "publish to url with with different ports" do Instances, [:passthrough], [] do - actor = insert(:user) - |> with_signing_key() + actor = + insert(:user) + |> with_signing_key() + inbox = "http://200.site/users/nick1/inbox" assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) @@ -178,8 +181,10 @@ test "publish to url with with different ports" do Instances, [:passthrough], [] do - actor = insert(:user) - |> with_signing_key() + actor = + insert(:user) + |> with_signing_key() + inbox = "http://200.site/users/nick1/inbox" assert {:ok, _} = @@ -198,8 +203,10 @@ test "publish to url with with different ports" do Instances, [:passthrough], [] do - actor = insert(:user) - |> with_signing_key() + actor = + insert(:user) + |> with_signing_key() + inbox = "http://200.site/users/nick1/inbox" assert {:ok, _} = @@ -218,8 +225,10 @@ test "publish to url with with different ports" do Instances, [:passthrough], [] do - actor = insert(:user) - |> with_signing_key() + actor = + insert(:user) + |> with_signing_key() + inbox = "http://404.site/users/nick1/inbox" assert {:error, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) @@ -231,8 +240,10 @@ test "publish to url with with different ports" do Instances, [:passthrough], [] do - actor = insert(:user) - |> with_signing_key() + actor = + insert(:user) + |> with_signing_key() + inbox = "http://connrefused.site/users/nick1/inbox" assert capture_log(fn -> @@ -247,8 +258,10 @@ test "publish to url with with different ports" do Instances, [:passthrough], [] do - actor = insert(:user) - |> with_signing_key() + actor = + insert(:user) + |> with_signing_key() + inbox = "http://200.site/users/nick1/inbox" assert {:ok, _} = Publisher.publish_one(%{inbox: inbox, json: "{}", actor: actor, id: 1}) @@ -260,8 +273,10 @@ test "publish to url with with different ports" do Instances, [:passthrough], [] do - actor = insert(:user) - |> with_signing_key() + actor = + insert(:user) + |> with_signing_key() + inbox = "http://connrefused.site/users/nick1/inbox" assert capture_log(fn -> @@ -302,8 +317,9 @@ test "publish to url with with different ports" do ap_enabled: true }) - actor = insert(:user, follower_address: follower.ap_id) - |> with_signing_key() + actor = + insert(:user, follower_address: follower.ap_id) + |> with_signing_key() {:ok, follower, actor} = Pleroma.User.follow(follower, actor) {:ok, _another_follower, actor} = Pleroma.User.follow(another_follower, actor) @@ -374,8 +390,9 @@ test "publish to url with with different ports" do ap_enabled: true }) - actor = insert(:user, follower_address: follower.ap_id) - |> with_signing_key() + actor = + insert(:user, follower_address: follower.ap_id) + |> with_signing_key() {:ok, follower, actor} = Pleroma.User.follow(follower, actor) actor = refresh_record(actor) diff --git a/test/support/helpers.ex b/test/support/helpers.ex index 8d481b5f5..f0beae8ec 100644 --- a/test/support/helpers.ex +++ b/test/support/helpers.ex @@ -65,6 +65,7 @@ defmacro __using__(_opts) do clear_config: 1, clear_config: 2 ] + import Pleroma.Test.MatchingHelpers def time_travel(entity, seconds) do diff --git a/test/support/matching_helpers.ex b/test/support/matching_helpers.ex index 39963171f..c8411b907 100644 --- a/test/support/matching_helpers.ex +++ b/test/support/matching_helpers.ex @@ -1,5 +1,6 @@ defmodule Pleroma.Test.MatchingHelpers do import ExUnit.Assertions + @assoc_fields [ :signing_key ] From 13215f5f060a7c7da014aca79b6700654791856c Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Sat, 26 Oct 2024 05:28:55 +0100 Subject: [PATCH 30/70] remove public key field --- lib/pleroma/object/fetcher.ex | 1 + lib/pleroma/signature.ex | 1 - lib/pleroma/user.ex | 3 --- lib/pleroma/user/signing_key.ex | 2 +- 4 files changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/object/fetcher.ex b/lib/pleroma/object/fetcher.ex index 897146bce..2cecfec67 100644 --- a/lib/pleroma/object/fetcher.ex +++ b/lib/pleroma/object/fetcher.ex @@ -343,6 +343,7 @@ 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() diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex index cedfee2e5..91e610fe1 100644 --- a/lib/pleroma/signature.ex +++ b/lib/pleroma/signature.ex @@ -11,7 +11,6 @@ defmodule Pleroma.Signature do require Logger def key_id_to_actor_id(key_id) do - # Given the key ID, first attempt to look it up in the signing keys table. case SigningKey.key_id_to_ap_id(key_id) do nil -> # hm, we SHOULD have gotten this in the pipeline before we hit here! diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 05d3011cb..bf8717ffb 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -101,7 +101,6 @@ defmodule Pleroma.User do field(:password, :string, virtual: true) field(:password_confirmation, :string, virtual: true) field(:keys, :string) - field(:public_key, :string) field(:ap_id, :string) field(:avatar, :map, default: %{}) field(:local, :boolean, default: true) @@ -471,7 +470,6 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do :inbox, :shared_inbox, :nickname, - :public_key, :avatar, :ap_enabled, :banner, @@ -532,7 +530,6 @@ def update_changeset(struct, params \\ %{}) do :name, :emoji, :avatar, - :public_key, :inbox, :shared_inbox, :is_locked, diff --git a/lib/pleroma/user/signing_key.ex b/lib/pleroma/user/signing_key.ex index 11786bbb0..60f1edb7c 100644 --- a/lib/pleroma/user/signing_key.ex +++ b/lib/pleroma/user/signing_key.ex @@ -187,7 +187,7 @@ def get_or_fetch_by_key_id(key_id) do def fetch_remote_key(key_id) do Logger.debug("Fetching remote key: #{key_id}") # we should probably sign this, just in case - resp = Pleroma.Object.Fetcher.get_object(key_id) + resp = Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(key_id) case resp do {:ok, _original_url, body} -> From bee10eab5ebf6aa4d50238afd3d9c37a014dd745 Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Sat, 26 Oct 2024 06:11:12 +0100 Subject: [PATCH 31/70] correct minor zip behaviour --- lib/pleroma/emoji/pack.ex | 2 +- test/pleroma/emoji/pack_test.exs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/emoji/pack.ex b/lib/pleroma/emoji/pack.ex index 142208854..e95320a07 100644 --- a/lib/pleroma/emoji/pack.ex +++ b/lib/pleroma/emoji/pack.ex @@ -125,7 +125,7 @@ def add_file(%Pack{} = pack, _, _, %Plug.Upload{content_type: "application/zip"} {:ok, _emoji_files} = :zip.unzip( to_charlist(file.path), - [{:file_list, Enum.map(emojies, & &1[:path])}, {:cwd, tmp_dir}] + [{:file_list, Enum.map(emojies, & &1[:path])}, {:cwd, to_charlist(tmp_dir)}] ) {_, updated_pack} = diff --git a/test/pleroma/emoji/pack_test.exs b/test/pleroma/emoji/pack_test.exs index f5d2e2eef..3843add13 100644 --- a/test/pleroma/emoji/pack_test.exs +++ b/test/pleroma/emoji/pack_test.exs @@ -64,7 +64,7 @@ test "returns error when zip file is bad", %{pack: pack} do path: Path.absname("test/instance_static/emoji/test_pack/blank.png") } - assert Pack.add_file(pack, nil, nil, file) == {:error, :einval} + assert Pack.add_file(pack, nil, nil, file) == {:error, :bad_eocd} end test "returns pack when zip file is empty", %{pack: pack} do From 58d5d9d7bf469f13300fc86d3ed9a14337d9eb3a Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Sat, 26 Oct 2024 06:58:47 +0100 Subject: [PATCH 32/70] fix tests, contain object --- lib/pleroma/signature.ex | 1 - lib/pleroma/user/signing_key.ex | 16 +++++-------- .../web/plugs/ensure_user_public_key_plug.ex | 2 +- .../activity_pub_controller_test.exs | 24 +++++++++++++------ .../web/activity_pub/views/user_view_test.exs | 21 ++++++++++++---- 5 files changed, 40 insertions(+), 24 deletions(-) diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex index 91e610fe1..25fad22f7 100644 --- a/lib/pleroma/signature.ex +++ b/lib/pleroma/signature.ex @@ -6,7 +6,6 @@ defmodule Pleroma.Signature do @behaviour HTTPSignatures.Adapter alias Pleroma.User - alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.User.SigningKey require Logger diff --git a/lib/pleroma/user/signing_key.ex b/lib/pleroma/user/signing_key.ex index 60f1edb7c..709ee2593 100644 --- a/lib/pleroma/user/signing_key.ex +++ b/lib/pleroma/user/signing_key.ex @@ -4,7 +4,6 @@ defmodule Pleroma.User.SigningKey do import Ecto.Changeset alias Pleroma.User alias Pleroma.Repo - alias Pleroma.HTTP require Logger @@ -136,7 +135,7 @@ def public_key_pem(%User{} = user) do end end - def public_key_pem(e) do + def public_key_pem(_e) do {:error, "key not found"} end @@ -190,7 +189,7 @@ def fetch_remote_key(key_id) do resp = Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(key_id) case resp do - {:ok, _original_url, body} -> + {:ok, _body} -> case handle_signature_response(resp) do {:ok, ap_id, public_key_pem} -> Logger.debug("Fetched remote key: #{ap_id}") @@ -227,16 +226,13 @@ defp extract_key_details(%{"id" => ap_id, "publicKey" => public_key}) do end end - defp handle_signature_response({:ok, _original_url, body}) do - case Jason.decode(body) do - {:ok, %{"id" => _user_id, "publicKey" => _public_key} = body} -> + defp handle_signature_response({:ok, body}) do + case body do + %{"id" => _user_id, "publicKey" => _public_key} -> extract_key_details(body) - {:ok, %{"error" => error}} -> + %{"error" => error} -> {:error, error} - - {:error, _} -> - {:error, "Could not parse key"} end end diff --git a/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex b/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex index 4da9215a3..3eaa131e9 100644 --- a/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex +++ b/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex @@ -14,7 +14,7 @@ def call(conn, _opts) do key_id = key_id_from_conn(conn) unless is_nil(key_id) do - User.SigningKey.fetch_remote_key(key_id) + User.SigningKey.get_or_fetch_by_key_id(key_id) # now we SHOULD have the user that owns the key locally. maybe. # if we don't, we'll error out when we try to validate. end diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs index 183818159..c41a19cc7 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -1094,7 +1094,7 @@ test "it returns a note activity in a collection", %{conn: conn} do test "it clears `unreachable` federation status of the sender", %{conn: conn, data: data} do user = - insert(:user) + insert(:user, ap_id: data["actor"]) |> with_signing_key() data = Map.put(data, "bcc", [user.ap_id]) @@ -1277,12 +1277,22 @@ test "forwarded report from mastodon", %{conn: conn} do |> File.read!() |> String.replace("{{DOMAIN}}", remote_domain) - Tesla.Mock.mock(fn %{url: ^remote_actor} -> - %Tesla.Env{ - status: 200, - body: mock_json_body, - headers: [{"content-type", "application/activity+json"}] - } + key_url = "#{remote_actor}#main-key" + + Tesla.Mock.mock(fn + %{url: ^remote_actor} -> + %Tesla.Env{ + status: 200, + body: mock_json_body, + headers: [{"content-type", "application/activity+json"}] + } + + %{url: ^key_url} -> + %Tesla.Env{ + status: 200, + body: mock_json_body, + headers: [{"content-type", "application/activity+json"}] + } end) data = %{ diff --git a/test/pleroma/web/activity_pub/views/user_view_test.exs b/test/pleroma/web/activity_pub/views/user_view_test.exs index 2a367b680..7e251e510 100644 --- a/test/pleroma/web/activity_pub/views/user_view_test.exs +++ b/test/pleroma/web/activity_pub/views/user_view_test.exs @@ -11,7 +11,9 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do alias Pleroma.Web.CommonAPI test "Renders a user, including the public key" do - user = insert(:user) + user = + insert(:user) + |> with_signing_key() result = UserView.render("user.json", %{user: user}) @@ -37,7 +39,9 @@ test "Renders profile fields" do end test "Renders with emoji tags" do - user = insert(:user, emoji: %{"bib" => "/test"}) + user = + insert(:user, emoji: %{"bib" => "/test"}) + |> with_signing_key() assert %{ "tag" => [ @@ -74,13 +78,18 @@ test "Does not add an avatar image if the user hasn't set one" do end test "renders an invisible user with the invisible property set to true" do - user = insert(:user, invisible: true) + user = + insert(:user, invisible: true) + |> with_signing_key() assert %{"invisible" => true} = UserView.render("service.json", %{user: user}) end test "service has a few essential fields" do - user = insert(:user) + user = + insert(:user) + |> with_signing_key() + result = UserView.render("service.json", %{user: user}) assert result["id"] assert result["type"] == "Application" @@ -120,7 +129,9 @@ test "remote users have an empty endpoints structure" do end test "instance users do not expose oAuth endpoints" do - user = insert(:user, nickname: nil, local: true) + user = + insert(:user, nickname: nil, local: true) + |> with_signing_key() result = UserView.render("user.json", %{user: user}) From c5a44a59db4366af611cf0d6c0f7246ea0612220 Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Sat, 26 Oct 2024 07:00:38 +0100 Subject: [PATCH 33/70] remove unneeded index --- .../repo/migrations/20240625213637_create_signing_key_table.exs | 2 -- 1 file changed, 2 deletions(-) diff --git a/priv/repo/migrations/20240625213637_create_signing_key_table.exs b/priv/repo/migrations/20240625213637_create_signing_key_table.exs index 5bd9bbcf2..062edc66f 100644 --- a/priv/repo/migrations/20240625213637_create_signing_key_table.exs +++ b/priv/repo/migrations/20240625213637_create_signing_key_table.exs @@ -9,7 +9,5 @@ def change do add :private_key, :text timestamps() end - - create unique_index(:signing_keys, [:key_id]) end end From ac25b051ae5d5b7a5b9e39e58f43606bd49d1021 Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Sat, 26 Oct 2024 07:28:28 +0100 Subject: [PATCH 34/70] remove previous "allow user routes" functionality --- .../web/plugs/mapped_signature_to_identity_plug.ex | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex index 93d269187..94ff17cc1 100644 --- a/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex +++ b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex @@ -67,7 +67,8 @@ def call(%{assigns: %{valid_signature: true}} = conn, _opts) do Logger.debug("Failed to map identity from signature (lookup failure)") Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}") - only_permit_user_routes(conn) + conn + |> assign(:valid_signature, false) _ -> Logger.debug("Failed to map identity from signature (no payload actor mismatch)") @@ -81,16 +82,6 @@ def call(%{assigns: %{valid_signature: true}} = conn, _opts) do # no signature at all def call(conn, _opts), do: conn - defp only_permit_user_routes(%{path_info: ["users", _]} = conn) do - conn - |> assign(:limited_ap, true) - end - - defp only_permit_user_routes(conn) do - conn - |> assign(:valid_signature, false) - end - defp key_id_from_conn(conn) do case HTTPSignatures.signature_for_conn(conn) do %{"keyId" => key_id} when is_binary(key_id) -> From 9d2c558f64fd7853f9317e74821d6687eb7a8886 Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Sat, 26 Oct 2024 07:42:43 +0100 Subject: [PATCH 35/70] remove unused import --- .../web/plugs/ensure_user_public_key_plug.ex | 2 - .../mapped_signature_to_identity_plug.ex | 4 +- mix.lock | 57 ++++++++++--------- 3 files changed, 31 insertions(+), 32 deletions(-) diff --git a/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex b/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex index 3eaa131e9..5b090f289 100644 --- a/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex +++ b/lib/pleroma/web/plugs/ensure_user_public_key_plug.ex @@ -4,8 +4,6 @@ defmodule Pleroma.Web.Plugs.EnsureUserPublicKeyPlug do We _should_ be able to request the URL from the key URL... """ - import Plug.Conn - alias Pleroma.User def init(options), do: options diff --git a/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex index 94ff17cc1..0d5cb9eab 100644 --- a/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex +++ b/lib/pleroma/web/plugs/mapped_signature_to_identity_plug.ex @@ -67,8 +67,8 @@ def call(%{assigns: %{valid_signature: true}} = conn, _opts) do Logger.debug("Failed to map identity from signature (lookup failure)") Logger.debug("key_id=#{inspect(key_id_from_conn(conn))}") - conn - |> assign(:valid_signature, false) + conn + |> assign(:valid_signature, false) _ -> Logger.debug("Failed to map identity from signature (no payload actor mismatch)") diff --git a/mix.lock b/mix.lock index 92a704add..cdf99d7bc 100644 --- a/mix.lock +++ b/mix.lock @@ -8,28 +8,28 @@ "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.7", "b651241514e5f6956028147fe6637f7ac13802537e895a724f90bf3e36ddd1dd", [:mix], [], "hexpm", "da7785a4b0d2a021cd1292a60875a784b6caef71e76bf4917bdee1f390455cf5"}, + "castore": {:hex, :castore, "1.0.9", "5cc77474afadf02c7c017823f460a17daa7908e991b0cc917febc90e466a375c", [:mix], [], "hexpm", "5ea956504f1ba6f2b4eb707061d8e17870de2bee95fb59d512872c2ef06925e7"}, "certifi": {:hex, :certifi, "2.12.0", "2d1cca2ec95f59643862af91f001478c9863c2ac9cb6e2f89780bfd8de987329", [:rebar3], [], "hexpm", "ee68d85df22e554040cdb4be100f33873ac6051387baf6a8f6ce82272340ff1c"}, "combine": {:hex, :combine, "0.10.0", "eff8224eeb56498a2af13011d142c5e7997a80c8f5b97c499f84c841032e429f", [:mix], [], "hexpm", "1b1dbc1790073076580d0d1d64e42eae2366583e7aecd455d1215b0d16f2451b"}, - "comeonin": {:hex, :comeonin, "5.4.0", "246a56ca3f41d404380fc6465650ddaa532c7f98be4bda1b4656b3a37cc13abe", [:mix], [], "hexpm", "796393a9e50d01999d56b7b8420ab0481a7538d0caf80919da493b4a6e51faf1"}, + "comeonin": {:hex, :comeonin, "5.5.0", "364d00df52545c44a139bad919d7eacb55abf39e86565878e17cebb787977368", [:mix], [], "hexpm", "6287fc3ba0aad34883cbe3f7949fc1d1e738e5ccdce77165bc99490aa69f47fb"}, "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.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, - "credo": {:hex, :credo, "1.7.7", "771445037228f763f9b2afd612b6aa2fd8e28432a95dbbc60d8e03ce71ba4446", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "8bc87496c9aaacdc3f90f01b7b0582467b69b4bd2441fe8aae3109d843cc2f2e"}, + "credo": {:hex, :credo, "1.7.8", "9722ba1681e973025908d542ec3d95db5f9c549251ba5b028e251ad8c24ab8c5", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "cb9e87cc64f152f3ed1c6e325e7b894dea8f5ef2e41123bd864e3cd5ceb44968"}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm", "8df019facc5ec9603e94f7270f1ac73ddf339f56ade76a721eaa57c1493ba463"}, - "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, + "db_connection": {:hex, :db_connection, "2.7.0", "b99faa9291bb09892c7da373bb82cba59aefa9b36300f6145c5f201c7adf48ec", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "dcf08f31b2701f857dfc787fbad78223d61a32204f217f15e881dd93e4bdd3ff"}, "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.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, - "earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"}, - "earmark_parser": {:hex, :earmark_parser, "1.4.39", "424642f8335b05bb9eb611aa1564c148a8ee35c9c8a8bba6e129d51a3e3c6769", [:mix], [], "hexpm", "06553a88d1f1846da9ef066b87b57c6f605552cfbe40d20bd8d59cc6bde41944"}, + "dialyxir": {:hex, :dialyxir, "1.4.4", "fb3ce8741edeaea59c9ae84d5cec75da00fa89fe401c72d6e047d11a61f65f70", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "cd6111e8017ccd563e65621a4d9a4a1c5cd333df30cebc7face8029cacb4eff6"}, + "earmark": {:hex, :earmark, "1.4.47", "7e7596b84fe4ebeb8751e14cbaeaf4d7a0237708f2ce43630cfd9065551f94ca", [:mix], [], "hexpm", "3e96bebea2c2d95f3b346a7ff22285bc68a99fbabdad9b655aa9c6be06c698f8"}, + "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, "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.8.0", "440719cd74f09b3f01c84455707a2c3972b725c513808e68eb6c5b0ab82bf523", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16.0 or ~> 0.17.0 or ~> 0.18.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "f1512812dc196bcb932a96c82e55f69b543dc125e9d39f5e3631a9c4ec65ef12"}, + "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.8.2", "79350a53246ac5ec27326d208496aebceb77fa82a91744f66a9154560f0759d3", [:mix], [{:ecto_sql, "~> 3.7", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "> 0.16.0 and < 0.20.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1 or ~> 4.0.0", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "6149c1c4a5ba6602a76cb09ee7a269eb60dab9694a1dbbb797f032555212de75"}, "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.8.4", "4960a03ce79081dee8fe119d80ad372c4e7badb84c493cc75983f9d3bc8bde0f", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:certifi, "~> 2.0", [hex: :certifi, repo: "hexpm", optional: true]}], "hexpm", "6e7f1d619b5f61dfabd0a20aa268e575572b542ac31723293a4c1a567d5ef040"}, @@ -37,10 +37,10 @@ "erlex": {:hex, :erlex, "0.2.7", "810e8725f96ab74d17aac676e748627a07bc87eb950d2b83acd29dc047a30595", [:mix], [], "hexpm", "3ed95f79d1a844c3f6bf0cea61e0d5612a42ce56da9c03f01df538685365efb0"}, "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.5.4", "86c5bb870a49e0ab6f5aa5dd58cf505f09d2624ebe17530db3c1b61c88a673af", [: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", "e82bd0091bb9a5bb190139599f922ff3fc7aebcca4374d65c99c4e23aa6d1625"}, - "ex_aws_s3": {:hex, :ex_aws_s3, "2.5.3", "422468e5c3e1a4da5298e66c3468b465cfd354b842e512cb1f6fbbe4e2f5bdaf", [: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", "4f09dd372cc386550e484808c5ac5027766c8d0cd8271ccc578b82ee6ef4f3b8"}, + "ex_aws": {:hex, :ex_aws, "2.5.6", "6f642e0f82eff10a9b470044f084b81a791cf15b393d647ea5f3e65da2794e3d", [: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]}, {:req, "~> 0.3", [hex: :req, repo: "hexpm", optional: true]}, {: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", "c69eec59e31fdd89d0beeb1d97e16518dd1b23ad95b3d5c9f1dcfec23d97f960"}, + "ex_aws_s3": {:hex, :ex_aws_s3, "2.5.4", "87aaf4a2f24a48f516d7f5aaced9d128dd5d0f655c4431f9037a11a85c71109c", [: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", "c06e7f68b33f7c0acba1361dbd951c79661a28f85aa2e0582990fccca4425355"}, "ex_const": {:hex, :ex_const, "0.3.0", "9d79516679991baf540ef445438eef1455ca91cf1a3c2680d8fb9e5bea2fe4de", [:mix], [], "hexpm", "76546322abb9e40ee4a2f454cf1c8a5b25c3672fa79bed1ea52c31e0d2428ca9"}, - "ex_doc": {:hex, :ex_doc, "0.34.1", "9751a0419bc15bc7580c73fde506b17b07f6402a1e5243be9e0f05a68c723368", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "d441f1a86a235f59088978eff870de2e815e290e44a8bd976fe5d64470a4c9d2"}, + "ex_doc": {:hex, :ex_doc, "0.34.2", "13eedf3844ccdce25cfd837b99bea9ad92c4e511233199440488d217c92571e8", [:mix], [{:earmark_parser, "~> 1.4.39", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_c, ">= 0.1.0", [hex: :makeup_c, repo: "hexpm", optional: true]}, {:makeup_elixir, "~> 0.14 or ~> 1.0", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1 or ~> 1.0", [hex: :makeup_erlang, repo: "hexpm", optional: false]}, {:makeup_html, ">= 0.1.0", [hex: :makeup_html, repo: "hexpm", optional: true]}], "hexpm", "5ce5f16b41208a50106afed3de6a2ed34f4acfd65715b82a0b84b49d995f95c1"}, "ex_machina": {:hex, :ex_machina, "2.8.0", "a0e847b5712065055ec3255840e2c78ef9366634d62390839d4880483be38abe", [: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", "79fe1a9c64c0c1c1fab6c4fa5d871682cb90de5885320c187d117004627a7729"}, "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"}, @@ -48,10 +48,10 @@ "fast_html": {:hex, :fast_html, "2.3.0", "08c1d8ead840dd3060ba02c761bed9f37f456a1ddfe30bcdcfee8f651cec06a6", [: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", "f18e3c7668f82d3ae0b15f48d48feeb257e28aa5ab1b0dbf781c7312e5da029d"}, "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", "cc7067c7d446c2526e9ecf91d40896b088851569", [ref: "cc7067c7d446c2526e9ecf91d40896b088851569"]}, - "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, + "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [: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", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, "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.36.2", "a7da0193538c93f937714a6704369711998a51a6164a222d710ebd54020aa7a3", [:mix], [], "hexpm", "a8766c0bc92f074e5cb36c4f9961982eda84c5d2b8e979ca67f5c268ec8ed580"}, + "floki": {:hex, :floki, "0.36.3", "1102f93b16a55bc5383b85ae3ec470f82dee056eaeff9195e8afdf0ef2a43c30", [:mix], [], "hexpm", "fe0158bff509e407735f6d40b3ee0d7deb47f3f3ee7c6c182ad28599f9f6b27a"}, "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.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"}, @@ -61,33 +61,34 @@ "httpoison": {:hex, :httpoison, "1.8.2", "9eb9c63ae289296a544842ef816a85d881d4a31f518a0fec089aaa744beae290", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "2bb350d26972e30c96e2ca74a1aaf8293d61d0742ff17f01e0279fef11599921"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "inet_cidr": {:hex, :inet_cidr, "1.0.8", "d26bb7bdbdf21ae401ead2092bf2bb4bf57fe44a62f5eaa5025280720ace8a40", [:mix], [], "hexpm", "d5b26da66603bb56c933c65214c72152f0de9a6ea53618b56d63302a68f6a90e"}, - "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, - "joken": {:hex, :joken, "2.6.1", "2ca3d8d7f83bf7196296a3d9b2ecda421a404634bfc618159981a960020480a1", [:mix], [{:jose, "~> 1.11.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "ab26122c400b3d254ce7d86ed066d6afad27e70416df947cdcb01e13a7382e68"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, + "joken": {:hex, :joken, "2.6.2", "5daaf82259ca603af4f0b065475099ada1b2b849ff140ccd37f4b6828ca6892a", [:mix], [{:jose, "~> 1.11.10", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "5134b5b0a6e37494e46dbf9e4dad53808e5e787904b7c73972651b51cce3d72b"}, "jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"}, "jumper": {:hex, :jumper, "1.0.2", "68cdcd84472a00ac596b4e6459a41b3062d4427cbd4f1e8c8793c5b54f1406a7", [:mix], [], "hexpm", "9b7782409021e01ab3c08270e26f36eb62976a38c1aa64b2eaf6348422f165e1"}, "linkify": {:hex, :linkify, "0.5.3", "5f8143d8f61f5ff08d3aeeff47ef6509492b4948d8f08007fbf66e4d2246a7f2", [:mix], [], "hexpm", "3ef35a1377d47c25506e07c1c005ea9d38d700699d92ee92825f024434258177"}, - "mail": {:hex, :mail, "0.3.1", "cb0a14e4ed8904e4e5a08214e686ccf6f9099346885db17d8c309381f865cc5c", [:mix], [], "hexpm", "1db701e89865c1d5fa296b2b57b1cd587587cca8d8a1a22892b35ef5a8e352a6"}, + "mail": {:hex, :mail, "0.4.2", "959229676ef11dfdfb1269ce4a611622cb70415a1d6f925d79e547848bafc14d", [:mix], [], "hexpm", "08e5b70c72b8d1605cb88ef2df2c7e41d002210a621503ea1c13f1a7916b6bd3"}, "majic": {:git, "https://akkoma.dev/AkkomaGang/majic.git", "80540b36939ec83f48e76c61e5000e0fd67706f0", [ref: "80540b36939ec83f48e76c61e5000e0fd67706f0"]}, "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [: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", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, - "makeup_erlang": {:hex, :makeup_erlang, "1.0.0", "6f0eff9c9c489f26b69b61440bf1b238d95badae49adac77973cbacae87e3c2e", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "ea7a9307de9d1548d2a72d299058d1fd2339e3d398560a0e46c27dab4891e4d2"}, + "makeup_erlang": {:hex, :makeup_erlang, "1.0.1", "c7f58c120b2b5aa5fd80d540a89fdf866ed42f1f3994e4fe189abebeab610839", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "8a89a1eeccc2d798d6ea15496a6e4870b75e014d1af514b1b71fa33134f57814"}, "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"}, + "mime": {:hex, :mime, "2.0.6", "8f18486773d9b15f95f4f4f1e39b710045fa1de891fada4516559967276e4dc2", [:mix], [], "hexpm", "c9945363a6b26d747389aac3643f8e0e09d30499a138ad64fe8fd1d13d9b153e"}, "mimerl": {:hex, :mimerl, "1.3.0", "d0cd9fc04b9061f82490f6581e0128379830e78535e017f7780f37fea7545726", [:rebar3], [], "hexpm", "a1e15a50d1887217de95f0b9b0793e32853f7c258a5cd227650889b38839fe9d"}, "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.1.0", "0f5e399649ce9ab7602f72e718305c0f9cdc351190f72844599545e4996af73c", [:mix], [], "hexpm", "d44474c50be02d5b72131070281a5d3895c0e7a95c780e90bc0cfe712f633a13"}, + "mox": {:hex, :mox, "1.2.0", "a2cd96b4b80a3883e3100a221e8adc1b98e4c3a332a8fc434c39526babafd5b3", [:mix], [{:nimble_ownership, "~> 1.0", [hex: :nimble_ownership, repo: "hexpm", optional: false]}], "hexpm", "c7b92b3cc69ee24a7eeeaf944cd7be22013c52fcb580c1f33f50845ec821089a"}, "nimble_options": {:hex, :nimble_options, "1.1.1", "e3a492d54d85fc3fd7c5baf411d9d2852922f66e69476317787a7b2bb000a61b", [:mix], [], "hexpm", "821b2470ca9442c4b6984882fe9bb0389371b8ddec4d45a9504f00a66f650b44"}, + "nimble_ownership": {:hex, :nimble_ownership, "1.0.0", "3f87744d42c21b2042a0aa1d48c83c77e6dd9dd357e425a038dd4b49ba8b79a1", [:mix], [], "hexpm", "7c16cc74f4e952464220a73055b557a273e8b1b7ace8489ec9d86e9ad56cb2cc"}, "nimble_parsec": {:hex, :nimble_parsec, "1.4.0", "51f9b613ea62cfa97b25ccc2c1b4216e81df970acd8e16e8d1bdc58fef21370d", [:mix], [], "hexpm", "9c565862810fb383e9838c1dd2d7d2c437b3d13b267414ba6af33e50d2d1cf28"}, "nimble_pool": {:hex, :nimble_pool, "1.1.0", "bf9c29fbdcba3564a8b800d1eeb5a3c58f36e1e11d7b7fb2e084a643f645f06b", [:mix], [], "hexpm", "af2e4e6b34197db81f7aad230c1118eac993acc0dae6bc83bac0126d4ae0813a"}, - "oban": {:hex, :oban, "2.17.11", "7a641f9f737b626030c3e2209b53df6db83740ac5537208bac7d3b9871c2d5e7", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c445c488151939d64265a5efea51973fa0b42ee4ebbb31aa83fac26543b8ac6d"}, - "open_api_spex": {:hex, :open_api_spex, "3.19.1", "65ccb5d06e3d664d1eec7c5ea2af2289bd2f37897094a74d7219fb03fc2b5994", [: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 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "392895827ce2984a3459c91a484e70708132d8c2c6c5363972b4b91d6bbac3dd"}, + "oban": {:hex, :oban, "2.17.12", "33fb0cbfb92b910d48dd91a908590fe3698bb85eacec8cd0d9bc6aa13dddd6d6", [:mix], [{:ecto_sql, "~> 3.10", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:ecto_sqlite3, "~> 0.9", [hex: :ecto_sqlite3, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7a647d6cd6bb300073db17faabce22d80ae135da3baf3180a064fa7c4fa046e3"}, + "open_api_spex": {:hex, :open_api_spex, "3.21.2", "6a704f3777761feeb5657340250d6d7332c545755116ca98f33d4b875777e1e5", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}, {: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 or ~> 6.0", [hex: :poison, repo: "hexpm", optional: true]}, {:ymlr, "~> 2.0 or ~> 3.0 or ~> 4.0 or ~> 5.0", [hex: :ymlr, repo: "hexpm", optional: true]}], "hexpm", "f42ae6ed668b895ebba3e02773cfb4b41050df26f803f2ef634c72a7687dc387"}, "parse_trans": {:hex, :parse_trans, "3.4.1", "6e6aa8167cb44cc8f39441d05193be6e6f4e7c2946cb2759f015f8c56b76e5ff", [:rebar3], [], "hexpm", "620a406ce75dada827b82e453c19cf06776be266f5a67cff34e1ef2cbb60e49a"}, "phoenix": {:hex, :phoenix, "1.7.14", "a7d0b3f1bc95987044ddada111e77bd7f75646a08518942c72a8440278ae7825", [: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.7", [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", "c7859bc56cc5dfef19ecfc240775dae358cbaa530231118a9e014df392ace61a"}, - "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.1", "96798325fab2fed5a824ca204e877b81f9afd2e480f581e81f7b4b64a5a477f2", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.17", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "0ae544ff99f3c482b0807c5cec2c8289e810ecacabc04959d82c3337f4703391"}, + "phoenix_ecto": {:hex, :phoenix_ecto, "4.6.3", "f686701b0499a07f2e3b122d84d52ff8a31f5def386e03706c916f6feddf69ef", [:mix], [{:ecto, "~> 3.5", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.1", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.16 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "909502956916a657a197f94cc1206d9a65247538de8a5e186f7537c895d95764"}, "phoenix_html": {:hex, :phoenix_html, "3.3.4", "42a09fc443bbc1da37e372a5c8e6755d046f22b9b11343bf885067357da21cb3", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "0249d3abec3714aff3415e7ee3d9786cb325be3151e6c4b3021502c585bf53fb"}, "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"}, @@ -96,7 +97,7 @@ "phoenix_template": {:hex, :phoenix_template, "1.0.4", "e2092c132f3b5e5b2d49c96695342eb36d0ed514c5b252a77048d5969330d639", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0 or ~> 4.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "2c0c81f0e5c6753faf5cca2f229c9709919aba34fab866d3bc05060c9c444206"}, "phoenix_view": {:hex, :phoenix_view, "2.0.4", "b45c9d9cf15b3a1af5fb555c674b525391b6a1fe975f040fb4d913397b31abf4", [: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", "4e992022ce14f31fe57335db27a28154afcc94e9983266835bb3040243eb620b"}, "plug": {:hex, :plug, "1.16.1", "40c74619c12f82736d2214557dedec2e9762029b2438d6d175c5074c933edc9d", [: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", "a13ff6b9006b03d7e33874945b2755253841b238c34071ed85b0e86057f8cddc"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.7.1", "87677ffe3b765bc96a89be7960f81703223fe2e21efa42c125fcd0127dd9d6b2", [: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", "02dbd5f9ab571b864ae39418db7811618506256f6d13b4a45037e5fe78dc5de3"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.7.2", "fdadb973799ae691bf9ecad99125b16625b1c6039999da5fe544d99218e662e4", [: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", "245d8a11ee2306094840c000e8816f0cbed69a23fc0ac2bcf8d7835ae019bb2f"}, "plug_crypto": {:hex, :plug_crypto, "2.1.0", "f44309c2b06d249c27c8d3f65cfe08158ade08418cf540fd4f72d4d6863abb7b", [:mix], [], "hexpm", "131216a4b030b8f8ce0f26038bc4421ae60e4bb95c5cf5395e1421437824c4fa"}, "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"}, @@ -104,7 +105,7 @@ "postgrex": {:hex, :postgrex, "0.17.5", "0483d054938a8dc069b21bdd636bf56c487404c241ce6c319c1f43588246b281", [: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", "50b8b11afbb2c4095a3ba675b4f055c416d0f3d7de6633a595fc131a828a67eb"}, "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.5", "c108a4c406fa301a529151a3bb53158cadc4064ec0c5f99b03ddb8c0e4281bdf", [:mix, :rebar3], [], "hexpm", "632a6f447df7ccc1a4a10bdcfce71514412b16660fe59deca0fcf0aa3c054404"}, + "recon": {:hex, :recon, "2.5.6", "9052588e83bfedfd9b72e1034532aee2a5369d9d9343b61aeb7fbce761010741", [:mix, :rebar3], [], "hexpm", "96c6799792d735cc0f0fd0f86267e9d351e63339cbe03df9d162010cefc26bb0"}, "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.3", "96a86460cc33b435c7310dbd27ec82ca2c1f24ae38e34f8edde97f756503441a", [:rebar3], [], "hexpm", "d3b3958552e6eb16f463921e70ae7c767519ef8f5be46d7696cc1ed649421321"}, @@ -114,22 +115,22 @@ "swoosh": {:hex, :swoosh, "1.14.4", "94e9dba91f7695a10f49b0172c4a4cb658ef24abef7e8140394521b7f3bbb2d4", [: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", "081c5a590e4ba85cc89baddf7b2beecf6c13f7f84a958f1cd969290815f0f026"}, "syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"}, "table_rex": {:hex, :table_rex, "4.0.0", "3c613a68ebdc6d4d1e731bc973c233500974ec3993c99fcdabb210407b90959b", [:mix], [], "hexpm", "c35c4d5612ca49ebb0344ea10387da4d2afe278387d4019e4d8111e815df8f55"}, - "telemetry": {:hex, :telemetry, "1.2.1", "68fdfe8d8f05a8428483a97d7aab2f268aaff24b49e0f599faa091f1d4e7f61c", [:rebar3], [], "hexpm", "dad9ce9d8effc621708f99eac538ef1cbe05d6a874dd741de2e689c47feafed5"}, + "telemetry": {:hex, :telemetry, "1.3.0", "fedebbae410d715cf8e7062c96a1ef32ec22e764197f70cda73d82778d61e7a2", [:rebar3], [], "hexpm", "7015fc8919dbe63764f4b4b87a95b7c0996bd539e0d499be6ec9d7f3875b79e6"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.2", "2caabe9344ec17eafe5403304771c3539f3b6e2f7fb6a6f602558c825d0d0bfb", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9b43db0dc33863930b9ef9d27137e78974756f5f198cae18409970ed6fa5b561"}, "telemetry_metrics_prometheus": {:hex, :telemetry_metrics_prometheus, "1.1.0", "1cc23e932c1ef9aa3b91db257ead31ea58d53229d407e059b29bb962c1505a13", [:mix], [{:plug_cowboy, "~> 2.1", [hex: :plug_cowboy, repo: "hexpm", optional: false]}, {:telemetry_metrics_prometheus_core, "~> 1.0", [hex: :telemetry_metrics_prometheus_core, repo: "hexpm", optional: false]}], "hexpm", "d43b3659b3244da44fe0275b717701542365d4519b79d9ce895b9719c1ce4d26"}, "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.1.0", "58fa7c216257291caaf8d05678c8d01bd45f4bdbc1286838a28c4bb62ef32999", [:rebar3], [{:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "9eb9d9cbfd81cbd7cdd24682f8711b6e2b691289a0de6826e58452f28c103c8f"}, "temple": {:git, "https://akkoma.dev/AkkomaGang/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]}, - "tesla": {:hex, :tesla, "1.11.0", "81b2b10213dddb27105ec6102d9eb0cc93d7097a918a0b1594f2dfd1a4601190", [: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", "b83ab5d4c2d202e1ea2b7e17a49f788d49a699513d7c4f08f2aef2c281be69db"}, + "tesla": {:hex, :tesla, "1.13.0", "24a068a48d107080dd7c943a593997eee265977a38020eb2ab657cca78a12502", [: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]}, {:mox, "~> 1.0", [hex: :mox, 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", "7b8fc8f6b0640fa0d090af7889d12eb396460e044b6f8688a8e55e30406a2200"}, "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"}, + "tzdata": {:hex, :tzdata, "1.1.2", "45e5f1fcf8729525ec27c65e163be5b3d247ab1702581a94674e008413eef50b", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "cec7b286e608371602318c414f344941d5eb0375e14cfdab605cca2fe66cba8b"}, "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.2", "fe061acc9e0907d983d46b51bf35d58176f0fe6eb7ba3b33c9336401bf42b6d1", [:mix], [], "hexpm", "76e709a9762e98c6b462dfce92e9b5dfbf712839227f2da8add6dd11549b12cb"}, "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.3", "2f69a6ebe810328555b6fe5c831a851f485e303a7c8ce6c5f675abeb20ebdadc", [:mix], [], "hexpm", "6105453d7fac22c712ad66fab1d45abdf049868f253cf719b625151460b8b453"}, - "websock_adapter": {:hex, :websock_adapter, "0.5.6", "0437fe56e093fd4ac422de33bf8fc89f7bc1416a3f2d732d8b2c8fd54792fe60", [: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", "e04378d26b0af627817ae84c92083b7e97aca3121196679b73c73b99d0d133ea"}, + "websock_adapter": {:hex, :websock_adapter, "0.5.7", "65fa74042530064ef0570b75b43f5c49bb8b235d6515671b3d250022cb8a1f9e", [: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", "d0f478ee64deddfec64b800673fd6e0c8888b079d9f3444dd96d2a98383bdbd1"}, "websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"}, } From bd64d070823b8d729f0fa9d445def1a32fbc9af4 Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Sat, 26 Oct 2024 07:51:41 +0100 Subject: [PATCH 36/70] ensure migration actually works --- priv/repo/migrations/20240625220752_move_signing_keys.exs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/priv/repo/migrations/20240625220752_move_signing_keys.exs b/priv/repo/migrations/20240625220752_move_signing_keys.exs index d9ef1acab..9531ceed8 100644 --- a/priv/repo/migrations/20240625220752_move_signing_keys.exs +++ b/priv/repo/migrations/20240625220752_move_signing_keys.exs @@ -14,7 +14,7 @@ def up do Repo.stream(query, timeout: :infinity) |> Enum.each(fn - %User{id: user_id, keys: private_key, local: true} -> + %User{id: user_id, keys: private_key, local: true, ap_id: ap_id} -> # we can precompute the public key here... # we do use it on every user view which makes it a bit of a dos attack vector # so we should probably cache it @@ -23,6 +23,7 @@ def up do key = %User.SigningKey{ user_id: user_id, public_key: public_key, + key_id: "#{ap_id}#main-key", private_key: private_key } From d330c57cda8d5d714688ef9dd677fa5b0d228867 Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Sat, 26 Oct 2024 08:42:07 +0100 Subject: [PATCH 37/70] make sure we correctly match key objects --- lib/pleroma/user/signing_key.ex | 17 +++++++++++++++-- .../20240625220752_move_signing_keys.exs | 2 +- 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/user/signing_key.ex b/lib/pleroma/user/signing_key.ex index 709ee2593..f88faf70f 100644 --- a/lib/pleroma/user/signing_key.ex +++ b/lib/pleroma/user/signing_key.ex @@ -2,6 +2,7 @@ defmodule Pleroma.User.SigningKey do use Ecto.Schema import Ecto.Query import Ecto.Changeset + require Pleroma.Constants alias Pleroma.User alias Pleroma.Repo @@ -185,7 +186,6 @@ def get_or_fetch_by_key_id(key_id) do """ def fetch_remote_key(key_id) do Logger.debug("Fetching remote key: #{key_id}") - # we should probably sign this, just in case resp = Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(key_id) case resp do @@ -228,7 +228,20 @@ defp extract_key_details(%{"id" => ap_id, "publicKey" => public_key}) do defp handle_signature_response({:ok, body}) do case body do - %{"id" => _user_id, "publicKey" => _public_key} -> + %{ + "type" => "CryptographicKey", + "publicKeyPem" => public_key_pem, + "owner" => ap_id + } -> + {:ok, ap_id, public_key_pem} + + # for when we get a subset of the user object + %{ + "id" => _user_id, + "publicKey" => _public_key, + "type" => actor_type + } + when actor_type in Pleroma.Constants.actor_types() -> extract_key_details(body) %{"error" => error} -> diff --git a/priv/repo/migrations/20240625220752_move_signing_keys.exs b/priv/repo/migrations/20240625220752_move_signing_keys.exs index 9531ceed8..4e8eef6c9 100644 --- a/priv/repo/migrations/20240625220752_move_signing_keys.exs +++ b/priv/repo/migrations/20240625220752_move_signing_keys.exs @@ -23,7 +23,7 @@ def up do key = %User.SigningKey{ user_id: user_id, public_key: public_key, - key_id: "#{ap_id}#main-key", + key_id: "#{ap_id}#main-key", private_key: private_key } From 180dc8b472fe401d7f9ffa1bdf71943405aacacc Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Sat, 26 Oct 2024 08:50:38 +0100 Subject: [PATCH 38/70] downgrade earmark --- mix.exs | 2 +- mix.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mix.exs b/mix.exs index 418db47a1..ff888fd35 100644 --- a/mix.exs +++ b/mix.exs @@ -144,7 +144,7 @@ defp deps do {:ex_aws, "~> 2.4"}, {:ex_aws_s3, "~> 2.4"}, {:sweet_xml, "~> 0.7"}, - {:earmark, "~> 1.4"}, + {:earmark, "1.4.46"}, {:bbcode_pleroma, "~> 0.2.0"}, {:argon2_elixir, "~> 3.1"}, {:cors_plug, "~> 3.0"}, diff --git a/mix.lock b/mix.lock index cdf99d7bc..04cc0aa0a 100644 --- a/mix.lock +++ b/mix.lock @@ -24,7 +24,7 @@ "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.4.4", "fb3ce8741edeaea59c9ae84d5cec75da00fa89fe401c72d6e047d11a61f65f70", [:mix], [{:erlex, ">= 0.2.7", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "cd6111e8017ccd563e65621a4d9a4a1c5cd333df30cebc7face8029cacb4eff6"}, - "earmark": {:hex, :earmark, "1.4.47", "7e7596b84fe4ebeb8751e14cbaeaf4d7a0237708f2ce43630cfd9065551f94ca", [:mix], [], "hexpm", "3e96bebea2c2d95f3b346a7ff22285bc68a99fbabdad9b655aa9c6be06c698f8"}, + "earmark": {:hex, :earmark, "1.4.46", "8c7287bd3137e99d26ae4643e5b7ef2129a260e3dcf41f251750cb4563c8fb81", [:mix], [], "hexpm", "798d86db3d79964e759ddc0c077d5eb254968ed426399fbf5a62de2b5ff8910a"}, "earmark_parser": {:hex, :earmark_parser, "1.4.41", "ab34711c9dc6212dda44fcd20ecb87ac3f3fce6f0ca2f28d4a00e4154f8cd599", [:mix], [], "hexpm", "a81a04c7e34b6617c2792e291b5a2e57ab316365c2644ddc553bb9ed863ebefa"}, "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"}, From 6ed5be61ff0cf126968c7e76f7c2f2574735ed31 Mon Sep 17 00:00:00 2001 From: Norm Date: Sat, 26 Oct 2024 18:51:45 -0400 Subject: [PATCH 39/70] docs: Note that Elixir 1.17 has been tested as working --- 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 87669bd23..f3a347ccd 100644 --- a/docs/docs/installation/generic_dependencies.include +++ b/docs/docs/installation/generic_dependencies.include @@ -1,7 +1,7 @@ ## Required dependencies * PostgreSQL 12+ -* Elixir 1.14+ (currently tested up to 1.16) +* Elixir 1.14+ (currently tested up to 1.17) * Erlang OTP 25+ (currently tested up to OTP26) * git * file / libmagic From 11c5838947815ff00c4fb8a8ebf01a6fe846e6b4 Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Wed, 30 Oct 2024 12:44:01 +0000 Subject: [PATCH 40/70] standardise local key id generation --- lib/pleroma/signature.ex | 2 +- lib/pleroma/user/signing_key.ex | 10 +++++++++- lib/pleroma/web/activity_pub/views/user_view.ex | 4 ++-- .../migrations/20240625220752_move_signing_keys.exs | 3 ++- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex index 25fad22f7..bc3baf433 100644 --- a/lib/pleroma/signature.ex +++ b/lib/pleroma/signature.ex @@ -47,7 +47,7 @@ def refetch_public_key(conn) do def sign(%User{} = user, headers) do with {:ok, private_key} <- SigningKey.private_key(user) do - HTTPSignatures.sign(private_key, user.ap_id <> "#main-key", headers) + HTTPSignatures.sign(private_key, SigningKey.local_key_id(user.ap_id), headers) end end diff --git a/lib/pleroma/user/signing_key.ex b/lib/pleroma/user/signing_key.ex index f88faf70f..f25489068 100644 --- a/lib/pleroma/user/signing_key.ex +++ b/lib/pleroma/user/signing_key.ex @@ -91,7 +91,15 @@ def generate_local_keys(ap_id) do |> change() |> put_change(:public_key, local_pem) |> put_change(:private_key, private_pem) - |> put_change(:key_id, ap_id <> "#main-key") + |> put_change(:key_id, local_key_id(ap_id)) + end + + @spec local_key_id(String.t()) :: String.t() + @doc """ + Given an AP ID, return the key ID for the local user. + """ + def local_key_id(ap_id) do + ap_id <> "#main-key" end @spec private_pem_to_public_pem(binary) :: {:ok, binary()} | {:error, String.t()} diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index ad6aeaff2..2ca31fc3c 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -49,7 +49,7 @@ def render("service.json", %{user: user}) do "url" => user.ap_id, "manuallyApprovesFollowers" => false, "publicKey" => %{ - "id" => "#{user.ap_id}#main-key", + "id" => User.SigningKey.local_key_id(user.ap_id), "owner" => user.ap_id, "publicKeyPem" => public_key }, @@ -97,7 +97,7 @@ def render("user.json", %{user: user}) do "url" => user.ap_id, "manuallyApprovesFollowers" => user.is_locked, "publicKey" => %{ - "id" => "#{user.ap_id}#main-key", + "id" => User.SigningKey.local_key_id(user.ap_id), "owner" => user.ap_id, "publicKeyPem" => public_key }, diff --git a/priv/repo/migrations/20240625220752_move_signing_keys.exs b/priv/repo/migrations/20240625220752_move_signing_keys.exs index 4e8eef6c9..9104b7c29 100644 --- a/priv/repo/migrations/20240625220752_move_signing_keys.exs +++ b/priv/repo/migrations/20240625220752_move_signing_keys.exs @@ -15,6 +15,7 @@ def up do Repo.stream(query, timeout: :infinity) |> Enum.each(fn %User{id: user_id, keys: private_key, local: true, ap_id: ap_id} -> + IO.puts("Migrating user #{user_id}") # we can precompute the public key here... # we do use it on every user view which makes it a bit of a dos attack vector # so we should probably cache it @@ -23,7 +24,7 @@ def up do key = %User.SigningKey{ user_id: user_id, public_key: public_key, - key_id: "#{ap_id}#main-key", + key_id: User.SigningKey.local_key_id(ap_id), private_key: private_key } From c9b3fcc1d3371b637ddd56498ea2e8ae84c04f29 Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Wed, 30 Oct 2024 14:43:18 +0000 Subject: [PATCH 41/70] allow for OTP code changes in :zip --- test/pleroma/emoji/pack_test.exs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/test/pleroma/emoji/pack_test.exs b/test/pleroma/emoji/pack_test.exs index 3843add13..049f27a92 100644 --- a/test/pleroma/emoji/pack_test.exs +++ b/test/pleroma/emoji/pack_test.exs @@ -64,7 +64,10 @@ test "returns error when zip file is bad", %{pack: pack} do path: Path.absname("test/instance_static/emoji/test_pack/blank.png") } - assert Pack.add_file(pack, nil, nil, file) == {:error, :bad_eocd} + # this varies by erlang OTP + possible_error_codes = [:bad_eocd, :einval] + {:error, code} = Pack.add_file(pack, nil, nil, file) + assert Enum.member?(possible_error_codes, code) end test "returns pack when zip file is empty", %{pack: pack} do From 0f9c9aac380a3864ee0099e2d398b395feda9fdc Mon Sep 17 00:00:00 2001 From: Oneric Date: Sat, 9 Nov 2024 16:52:12 +0100 Subject: [PATCH 42/70] Completely omit id for anonymous objects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Current AP spec demands anonymous objects to have an id value, but explicitly set it to JSON null. Howeveras it turns out this is incompatible with JSON-LD requiring `@id` to be a string and thus AP spec is incompatible iwth the Ativity Streams spec it is based on. This is an issue for (the few) AP implementers actually performing JSON-LD processing, like IceShrimp.NET. This was uncovered by IceShrimp.NET’s zotan due to our adoption of anonymous objects for emoj in f1018867097e6f293d8b2b5b6935f0a7ebf99bd0. The issues is being discussed by W3C, and will most likely be resolved via an errata redefining anonymous objects to completely omit the id field just like transient objects already do. See: https://github.com/w3c/activitypub/issues/476 Fixes: https://akkoma.dev/AkkomaGang/akkoma/issues/848 --- lib/pleroma/web/activity_pub/transmogrifier.ex | 3 +-- .../web/activity_pub/transmogrifier/note_handling_test.exs | 1 - test/pleroma/web/activity_pub/views/user_view_test.exs | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 75c1f0f0c..5c4db39b9 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -950,8 +950,7 @@ defp build_emoji_tag({name, url}) do "icon" => %{"url" => "#{URI.encode(url)}", "type" => "Image"}, "name" => ":" <> name <> ":", "type" => "Emoji", - "updated" => "1970-01-01T00:00:00Z", - "id" => nil + "updated" => "1970-01-01T00:00:00Z" } end diff --git a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs index 92a096c2d..234a48990 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/note_handling_test.exs @@ -698,7 +698,6 @@ test "take_emoji_tags/1" do assert Transmogrifier.take_emoji_tags(user) == [ %{ "icon" => %{"type" => "Image", "url" => "https://example.org/firefox.png"}, - "id" => nil, "name" => ":firefox:", "type" => "Emoji", "updated" => "1970-01-01T00:00:00Z" 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 7e251e510..4283fb0c8 100644 --- a/test/pleroma/web/activity_pub/views/user_view_test.exs +++ b/test/pleroma/web/activity_pub/views/user_view_test.exs @@ -47,7 +47,6 @@ test "Renders with emoji tags" do "tag" => [ %{ "icon" => %{"type" => "Image", "url" => "/test"}, - "id" => nil, "name" => ":bib:", "type" => "Emoji", "updated" => "1970-01-01T00:00:00Z" From 4c7ef1e027177f41305e95ca9999482126d5be1d Mon Sep 17 00:00:00 2001 From: Norm Date: Sat, 9 Nov 2024 18:24:53 -0500 Subject: [PATCH 43/70] Update supported OTP version to 27 in docs The minor incompatibility should have been fixed with commit bee10eab5ebf6aa4d50238afd3d9c37a014dd745 and PRs #839 and #841. --- 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 87669bd23..8bf55237b 100644 --- a/docs/docs/installation/generic_dependencies.include +++ b/docs/docs/installation/generic_dependencies.include @@ -2,7 +2,7 @@ * PostgreSQL 12+ * Elixir 1.14+ (currently tested up to 1.16) -* Erlang OTP 25+ (currently tested up to OTP26) +* Erlang OTP 25+ (currently tested up to OTP27) * git * file / libmagic * gcc (clang might also work) From 932810c35ecba9d11fa4f11112c3444b62c45b65 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sat, 16 Nov 2024 00:43:38 +0100 Subject: [PATCH 44/70] mrf/object_age: fix handling of non-public objects MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Current logic unconditionally adds public adressing to "cc" and follower adressing to "to" after attempting to strip it from the other one. This creates serious problems: First the bug prompting this investigation and fix, unconditional addition creates duplicates when adressing URIs already were in their intended final field; e.g. this is prominently the case for all "unlisted" posts. Since List.delete only removes the first occurence, this then broke follower-adress stripping later on making the policy ineffective. It’s also just not safe in general wrt to non-public adressing: e.g. pre-existing duplicates didn’t get fully stripped, bespoke adressing modes with only one of public addressing or follower addressing are mangled — and most importantly: any belatedly received DM or follower-only post also got public adressing added! Shockingly this last point was actually asserted as "correct" in tests; it appears to be a mistake from mindless match adjustments while fixing crashes on nil adressing in 10c792110e6ea8ed21f739ef8f4f0eff4659ebf9. Clean up this sloppy logic up, making sure no more duplicates are added by us, all instances of relevant adresses are purged and only readded when they actually existed to begin with. --- CHANGELOG.md | 2 ++ .../web/activity_pub/mrf/object_age_policy.ex | 34 ++++++++++++++----- .../mrf/object_age_policy_test.exs | 2 +- 3 files changed, 29 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f1644dad..3959f66f8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - AP objects with additional JSON-LD profiles beyond ActivityStreams can now be fetched - Single-selection polls no longer expose the voter_count; MastoAPI demands it be null and this confused some clients leading to vote distributions >100% +- ObjectAge policy no longer lets unlisted posts slip through +- ObjectAge policy no longer leaks belated DMs and follower-only posts ## Changed - Refactored Rich Media to cache the content in the database. Fetching operations that could block status rendering have been eliminated. diff --git a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex index 02c9b18ed..b0c940339 100644 --- a/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/object_age_policy.ex @@ -34,16 +34,34 @@ defp check_reject(message, actions) do end end + @spec delete_and_count(list(), term()) :: {integer(), list()} + defp delete_and_count(list, element), do: delete_and_count(list, element, {0, [], list}) + + defp delete_and_count([], _element, {0, _nlist, olist}), do: {0, olist} + defp delete_and_count([], _element, {count, nlist, _olist}), do: {count, Enum.reverse(nlist)} + + defp delete_and_count([h | r], h, {count, nlist, olist}), + do: delete_and_count(r, h, {count + 1, nlist, olist}) + + defp delete_and_count([h | r], element, {count, nlist, olist}), + do: delete_and_count(r, element, {count, [h | nlist], olist}) + + defp insert_if_needed(list, oldcount, element) do + if oldcount <= 0 || Enum.member?(list, element) do + list + else + [element | list] + end + end + defp check_delist(message, actions) do if :delist in actions do with %User{} = user <- User.get_cached_by_ap_id(message["actor"]) do - to = - List.delete(message["to"] || [], Pleroma.Constants.as_public()) ++ - [user.follower_address] + {pubcnt, to} = delete_and_count(message["to"] || [], Pleroma.Constants.as_public()) + {flwcnt, cc} = delete_and_count(message["cc"] || [], user.follower_address) - cc = - List.delete(message["cc"] || [], user.follower_address) ++ - [Pleroma.Constants.as_public()] + cc = insert_if_needed(cc, pubcnt, Pleroma.Constants.as_public()) + to = insert_if_needed(to, flwcnt, user.follower_address) message = message @@ -65,8 +83,8 @@ defp check_delist(message, actions) do defp check_strip_followers(message, actions) do if :strip_followers in actions do with %User{} = user <- User.get_cached_by_ap_id(message["actor"]) do - to = List.delete(message["to"] || [], user.follower_address) - cc = List.delete(message["cc"] || [], user.follower_address) + {_, to} = delete_and_count(message["to"] || [], user.follower_address) + {_, cc} = delete_and_count(message["cc"] || [], user.follower_address) message = message diff --git a/test/pleroma/web/activity_pub/mrf/object_age_policy_test.exs b/test/pleroma/web/activity_pub/mrf/object_age_policy_test.exs index 2f649a0a4..9b61d31f4 100644 --- a/test/pleroma/web/activity_pub/mrf/object_age_policy_test.exs +++ b/test/pleroma/web/activity_pub/mrf/object_age_policy_test.exs @@ -79,7 +79,7 @@ test "works with objects with empty to or cc fields" do {:ok, data} = ObjectAgePolicy.filter(data) - assert Visibility.get_visibility(%{data: data}) == "unlisted" + assert Visibility.get_visibility(%{data: data}) == "direct" end test "it delists an old post" do From 416aebb76a6b8a307d844f25f8d962830ef0fdff Mon Sep 17 00:00:00 2001 From: Oneric Date: Tue, 19 Nov 2024 19:25:31 +0100 Subject: [PATCH 45/70] Fix NodeInfo content-type Fixes: https://akkoma.dev/AkkomaGang/akkoma/issues/852 --- lib/pleroma/web/nodeinfo/nodeinfo_controller.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index ea2d86f92..9975b8dbb 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -31,7 +31,7 @@ def nodeinfo(conn, %{"version" => version}) when version in ["2.0", "2.1"] do conn |> put_resp_header( "content-type", - "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8" + "application/json; profile=\"http://nodeinfo.diaspora.software/ns/schema/#{version}#\"; charset=utf-8" ) |> json(Nodeinfo.get_nodeinfo(version)) end From f048e0cf1bc7a2ca4c415bb29ae80bdea13d1d4b Mon Sep 17 00:00:00 2001 From: Calvin Lee Date: Mon, 25 Nov 2024 23:18:52 +0000 Subject: [PATCH 46/70] Allow MathML core tags in sanitized content --- config/config.exs | 1 + priv/scrubbers/default.ex | 113 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+) diff --git a/config/config.exs b/config/config.exs index e919910b3..bca7211d5 100644 --- a/config/config.exs +++ b/config/config.exs @@ -302,6 +302,7 @@ allow_headings: false, allow_tables: false, allow_fonts: false, + allow_math: true, scrub_policy: [ Pleroma.HTML.Scrubber.Default, Pleroma.HTML.Transform.MediaProxy diff --git a/priv/scrubbers/default.ex b/priv/scrubbers/default.ex index 74de910fd..96473203e 100644 --- a/priv/scrubbers/default.ex +++ b/priv/scrubbers/default.ex @@ -124,6 +124,119 @@ defmodule Pleroma.HTML.Scrubber.Default do Meta.allow_tag_with_these_attributes(:font, ["face"]) end + if Pleroma.Config.get!([:markup, :allow_math]) do + Meta.allow_tag_with_these_attributes("annotation", ["encoding"]) + Meta.allow_tag_with_these_attributes(:"annotation-xml", ["encoding"]) + + Meta.allow_tag_with_these_attributes(:math, [ + "display", + "displaystyle", + "mathvariant", + "scriptlevel" + ]) + + basic_math_tags = [ + "maction", + "merror", + :mi, + "mmultiscripts", + :mn, + "mphantom", + "mprescripts", + "mroot", + "mrow", + "ms", + "msqrt", + "mstyle", + "msub", + "msubsup", + "msup", + "mtable", + "mtext", + "mtr", + "semantics" + ] + + for tag <- basic_math_tags do + Meta.allow_tag_with_these_attributes(unquote(tag), [ + "mathvariant", + "displaystyle", + "scriptlevel" + ]) + end + + Meta.allow_tag_with_these_attributes("mfrac", [ + "displaystyle", + "linethickness", + "mathvariant", + "scriptlevel" + ]) + + Meta.allow_tag_with_these_attributes(:mo, [ + "displaystyle", + "form", + "largeop", + "lspace", + "mathvariant", + "minsize", + "movablelimits", + "rspace", + "scriptlevel", + "stretchy", + "symmetric" + ]) + + Meta.allow_tag_with_these_attributes("mover", [ + "accent", + "displaystyle", + "mathvariant", + "scriptlevel" + ]) + + Meta.allow_tag_with_these_attributes("mpadded", [ + "depth", + "displaystyle", + "height", + "lspace", + "mathvariant", + "scriptlevel", + "voffset", + "width" + ]) + + Meta.allow_tag_with_these_attributes("mspace", [ + "depth", + "displaystyle", + "height", + "mathvariant", + "scriptlevel", + "width" + ]) + + Meta.allow_tag_with_these_attributes("mtd", [ + "columnspan", + "displaystyle", + "mathvariant", + "rowspan", + "scriptlevel" + ]) + + Meta.allow_tag_with_these_attributes("munder", [ + "accentunder", + "displaystyle", + "mathvariant", + "scriptlevel" + ]) + + Meta.allow_tag_with_these_attributes("munderover", [ + "accent", + "accentunder", + "displaystyle", + "mathvariant", + "scriptlevel" + ]) + end + Meta.allow_tag_with_these_attributes(:center, []) Meta.allow_tag_with_these_attributes(:small, []) From 2b1a252cc78dbb3ff8a34a8365b8c049c0b531fb Mon Sep 17 00:00:00 2001 From: "Haelwenn (lanodan) Monnier" Date: Tue, 13 Aug 2024 20:06:01 +0200 Subject: [PATCH 47/70] User: truncate remote user fields instead of rejecting --- .../bugfix-truncate-remote-user-fields.fix | 1 + lib/pleroma/user.ex | 2 ++ test/pleroma/user_test.exs | 15 +++++++++++++++ .../transmogrifier/user_update_handling_test.exs | 4 ++-- 4 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 changelog.d/bugfix-truncate-remote-user-fields.fix diff --git a/changelog.d/bugfix-truncate-remote-user-fields.fix b/changelog.d/bugfix-truncate-remote-user-fields.fix new file mode 100644 index 000000000..239a3c224 --- /dev/null +++ b/changelog.d/bugfix-truncate-remote-user-fields.fix @@ -0,0 +1 @@ +Truncate remote user fields, avoids them getting rejected diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index bf8717ffb..dfeab0410 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -443,6 +443,7 @@ defp fix_follower_address(params), do: params def remote_user_changeset(struct \\ %User{local: false}, params) do bio_limit = Config.get([:instance, :user_bio_length], 5000) name_limit = Config.get([:instance, :user_name_length], 100) + fields_limit = Config.get([:instance, :max_remote_account_fields], 0) name = case params[:name] do @@ -456,6 +457,7 @@ def remote_user_changeset(struct \\ %User{local: false}, params) do |> Map.put_new(:last_refreshed_at, NaiveDateTime.utc_now()) |> truncate_if_exists(:name, name_limit) |> truncate_if_exists(:bio, bio_limit) + |> Map.update(:fields, [], &Enum.take(&1, fields_limit)) |> truncate_fields_param() |> fix_follower_address() diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index cf9cc7519..ac886aaf9 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -966,6 +966,21 @@ test "it is invalid given a local user" do refute cs.valid? end + + test "it truncates fields" do + clear_config([:instance, :max_remote_account_fields], 2) + + fields = [ + %{"name" => "One", "value" => "Uno"}, + %{"name" => "Two", "value" => "Dos"}, + %{"name" => "Three", "value" => "Tres"} + ] + + cs = User.remote_user_changeset(@valid_remote |> Map.put(:fields, fields)) + + assert [%{"name" => "One", "value" => "Uno"}, %{"name" => "Two", "value" => "Dos"}] == + Ecto.Changeset.get_field(cs, :fields) + end end describe "followers and friends" do diff --git a/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs b/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs index b1a064772..35a5fe03d 100644 --- a/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier/user_update_handling_test.exs @@ -119,8 +119,8 @@ test "it works with custom profile fields" do user = User.get_cached_by_ap_id(user.ap_id) assert user.fields == [ - %{"name" => "foo", "value" => "updated"}, - %{"name" => "foo1", "value" => "updated"} + %{"name" => "foo", "value" => "bar"}, + %{"name" => "foo11", "value" => "bar11"} ] update_data = From d1d82782dbd93c1037a438526b92670464b19b61 Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Tue, 26 Nov 2024 09:35:56 +0000 Subject: [PATCH 48/70] add signing key index --- .../migrations/20241126093029_add_signing_key_index.exs | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 priv/repo/migrations/20241126093029_add_signing_key_index.exs diff --git a/priv/repo/migrations/20241126093029_add_signing_key_index.exs b/priv/repo/migrations/20241126093029_add_signing_key_index.exs new file mode 100644 index 000000000..25df1bb7f --- /dev/null +++ b/priv/repo/migrations/20241126093029_add_signing_key_index.exs @@ -0,0 +1,7 @@ +defmodule Pleroma.Repo.Migrations.AddSigningKeyIndex do + use Ecto.Migration + + def change do + create_if_not_exists(index(:signing_keys, [:user_id], name: :signing_keys_user_id_index)) + end +end From 79b282dea624a1f933a45b471753097231d2b680 Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Tue, 26 Nov 2024 09:36:20 +0000 Subject: [PATCH 49/70] bump version --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 7afc7f16a..982876fcb 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("3.13.2"), + version: version("3.13.3"), elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), compilers: Mix.compilers(), From 834edfcf96cd7149adffec6d81724ff4be3c18ac Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Tue, 26 Nov 2024 09:49:37 +0000 Subject: [PATCH 50/70] add changelog --- CHANGELOG.md | 2 +- changelog.d/bugfix-truncate-remote-user-fields.fix | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) delete mode 100644 changelog.d/bugfix-truncate-remote-user-fields.fix diff --git a/CHANGELOG.md b/CHANGELOG.md index 0f1644dad..74a925a3f 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 +## 3.13.3 ## BREAKING - Minimum PostgreSQL version is raised to 12 diff --git a/changelog.d/bugfix-truncate-remote-user-fields.fix b/changelog.d/bugfix-truncate-remote-user-fields.fix deleted file mode 100644 index 239a3c224..000000000 --- a/changelog.d/bugfix-truncate-remote-user-fields.fix +++ /dev/null @@ -1 +0,0 @@ -Truncate remote user fields, avoids them getting rejected From 7583eceb38c3bcfa8a10d31c12963667178fd2ec Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 1 Dec 2024 01:40:52 +0100 Subject: [PATCH 51/70] Make SigningKey data migration future-proof Bug originally discovered by tudbut --- .../migrations/20240625220752_move_signing_keys.exs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/priv/repo/migrations/20240625220752_move_signing_keys.exs b/priv/repo/migrations/20240625220752_move_signing_keys.exs index 9104b7c29..f5569ce09 100644 --- a/priv/repo/migrations/20240625220752_move_signing_keys.exs +++ b/priv/repo/migrations/20240625220752_move_signing_keys.exs @@ -8,13 +8,14 @@ def up do # we do not handle remote users here! # because we want to store a key id -> user id mapping, and we don't # currently store key ids for remote users... - query = - from(u in User) - |> where(local: true) - - Repo.stream(query, timeout: :infinity) + # Also this MUST use select, else the migration will fail in future installs with new user fields! + from(u in Pleroma.User, + where: u.local == true, + select: {u.id, u.keys, u.ap_id} + ) + |> Repo.stream(timeout: :infinity) |> Enum.each(fn - %User{id: user_id, keys: private_key, local: true, ap_id: ap_id} -> + {user_id, private_key, ap_id} -> IO.puts("Migrating user #{user_id}") # we can precompute the public key here... # we do use it on every user view which makes it a bit of a dos attack vector From 294de939cb262e35f5979988ddbe479ba4089ea1 Mon Sep 17 00:00:00 2001 From: Oneric Date: Wed, 30 Oct 2024 23:05:57 +0100 Subject: [PATCH 52/70] signing_key: refactor nested case into with statement The error branches were already effectively identical before. This change is purely cosmetic. --- lib/pleroma/user/signing_key.ex | 39 ++++++++++++++------------------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/lib/pleroma/user/signing_key.ex b/lib/pleroma/user/signing_key.ex index f25489068..87149aa58 100644 --- a/lib/pleroma/user/signing_key.ex +++ b/lib/pleroma/user/signing_key.ex @@ -194,31 +194,24 @@ def get_or_fetch_by_key_id(key_id) do """ def fetch_remote_key(key_id) do Logger.debug("Fetching remote key: #{key_id}") - resp = Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(key_id) - case resp do - {:ok, _body} -> - case handle_signature_response(resp) do - {:ok, ap_id, public_key_pem} -> - Logger.debug("Fetched remote key: #{ap_id}") - # fetch the user - {:ok, user} = User.get_or_fetch_by_ap_id(ap_id) - # store the key - key = %__MODULE__{ - user_id: user.id, - public_key: public_key_pem, - key_id: key_id - } + with {:ok, _body} = resp <- + Pleroma.Object.Fetcher.fetch_and_contain_remote_object_from_id(key_id), + {:ok, ap_id, public_key_pem} <- handle_signature_response(resp) do + Logger.debug("Fetched remote key: #{ap_id}") + # fetch the user + {:ok, user} = User.get_or_fetch_by_ap_id(ap_id) + # store the key + key = %__MODULE__{ + user_id: user.id, + public_key: public_key_pem, + key_id: key_id + } - Repo.insert(key, on_conflict: :replace_all, conflict_target: :key_id) - - e -> - Logger.debug("Failed to fetch remote key: #{inspect(e)}") - {:error, "Could not fetch key"} - end - - _ -> - Logger.debug("Failed to fetch remote key: #{inspect(resp)}") + Repo.insert(key, on_conflict: :replace_all, conflict_target: :key_id) + else + e -> + Logger.debug("Failed to fetch remote key: #{inspect(e)}") {:error, "Could not fetch key"} end end From 7632765b4365511bfde1797f4eb81cf5b45628d0 Mon Sep 17 00:00:00 2001 From: nopjmp Date: Sun, 15 Dec 2024 16:12:37 -0600 Subject: [PATCH 53/70] Only proxy HTTP and HTTP urls via Media Proxy We make an assumption that we are only proxying HTTP/HTTPS hosted media through the media proxy endpoint. Fixes: #859 --- lib/pleroma/web/media_proxy.ex | 6 ++++-- test/pleroma/web/media_proxy_test.exs | 4 ++++ 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/lib/pleroma/web/media_proxy.ex b/lib/pleroma/web/media_proxy.ex index 61b6f2a62..9e48dda74 100644 --- a/lib/pleroma/web/media_proxy.ex +++ b/lib/pleroma/web/media_proxy.ex @@ -52,11 +52,11 @@ def url(url) do @spec url_proxiable?(String.t()) :: boolean() def url_proxiable?(url) do - not local?(url) and not whitelisted?(url) and not blocked?(url) + not local?(url) and not whitelisted?(url) and not blocked?(url) and http_scheme?(url) end def preview_url(url, preview_params \\ []) do - if preview_enabled?() do + if preview_enabled?() and url_proxiable?(url) do encode_preview_url(url, preview_params) else url(url) @@ -71,6 +71,8 @@ def preview_enabled?, do: enabled?() and !!Config.get([:media_preview_proxy, :en def local?(url), do: String.starts_with?(url, Endpoint.url()) + def http_scheme?(url), do: String.starts_with?(url, ["http:", "https:"]) + def whitelisted?(url) do %{host: domain} = URI.parse(url) diff --git a/test/pleroma/web/media_proxy_test.exs b/test/pleroma/web/media_proxy_test.exs index bd5efe4c9..1a6e9a521 100644 --- a/test/pleroma/web/media_proxy_test.exs +++ b/test/pleroma/web/media_proxy_test.exs @@ -37,6 +37,10 @@ test "ignores local url" do assert MediaProxy.url(local_root) == local_root end + test "ignores data url" do + assert MediaProxy.url("data:image/png;base64,") == "data:image/png;base64," + end + test "encodes and decodes URL" do url = "https://pleroma.soykaf.com/static/logo.png" encoded = MediaProxy.url(url) From ff5d1973411b62dea0e54e71315c7479ff35f9cd Mon Sep 17 00:00:00 2001 From: nopjmp Date: Sun, 15 Dec 2024 17:36:24 -0600 Subject: [PATCH 54/70] Update CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74a925a3f..86269583c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ 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 + +## Fixed +- Media proxy no longer attempts to proxy embedded images + ## 3.13.3 ## BREAKING From f19d5d13809f044580018d1ff65fa41e0335fa31 Mon Sep 17 00:00:00 2001 From: Norm Date: Tue, 17 Dec 2024 18:30:01 -0500 Subject: [PATCH 55/70] Set customize_hostname_check for Swoosh.Adapters.SMTP This should hopefully fix issues with connecting to SMTP servers with wildcard TLS certificates. Taken from https://erlef.github.io/security-wg/secure_coding_and_deployment_hardening/ssl Fixes https://akkoma.dev/AkkomaGang/akkoma/issues/660 --- lib/pleroma/emails/mailer.ex | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/emails/mailer.ex b/lib/pleroma/emails/mailer.ex index 6a79a7694..af513f1f1 100644 --- a/lib/pleroma/emails/mailer.ex +++ b/lib/pleroma/emails/mailer.ex @@ -84,8 +84,14 @@ defp default_config(Swoosh.Adapters.SMTP, conf, _) do 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, + # This allows wildcard ceritifcates to be verified properly. + # The :https parameter simply means to use the HTTPS wildcard format + # (as opposed to say LDAP). SMTP servers tend to use the same type of + # certs as HTTPS ones so this should work for most. + customize_hostname_check: [ + match_fun: :public_key.pkix_verify_hostname_match_fun(:https) + ], # the default of 10 is too restrictive depth: 32 ] From 7615a11a1ef826ccf12455e4ad149f9da7f0f7bb Mon Sep 17 00:00:00 2001 From: Oneric Date: Fri, 3 Jan 2025 20:33:41 +0100 Subject: [PATCH 56/70] changelog: fix shuffled and add missing entries --- CHANGELOG.md | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 744e77dc8..3306f47c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Fixed - Media proxy no longer attempts to proxy embedded images +- ObjectAge policy no longer lets unlisted posts slip through +- ObjectAge policy no longer leaks belated DMs and follower-only posts +- the NodeINfo endpoint now uses the correct content type + +## Changed +- Anonymous objects now federate completely without an id + adopting a proposed AP spec errata and restoring federation + with e.g. IceShrimp.NET and fedify-based implementations ## 3.13.3 @@ -28,8 +36,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - AP objects with additional JSON-LD profiles beyond ActivityStreams can now be fetched - Single-selection polls no longer expose the voter_count; MastoAPI demands it be null and this confused some clients leading to vote distributions >100% -- ObjectAge policy no longer lets unlisted posts slip through -- ObjectAge policy no longer leaks belated DMs and follower-only posts ## Changed - Refactored Rich Media to cache the content in the database. Fetching operations that could block status rendering have been eliminated. From f2e45d4d4bd7a87b5b6aa5abb975244c5eff31b5 Mon Sep 17 00:00:00 2001 From: Oneric Date: Thu, 10 Oct 2024 00:22:40 +0000 Subject: [PATCH 57/70] Teach admin-fe about custom source URLs Matching https://akkoma.dev/AkkomaGang/akkoma-fe/pulls/421 --- CHANGELOG.md | 4 ++++ config/description.exs | 16 +++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 744e77dc8..238e23ef8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## UNRELEASED +## Added +- It is now possible to display custom source URLs in akkoma-fe; + the settings are part of the frontend configuration + ## Fixed - Media proxy no longer attempts to proxy embedded images diff --git a/config/description.exs b/config/description.exs index b69478fdb..63113439a 100644 --- a/config/description.exs +++ b/config/description.exs @@ -1194,7 +1194,9 @@ showInstanceSpecificPanel: false, subjectLineBehavior: "email", theme: "pleroma-dark", - webPushNotifications: false + webPushNotifications: false, + backendCommitUrl: "", + frontendCommitUrl: "" } ], children: [ @@ -1398,6 +1400,18 @@ label: "Stop Gifs", type: :boolean, description: "Whether to pause animated images until they're hovered on" + }, + %{ + key: :backendCommitUrl, + label: "Backend Commit URL", + type: :string, + description: "URL prefix for backend commit hashes" + }, + %{ + key: :frontendCommitUrl, + label: "Frontend Commit URL", + type: :string, + description: "URL prefix for frontend commit hashes" } ] }, From bcfbfbcff594d3b4dc9241ad38df5c1ca5729145 Mon Sep 17 00:00:00 2001 From: Oneric Date: Sun, 2 Jun 2024 21:42:36 +0200 Subject: [PATCH 58/70] Don't try to cleanup remote attachments MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The cleanup attachment worker was run for every deleted post, even if it’s a remote post whose attachments we don't even store. This was especially bad due to attachment cleanup involving a particularly heavy query wasting a bunch of database perf for nil. This was uncovered by comparing statistics from https://akkoma.dev/AkkomaGang/akkoma/issues/784 and https://akkoma.dev/AkkomaGang/akkoma/issues/765#issuecomment-12256 --- CHANGELOG.md | 2 + lib/pleroma/object.ex | 15 +---- .../workers/attachments_cleanup_worker.ex | 49 ++++++++++++--- .../attachments_cleanup_worker_test.exs | 60 +++++++++++++++++++ 4 files changed, 103 insertions(+), 23 deletions(-) create mode 100644 test/pleroma/workers/attachments_cleanup_worker_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 744e77dc8..04186f771 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Fixed - Media proxy no longer attempts to proxy embedded images +- Fix significant uneccessary overhead of attachment cleanup; + it no longer attempts to cleanup attachments of deleted remote posts ## 3.13.3 diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index 379b361f8..5d84bb286 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -9,7 +9,6 @@ defmodule Pleroma.Object do import Ecto.Changeset alias Pleroma.Activity - alias Pleroma.Config alias Pleroma.Hashtag alias Pleroma.Object alias Pleroma.Object.Fetcher @@ -241,23 +240,11 @@ def delete(%Object{data: %{"id" => id}} = object) do with {:ok, _obj} = swap_object_with_tombstone(object), deleted_activity = Activity.delete_all_by_object_ap_id(id), {:ok, _} <- invalid_object_cache(object) do - cleanup_attachments( - Config.get([:instance, :cleanup_attachments]), - %{object: object} - ) - + AttachmentsCleanupWorker.enqueue_if_needed(object.data) {:ok, object, deleted_activity} end end - @spec cleanup_attachments(boolean(), %{required(:object) => map()}) :: - {:ok, Oban.Job.t() | nil} - def cleanup_attachments(true, %{object: _} = params) do - AttachmentsCleanupWorker.enqueue("cleanup_attachments", params) - end - - def cleanup_attachments(_, _), do: {:ok, nil} - def prune(%Object{data: %{"id" => _id}} = object) do with {:ok, object} <- Repo.delete(object), {:ok, _} <- invalid_object_cache(object) do diff --git a/lib/pleroma/workers/attachments_cleanup_worker.ex b/lib/pleroma/workers/attachments_cleanup_worker.ex index f5090dae7..58bbda94b 100644 --- a/lib/pleroma/workers/attachments_cleanup_worker.ex +++ b/lib/pleroma/workers/attachments_cleanup_worker.ex @@ -5,30 +5,61 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do import Ecto.Query + alias Pleroma.Config alias Pleroma.Object alias Pleroma.Repo use Pleroma.Workers.WorkerHelper, queue: "attachments_cleanup" + @doc """ + Takes object data and if necessary enqueues a job, + deleting all attachments of the post eligible for cleanup + """ + @spec enqueue_if_needed(map()) :: {:ok, Oban.Job.t()} | {:ok, :skip} | {:error, any()} + def enqueue_if_needed(%{ + "actor" => actor, + "attachment" => [_ | _] = attachments + }) do + with true <- Config.get([:instance, :cleanup_attachments]), + true <- URI.parse(actor).host == Pleroma.Web.Endpoint.host(), + [_ | _] <- attachments do + enqueue("cleanup_attachments", %{"actor" => actor, "attachments" => attachments}) + else + _ -> {:ok, :skip} + end + end + + def enqueue_if_needed(_), do: {:ok, :skip} + @impl Oban.Worker def perform(%Job{ args: %{ "op" => "cleanup_attachments", - "object" => %{"data" => %{"attachment" => [_ | _] = attachments, "actor" => actor}} + "attachments" => [_ | _] = attachments, + "actor" => actor } }) do - if Pleroma.Config.get([:instance, :cleanup_attachments], false) do - attachments - |> Enum.flat_map(fn item -> Enum.map(item["url"], & &1["href"]) end) - |> fetch_objects - |> prepare_objects(actor, Enum.map(attachments, & &1["name"])) - |> filter_objects - |> do_clean - end + attachments + |> Enum.flat_map(fn item -> Enum.map(item["url"], & &1["href"]) end) + |> fetch_objects + |> prepare_objects(actor, Enum.map(attachments, & &1["name"])) + |> filter_objects + |> do_clean {:ok, :success} end + # Left over already enqueued jobs in the old format + # This function clause can be deleted once sufficient time passed after 3.14 + def perform(%Job{ + args: %{ + "op" => "cleanup_attachments", + "object" => %{"data" => data} + } + }) do + enqueue_if_needed(data) + end + def perform(%Job{args: %{"op" => "cleanup_attachments", "object" => _object}}), do: {:ok, :skip} defp do_clean({object_ids, attachment_urls}) do diff --git a/test/pleroma/workers/attachments_cleanup_worker_test.exs b/test/pleroma/workers/attachments_cleanup_worker_test.exs new file mode 100644 index 000000000..2212db927 --- /dev/null +++ b/test/pleroma/workers/attachments_cleanup_worker_test.exs @@ -0,0 +1,60 @@ +# Akkoma: Magically expressive social media +# Copyright © 2024 Akkoma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Workers.AttachmentsCleanupWorkerTest do + use Pleroma.DataCase, async: false + use Oban.Testing, repo: Pleroma.Repo + + import Pleroma.Factory + + alias Pleroma.Workers.AttachmentsCleanupWorker + + setup do + clear_config([:instance, :cleanup_attachments], true) + + file = %Plug.Upload{ + content_type: "image/jpeg", + path: Path.absname("test/fixtures/image.jpg"), + filename: "an_image.jpg" + } + + user = insert(:user) + + {:ok, %Pleroma.Object{} = attachment} = + Pleroma.Web.ActivityPub.ActivityPub.upload(file, actor: user.ap_id) + + {:ok, attachment: attachment, user: user} + end + + test "does not enqueue remote post" do + remote_data = %{ + "id" => "https://remote.example/obj/123", + "actor" => "https://remote.example/user/1", + "content" => "content", + "attachment" => [ + %{ + "type" => "Document", + "mediaType" => "image/png", + "name" => "marvellous image", + "url" => "https://remote.example/files/image.png" + } + ] + } + + assert {:ok, :skip} = AttachmentsCleanupWorker.enqueue_if_needed(remote_data) + end + + test "enqueues local post", %{attachment: attachment, user: user} do + local_url = Pleroma.Web.Endpoint.url() + + local_data = %{ + "id" => local_url <> "/obj/123", + "actor" => user.ap_id, + "content" => "content", + "attachment" => [attachment.data] + } + + assert {:ok, %Oban.Job{}} = AttachmentsCleanupWorker.enqueue_if_needed(local_data) + end +end From e8bf4422ff6440d4404ba6a5ed4092e717649f5e Mon Sep 17 00:00:00 2001 From: Oneric Date: Mon, 3 Jun 2024 23:07:10 +0200 Subject: [PATCH 59/70] Delay attachment deletion MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Otherwise attachments have a high chance to disappear with akkoma-fe’s “delete & redraft” feature when cleanup is enabled in the backend. Since we don't know whether a deletion was intended to be part of a redraft process or even if whether the redraft was abandoned we still have to delete attachments eventually. A thirty minute delay should provide sufficient time for redrafting. Fixes: https://akkoma.dev/AkkomaGang/akkoma/issues/775 --- CHANGELOG.md | 4 +++ config/config.exs | 1 + docs/docs/configuration/cheatsheet.md | 1 + .../workers/attachments_cleanup_worker.ex | 6 ++++- .../attachments_cleanup_worker_test.exs | 26 +++++++++++++++++++ 5 files changed, 37 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04186f771..bd4bcccf9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,10 +6,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## UNRELEASED +## Added +- New config option `:instance, :cleanup_attachments_delay` + ## Fixed - Media proxy no longer attempts to proxy embedded images - Fix significant uneccessary overhead of attachment cleanup; it no longer attempts to cleanup attachments of deleted remote posts +- Fix “Delete & Redraft” often losing attachments if attachment cleanup was enabled ## 3.13.3 diff --git a/config/config.exs b/config/config.exs index e919910b3..39b53a010 100644 --- a/config/config.exs +++ b/config/config.exs @@ -255,6 +255,7 @@ external_user_synchronization: true, extended_nickname_format: true, cleanup_attachments: false, + cleanup_attachments_delay: 1800, multi_factor_authentication: [ totp: [ # digits 6 or 8 diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index 916e1cc0c..9a50fc2bb 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -58,6 +58,7 @@ To add configuration to your config file, you can copy it from the base config. * `registration_reason_length`: Maximum registration reason length (default: `500`). * `external_user_synchronization`: Enabling following/followers counters synchronization for external users. * `cleanup_attachments`: Remove attachments along with statuses. Does not affect duplicate files and attachments without status. Enabling this will increase load to database when deleting statuses on larger instances. +* `cleanup_attachments_delay`: How many seconds to wait after post deletion before attempting to deletion; useful for “delete & redraft” functionality (default: `1800`) * `show_reactions`: Let favourites and emoji reactions be viewed through the API (default: `true`). * `password_reset_token_validity`: The time after which reset tokens aren't accepted anymore, in seconds (default: one day). * `local_bubble`: Array of domains representing instances closely related to yours. Used to populate the `bubble` timeline. e.g `["example.com"]`, (default: `[]`) diff --git a/lib/pleroma/workers/attachments_cleanup_worker.ex b/lib/pleroma/workers/attachments_cleanup_worker.ex index 58bbda94b..f1204a861 100644 --- a/lib/pleroma/workers/attachments_cleanup_worker.ex +++ b/lib/pleroma/workers/attachments_cleanup_worker.ex @@ -23,7 +23,11 @@ def enqueue_if_needed(%{ with true <- Config.get([:instance, :cleanup_attachments]), true <- URI.parse(actor).host == Pleroma.Web.Endpoint.host(), [_ | _] <- attachments do - enqueue("cleanup_attachments", %{"actor" => actor, "attachments" => attachments}) + enqueue( + "cleanup_attachments", + %{"actor" => actor, "attachments" => attachments}, + schedule_in: Config.get!([:instance, :cleanup_attachments_delay]) + ) else _ -> {:ok, :skip} end diff --git a/test/pleroma/workers/attachments_cleanup_worker_test.exs b/test/pleroma/workers/attachments_cleanup_worker_test.exs index 2212db927..d180763fb 100644 --- a/test/pleroma/workers/attachments_cleanup_worker_test.exs +++ b/test/pleroma/workers/attachments_cleanup_worker_test.exs @@ -8,7 +8,9 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorkerTest do import Pleroma.Factory + alias Pleroma.Object alias Pleroma.Workers.AttachmentsCleanupWorker + alias Pleroma.Tests.ObanHelpers setup do clear_config([:instance, :cleanup_attachments], true) @@ -57,4 +59,28 @@ test "enqueues local post", %{attachment: attachment, user: user} do assert {:ok, %Oban.Job{}} = AttachmentsCleanupWorker.enqueue_if_needed(local_data) end + + test "doesn't delete immediately", %{attachment: attachment, user: user} do + delay = 6000 + clear_config([:instance, :cleanup_attachments_delay], delay) + + note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}}) + + uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads]) + %{"url" => [%{"href" => href}]} = attachment.data + path = "#{uploads_dir}/#{Path.basename(href)}" + + assert File.exists?(path) + + Object.delete(note) + Process.sleep(2000) + + assert File.exists?(path) + + ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker)) + + assert Object.get_by_id(note.id).data["deleted"] + assert Object.get_by_id(attachment.id) == nil + refute File.exists?(path) + end end From d8c7ed70d0152059d60cd7d492927a7a407675ff Mon Sep 17 00:00:00 2001 From: eviloatmeal Date: Fri, 3 Jan 2025 21:17:32 +0100 Subject: [PATCH 60/70] openbsd: update service file Changes suggested and tested by eviloatmeal Fixes: https://akkoma.dev/AkkomaGang/akkoma/issues/864 --- installation/openbsd/rc.d/akkomad | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/installation/openbsd/rc.d/akkomad b/installation/openbsd/rc.d/akkomad index 68be46c9a..fa3c19b2b 100755 --- a/installation/openbsd/rc.d/akkomad +++ b/installation/openbsd/rc.d/akkomad @@ -11,11 +11,13 @@ # daemon="/usr/local/bin/elixir" -daemon_flags="--detached -S /usr/local/bin/mix phx.server" +daemon_flags="-S /usr/local/bin/mix phx.server" daemon_user="_akkoma" +daemon_execdir="/home/_akkoma/akkoma" . /etc/rc.d/rc.subr +rc_bg="YES" rc_reload=NO pexp="phx.server" @@ -24,7 +26,7 @@ rc_check() { } rc_start() { - ${rcexec} "cd akkoma; ${daemon} ${daemon_flags}" + rc_exec "${daemon} ${daemon_flags}" } rc_stop() { From 7ed52838f457a0ce20235892a35b56523ed0afee Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Sun, 5 Jan 2025 16:22:38 +0000 Subject: [PATCH 61/70] fix test --- test/pleroma/web/mastodon_api/views/status_view_test.exs | 1 + 1 file changed, 1 insertion(+) 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 6421df132..6315a4806 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -588,6 +588,7 @@ test "attachments" do end test "put the url advertised in the Activity in to the url attribute" do + Pleroma.Config.put([:instance, :limit_to_local_content], false) id = "https://wedistribute.org/wp-json/pterotype/v1/object/85810" [activity] = Activity.search(nil, id) From ae40ccb8caf330456f1709065fe25ec452857fcc Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Sun, 5 Jan 2025 16:23:09 +0000 Subject: [PATCH 62/70] add changelog entry --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e463b33b6..1ca19e9c7 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 +## 2025.01 ## Added - New config option `:instance, :cleanup_attachments_delay` From 55fc410f80db23feaf3fd0f7025233382d7c83a4 Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Sun, 5 Jan 2025 16:25:42 +0000 Subject: [PATCH 63/70] bump version number --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 982876fcb..cd2485a20 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("3.13.3"), + version: version("3.14.0"), elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), compilers: Mix.compilers(), From 1be18a9f4cdfb722189c879f44335c4a8fa32be7 Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Sun, 5 Jan 2025 16:37:54 +0000 Subject: [PATCH 64/70] update deprecated CI config --- .woodpecker/build-amd64.yml | 15 +++++++++------ .woodpecker/build-arm64.yml | 13 ++++++++----- .woodpecker/docs.yml | 4 ---- .woodpecker/lint.yml | 4 ---- .woodpecker/test.yml | 4 ---- 5 files changed, 17 insertions(+), 23 deletions(-) diff --git a/.woodpecker/build-amd64.yml b/.woodpecker/build-amd64.yml index 32bbd2456..0a41fafef 100644 --- a/.woodpecker/build-amd64.yml +++ b/.woodpecker/build-amd64.yml @@ -6,9 +6,12 @@ depends_on: variables: - &scw-secrets - - SCW_ACCESS_KEY - - SCW_SECRET_KEY - - SCW_DEFAULT_ORGANIZATION_ID + SCW_ACCESS_KEY: + from_secret: SCW_ACCESS_KEY + SCW_SECRET_KEY: + from_secret: SCW_SECRET_KEY + SCW_DEFAULT_ORGANIZATION_ID: + from_secret: SCW_DEFAULT_ORGANIZATION_ID - &setup-hex "mix local.hex --force && mix local.rebar --force" - &on-release when: @@ -56,7 +59,7 @@ steps: release-debian-bookworm: image: akkoma/releaser <<: *on-release - secrets: *scw-secrets + environment: *scw-secrets commands: - export SOURCE=akkoma-amd64.zip # AMD64 @@ -85,7 +88,7 @@ steps: release-debian-bullseye: image: akkoma/releaser <<: *on-release - secrets: *scw-secrets + environment: *scw-secrets commands: - export SOURCE=akkoma-amd64-debian-bullseye.zip # AMD64 @@ -111,7 +114,7 @@ steps: release-musl: image: akkoma/releaser <<: *on-stable - secrets: *scw-secrets + environment: *scw-secrets commands: - export SOURCE=akkoma-amd64-musl.zip - export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-amd64-musl.zip diff --git a/.woodpecker/build-arm64.yml b/.woodpecker/build-arm64.yml index 7bcdc1435..2ee94be3b 100644 --- a/.woodpecker/build-arm64.yml +++ b/.woodpecker/build-arm64.yml @@ -6,9 +6,12 @@ depends_on: variables: - &scw-secrets - - SCW_ACCESS_KEY - - SCW_SECRET_KEY - - SCW_DEFAULT_ORGANIZATION_ID + SCW_ACCESS_KEY: + from_secret: SCW_ACCESS_KEY + SCW_SECRET_KEY: + from_secret: SCW_SECRET_KEY + SCW_DEFAULT_ORGANIZATION_ID: + from_secret: SCW_DEFAULT_ORGANIZATION_ID - &setup-hex "mix local.hex --force && mix local.rebar --force" - &on-release when: @@ -56,7 +59,7 @@ steps: release-debian-bookworm: image: akkoma/releaser:arm64 <<: *on-release - secrets: *scw-secrets + environment: *scw-secrets commands: - export SOURCE=akkoma-arm64.zip - export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-arm64-ubuntu-jammy.zip @@ -83,7 +86,7 @@ steps: release-musl: image: akkoma/releaser:arm64 <<: *on-stable - secrets: *scw-secrets + environment: *scw-secrets commands: - export SOURCE=akkoma-arm64-musl.zip - export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-arm64-musl.zip diff --git a/.woodpecker/docs.yml b/.woodpecker/docs.yml index be7444e28..c550c3022 100644 --- a/.woodpecker/docs.yml +++ b/.woodpecker/docs.yml @@ -6,10 +6,6 @@ depends_on: - build-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: diff --git a/.woodpecker/lint.yml b/.woodpecker/lint.yml index 623494301..70e8fc95d 100644 --- a/.woodpecker/lint.yml +++ b/.woodpecker/lint.yml @@ -2,10 +2,6 @@ labels: 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: diff --git a/.woodpecker/test.yml b/.woodpecker/test.yml index 45cf7f93d..bef503752 100644 --- a/.woodpecker/test.yml +++ b/.woodpecker/test.yml @@ -17,10 +17,6 @@ matrix: OTP_VERSION: 26 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: From a846e60d718ff14dcd7a80e8b7b5e20caca96eee Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Sun, 5 Jan 2025 16:39:30 +0000 Subject: [PATCH 65/70] and the docs one --- .woodpecker/docs.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/.woodpecker/docs.yml b/.woodpecker/docs.yml index c550c3022..f8cd12145 100644 --- a/.woodpecker/docs.yml +++ b/.woodpecker/docs.yml @@ -45,12 +45,14 @@ variables: steps: docs: <<: *on-point-release - secrets: - - SCW_ACCESS_KEY - - SCW_SECRET_KEY - - SCW_DEFAULT_ORGANIZATION_ID environment: CI: "true" + SCW_ACCESS_KEY: + from_secret: SCW_ACCESS_KEY + SCW_SECRET_KEY: + from_secret: SCW_SECRET_KEY + SCW_DEFAULT_ORGANIZATION_ID: + from_secret: SCW_DEFAULT_ORGANIZATION_ID image: python:3.10-slim commands: - apt-get update && apt-get install -y rclone wget git zip From 054396a99ed81fd659f4394e3bfa7fab17382773 Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Sun, 5 Jan 2025 17:23:52 +0000 Subject: [PATCH 66/70] ARM64 --- .woodpecker/build-arm64.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.woodpecker/build-arm64.yml b/.woodpecker/build-arm64.yml index 2ee94be3b..b1e495153 100644 --- a/.woodpecker/build-arm64.yml +++ b/.woodpecker/build-arm64.yml @@ -1,5 +1,5 @@ labels: - platform: linux/aarch64 + platform: linux/arm64 depends_on: - test From a2256b3f9edf9836efa4968509307887ccc0c63d Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Sun, 5 Jan 2025 17:30:32 +0000 Subject: [PATCH 67/70] add unreleased section --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1ca19e9c7..f979889e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ 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 + ## 2025.01 ## Added From 2e049037de033d51a6366014de7037a5f585d9ed Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Sun, 5 Jan 2025 17:35:01 +0000 Subject: [PATCH 68/70] force CI build --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f979889e2..07b758ac6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased + ## 2025.01 ## Added From 1ffbaa2924a84bb6fab19062d73996fb13de8e9b Mon Sep 17 00:00:00 2001 From: Floatingghost Date: Mon, 6 Jan 2025 11:43:41 +0000 Subject: [PATCH 69/70] don't allow a nil inbox to obliterate federation --- CHANGELOG.md | 3 +++ lib/pleroma/web/activity_pub/publisher.ex | 4 +++- test/pleroma/web/activity_pub/publisher_test.exs | 3 +++ 3 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 07b758ac6..42ed630fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Unreleased +## 2025.01.01 + +Hotfix: Federation could break if a null value found its way into `should_federate?\1` ## 2025.01 diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index 4fe394be6..07f430805 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -112,7 +112,7 @@ defp allowed_instances do Config.get([:mrf_simple, :accept]) end - def should_federate?(url) do + def should_federate?(url) when is_binary(url) do %{host: host} = URI.parse(url) with {nil, false} <- {nil, is_nil(host)}, @@ -137,6 +137,8 @@ def should_federate?(url) do end end + def should_federate?(_), do: false + @spec recipients(User.t(), Activity.t()) :: list(User.t()) | [] defp recipients(actor, activity) do followers = diff --git a/test/pleroma/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs index eeec59cfb..5896568b8 100644 --- a/test/pleroma/web/activity_pub/publisher_test.exs +++ b/test/pleroma/web/activity_pub/publisher_test.exs @@ -519,6 +519,9 @@ test "publish to url with with different ports" do test "should not obliterate itself if the inbox URL is bad" do url = "/inbox" refute Pleroma.Web.ActivityPub.Publisher.should_federate?(url) + + url = nil + refute Pleroma.Web.ActivityPub.Publisher.should_federate?(url) end end end From ad92e504d773e6bbeaee5a2ed47154a021ec66ed Mon Sep 17 00:00:00 2001 From: floatingghost Date: Mon, 6 Jan 2025 12:01:53 +0000 Subject: [PATCH 70/70] Update mix.exs --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index cd2485a20..e7eebb815 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("3.14.0"), + version: version("3.14.1"), elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), compilers: Mix.compilers(),