From f376eb710626d357832258f1294bb3bd08b01e01 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sat, 10 Sep 2022 21:08:06 +0100 Subject: [PATCH 01/24] Revert "tmp: use akkoma build image" This reverts commit cad2745734fb8a0192b0ec9ecd3ff2dc3eaedde8. --- .woodpecker.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.woodpecker.yml b/.woodpecker.yml index 79bb674ea..fd4ccb72a 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -112,7 +112,7 @@ pipeline: - /bin/sh /entrypoint.sh debian-bullseye: - image: akkoma/debian + image: elixir:1.13.4 <<: *on-release environment: MIX_ENV: prod From dfba26a09c7def7c15135aa8cae1b91e40106bd3 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sat, 10 Sep 2022 21:08:22 +0100 Subject: [PATCH 02/24] Revert "use prebuilt image for docs" This reverts commit ef4282b348d49f0bb5b749ae1db27e6d44a366b2. --- .woodpecker.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.woodpecker.yml b/.woodpecker.yml index fd4ccb72a..32db2f1c5 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -172,8 +172,12 @@ pipeline: - SCW_DEFAULT_ORGANIZATION_ID environment: CI: "true" - image: akkoma/docs-builder + image: python:3.10-slim commands: + - apt-get update && apt-get install -y rclone wget git zip + - wget https://github.com/scaleway/scaleway-cli/releases/download/v2.5.1/scaleway-cli_2.5.1_linux_amd64 + - mv scaleway-cli_2.5.1_linux_amd64 scaleway-cli + - chmod +x scaleway-cli - ./scaleway-cli object config install type=rclone - cd docs - pip install -r requirements.txt From e88f36f72b5317debafcc4209b91eb35ad8f0691 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne?= Date: Sun, 11 Sep 2022 04:54:04 +0200 Subject: [PATCH 03/24] ObjectView: do not fetch an object for its ID Non-Create/Listen activities had their associated object field normalized and fetched, but only to use their `id` field, which is both slow and redundant. This also failed on Undo activities, which delete the associated object/activity in database. Undo activities will now render properly and database loads should improve ever so slightly. --- lib/pleroma/object.ex | 15 ++++++++++----- lib/pleroma/web/activity_pub/views/object_view.ex | 4 ++-- .../web/activity_pub/views/object_view_test.exs | 14 ++++++++++++++ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index 00af77f57..a75d85c47 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -145,7 +145,7 @@ defp warn_on_no_object_preloaded(ap_id) do Logger.debug("Backtrace: #{inspect(Process.info(:erlang.self(), :current_stacktrace))}") end - def normalize(_, options \\ [fetch: false]) + def normalize(_, options \\ [fetch: false, id_only: false]) # If we pass an Activity to Object.normalize(), we can try to use the preloaded object. # Use this whenever possible, especially when walking graphs in an O(N) loop! @@ -173,10 +173,15 @@ def normalize(%Activity{data: %{"object" => ap_id}}, options) do def normalize(%{"id" => ap_id}, options), do: normalize(ap_id, options) def normalize(ap_id, options) when is_binary(ap_id) do - if Keyword.get(options, :fetch) do - Fetcher.fetch_object_from_id!(ap_id, options) - else - get_cached_by_ap_id(ap_id) + cond do + Keyword.get(options, :id_only) -> + ap_id + + Keyword.get(options, :fetch) -> + Fetcher.fetch_object_from_id!(ap_id, options) + + true -> + get_cached_by_ap_id(ap_id) end end diff --git a/lib/pleroma/web/activity_pub/views/object_view.ex b/lib/pleroma/web/activity_pub/views/object_view.ex index d9b59406c..29e2bbc81 100644 --- a/lib/pleroma/web/activity_pub/views/object_view.ex +++ b/lib/pleroma/web/activity_pub/views/object_view.ex @@ -29,11 +29,11 @@ def render("object.json", %{object: %Activity{data: %{"type" => activity_type}} def render("object.json", %{object: %Activity{} = activity}) do base = Pleroma.Web.ActivityPub.Utils.make_json_ld_header() - object = Object.normalize(activity, fetch: false) + object_id = Object.normalize(activity, id_only: true) additional = Transmogrifier.prepare_object(activity.data) - |> Map.put("object", object.data["id"]) + |> Map.put("object", object_id) Map.merge(base, additional) end diff --git a/test/pleroma/web/activity_pub/views/object_view_test.exs b/test/pleroma/web/activity_pub/views/object_view_test.exs index 923515dec..9348c09be 100644 --- a/test/pleroma/web/activity_pub/views/object_view_test.exs +++ b/test/pleroma/web/activity_pub/views/object_view_test.exs @@ -81,4 +81,18 @@ test "renders an announce activity" do assert result["object"] == object.data["id"] assert result["type"] == "Announce" end + + test "renders an undo announce activity" do + note = insert(:note_activity) + user = insert(:user) + + {:ok, announce} = CommonAPI.repeat(note.id, user) + {:ok, undo} = CommonAPI.unrepeat(note.id, user) + + result = ObjectView.render("object.json", %{object: undo}) + + assert result["id"] == undo.data["id"] + assert result["object"] == announce.data["id"] + assert result["type"] == "Undo" + end end From b6891fe190d3ae186d777ab34c4778c5f48fa18a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne?= Date: Mon, 5 Sep 2022 03:51:35 +0200 Subject: [PATCH 04/24] Migrations: generate unset user keys User keys are now generated on user creation instead of "when needed", to prevent race conditions in federation and a few other issues. This migration will generate keys missing for local users. --- ...0220905011454_generate_unset_user_keys.exs | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 priv/repo/migrations/20220905011454_generate_unset_user_keys.exs diff --git a/priv/repo/migrations/20220905011454_generate_unset_user_keys.exs b/priv/repo/migrations/20220905011454_generate_unset_user_keys.exs new file mode 100644 index 000000000..43bc7100b --- /dev/null +++ b/priv/repo/migrations/20220905011454_generate_unset_user_keys.exs @@ -0,0 +1,28 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Repo.Migrations.GenerateUnsetUserKeys do + use Ecto.Migration + import Ecto.Query + alias Pleroma.Keys + alias Pleroma.Repo + alias Pleroma.User + + def change do + query = + from(u in User, + where: u.local == true, + where: is_nil(u.keys), + select: u + ) + + Repo.stream(query) + |> Enum.each(fn user -> + with {:ok, pem} <- Keys.generate_rsa_pem() do + Ecto.Changeset.cast(user, %{keys: pem}, [:keys]) + |> Repo.update() + end + end) + end +end From 0b14f02ed20173c03dada9edcdfa55dc98cc2420 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne?= Date: Fri, 26 Aug 2022 18:30:43 +0200 Subject: [PATCH 05/24] User: generate private keys on user creation This fixes a race condition bug where keys could be regenerated post-federation, causing activities and HTTP signatures from an user to be dropped due to key differences. --- lib/pleroma/signature.ex | 5 +- lib/pleroma/user.ex | 26 +++------- .../activity_pub/activity_pub_controller.ex | 52 +++++-------------- .../web/activity_pub/views/user_view.ex | 2 - lib/pleroma/web/federator.ex | 6 +-- lib/pleroma/web/web_finger.ex | 4 -- test/pleroma/user_test.exs | 19 +------ .../web/activity_pub/views/user_view_test.exs | 7 --- test/support/factory.ex | 6 ++- 9 files changed, 32 insertions(+), 95 deletions(-) diff --git a/lib/pleroma/signature.ex b/lib/pleroma/signature.ex index a71a504b3..043a0643e 100644 --- a/lib/pleroma/signature.ex +++ b/lib/pleroma/signature.ex @@ -66,9 +66,8 @@ def refetch_public_key(conn) do end end - def sign(%User{} = user, headers) do - with {:ok, %{keys: keys}} <- User.ensure_keys_present(user), - {:ok, private_key, _} <- Keys.keys_from_pem(keys) do + def sign(%User{keys: keys} = user, headers) do + with {:ok, private_key, _} <- Keys.keys_from_pem(keys) 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 4383f8f53..138f3c851 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -681,9 +681,9 @@ def register_changeset_ldap(struct, params = %{password: password}) |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames])) |> validate_format(:nickname, local_nickname_regex()) |> put_ap_id() - |> put_keys() |> unique_constraint(:ap_id) |> put_following_and_follower_and_featured_address() + |> put_private_key() end def register_changeset(struct, params \\ %{}, opts \\ []) do @@ -741,10 +741,10 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do |> validate_length(:registration_reason, max: reason_limit) |> maybe_validate_required_email(opts[:external]) |> put_password_hash - |> put_keys() |> put_ap_id() |> unique_constraint(:ap_id) |> put_following_and_follower_and_featured_address() + |> put_private_key() end def maybe_validate_required_email(changeset, true), do: changeset @@ -757,11 +757,6 @@ def maybe_validate_required_email(changeset, _) do end end - def put_keys(changeset) do - {:ok, pem} = Keys.generate_rsa_pem() - put_change(changeset, :keys, pem) - end - def put_ap_id(changeset) do ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)}) put_change(changeset, :ap_id, ap_id) @@ -779,6 +774,11 @@ def put_following_and_follower_and_featured_address(changeset) do |> put_change(:featured_address, featured) end + defp put_private_key(changeset) do + {:ok, pem} = Keys.generate_rsa_pem() + put_change(changeset, :keys, pem) + end + defp autofollow_users(user) do candidates = Config.get([:instance, :autofollowed_nicknames]) @@ -1955,6 +1955,7 @@ defp create_service_actor(uri, nickname) do follower_address: uri <> "/followers" } |> change + |> put_private_key() |> unique_constraint(:nickname) |> Repo.insert() |> set_cache() @@ -2220,17 +2221,6 @@ def get_mascot(%{mascot: mascot}) when is_nil(mascot) do } end - def ensure_keys_present(%{keys: keys} = user) when not is_nil(keys), do: {:ok, user} - - def ensure_keys_present(%User{} = user) do - with {:ok, pem} <- Keys.generate_rsa_pem() do - user - |> cast(%{keys: pem}, [:keys]) - |> validate_required([:keys]) - |> update_and_set_cache() - end - end - def get_ap_ids_by_nicknames(nicknames) do from(u in User, where: u.nickname in ^nicknames, diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 1eb0a3620..c07f91b2e 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -66,8 +66,7 @@ defp relay_active?(conn, _) do end def user(conn, %{"nickname" => nickname}) do - with %User{local: true} = user <- User.get_cached_by_nickname(nickname), - {:ok, user} <- User.ensure_keys_present(user) do + with %User{local: true} = user <- User.get_cached_by_nickname(nickname) do conn |> put_resp_content_type("application/activity+json") |> put_view(UserView) @@ -174,7 +173,6 @@ def relay_following(conn, _params) do def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do with %User{} = user <- User.get_cached_by_nickname(nickname), - {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user), {:show_follows, true} <- {:show_follows, (for_user && for_user == user) || !user.hide_follows} do {page, _} = Integer.parse(page) @@ -192,8 +190,7 @@ def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "p end def following(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do - with %User{} = user <- User.get_cached_by_nickname(nickname), - {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do + with %User{} = user <- User.get_cached_by_nickname(nickname) do conn |> put_resp_content_type("application/activity+json") |> put_view(UserView) @@ -213,7 +210,6 @@ def relay_followers(conn, _params) do def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "page" => page}) do with %User{} = user <- User.get_cached_by_nickname(nickname), - {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user), {:show_followers, true} <- {:show_followers, (for_user && for_user == user) || !user.hide_followers} do {page, _} = Integer.parse(page) @@ -231,8 +227,7 @@ def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname, "p end def followers(%{assigns: %{user: for_user}} = conn, %{"nickname" => nickname}) do - with %User{} = user <- User.get_cached_by_nickname(nickname), - {user, for_user} <- ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do + with %User{} = user <- User.get_cached_by_nickname(nickname) do conn |> put_resp_content_type("application/activity+json") |> put_view(UserView) @@ -245,8 +240,7 @@ def outbox( %{"nickname" => nickname, "page" => page?} = params ) when page? in [true, "true"] do - with %User{} = user <- User.get_cached_by_nickname(nickname), - {:ok, user} <- User.ensure_keys_present(user) do + with %User{} = user <- User.get_cached_by_nickname(nickname) do # "include_poll_votes" is a hack because postgres generates inefficient # queries when filtering by 'Answer', poll votes will be hidden by the # visibility filter in this case anyway @@ -270,8 +264,7 @@ def outbox( end def outbox(conn, %{"nickname" => nickname}) do - with %User{} = user <- User.get_cached_by_nickname(nickname), - {:ok, user} <- User.ensure_keys_present(user) do + with %User{} = user <- User.get_cached_by_nickname(nickname) do conn |> put_resp_content_type("application/activity+json") |> put_view(UserView) @@ -328,14 +321,10 @@ defp post_inbox_relayed_create(conn, params) do end defp represent_service_actor(%User{} = user, conn) do - with {:ok, user} <- User.ensure_keys_present(user) do - conn - |> put_resp_content_type("application/activity+json") - |> put_view(UserView) - |> render("user.json", %{user: user}) - else - nil -> {:error, :not_found} - end + conn + |> put_resp_content_type("application/activity+json") + |> put_view(UserView) + |> render("user.json", %{user: user}) end defp represent_service_actor(nil, _), do: {:error, :not_found} @@ -388,12 +377,10 @@ def read_inbox( def read_inbox(%{assigns: %{user: %User{nickname: nickname} = user}} = conn, %{ "nickname" => nickname }) do - with {:ok, user} <- User.ensure_keys_present(user) do - conn - |> put_resp_content_type("application/activity+json") - |> put_view(UserView) - |> render("activity_collection.json", %{iri: "#{user.ap_id}/inbox"}) - end + conn + |> put_resp_content_type("application/activity+json") + |> put_view(UserView) + |> render("activity_collection.json", %{iri: "#{user.ap_id}/inbox"}) end def read_inbox(%{assigns: %{user: %User{nickname: as_nickname}}} = conn, %{ @@ -530,19 +517,6 @@ defp set_requester_reachable(%Plug.Conn{} = conn, _) do conn end - defp ensure_user_keys_present_and_maybe_refresh_for_user(user, for_user) do - {:ok, new_user} = User.ensure_keys_present(user) - - for_user = - if new_user != user and match?(%User{}, for_user) do - User.get_cached_by_nickname(for_user.nickname) - else - for_user - end - - {new_user, for_user} - end - def upload_media(%{assigns: %{user: %User{} = user}} = conn, %{"file" => file} = data) do with {:ok, object} <- ActivityPub.upload( diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 760515f34..310f3ce3e 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -34,7 +34,6 @@ def render("endpoints.json", %{user: %User{local: true} = _user}) do def render("endpoints.json", _), do: %{} def render("service.json", %{user: user}) do - {:ok, user} = User.ensure_keys_present(user) {: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]) @@ -71,7 +70,6 @@ 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, user} = User.ensure_keys_present(user) {: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]) diff --git a/lib/pleroma/web/federator.ex b/lib/pleroma/web/federator.ex index 82fb9e4e0..bc61130f1 100644 --- a/lib/pleroma/web/federator.ex +++ b/lib/pleroma/web/federator.ex @@ -69,10 +69,8 @@ def perform(:publish_one, module, params) do def perform(:publish, activity) do Logger.debug(fn -> "Running publish for #{activity.data["id"]}" end) - with %User{} = actor <- User.get_cached_by_ap_id(activity.data["actor"]), - {:ok, actor} <- User.ensure_keys_present(actor) do - Publisher.publish(actor, activity) - end + %User{} = actor = User.get_cached_by_ap_id(activity.data["actor"]) + Publisher.publish(actor, activity) end def perform(:incoming_ap_doc, params) do diff --git a/lib/pleroma/web/web_finger.ex b/lib/pleroma/web/web_finger.ex index b5f2cb72e..f5a46ce25 100644 --- a/lib/pleroma/web/web_finger.ex +++ b/lib/pleroma/web/web_finger.ex @@ -69,8 +69,6 @@ defp gather_aliases(%User{} = user) do end def represent_user(user, "JSON") do - {:ok, user} = User.ensure_keys_present(user) - %{ "subject" => "acct:#{user.nickname}@#{domain()}", "aliases" => gather_aliases(user), @@ -79,8 +77,6 @@ def represent_user(user, "JSON") do end def represent_user(user, "XML") do - {:ok, user} = User.ensure_keys_present(user) - aliases = user |> gather_aliases() diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index 645622e43..9d35f9780 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -620,15 +620,15 @@ test "it blocks blacklisted email domains" do assert changeset.valid? end - test "it sets the password_hash, ap_id and PEM key" do + test "it sets the password_hash, ap_id, private key and followers collection address" do 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.follower_address == "#{changeset.changes.ap_id}/followers" end @@ -2130,21 +2130,6 @@ test "Only includes users with no read notifications" do end end - describe "ensure_keys_present" do - test "it creates keys for a user and stores them in info" do - user = insert(:user) - refute is_binary(user.keys) - {:ok, user} = User.ensure_keys_present(user) - assert is_binary(user.keys) - end - - test "it doesn't create keys if there already are some" do - user = insert(:user, keys: "xxx") - {:ok, user} = User.ensure_keys_present(user) - assert user.keys == "xxx" - end - end - describe "get_ap_ids_by_nicknames" do test "it returns a list of AP ids for a given set of nicknames" do user = insert(:user) 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 e49cb99d3..5501e64d6 100644 --- a/test/pleroma/web/activity_pub/views/user_view_test.exs +++ b/test/pleroma/web/activity_pub/views/user_view_test.exs @@ -12,7 +12,6 @@ defmodule Pleroma.Web.ActivityPub.UserViewTest do test "Renders a user, including the public key" do user = insert(:user) - {:ok, user} = User.ensure_keys_present(user) result = UserView.render("user.json", %{user: user}) @@ -55,7 +54,6 @@ test "Renders with emoji tags" do test "Does not add an avatar image if the user hasn't set one" do user = insert(:user) - {:ok, user} = User.ensure_keys_present(user) result = UserView.render("user.json", %{user: user}) refute result["icon"] @@ -67,8 +65,6 @@ test "Does not add an avatar image if the user hasn't set one" do banner: %{"url" => [%{"href" => "https://somebanner"}]} ) - {:ok, user} = User.ensure_keys_present(user) - result = UserView.render("user.json", %{user: user}) assert result["icon"]["url"] == "https://someurl" assert result["image"]["url"] == "https://somebanner" @@ -89,7 +85,6 @@ test "renders AKAs" do describe "endpoints" do test "local users have a usable endpoints structure" do user = insert(:user) - {:ok, user} = User.ensure_keys_present(user) result = UserView.render("user.json", %{user: user}) @@ -105,7 +100,6 @@ test "local users have a usable endpoints structure" do test "remote users have an empty endpoints structure" do user = insert(:user, local: false) - {:ok, user} = User.ensure_keys_present(user) result = UserView.render("user.json", %{user: user}) @@ -115,7 +109,6 @@ test "remote users have an empty endpoints structure" do test "instance users do not expose oAuth endpoints" do user = insert(:user, nickname: nil, local: true) - {:ok, user} = User.ensure_keys_present(user) result = UserView.render("user.json", %{user: user}) diff --git a/test/support/factory.ex b/test/support/factory.ex index 6695886dc..2b0426bb7 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Factory do require Pleroma.Constants + alias Pleroma.Keys alias Pleroma.Object alias Pleroma.User @@ -28,6 +29,8 @@ def conversation_factory do end def user_factory(attrs \\ %{}) do + {:ok, pem} = Keys.generate_rsa_pem() + user = %User{ name: sequence(:name, &"Test テスト User #{&1}"), email: sequence(:email, &"user#{&1}@example.com"), @@ -39,7 +42,8 @@ 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 + ap_enabled: true, + keys: pem } urls = From 8683252fc556f31896687aeaad387a2f2a85ada6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne?= Date: Tue, 23 Aug 2022 14:49:05 +0200 Subject: [PATCH 06/24] Metadata/Utils: use summary as description if set When generating OpenGraph and TwitterCard metadata for a post, the summary field will be used first if it is set to generate the post description. --- lib/pleroma/web/metadata/utils.ex | 15 ++++++-- .../metadata/providers/twitter_card_test.exs | 33 +++++++++++++++++ test/pleroma/web/metadata/utils_test.exs | 36 ++++++++++++++++++- 3 files changed, 81 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex index caca42934..8990bef54 100644 --- a/lib/pleroma/web/metadata/utils.ex +++ b/lib/pleroma/web/metadata/utils.ex @@ -8,8 +8,8 @@ defmodule Pleroma.Web.Metadata.Utils do alias Pleroma.Formatter alias Pleroma.HTML - def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do - content + defp scrub_html_and_truncate_object_field(field, object) do + field # html content comes from DB already encoded, decode first and scrub after |> HtmlEntities.decode() |> String.replace(~r//, " ") @@ -19,6 +19,17 @@ def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do |> Formatter.truncate() end + def scrub_html_and_truncate(%{data: %{"summary" => summary}} = object) + when is_binary(summary) and summary != "" do + summary + |> scrub_html_and_truncate_object_field(object) + end + + def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do + content + |> scrub_html_and_truncate_object_field(object) + end + def scrub_html_and_truncate(content, max_length \\ 200) when is_binary(content) do content |> scrub_html diff --git a/test/pleroma/web/metadata/providers/twitter_card_test.exs b/test/pleroma/web/metadata/providers/twitter_card_test.exs index 1b8d27cda..5d7ad08ef 100644 --- a/test/pleroma/web/metadata/providers/twitter_card_test.exs +++ b/test/pleroma/web/metadata/providers/twitter_card_test.exs @@ -39,6 +39,7 @@ test "it uses summary twittercard if post has no attachment" do "actor" => user.ap_id, "tag" => [], "id" => "https://pleroma.gov/objects/whatever", + "summary" => "", "content" => "pleroma in a nutshell" } }) @@ -54,6 +55,36 @@ test "it uses summary twittercard if post has no attachment" do ] == result end + test "it uses summary as description if post has one" do + user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994") + {:ok, activity} = CommonAPI.post(user, %{status: "HI"}) + + note = + insert(:note, %{ + data: %{ + "actor" => user.ap_id, + "tag" => [], + "id" => "https://pleroma.gov/objects/whatever", + "summary" => "Public service announcement on caffeine consumption", + "content" => "cofe" + } + }) + + result = TwitterCard.build_tags(%{object: note, user: user, activity_id: activity.id}) + + assert [ + {:meta, [property: "twitter:title", content: Utils.user_name_string(user)], []}, + {:meta, + [ + property: "twitter:description", + content: "Public service announcement on caffeine consumption" + ], []}, + {:meta, [property: "twitter:image", content: "http://localhost:4001/images/avi.png"], + []}, + {:meta, [property: "twitter:card", content: "summary"], []} + ] == result + end + test "it renders avatar not attachment if post is nsfw and unfurl_nsfw is disabled" do clear_config([Pleroma.Web.Metadata, :unfurl_nsfw], false) user = insert(:user, name: "Jimmy Hendriks", bio: "born 19 March 1994") @@ -65,6 +96,7 @@ test "it renders avatar not attachment if post is nsfw and unfurl_nsfw is disabl "actor" => user.ap_id, "tag" => [], "id" => "https://pleroma.gov/objects/whatever", + "summary" => "", "content" => "pleroma in a nutshell", "sensitive" => true, "attachment" => [ @@ -109,6 +141,7 @@ test "it renders supported types of attachments and skips unknown types" do "actor" => user.ap_id, "tag" => [], "id" => "https://pleroma.gov/objects/whatever", + "summary" => "", "content" => "pleroma in a nutshell", "attachment" => [ %{ diff --git a/test/pleroma/web/metadata/utils_test.exs b/test/pleroma/web/metadata/utils_test.exs index c99d11596..665efb9ca 100644 --- a/test/pleroma/web/metadata/utils_test.exs +++ b/test/pleroma/web/metadata/utils_test.exs @@ -8,7 +8,7 @@ defmodule Pleroma.Web.Metadata.UtilsTest do alias Pleroma.Web.Metadata.Utils describe "scrub_html_and_truncate/1" do - test "it returns text without encode HTML" do + test "it returns content text without encode HTML if summary is nil" do user = insert(:user) note = @@ -16,6 +16,7 @@ test "it returns text without encode HTML" do data: %{ "actor" => user.ap_id, "id" => "https://pleroma.gov/objects/whatever", + "summary" => nil, "content" => "Pleroma's really cool!" } }) @@ -23,6 +24,39 @@ test "it returns text without encode HTML" do assert Utils.scrub_html_and_truncate(note) == "Pleroma's really cool!" end + test "it returns context text without encode HTML if summary is empty" do + user = insert(:user) + + note = + insert(:note, %{ + data: %{ + "actor" => user.ap_id, + "id" => "https://pleroma.gov/objects/whatever", + "summary" => "", + "content" => "Pleroma's really cool!" + } + }) + + assert Utils.scrub_html_and_truncate(note) == "Pleroma's really cool!" + end + + test "it returns summary text without encode HTML if summary is filled" do + user = insert(:user) + + note = + insert(:note, %{ + data: %{ + "actor" => user.ap_id, + "id" => "https://pleroma.gov/objects/whatever", + "summary" => "Public service announcement on caffeine consumption", + "content" => "cofe" + } + }) + + assert Utils.scrub_html_and_truncate(note) == + "Public service announcement on caffeine consumption" + end + test "it does not return old content after editing" do user = insert(:user) From 3e2d15c71d3b3b879c46e0da7ab6f79f84de6202 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne?= Date: Sat, 20 Aug 2022 00:21:07 +0200 Subject: [PATCH 07/24] emoji-test: update to latest 15.0 draft --- lib/pleroma/emoji-test.txt | 125 +++++++++++++++++++++++-------------- 1 file changed, 79 insertions(+), 46 deletions(-) diff --git a/lib/pleroma/emoji-test.txt b/lib/pleroma/emoji-test.txt index dd5493366..87d093d64 100644 --- a/lib/pleroma/emoji-test.txt +++ b/lib/pleroma/emoji-test.txt @@ -1,13 +1,13 @@ # emoji-test.txt -# Date: 2021-08-26, 17:22:23 GMT -# © 2021 Unicode®, Inc. +# Date: 2022-08-12, 20:24:39 GMT +# © 2022 Unicode®, Inc. # Unicode and the Unicode Logo are registered trademarks of Unicode, Inc. in the U.S. and other countries. -# For terms of use, see http://www.unicode.org/terms_of_use.html +# For terms of use, see https://www.unicode.org/terms_of_use.html # # Emoji Keyboard/Display Test Data for UTS #51 -# Version: 14.0 +# Version: 15.0 # -# For documentation and usage, see http://www.unicode.org/reports/tr51 +# For documentation and usage, see https://www.unicode.org/reports/tr51 # # This file provides data for testing which emoji forms should be in keyboards and which should also be displayed/processed. # Format: code points; status # emoji name @@ -92,6 +92,7 @@ 1F62C ; fully-qualified # 😬 E1.0 grimacing face 1F62E 200D 1F4A8 ; fully-qualified # 😮‍💨 E13.1 face exhaling 1F925 ; fully-qualified # 🤥 E3.0 lying face +1FAE8 ; fully-qualified # 🫨 E15.0 shaking face # subgroup: face-sleepy 1F60C ; fully-qualified # 😌 E0.6 relieved face @@ -155,7 +156,7 @@ # subgroup: face-negative 1F624 ; fully-qualified # 😤 E0.6 face with steam from nose -1F621 ; fully-qualified # 😡 E0.6 pouting face +1F621 ; fully-qualified # 😡 E0.6 enraged face 1F620 ; fully-qualified # 😠 E0.6 angry face 1F92C ; fully-qualified # 🤬 E5.0 face with symbols on mouth 1F608 ; fully-qualified # 😈 E1.0 smiling face with horns @@ -190,8 +191,7 @@ 1F649 ; fully-qualified # 🙉 E0.6 hear-no-evil monkey 1F64A ; fully-qualified # 🙊 E0.6 speak-no-evil monkey -# subgroup: emotion -1F48B ; fully-qualified # 💋 E0.6 kiss mark +# subgroup: heart 1F48C ; fully-qualified # 💌 E0.6 love letter 1F498 ; fully-qualified # 💘 E0.6 heart with arrow 1F49D ; fully-qualified # 💝 E0.6 heart with ribbon @@ -210,14 +210,20 @@ 2764 200D 1FA79 ; unqualified # ❤‍🩹 E13.1 mending heart 2764 FE0F ; fully-qualified # ❤️ E0.6 red heart 2764 ; unqualified # ❤ E0.6 red heart +1FA77 ; fully-qualified # 🩷 E15.0 pink heart 1F9E1 ; fully-qualified # 🧡 E5.0 orange heart 1F49B ; fully-qualified # 💛 E0.6 yellow heart 1F49A ; fully-qualified # 💚 E0.6 green heart 1F499 ; fully-qualified # 💙 E0.6 blue heart +1FA75 ; fully-qualified # 🩵 E15.0 light blue heart 1F49C ; fully-qualified # 💜 E0.6 purple heart 1F90E ; fully-qualified # 🤎 E12.0 brown heart 1F5A4 ; fully-qualified # 🖤 E3.0 black heart +1FA76 ; fully-qualified # 🩶 E15.0 grey heart 1F90D ; fully-qualified # 🤍 E12.0 white heart + +# subgroup: emotion +1F48B ; fully-qualified # 💋 E0.6 kiss mark 1F4AF ; fully-qualified # 💯 E0.6 hundred points 1F4A2 ; fully-qualified # 💢 E0.6 anger symbol 1F4A5 ; fully-qualified # 💥 E0.6 collision @@ -226,21 +232,20 @@ 1F4A8 ; fully-qualified # 💨 E0.6 dashing away 1F573 FE0F ; fully-qualified # 🕳️ E0.7 hole 1F573 ; unqualified # 🕳 E0.7 hole -1F4A3 ; fully-qualified # 💣 E0.6 bomb 1F4AC ; fully-qualified # 💬 E0.6 speech balloon 1F441 FE0F 200D 1F5E8 FE0F ; fully-qualified # 👁️‍🗨️ E2.0 eye in speech bubble 1F441 200D 1F5E8 FE0F ; unqualified # 👁‍🗨️ E2.0 eye in speech bubble -1F441 FE0F 200D 1F5E8 ; unqualified # 👁️‍🗨 E2.0 eye in speech bubble +1F441 FE0F 200D 1F5E8 ; minimally-qualified # 👁️‍🗨 E2.0 eye in speech bubble 1F441 200D 1F5E8 ; unqualified # 👁‍🗨 E2.0 eye in speech bubble 1F5E8 FE0F ; fully-qualified # 🗨️ E2.0 left speech bubble 1F5E8 ; unqualified # 🗨 E2.0 left speech bubble 1F5EF FE0F ; fully-qualified # 🗯️ E0.7 right anger bubble 1F5EF ; unqualified # 🗯 E0.7 right anger bubble 1F4AD ; fully-qualified # 💭 E1.0 thought balloon -1F4A4 ; fully-qualified # 💤 E0.6 zzz +1F4A4 ; fully-qualified # 💤 E0.6 ZZZ -# Smileys & Emotion subtotal: 177 -# Smileys & Emotion subtotal: 177 w/o modifiers +# Smileys & Emotion subtotal: 180 +# Smileys & Emotion subtotal: 180 w/o modifiers # group: People & Body @@ -300,6 +305,18 @@ 1FAF4 1F3FD ; fully-qualified # 🫴🏽 E14.0 palm up hand: medium skin tone 1FAF4 1F3FE ; fully-qualified # 🫴🏾 E14.0 palm up hand: medium-dark skin tone 1FAF4 1F3FF ; fully-qualified # 🫴🏿 E14.0 palm up hand: dark skin tone +1FAF7 ; fully-qualified # 🫷 E15.0 leftwards pushing hand +1FAF7 1F3FB ; fully-qualified # 🫷🏻 E15.0 leftwards pushing hand: light skin tone +1FAF7 1F3FC ; fully-qualified # 🫷🏼 E15.0 leftwards pushing hand: medium-light skin tone +1FAF7 1F3FD ; fully-qualified # 🫷🏽 E15.0 leftwards pushing hand: medium skin tone +1FAF7 1F3FE ; fully-qualified # 🫷🏾 E15.0 leftwards pushing hand: medium-dark skin tone +1FAF7 1F3FF ; fully-qualified # 🫷🏿 E15.0 leftwards pushing hand: dark skin tone +1FAF8 ; fully-qualified # 🫸 E15.0 rightwards pushing hand +1FAF8 1F3FB ; fully-qualified # 🫸🏻 E15.0 rightwards pushing hand: light skin tone +1FAF8 1F3FC ; fully-qualified # 🫸🏼 E15.0 rightwards pushing hand: medium-light skin tone +1FAF8 1F3FD ; fully-qualified # 🫸🏽 E15.0 rightwards pushing hand: medium skin tone +1FAF8 1F3FE ; fully-qualified # 🫸🏾 E15.0 rightwards pushing hand: medium-dark skin tone +1FAF8 1F3FF ; fully-qualified # 🫸🏿 E15.0 rightwards pushing hand: dark skin tone # subgroup: hand-fingers-partial 1F44C ; fully-qualified # 👌 E0.6 OK hand @@ -473,11 +490,11 @@ 1F932 1F3FE ; fully-qualified # 🤲🏾 E5.0 palms up together: medium-dark skin tone 1F932 1F3FF ; fully-qualified # 🤲🏿 E5.0 palms up together: dark skin tone 1F91D ; fully-qualified # 🤝 E3.0 handshake -1F91D 1F3FB ; fully-qualified # 🤝🏻 E3.0 handshake: light skin tone -1F91D 1F3FC ; fully-qualified # 🤝🏼 E3.0 handshake: medium-light skin tone -1F91D 1F3FD ; fully-qualified # 🤝🏽 E3.0 handshake: medium skin tone -1F91D 1F3FE ; fully-qualified # 🤝🏾 E3.0 handshake: medium-dark skin tone -1F91D 1F3FF ; fully-qualified # 🤝🏿 E3.0 handshake: dark skin tone +1F91D 1F3FB ; fully-qualified # 🤝🏻 E14.0 handshake: light skin tone +1F91D 1F3FC ; fully-qualified # 🤝🏼 E14.0 handshake: medium-light skin tone +1F91D 1F3FD ; fully-qualified # 🤝🏽 E14.0 handshake: medium skin tone +1F91D 1F3FE ; fully-qualified # 🤝🏾 E14.0 handshake: medium-dark skin tone +1F91D 1F3FF ; fully-qualified # 🤝🏿 E14.0 handshake: dark skin tone 1FAF1 1F3FB 200D 1FAF2 1F3FC ; fully-qualified # 🫱🏻‍🫲🏼 E14.0 handshake: light skin tone, medium-light skin tone 1FAF1 1F3FB 200D 1FAF2 1F3FD ; fully-qualified # 🫱🏻‍🫲🏽 E14.0 handshake: light skin tone, medium skin tone 1FAF1 1F3FB 200D 1FAF2 1F3FE ; fully-qualified # 🫱🏻‍🫲🏾 E14.0 handshake: light skin tone, medium-dark skin tone @@ -1455,7 +1472,7 @@ 1F575 1F3FF ; fully-qualified # 🕵🏿 E2.0 detective: dark skin tone 1F575 FE0F 200D 2642 FE0F ; fully-qualified # 🕵️‍♂️ E4.0 man detective 1F575 200D 2642 FE0F ; unqualified # 🕵‍♂️ E4.0 man detective -1F575 FE0F 200D 2642 ; unqualified # 🕵️‍♂ E4.0 man detective +1F575 FE0F 200D 2642 ; minimally-qualified # 🕵️‍♂ E4.0 man detective 1F575 200D 2642 ; unqualified # 🕵‍♂ E4.0 man detective 1F575 1F3FB 200D 2642 FE0F ; fully-qualified # 🕵🏻‍♂️ E4.0 man detective: light skin tone 1F575 1F3FB 200D 2642 ; minimally-qualified # 🕵🏻‍♂ E4.0 man detective: light skin tone @@ -1469,7 +1486,7 @@ 1F575 1F3FF 200D 2642 ; minimally-qualified # 🕵🏿‍♂ E4.0 man detective: dark skin tone 1F575 FE0F 200D 2640 FE0F ; fully-qualified # 🕵️‍♀️ E4.0 woman detective 1F575 200D 2640 FE0F ; unqualified # 🕵‍♀️ E4.0 woman detective -1F575 FE0F 200D 2640 ; unqualified # 🕵️‍♀ E4.0 woman detective +1F575 FE0F 200D 2640 ; minimally-qualified # 🕵️‍♀ E4.0 woman detective 1F575 200D 2640 ; unqualified # 🕵‍♀ E4.0 woman detective 1F575 1F3FB 200D 2640 FE0F ; fully-qualified # 🕵🏻‍♀️ E4.0 woman detective: light skin tone 1F575 1F3FB 200D 2640 ; minimally-qualified # 🕵🏻‍♀ E4.0 woman detective: light skin tone @@ -2302,7 +2319,7 @@ 1F3CC 1F3FF ; fully-qualified # 🏌🏿 E4.0 person golfing: dark skin tone 1F3CC FE0F 200D 2642 FE0F ; fully-qualified # 🏌️‍♂️ E4.0 man golfing 1F3CC 200D 2642 FE0F ; unqualified # 🏌‍♂️ E4.0 man golfing -1F3CC FE0F 200D 2642 ; unqualified # 🏌️‍♂ E4.0 man golfing +1F3CC FE0F 200D 2642 ; minimally-qualified # 🏌️‍♂ E4.0 man golfing 1F3CC 200D 2642 ; unqualified # 🏌‍♂ E4.0 man golfing 1F3CC 1F3FB 200D 2642 FE0F ; fully-qualified # 🏌🏻‍♂️ E4.0 man golfing: light skin tone 1F3CC 1F3FB 200D 2642 ; minimally-qualified # 🏌🏻‍♂ E4.0 man golfing: light skin tone @@ -2316,7 +2333,7 @@ 1F3CC 1F3FF 200D 2642 ; minimally-qualified # 🏌🏿‍♂ E4.0 man golfing: dark skin tone 1F3CC FE0F 200D 2640 FE0F ; fully-qualified # 🏌️‍♀️ E4.0 woman golfing 1F3CC 200D 2640 FE0F ; unqualified # 🏌‍♀️ E4.0 woman golfing -1F3CC FE0F 200D 2640 ; unqualified # 🏌️‍♀ E4.0 woman golfing +1F3CC FE0F 200D 2640 ; minimally-qualified # 🏌️‍♀ E4.0 woman golfing 1F3CC 200D 2640 ; unqualified # 🏌‍♀ E4.0 woman golfing 1F3CC 1F3FB 200D 2640 FE0F ; fully-qualified # 🏌🏻‍♀️ E4.0 woman golfing: light skin tone 1F3CC 1F3FB 200D 2640 ; minimally-qualified # 🏌🏻‍♀ E4.0 woman golfing: light skin tone @@ -2427,7 +2444,7 @@ 26F9 1F3FF ; fully-qualified # ⛹🏿 E2.0 person bouncing ball: dark skin tone 26F9 FE0F 200D 2642 FE0F ; fully-qualified # ⛹️‍♂️ E4.0 man bouncing ball 26F9 200D 2642 FE0F ; unqualified # ⛹‍♂️ E4.0 man bouncing ball -26F9 FE0F 200D 2642 ; unqualified # ⛹️‍♂ E4.0 man bouncing ball +26F9 FE0F 200D 2642 ; minimally-qualified # ⛹️‍♂ E4.0 man bouncing ball 26F9 200D 2642 ; unqualified # ⛹‍♂ E4.0 man bouncing ball 26F9 1F3FB 200D 2642 FE0F ; fully-qualified # ⛹🏻‍♂️ E4.0 man bouncing ball: light skin tone 26F9 1F3FB 200D 2642 ; minimally-qualified # ⛹🏻‍♂ E4.0 man bouncing ball: light skin tone @@ -2441,7 +2458,7 @@ 26F9 1F3FF 200D 2642 ; minimally-qualified # ⛹🏿‍♂ E4.0 man bouncing ball: dark skin tone 26F9 FE0F 200D 2640 FE0F ; fully-qualified # ⛹️‍♀️ E4.0 woman bouncing ball 26F9 200D 2640 FE0F ; unqualified # ⛹‍♀️ E4.0 woman bouncing ball -26F9 FE0F 200D 2640 ; unqualified # ⛹️‍♀ E4.0 woman bouncing ball +26F9 FE0F 200D 2640 ; minimally-qualified # ⛹️‍♀ E4.0 woman bouncing ball 26F9 200D 2640 ; unqualified # ⛹‍♀ E4.0 woman bouncing ball 26F9 1F3FB 200D 2640 FE0F ; fully-qualified # ⛹🏻‍♀️ E4.0 woman bouncing ball: light skin tone 26F9 1F3FB 200D 2640 ; minimally-qualified # ⛹🏻‍♀ E4.0 woman bouncing ball: light skin tone @@ -2462,7 +2479,7 @@ 1F3CB 1F3FF ; fully-qualified # 🏋🏿 E2.0 person lifting weights: dark skin tone 1F3CB FE0F 200D 2642 FE0F ; fully-qualified # 🏋️‍♂️ E4.0 man lifting weights 1F3CB 200D 2642 FE0F ; unqualified # 🏋‍♂️ E4.0 man lifting weights -1F3CB FE0F 200D 2642 ; unqualified # 🏋️‍♂ E4.0 man lifting weights +1F3CB FE0F 200D 2642 ; minimally-qualified # 🏋️‍♂ E4.0 man lifting weights 1F3CB 200D 2642 ; unqualified # 🏋‍♂ E4.0 man lifting weights 1F3CB 1F3FB 200D 2642 FE0F ; fully-qualified # 🏋🏻‍♂️ E4.0 man lifting weights: light skin tone 1F3CB 1F3FB 200D 2642 ; minimally-qualified # 🏋🏻‍♂ E4.0 man lifting weights: light skin tone @@ -2476,7 +2493,7 @@ 1F3CB 1F3FF 200D 2642 ; minimally-qualified # 🏋🏿‍♂ E4.0 man lifting weights: dark skin tone 1F3CB FE0F 200D 2640 FE0F ; fully-qualified # 🏋️‍♀️ E4.0 woman lifting weights 1F3CB 200D 2640 FE0F ; unqualified # 🏋‍♀️ E4.0 woman lifting weights -1F3CB FE0F 200D 2640 ; unqualified # 🏋️‍♀ E4.0 woman lifting weights +1F3CB FE0F 200D 2640 ; minimally-qualified # 🏋️‍♀ E4.0 woman lifting weights 1F3CB 200D 2640 ; unqualified # 🏋‍♀ E4.0 woman lifting weights 1F3CB 1F3FB 200D 2640 FE0F ; fully-qualified # 🏋🏻‍♀️ E4.0 woman lifting weights: light skin tone 1F3CB 1F3FB 200D 2640 ; minimally-qualified # 🏋🏻‍♀ E4.0 woman lifting weights: light skin tone @@ -3262,8 +3279,8 @@ 1FAC2 ; fully-qualified # 🫂 E13.0 people hugging 1F463 ; fully-qualified # 👣 E0.6 footprints -# People & Body subtotal: 2986 -# People & Body subtotal: 506 w/o modifiers +# People & Body subtotal: 2998 +# People & Body subtotal: 508 w/o modifiers # group: Component @@ -3306,6 +3323,8 @@ 1F405 ; fully-qualified # 🐅 E1.0 tiger 1F406 ; fully-qualified # 🐆 E1.0 leopard 1F434 ; fully-qualified # 🐴 E0.6 horse face +1FACE ; fully-qualified # 🫎 E15.0 moose +1FACF ; fully-qualified # 🫏 E15.0 donkey 1F40E ; fully-qualified # 🐎 E0.6 horse 1F984 ; fully-qualified # 🦄 E1.0 unicorn 1F993 ; fully-qualified # 🦓 E5.0 zebra @@ -3373,6 +3392,9 @@ 1F9A9 ; fully-qualified # 🦩 E12.0 flamingo 1F99A ; fully-qualified # 🦚 E11.0 peacock 1F99C ; fully-qualified # 🦜 E11.0 parrot +1FABD ; fully-qualified # 🪽 E15.0 wing +1F426 200D 2B1B ; fully-qualified # 🐦‍⬛ E15.0 black bird +1FABF ; fully-qualified # 🪿 E15.0 goose # subgroup: animal-amphibian 1F438 ; fully-qualified # 🐸 E0.6 frog @@ -3399,6 +3421,7 @@ 1F419 ; fully-qualified # 🐙 E0.6 octopus 1F41A ; fully-qualified # 🐚 E0.6 spiral shell 1FAB8 ; fully-qualified # 🪸 E14.0 coral +1FABC ; fully-qualified # 🪼 E15.0 jellyfish # subgroup: animal-bug 1F40C ; fully-qualified # 🐌 E0.6 snail @@ -3433,6 +3456,7 @@ 1F33B ; fully-qualified # 🌻 E0.6 sunflower 1F33C ; fully-qualified # 🌼 E0.6 blossom 1F337 ; fully-qualified # 🌷 E0.6 tulip +1FABB ; fully-qualified # 🪻 E15.0 hyacinth # subgroup: plant-other 1F331 ; fully-qualified # 🌱 E0.6 seedling @@ -3451,9 +3475,10 @@ 1F343 ; fully-qualified # 🍃 E0.6 leaf fluttering in wind 1FAB9 ; fully-qualified # 🪹 E14.0 empty nest 1FABA ; fully-qualified # 🪺 E14.0 nest with eggs +1F344 ; fully-qualified # 🍄 E0.6 mushroom -# Animals & Nature subtotal: 151 -# Animals & Nature subtotal: 151 w/o modifiers +# Animals & Nature subtotal: 159 +# Animals & Nature subtotal: 159 w/o modifiers # group: Food & Drink @@ -3492,10 +3517,11 @@ 1F966 ; fully-qualified # 🥦 E5.0 broccoli 1F9C4 ; fully-qualified # 🧄 E12.0 garlic 1F9C5 ; fully-qualified # 🧅 E12.0 onion -1F344 ; fully-qualified # 🍄 E0.6 mushroom 1F95C ; fully-qualified # 🥜 E3.0 peanuts 1FAD8 ; fully-qualified # 🫘 E14.0 beans 1F330 ; fully-qualified # 🌰 E0.6 chestnut +1FADA ; fully-qualified # 🫚 E15.0 ginger root +1FADB ; fully-qualified # 🫛 E15.0 pea pod # subgroup: food-prepared 1F35E ; fully-qualified # 🍞 E0.6 bread @@ -3607,8 +3633,8 @@ 1FAD9 ; fully-qualified # 🫙 E14.0 jar 1F3FA ; fully-qualified # 🏺 E1.0 amphora -# Food & Drink subtotal: 134 -# Food & Drink subtotal: 134 w/o modifiers +# Food & Drink subtotal: 135 +# Food & Drink subtotal: 135 w/o modifiers # group: Travel & Places @@ -3974,11 +4000,10 @@ 1F3AF ; fully-qualified # 🎯 E0.6 bullseye 1FA80 ; fully-qualified # 🪀 E12.0 yo-yo 1FA81 ; fully-qualified # 🪁 E12.0 kite +1F52B ; fully-qualified # 🔫 E0.6 water pistol 1F3B1 ; fully-qualified # 🎱 E0.6 pool 8 ball 1F52E ; fully-qualified # 🔮 E0.6 crystal ball 1FA84 ; fully-qualified # 🪄 E13.0 magic wand -1F9FF ; fully-qualified # 🧿 E11.0 nazar amulet -1FAAC ; fully-qualified # 🪬 E14.0 hamsa 1F3AE ; fully-qualified # 🎮 E0.6 video game 1F579 FE0F ; fully-qualified # 🕹️ E0.7 joystick 1F579 ; unqualified # 🕹 E0.7 joystick @@ -4013,8 +4038,8 @@ 1F9F6 ; fully-qualified # 🧶 E11.0 yarn 1FAA2 ; fully-qualified # 🪢 E13.0 knot -# Activities subtotal: 97 -# Activities subtotal: 97 w/o modifiers +# Activities subtotal: 96 +# Activities subtotal: 96 w/o modifiers # group: Objects @@ -4040,6 +4065,7 @@ 1FA73 ; fully-qualified # 🩳 E12.0 shorts 1F459 ; fully-qualified # 👙 E0.6 bikini 1F45A ; fully-qualified # 👚 E0.6 woman’s clothes +1FAAD ; fully-qualified # 🪭 E15.0 folding hand fan 1F45B ; fully-qualified # 👛 E0.6 purse 1F45C ; fully-qualified # 👜 E0.6 handbag 1F45D ; fully-qualified # 👝 E0.6 clutch bag @@ -4055,6 +4081,7 @@ 1F461 ; fully-qualified # 👡 E0.6 woman’s sandal 1FA70 ; fully-qualified # 🩰 E12.0 ballet shoes 1F462 ; fully-qualified # 👢 E0.6 woman’s boot +1FAAE ; fully-qualified # 🪮 E15.0 hair pick 1F451 ; fully-qualified # 👑 E0.6 crown 1F452 ; fully-qualified # 👒 E0.6 woman’s hat 1F3A9 ; fully-qualified # 🎩 E0.6 top hat @@ -4103,6 +4130,8 @@ 1FA95 ; fully-qualified # 🪕 E12.0 banjo 1F941 ; fully-qualified # 🥁 E3.0 drum 1FA98 ; fully-qualified # 🪘 E13.0 long drum +1FA87 ; fully-qualified # 🪇 E15.0 maracas +1FA88 ; fully-qualified # 🪈 E15.0 flute # subgroup: phone 1F4F1 ; fully-qualified # 📱 E0.6 mobile phone @@ -4275,7 +4304,7 @@ 1F5E1 ; unqualified # 🗡 E0.7 dagger 2694 FE0F ; fully-qualified # ⚔️ E1.0 crossed swords 2694 ; unqualified # ⚔ E1.0 crossed swords -1F52B ; fully-qualified # 🔫 E0.6 water pistol +1F4A3 ; fully-qualified # 💣 E0.6 bomb 1FA83 ; fully-qualified # 🪃 E13.0 boomerang 1F3F9 ; fully-qualified # 🏹 E1.0 bow and arrow 1F6E1 FE0F ; fully-qualified # 🛡️ E0.7 shield @@ -4354,12 +4383,14 @@ 1FAA6 ; fully-qualified # 🪦 E13.0 headstone 26B1 FE0F ; fully-qualified # ⚱️ E1.0 funeral urn 26B1 ; unqualified # ⚱ E1.0 funeral urn +1F9FF ; fully-qualified # 🧿 E11.0 nazar amulet +1FAAC ; fully-qualified # 🪬 E14.0 hamsa 1F5FF ; fully-qualified # 🗿 E0.6 moai 1FAA7 ; fully-qualified # 🪧 E13.0 placard 1FAAA ; fully-qualified # 🪪 E14.0 identification card -# Objects subtotal: 304 -# Objects subtotal: 304 w/o modifiers +# Objects subtotal: 310 +# Objects subtotal: 310 w/o modifiers # group: Symbols @@ -4455,6 +4486,7 @@ 262E ; unqualified # ☮ E1.0 peace symbol 1F54E ; fully-qualified # 🕎 E1.0 menorah 1F52F ; fully-qualified # 🔯 E0.6 dotted six-pointed star +1FAAF ; fully-qualified # 🪯 E15.0 khanda # subgroup: zodiac 2648 ; fully-qualified # ♈ E0.6 Aries @@ -4503,6 +4535,7 @@ 1F505 ; fully-qualified # 🔅 E1.0 dim button 1F506 ; fully-qualified # 🔆 E1.0 bright button 1F4F6 ; fully-qualified # 📶 E0.6 antenna bars +1F6DC ; fully-qualified # 🛜 E15.0 wireless 1F4F3 ; fully-qualified # 📳 E0.6 vibration mode 1F4F4 ; fully-qualified # 📴 E0.6 mobile phone off @@ -4693,8 +4726,8 @@ 1F533 ; fully-qualified # 🔳 E0.6 white square button 1F532 ; fully-qualified # 🔲 E0.6 black square button -# Symbols subtotal: 302 -# Symbols subtotal: 302 w/o modifiers +# Symbols subtotal: 304 +# Symbols subtotal: 304 w/o modifiers # group: Flags @@ -4709,7 +4742,7 @@ 1F3F3 200D 1F308 ; unqualified # 🏳‍🌈 E4.0 rainbow flag 1F3F3 FE0F 200D 26A7 FE0F ; fully-qualified # 🏳️‍⚧️ E13.0 transgender flag 1F3F3 200D 26A7 FE0F ; unqualified # 🏳‍⚧️ E13.0 transgender flag -1F3F3 FE0F 200D 26A7 ; unqualified # 🏳️‍⚧ E13.0 transgender flag +1F3F3 FE0F 200D 26A7 ; minimally-qualified # 🏳️‍⚧ E13.0 transgender flag 1F3F3 200D 26A7 ; unqualified # 🏳‍⚧ E13.0 transgender flag 1F3F4 200D 2620 FE0F ; fully-qualified # 🏴‍☠️ E11.0 pirate flag 1F3F4 200D 2620 ; minimally-qualified # 🏴‍☠ E11.0 pirate flag @@ -4983,9 +5016,9 @@ # Flags subtotal: 275 w/o modifiers # Status Counts -# fully-qualified : 3624 -# minimally-qualified : 817 -# unqualified : 252 +# fully-qualified : 3655 +# minimally-qualified : 827 +# unqualified : 242 # component : 9 #EOF From 1acd38fe7ff61be9256e55e954e546b3340c88ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=A9l=C3=A8ne?= Date: Tue, 23 Aug 2022 17:15:06 +0200 Subject: [PATCH 08/24] OAuthPlug: use user cache instead of joining As this plug is called on every request, this should reduce load on the database by not requiring to select on the users table every single time, and to instead use the by-ID user cache whenever possible. --- lib/pleroma/web/plugs/o_auth_plug.ex | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/web/plugs/o_auth_plug.ex b/lib/pleroma/web/plugs/o_auth_plug.ex index 5e06ac3f6..91f6e9974 100644 --- a/lib/pleroma/web/plugs/o_auth_plug.ex +++ b/lib/pleroma/web/plugs/o_auth_plug.ex @@ -47,15 +47,17 @@ def call(conn, _) do # @spec fetch_user_and_token(String.t()) :: {:ok, User.t(), Token.t()} | nil defp fetch_user_and_token(token) do - query = + token_query = from(t in Token, - where: t.token == ^token, - join: user in assoc(t, :user), - preload: [user: user] + where: t.token == ^token ) - with %Token{user: user} = token_record <- Repo.one(query) do + with %Token{user_id: user_id} = token_record <- Repo.one(token_query), + false <- is_nil(user_id), + %User{} = user <- User.get_cached_by_id(user_id) do {:ok, user, token_record} + else + _ -> nil end end From b4261b0335e8dac485efb7bfba7dadbd876686e4 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sun, 11 Sep 2022 20:14:58 +0100 Subject: [PATCH 09/24] Use set of pregenerated RSA keys Randomness is a huge resource sink, so let's just use a some that we made earlier --- test/fixtures/rsa_keys/key_1.pem | 27 +++++++++++++++++++++++++++ test/fixtures/rsa_keys/key_2.pem | 27 +++++++++++++++++++++++++++ test/fixtures/rsa_keys/key_3.pem | 27 +++++++++++++++++++++++++++ test/fixtures/rsa_keys/key_4.pem | 27 +++++++++++++++++++++++++++ test/fixtures/rsa_keys/key_5.pem | 27 +++++++++++++++++++++++++++ test/support/factory.ex | 12 ++++++++++-- 6 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/rsa_keys/key_1.pem create mode 100644 test/fixtures/rsa_keys/key_2.pem create mode 100644 test/fixtures/rsa_keys/key_3.pem create mode 100644 test/fixtures/rsa_keys/key_4.pem create mode 100644 test/fixtures/rsa_keys/key_5.pem diff --git a/test/fixtures/rsa_keys/key_1.pem b/test/fixtures/rsa_keys/key_1.pem new file mode 100644 index 000000000..3da357500 --- /dev/null +++ b/test/fixtures/rsa_keys/key_1.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEA2gdPJM5bWarGZ6QujfQ296l1yEQohS5fdtnxYQc+RXuS1gqZ +R/jVGHG25o4tmwyCLClyREU1CBTOCQBsg+BSehXlxNR9fiB4KaVQW9MMNa2vhHuG +f7HLdILiC+SPPTV1Bi8LCpxJowiSpnFPP4BDDeRKib7nOxll9Ln9gEpUueKKabsQ +EQKCmEJYhIz/8g5R0Qz+6VjASdejDjTEdZbr/rwyldRRjIklyeZ3lBzB/c8/51wn +HT2Dt0r9NiapxYC3oNhbE2A+4FU9pZTqS8yc3KqWZAy74snaRO9QQSednKlOJpXP +V3vwWo5CxuSNLttV7zRcrqeYOkIVNF4dQ/bHzQIDAQABAoIBADTCfglnEj4BkF92 +IHnjdgW6cTEUJUYNMba+CKY1LYF85Mx85hi/gzmWEu95yllxznJHWUpiAPJCrpUJ +EDldaDf44pAd53xE+S8CvQ5rZNH8hLOnfKWb7aL1JSRBm9PxAq+LZL2dkkgsg+hZ +FRdFv3Q2IT9x/dyUSdLNyyVnV1dfoya/7zOFc7+TwqlofznzrlBgNoAe8Lb4AN/q +itormPxskqATiq11XtP4F6eQ556eRgHCBxmktx/rRDl6f9G9dvjRQOA2qZlHQdFq +kjOZsrvItL46LdVoLPOdCYG+3HFeKoDUR1NNXEkt66eqmEhLY4MgzGUT1wqXWk7N +XowZc9UCgYEA+L5h4PhANiY5Kd+PkRI8zTlJMv8hFqLK17Q0p9eL+mAyOgXjH9so +QutJf4wU+h6ESDxH+1tCjCN307uUqT7YnT2zHf3b6GcmA+t6ewxfxOY2nJ82HENq +hK1aodnPTvRRRqCGfrx9qUHRTarTzi+2u86zH+KoMHSiuzn4VpQhg4MCgYEA4GOL +1tLR9+hyfYuMFo2CtQjp3KpJeGNKEqc33vFD05xJQX+m5THamBv8vzdVlVrMh/7j +iV85mlA7HaaP+r5DGwtonw9bqY76lYRgJJprsS5lHcRnXsDmU4Ne8RdB3dHNsT5P +n4P6v8y4jaT638iJ/qLt4e8itOBlZwS//VIglm8CgYEA7KXD3RKRlHK9A7drkOs2 +6VBM8bWEN1LdhGYvilcpFyUZ49XiBVatcS0EGdKdym/qDgc7vElQgJ7ly4y0nGfs +EXy3whrYcrxfkG8hcZuOKXeUEWHvSuhgmKWMilr8PfN2t6jVDBIrwzGY/Tk+lPUT +9o1qITW0KZVtlI5MU6JOWB0CgYAHwwnETZibxbuoIhqfcRezYXKNgop2EqEuUgB5 +wsjA2igijuLcDMRt/JHan3RjbTekAKooR1X7w4i39toGJ2y008kzr1lRXTPH1kNp +ILpW767pv7B/s5aEDwhKuK47mRVPa0Nf1jXnSpKbu7g943b6ivJFnXsK3LRFQwHN +JnkgGwKBgGUleQVd2GPr1dkqLVOF/s2aNB/+h2b1WFWwq0YTnW81OLwAcUVE4p58 +3GQgz8PCsWbNdTb9yFY5fq0fXgi0+T54FEoZWH09DrOepA433llAwI6sq7egrFdr +kKQttZMzs6ST9q/IOF4wgqSnBjjTC06vKSkNAlXJz+LMvIRMeBr0 +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/rsa_keys/key_2.pem b/test/fixtures/rsa_keys/key_2.pem new file mode 100644 index 000000000..7a8e8e670 --- /dev/null +++ b/test/fixtures/rsa_keys/key_2.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAwu0VqVGRVDW09V3zZ0+08K9HMKivIzIInO0xim3jbfVcg8r1 +sR7vNLorYAB6TDDlXYAWKx1OxUMZusbOigrpQd+5wy8VdCogDD7qk4bbZ+NjXkuD +ETzrQsGWUXe+IdeH8L0Zh0bGjbarCuA0qAeY1TEteGl+Qwo2dsrBUH7yKmWO6Mz9 +XfPshrIDOGo4QNyVfEBNGq2K9eRrQUHeAPcM2/qu4ZAZRK+VCifDZrF8ZNpoAsnS +R2mJDhOBUMvI/ZaxOc2ry4EzwcS4uBaM2wONkGWDaqO6jNAQflaX7vtzOAeJB7Dt +VKXUUcZAGN7uI3c2mG5IKGMhTYUtUdrzmqmtZwIDAQABAoIBAQCHBJfTf3dt4AGn +T9twfSp06MQj9UPS2i5THI0LONCm8qSReX0zoZzJZgbzaYFM0zWczUMNvDA6vR7O +XDTmM2acxW4zv6JZo3Ata0sqwuepDz1eLGnt/8dppxQK/ClL4bH8088h/6k6sgPJ +9cEjfpejXHwFgvT9VM6i/BBpRHVTXWuJqwpDtg+bleQNN3L3RapluDd7BGiKoCwQ +cCTKd+lxTu9gVJkbRTI/Jn3kV+rnedYxHTxVp5cU1qIabsJWBcdDz25mRHupxQsn +JbQR4+ZnRLeAsC6WJZtEJz2KjXgBaYroHbGZY3KcGW95ILqiCJoJJugbW1eABKnN +Q5k8XVspAoGBAPzGJBZuX3c0quorhMIpREmGq2vS6VCQwLhH5qayYYH1LiPDfpdq +69lOROxZodzLxBgTf5z/a5kBF+eNKvOqfZJeRTxmllxxO1MuJQuRLi/b7BHHLuyN +Eea+YwtehA0T0CbD2hydefARNDruor2BLvt/kt6qEoIFiPauTsMfXP39AoGBAMVp +8argtnB+vsk5Z7rpQ4b9gF5QxfNbA0Hpg5wUUdYrUjFr50KWt1iowj6AOVp/EYgr +xRfvOQdYODDH7R5cjgMbwvtpHo39Zwq7ewaiT1sJXnpGmCDVh+pdTHePC5OOXnxN +0USK3M4KjltjVqJo7xPPElgJvCejudD47mtHMaQzAoGBAIFQ/PVc0goyL55NVUXf +xse21cv7wtEsvOuKHT361FegD1LMmN7uHGq32BryYBSNSmzmzMqNAYbtQEV9uxOd +jVBsWg9kjFgOtcMAQIOCapahdExEEoWCRj49+H3AhN4L3Nl4KQWqqs9efdIIc8lv +ZZHU2lZ/u6g5HLDWzASW7wQhAoGAdERPRrqN+HdNWinrA9Q6JxjKL8IWs5rYsksb +biMxh5eAEwdf7oHhfd/2duUB4mCQLMjKjawgxEia33AAIS+VnBMPpQ5mJm4l79Y3 +QNL7Nbyw3gcRtdTM9aT5Ujj3MnJZB5C1PU8jeF4TNZOuBH0UwW/ld+BT5myxFXhm +wtvtSq0CgYEA19b0/7il4Em6uiLOmYUuqaUoFhUPqzjaS6OM/lRAw12coWv/8/1P +cwaNZHNMW9Me/bNH3zcOTz0lxnYp2BeRehjFYVPRuS1GU7uwqKtlL2wCPptTfAhN +aJWIplzUCTg786u+sdNZ0umWRuCLoUpsKTgP/yt4RglzEcfxAuBDljk= +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/rsa_keys/key_3.pem b/test/fixtures/rsa_keys/key_3.pem new file mode 100644 index 000000000..fbd25c80f --- /dev/null +++ b/test/fixtures/rsa_keys/key_3.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEA0GvzqZ3r78GLa7guGn+palKRLGru4D4jnriHgfrUAJrdLyZ5 +9d0zAA4qnS2L6YAMoPPBhBUtIV5e2sn1+rwTClWU3dm3FyBAeqdeIBKN+04AyrUc +HXYaZtOPJXCTeytzoSQE359Tq6+xwgoHlUWSWxQF51/z/PDQcUvqFjJqAtdiDchd +3CiFRtdjegyxXGnqvPmBix+vEjDytcVydfch+R1Twf6f5EL7a1jFVWNGcratYBEl +nqOWKI2fBu/WA8QlrcVW5zmtZo9aJ6IrFddQgQTxPk/mEHgCzv8tbCRI9TxiXeYH +YqxZFYBW40xbZQwGRjaYHJlIRYp9+TOynW9OZQIDAQABAoIBAQC97cIMDbdVsyAk +N6D70N5H35ofygqJGtdG6o3B6xuKuZVaREvbu4mgQUigF0Nqs5/OhJMSlGGeCOuT +oXug1Abd4gNY7++jCWb43tAtlfsAyaJ7FvPZ/SguEBhgW+hp07z5WWN/jSeoSuFI +G++xHcczbFm88XncRG8O78kQFTz5/DlQYkFXfbqpuS3BqxnrACpDCUfrUwZNYFIp +CUNq21jdifhHwlS0K3PX8A5HdOYeVnVHaE78LGE4oJVHwcokELv+PYqarWZq/a6L +vKU3yn2+4pj2WO490iGQaRKVM35vrtjdVxiWEIUiFc3Jg5fKZA3wuHXoF1N1DpPO +BO6Att55AoGBAP/nC2szmDcnU5Sh8LDeQbL+FpSBwOmFnmel5uqbjKnDzf9emPQu +NFUls1N9OGgyUq08TnmcY/7wLZzcu7Y9XOUURuYtx9nGRs4RmE2VEBhK1r7CkDIx +oOb+NtdqnPtQASAxCHszoGCFxpuV7UVoo2SRgc+M4ceX128arvBUtvdrAoGBANCA +RuO3eelkXaJoCeogEUVWXZ6QmPeYzbMD4vg2DM0ynUbReyuEIIhn+SR7tehlj5ie +4T3ixVdur6k+YUdiFhUYgXaHBJWHoHl1lrU3ZON8n7AeEk9ft6gg4L07ouj78UMZ +sArJIlU5mLnW02zbV9XryU39dIgpQREqC0bIOtVvAoGBAORv1JKq6Rt7ALJy6VCJ +5y4ogfGp7pLHk8NEpuERYDz/rLllMbbwNAk6cV17L8pb+c/pQMhwohcnQiCALxUc +q/tW4X+CqJ+vzu8PZ90Bzu9Qh2iceGpGQTNTBZPA+UeigI7DFqYcTPM9GDE1YiyO +nyUcezvSsI4i7s6gjD+/7+DnAoGABm3+QaV1z/m1XX3B2IN2pOG971bcML54kW2s +QSVBjc5ixT1OhBAGBM7YAwUBnhILtJQptAPbPBAAwMJYs5/VuH7R9zrArG/LRhOX +Oy1jIhTEw+SZgfMcscWZyJwfMPob/Yq8QAjl0yT8jbaPPIsjEUi9I3eOcWh8RjA6 +ussP7WcCgYEAm3yvJR9z6QGoQQwtDbwjyZPYOSgK9wFS/65aupi6cm/Qk2N1YaLY +q2amNrzNsIc9vQwYGEHUwogn4MieHk96V7m2f0Hx9EHCMwizU9EiS6oyiLVowTG6 +YsBgSzcpnt0Vkgil4CQks5uQoan0tubEUQ5DI79lLnb02n4o46iAYK0= +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/rsa_keys/key_4.pem b/test/fixtures/rsa_keys/key_4.pem new file mode 100644 index 000000000..f72b29fb1 --- /dev/null +++ b/test/fixtures/rsa_keys/key_4.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpQIBAAKCAQEAw6MLRbP/henX2JxwdMkQlskKghBoMyUPu9kZpUQ9yYfIm9I4 +a3gEfzef75jKLOSf+BkZulvEUGjC+VnkpV3s+OZCSq81Ykv5PHuTqbj8Cn/dEt/g +lBXxPcOBKWqa+1cDX6QVIVJsBihLB/1b64H3U96Yu9+knmXvT1Az5MFA2KtSq7HJ +O+GJNn0EMI7xwPz/atUGlMLrhzwS4UDpw9CAaRPojplJYl4K1JMCFTgTt3hJILXZ +tw1MKTeeyWzNiuQRBQJuCnqfvsBYsasIlHWfqIL/uBzcGHHCIK5ZW9luntJXyLVj +zzaF7etIJk1uddM2wnqOOaVyqbssZXGt7Tb9IQIDAQABAoIBAH5QJRUKFK8Xvp9C +0nD06NsSTtCPW1e6VCBLGf3Uw7f9DY9d+cOZp/2jooYGNnMp4gdD3ZKvcV8hZNGu +Mqx6qmhB8wdZfLRMrU1Z1Is+vqzgxZJMLiouyKXCNwDQreQd2DXGMUZkew62sUsl +UFYMge4KyL50tUr4Mb0Z4YePJxk804tcqgw0n+D0lR7ZKhSqoQpoMqEiO+27Yw7E +Txj/MKH8f/ZJ6LBLRISOdBOrxonHqqeYWchczykCwojOZc3bIlWZGhg727dFTHDC +yrj3/zsZ2hy+TQsucCFY0RljIbacmHvrF/VqfhTIhg98H0F27V/jiPGsdKhptyst +E9iQVMkCgYEA42ge4H2Wl42sRh61GOrOgzzr0WZS54bF5skMxiGGnLwnb82rwUBt +xw94PRORJbV9l+2fkxbfiW0uzornfN8OBHSB64Pcjzzbl5Qm+eaDOiuTLtakYOWQ +/ipGqw8iE4J9iRteZCo8GnMxWbTkYCporTlFDTeYguXmwR4yCXtlCbMCgYEA3DxM +7R5HMUWRe64ucdekMh742McS8q/X5jdN9iFGy0M8P1WTyspSlaPDXgjaO4XqpRqg +djkL993kCDvOAiDl6Tpdiu1iFcOaRLb19Tj1pm8sKdk6X4d10U9lFri4NVYCmvVi +yOahUYFK/k5bA+1o+KU9Pi82H36H3WNeF4evC9sCgYEAs1zNdc04uQKiTZAs0KFr +DzI+4aOuYjT35ObQr3mD/h2dkV6MSNmzfF1kPfAv/KkgjXN7+H0DBRbb40bF/MTF +/peSXZtcnJGote7Bqzu4Z2o1Ja1ga5jF+uKHaKZ//xleQIUYtzJkw4v18cZulrb8 +ZxyTrTAbl6sTjWBuoPH1qGcCgYEAsQNahR9X81dKJpGKTQAYvhw8wOfI5/zD2ArN +g62dXBRPYUxkPJM/q3xzs6oD1eG+BjQPktYpM3FKLf/7haRxhnLd6qL/uiR8Ywx3 +RkEg2EP0yDIMA+o5nSFmS8vuaxgVgf0HCBiuwnbcEuhhqRdxzp/pSIjjxI6LnzqV +zu3EmQ8CgYEAhq8Uhvw+79tK7q2PCjDbiucA0n/4a3aguuvRoEh7F93Pf6VGZmT+ +Yld54Cd4P5ATI3r5YdD+JBuvgNMOTVPCaD/WpjbJKnrpNEXtXRQD6LzAXZDNk0sF +IO9i4gjhBolRykWn10khoPdxw/34FWBP5SxU1JYk75NQXvI3TD+5xbU= +-----END RSA PRIVATE KEY----- diff --git a/test/fixtures/rsa_keys/key_5.pem b/test/fixtures/rsa_keys/key_5.pem new file mode 100644 index 000000000..49342b54e --- /dev/null +++ b/test/fixtures/rsa_keys/key_5.pem @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpgIBAAKCAQEA0jdKtMkgqnEGO3dn4OKxtggfFDzv+ddXToO0cdPXkUgPajCo +UGPunz+A1KmkAmLY0Vwk0tkOmKK8GFHek/5zQ+1N2FHBi19fbwlJk7hzh5OiYRhu +YZi0d6LsqEMKhDk6NqIeiFmOe2YHgklVvZV0hebvHlHLgzDhYrDltSPe33UZa3MS +g2Knf4WQAjLOo2BAb+oyj/UNXeAqaMGcOr6/kAHPcODW2EGhF3H3umFLv7t/Kq5i +WPBgarbCGPR5qq9SW5ZIjS3Sz0dl105Grw8wU23CC/2IBZ5vNiu+bkmLEoh/KpX2 +YBILoLmwtVX0Qxc15CrpOi12p+/4pLR8kuEowQIDAQABAoIBAQDMDQ3AJMdHisSQ +7pvvyDzWRFXesDQE4YmG1gNOxmImTLthyW9n8UjMXbjxNOXVxxtNRdMcs8MeWECa +nsWeBEzgr7VzeBCV9/LL9kjsUgwamyzwcOWcaL0ssAJmZgUMSfx+0akvkzbiAyzg +w8ytZSihXYPYe28/ni/5O1sOFI6feenOnJ9NSmVUA24c9TTJGNQs7XRUMZ8f9wt6 +KwRmYeNDKyqH7NvLmmKoDp6m7bMDQxWArVTAoRWTVApnj35iLQtmSi8DBdw6xSzQ +fKpUe/B4iQmMNxUW7KmolOvCIS5wcYZJE+/j7xshA2GGnOpx4aC+N+w2GSX4Bz/q +OnYSpGUBAoGBAOwnSeg17xlZqmd86qdiCxg0hRtAjwrd7btYq6nkK+t9woXgcV99 +FBS3nLbk/SIdXCW8vHFJTmld60j2q2kdestYBdHznwNZJ4Ee8JhamzcC64wY7O0x +RameO/6uoKS4C3VF+Zc9CCPfZOqYujkGvSqbTjFZWuFtDp0GHDk+qEIRAoGBAOPh ++PCB2QkGgiujSPmuCT5PTuNylAug3D4ZdMRKpQb9Rnzlia1Rpdrihq+PvB2vwa+S +mB6dgb0E7M2AyEMVu5buris0mVpRdmEeLCXR8mYJ48kOslIGArEStXDetfbRaXdK +7vf4APq2d78AQYldU2fYlo754Dh/3MZIguzpqMuxAoGBAIDJqG/AQiYkFV+c62ff +e0d3FQRYv+ngQE9Eu1HKwv0Jt7VFQu8din8F56yC013wfxmBhY+Ot/mUo8VF6RNJ +ZXdSCNKINzcfPwEW+4VLHIzyxbzAty1gCqrHRdbOK4PJb05EnCqTuUW/Bg0+v4hs +GWwMCKe3IG4CCM8vzuKVPjPRAoGBANYCQtJDb3q9ZQPsTb1FxyKAQprx4Lzm7c9Y +AsPRQhhFRaxHuLtPQU5FjK1VdBoBFAl5x2iBDPVhqa348pml0E0Xi/PBav9aH61n +M5i1CUrwoL4SEj9bq61133XHgeXwlnZUpgW0H99T+zMh32pMfea5jfNqETueQMzq +DiLF8SKRAoGBAOFlU0kRZmAx3Y4rhygp1ydPBt5+zfDaGINRWEN7QWjhX2QQan3C +SnXZlP3POXLessKxdCpBDq/RqVQhLea6KJMfP3F0YbohfWHt96WjiriJ0d0ZYVhu +34aUM2UGGG0Kia9OVvftESBaXk02vrY9zU3LAVAv0eLgIADm1kpj85v7 +-----END RSA PRIVATE KEY----- diff --git a/test/support/factory.ex b/test/support/factory.ex index 2b0426bb7..efcd8039e 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -7,10 +7,18 @@ defmodule Pleroma.Factory do require Pleroma.Constants - alias Pleroma.Keys alias Pleroma.Object alias Pleroma.User + @rsa_keys [ + "test/fixtures/rsa_keys/key_1.pem", + "test/fixtures/rsa_keys/key_2.pem", + "test/fixtures/rsa_keys/key_3.pem", + "test/fixtures/rsa_keys/key_4.pem", + "test/fixtures/rsa_keys/key_5.pem" + ] + |> Enum.map(&File.read!/1) + def participation_factory do conversation = insert(:conversation) user = insert(:user) @@ -29,7 +37,7 @@ def conversation_factory do end def user_factory(attrs \\ %{}) do - {:ok, pem} = Keys.generate_rsa_pem() + pem = Enum.random(@rsa_keys) user = %User{ name: sequence(:name, &"Test テスト User #{&1}"), From 2aa8e66527fd763b59c1592e53c25d3eaf28f7f0 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Thu, 8 Sep 2022 11:58:17 -0400 Subject: [PATCH 10/24] Fix User.get_or_fetch/1 with usernames starting with http --- lib/pleroma/user.ex | 3 ++- test/pleroma/user_test.exs | 7 +++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 138f3c851..a36c1c330 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1988,7 +1988,8 @@ def ap_enabled?(_), do: false @doc "Gets or fetch a user by uri or nickname." @spec get_or_fetch(String.t()) :: {:ok, User.t()} | {:error, String.t()} - def get_or_fetch("http" <> _host = uri), do: get_or_fetch_by_ap_id(uri) + def get_or_fetch("http://" <> _host = uri), do: get_or_fetch_by_ap_id(uri) + def get_or_fetch("https://" <> _host = uri), do: get_or_fetch_by_ap_id(uri) def get_or_fetch(nickname), do: get_or_fetch_by_nickname(nickname) # wait a period of time and return newest version of the User structs diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index 9d35f9780..0272e3142 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -737,6 +737,13 @@ test "gets an existing user by ap_id" do freshed_user = refresh_record(user) assert freshed_user == fetched_user end + + test "gets an existing user by nickname starting with http" do + user = insert(:user, nickname: "httpssome") + {:ok, fetched_user} = User.get_or_fetch("httpssome") + + assert user == fetched_user + end end describe "get_or_fetch/1 remote users with tld, while BE is runned on subdomain" do From 4c06c4ecb10879d2f8272ca79c98d4acaa9737b6 Mon Sep 17 00:00:00 2001 From: Tusooa Zhu Date: Sat, 20 Aug 2022 21:52:20 -0400 Subject: [PATCH 11/24] Add margin to forms and make inputs fill whole width --- priv/static/instance/static.css | Bin 5021 -> 5066 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/priv/static/instance/static.css b/priv/static/instance/static.css index 487e1ec27d6a1f3f1403c811fd3d896353b37bfc..48c74c125c3c7bbd8d9ac49f1b110f585b9b2074 100644 GIT binary patch delta 91 zcmbQMeoB4AHD)OVg@VMCl+3(zD+PnpTm?fQhSHm_Fi&ON+`=lvsHvb(o|#gTVWp6n lmyueOSz^tlppcYbp Date: Wed, 14 Sep 2022 10:20:07 +0000 Subject: [PATCH 12/24] Update styles.json path in frontend config doc (#212) Co-authored-by: Francis Dinh Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/212 Co-authored-by: Norm Co-committed-by: Norm --- docs/docs/configuration/howto_theming_your_instance.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/configuration/howto_theming_your_instance.md b/docs/docs/configuration/howto_theming_your_instance.md index 213afcf13..af417aee4 100644 --- a/docs/docs/configuration/howto_theming_your_instance.md +++ b/docs/docs/configuration/howto_theming_your_instance.md @@ -21,7 +21,7 @@ This will only save the theme for you personally. To make it available to the wh ### Upload the theme to the server -Themes can be found in the [static directory](static_dir.md). Create `STATIC-DIR/static/themes/` if needed and copy your theme there. Next you need to add an entry for your theme to `STATIC-DIR/static/styles.json`. If you use a from source installation, you'll first need to copy the file from `priv/static/static/styles.json`. +Themes can be found in the [static directory](static_dir.md). Create `STATIC-DIR/static/themes/` if needed and copy your theme there. Next you need to add an entry for your theme to `STATIC-DIR/static/styles.json`. If you use a from source installation, you'll first need to copy the file from `STATIC-DIR/frontends/pleroma-fe/REF/static/styles.json` (where `REF` is `stable` or `develop` depending on which ref you decided to install). Example of `styles.json` where we add our own `my-awesome-theme.json` ```json From 77596a302181aaa8617cd08f110586c36d517127 Mon Sep 17 00:00:00 2001 From: a1batross Date: Thu, 15 Sep 2022 22:38:35 +0200 Subject: [PATCH 13/24] User: search: exclude deactivated users from user search This way we don't pollute search results with deactivated and deleted users --- lib/pleroma/user/search.ex | 5 +++++ test/pleroma/user_search_test.exs | 8 ++++++++ 2 files changed, 13 insertions(+) diff --git a/lib/pleroma/user/search.ex b/lib/pleroma/user/search.ex index a4f6abca2..6b3f58999 100644 --- a/lib/pleroma/user/search.ex +++ b/lib/pleroma/user/search.ex @@ -94,6 +94,7 @@ defp search_query(query_string, for_user, following, top_user_ids) do |> subquery() |> order_by(desc: :search_rank) |> maybe_restrict_local(for_user) + |> filter_deactivated_users() end defp select_top_users(query, top_user_ids) do @@ -166,6 +167,10 @@ defp filter_internal_users(query) do from(q in query, where: q.actor_type != "Application") end + defp filter_deactivated_users(query) do + from(q in query, where: q.is_active == true) + end + defp filter_blocked_user(query, %User{} = blocker) do query |> join(:left, [u], b in Pleroma.UserRelationship, diff --git a/test/pleroma/user_search_test.exs b/test/pleroma/user_search_test.exs index 69167bb0c..8634a2e2b 100644 --- a/test/pleroma/user_search_test.exs +++ b/test/pleroma/user_search_test.exs @@ -65,6 +65,14 @@ test "excludes invisible users from results" do assert found_user.id == user.id end + test "excludes deactivated users from results" do + user = insert(:user, %{nickname: "john t1000"}) + insert(:user, %{is_active: false, nickname: "john t800"}) + + [found_user] = User.search("john") + assert found_user.id == user.id + end + # Note: as in Mastodon, `is_discoverable` doesn't anyhow relate to user searchability test "includes non-discoverable users in results" do insert(:user, %{nickname: "john 3000", is_discoverable: false}) From 911f8feb0a2d953192f2243f2cc6cee138f9e09e Mon Sep 17 00:00:00 2001 From: floatingghost Date: Fri, 16 Sep 2022 11:53:11 +0000 Subject: [PATCH 14/24] Ensure migrations succeed (#216) Co-authored-by: FloatingGhost Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/216 --- .../migrations/20220718102634_upgrade_oban_to_v11.exs | 5 ++++- .../20220916115149_ensure_mastofe_settings.exs | 9 +++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 priv/repo/migrations/20220916115149_ensure_mastofe_settings.exs diff --git a/priv/repo/migrations/20220718102634_upgrade_oban_to_v11.exs b/priv/repo/migrations/20220718102634_upgrade_oban_to_v11.exs index eb9c4986c..ba1c849c4 100644 --- a/priv/repo/migrations/20220718102634_upgrade_oban_to_v11.exs +++ b/priv/repo/migrations/20220718102634_upgrade_oban_to_v11.exs @@ -1,7 +1,10 @@ defmodule Pleroma.Repo.Migrations.UpgradeObanToV11 do use Ecto.Migration - def up, do: Oban.Migrations.up(version: 11) + def up do + execute("UPDATE oban_jobs SET priority = 0 WHERE priority IS NULL;") + Oban.Migrations.up(version: 11) + end def down, do: Oban.Migrations.down(version: 11) end diff --git a/priv/repo/migrations/20220916115149_ensure_mastofe_settings.exs b/priv/repo/migrations/20220916115149_ensure_mastofe_settings.exs new file mode 100644 index 000000000..37880ff99 --- /dev/null +++ b/priv/repo/migrations/20220916115149_ensure_mastofe_settings.exs @@ -0,0 +1,9 @@ +defmodule Pleroma.Repo.Migrations.EnsureMastofeSettings do + use Ecto.Migration + + def change do + alter table(:users) do + add_if_not_exists(:mastofe_settings, :map) + end + end +end From ee2eb7752d6a6f3542fa3a883faa02de2ccbbef8 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Fri, 16 Sep 2022 13:00:40 +0100 Subject: [PATCH 15/24] Ensure rollback succeeds --- .../migrations/20220916115149_ensure_mastofe_settings.exs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/priv/repo/migrations/20220916115149_ensure_mastofe_settings.exs b/priv/repo/migrations/20220916115149_ensure_mastofe_settings.exs index 37880ff99..1d0a6e050 100644 --- a/priv/repo/migrations/20220916115149_ensure_mastofe_settings.exs +++ b/priv/repo/migrations/20220916115149_ensure_mastofe_settings.exs @@ -1,9 +1,15 @@ defmodule Pleroma.Repo.Migrations.EnsureMastofeSettings do use Ecto.Migration - def change do + def up do alter table(:users) do add_if_not_exists(:mastofe_settings, :map) end end + + def down do + alter table(:users) do + remove_if_exists(:mastofe_settings, :map) + end + end end From ad1a6d3dc2b431f0d6d5cbe523093a7a3fb0308d Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Fri, 16 Sep 2022 14:23:31 +0100 Subject: [PATCH 16/24] ensure queue_target can't be silly low --- config/config.exs | 1 + 1 file changed, 1 insertion(+) diff --git a/config/config.exs b/config/config.exs index bf7e7db44..8e0b751a0 100644 --- a/config/config.exs +++ b/config/config.exs @@ -48,6 +48,7 @@ config :pleroma, Pleroma.Repo, telemetry_event: [Pleroma.Repo.Instrumenter], + queue_target: 20_000, migration_lock: nil config :pleroma, Pleroma.Captcha, From 84f8f32ef96c574358f196fd34e5cb64ec07a2a3 Mon Sep 17 00:00:00 2001 From: Norm Date: Sun, 18 Sep 2022 03:21:05 +0000 Subject: [PATCH 17/24] Move remote user interaction changelog entry to correct version That feature was added in 2022.09, not 2022.08. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cd48b07c..73093bc54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - MFM parsing is now done on the backend by a modified version of ilja's parser -> https://akkoma.dev/AkkomaGang/mfm-parser - InlineQuotePolicy is now on by default +- Enable remote users to interact with posts ### Fixed - Compatibility with latest meilisearch @@ -44,7 +45,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - amd64 is built for debian stable. Compatible with ubuntu 20. - ubuntu-jammy is built for... well, ubuntu 22 (LTS) - amd64-musl is built for alpine 3.16 -- Enable remote users to interact with posts ### Fixed - Updated mastoFE path, for the newer version From 8fe59d495df0c7fb999579b214f0b241835211b2 Mon Sep 17 00:00:00 2001 From: lou_de_sel Date: Sun, 18 Sep 2022 07:45:30 +0000 Subject: [PATCH 18/24] Update soapbox base url At some point 'soapbox-pub/soapbox-fe' was moved to 'soapbox-pub/soapbox' and the build url is now updated. --- config/config.exs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/config.exs b/config/config.exs index 8e0b751a0..7fbfb9ad2 100644 --- a/config/config.exs +++ b/config/config.exs @@ -753,9 +753,9 @@ }, "soapbox-fe" => %{ "name" => "soapbox-fe", - "git" => "https://gitlab.com/soapbox-pub/soapbox-fe", + "git" => "https://gitlab.com/soapbox-pub/soapbox", "build_url" => - "https://gitlab.com/soapbox-pub/soapbox-fe/-/jobs/artifacts/${ref}/download?job=build-production", + "https://gitlab.com/soapbox-pub/soapbox/-/jobs/artifacts/${ref}/download?job=build-production", "ref" => "v2.0.0", "build_dir" => "static" }, From 561e1f2470d813aa7b894a59779b8bccb91122f0 Mon Sep 17 00:00:00 2001 From: Norm Date: Mon, 19 Sep 2022 17:31:35 +0000 Subject: [PATCH 19/24] Make backups require its own scope (#218) Pulled from https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3721. This makes backups require its own scope (`read:backups`) instead of the `read:accounts` scope. Co-authored-by: Tusooa Zhu Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/218 Co-authored-by: Norm Co-committed-by: Norm --- CHANGELOG.md | 5 +++++ .../web/api_spec/operations/pleroma_backup_operation.ex | 4 ++-- lib/pleroma/web/pleroma_api/controllers/backup_controller.ex | 2 +- .../web/pleroma_api/controllers/backup_controller_test.exs | 4 ++-- 4 files changed, 10 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5cd48b07c..8eb2df1d7 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 + +### Changed +- **Breaking**: `/api/v1/pleroma/backups` endpoints now requires `read:backups` scope instead of `read:accounts` + ## 2022.09 ### Added diff --git a/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex index c78e9780f..9af556736 100644 --- a/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_backup_operation.ex @@ -16,7 +16,7 @@ def index_operation do %Operation{ tags: ["Backups"], summary: "List backups", - security: [%{"oAuth" => ["read:account"]}], + security: [%{"oAuth" => ["read:backups"]}], operationId: "PleromaAPI.BackupController.index", responses: %{ 200 => @@ -37,7 +37,7 @@ def create_operation do %Operation{ tags: ["Backups"], summary: "Create a backup", - security: [%{"oAuth" => ["read:account"]}], + security: [%{"oAuth" => ["read:backups"]}], operationId: "PleromaAPI.BackupController.create", responses: %{ 200 => diff --git a/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex b/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex index fc5d16771..88f38a911 100644 --- a/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/backup_controller.ex @@ -9,7 +9,7 @@ defmodule Pleroma.Web.PleromaAPI.BackupController do alias Pleroma.Web.Plugs.OAuthScopesPlug action_fallback(Pleroma.Web.MastodonAPI.FallbackController) - plug(OAuthScopesPlug, %{scopes: ["read:accounts"]} when action in [:index, :create]) + plug(OAuthScopesPlug, %{scopes: ["read:backups"]} when action in [:index, :create]) plug(Pleroma.Web.ApiSpec.CastAndValidate) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaBackupOperation diff --git a/test/pleroma/web/pleroma_api/controllers/backup_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/backup_controller_test.exs index ba17636da..2c7264016 100644 --- a/test/pleroma/web/pleroma_api/controllers/backup_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/backup_controller_test.exs @@ -11,7 +11,7 @@ defmodule Pleroma.Web.PleromaAPI.BackupControllerTest do setup do clear_config([Pleroma.Upload, :uploader]) clear_config([Backup, :limit_days]) - oauth_access(["read:accounts"]) + oauth_access(["read:backups"]) end test "GET /api/v1/pleroma/backups", %{user: user, conn: conn} do @@ -85,7 +85,7 @@ test "POST /api/v1/pleroma/backups", %{user: _user, conn: conn} do test "Backup without email address" do user = Pleroma.Factory.insert(:user, email: nil) - %{conn: conn} = oauth_access(["read:accounts"], user: user) + %{conn: conn} = oauth_access(["read:backups"], user: user) assert is_nil(user.email) From b2aa82cee5b53fd04f6b6aefcb9c2cf6aa46efba Mon Sep 17 00:00:00 2001 From: floatingghost Date: Tue, 20 Sep 2022 10:36:21 +0000 Subject: [PATCH 20/24] Fix false error in meilisearch index (#221) the schema changed https://docs.meilisearch.com/reference/api/documents.html#add-or-update-documents this wasn't breaking anything, it would just report errors that were actually successes Co-authored-by: FloatingGhost Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/221 --- CHANGELOG.md | 3 +++ docs/docs/configuration/search.md | 3 +-- docs/docs/index.md | 15 +++++++++++++++ lib/pleroma/search/meilisearch.ex | 2 +- test/pleroma/search/meilisearch_test.exs | 6 +++--- 5 files changed, 23 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index be95ceb3c..104164dec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Changed - **Breaking**: `/api/v1/pleroma/backups` endpoints now requires `read:backups` scope instead of `read:accounts` +### Fixed +- prevent false-errors from meilisearch + ## 2022.09 ### Added diff --git a/docs/docs/configuration/search.md b/docs/docs/configuration/search.md index ebb2c6ab7..1e343032f 100644 --- a/docs/docs/configuration/search.md +++ b/docs/docs/configuration/search.md @@ -141,8 +141,7 @@ You then need to set the URL and authentication credentials if relevant. ### Initial indexing -After setting up the configuration, you'll want to index all of your already existsing posts. Only public posts are indexed. You'll only -have to do it one time, but it might take a while, depending on the amount of posts your instance has seen. +After setting up the configuration, you'll want to index all of your already existsing posts. You'll only have to do it one time, but it might take a while, depending on the amount of posts your instance has seen. The sequence of actions is as follows: diff --git a/docs/docs/index.md b/docs/docs/index.md index f9340d5d3..1018e9c2b 100644 --- a/docs/docs/index.md +++ b/docs/docs/index.md @@ -7,6 +7,20 @@ It actually consists of two components: a backend, named simply Akkoma, and a us It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other. One account on an instance is enough to talk to the entire fediverse! +## Community Channels + +### IRC + +For support or general questions, pop over to #akkoma and #akkoma-dev at [irc.akkoma.dev](https://irc.akkoma.dev) (port 6697, SSL) + +### Discourse + +For more general meta-discussion, for example discussion of potential future features, head on over to [meta.akkoma.dev](https://meta.akkoma.dev) + +### Dev diaries and release notifications + +will be posted via [@akkoma@ihba](https://ihatebeinga.live/users/akkoma) + ## How can I use it? Akkoma instances are already widely deployed, a list can be found at and . @@ -26,3 +40,4 @@ Just add a "/web" after your instance url (e.g. The Mastodon interface is from the Glitch-soc fork. For more information on the Mastodon interface you can check the [Mastodon](https://docs.joinmastodon.org/) and [Glitch-soc](https://glitch-soc.github.io/docs/) documentation. Remember, what you see is only the frontend part of Mastodon, the backend is still Akkoma. + diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index 3db65f261..770557858 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -153,7 +153,7 @@ def add_to_index(activity) do ) with {:ok, res} <- result, - true <- Map.has_key?(res, "uid") do + true <- Map.has_key?(res, "taskUid") do # Do nothing else _ -> diff --git a/test/pleroma/search/meilisearch_test.exs b/test/pleroma/search/meilisearch_test.exs index 04a2d75d9..fe09c9485 100644 --- a/test/pleroma/search/meilisearch_test.exs +++ b/test/pleroma/search/meilisearch_test.exs @@ -47,7 +47,7 @@ test "indexes a local post on creation" do Jason.decode!(body) ) - json(%{updateId: 1}) + json(%{taskUid: 1}) end) {:ok, activity} = @@ -100,11 +100,11 @@ test "deletes posts from index when deleted locally" do Jason.decode!(body) ) - json(%{updateId: 1}) + json(%{taskUid: 1}) %{method: :delete, url: "http://127.0.0.1:7700/indexes/objects/documents/" <> id} -> assert String.length(id) > 1 - json(%{updateId: 2}) + json(%{taskUid: 2}) end) {:ok, activity} = From 5827f7781ff4497dc22b91a628c74546b59b3bc6 Mon Sep 17 00:00:00 2001 From: floatingghost Date: Tue, 20 Sep 2022 11:04:26 +0000 Subject: [PATCH 21/24] Add installation note about flavour, support special cases (#222) Fixes #210 Co-authored-by: FloatingGhost Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/222 --- .woodpecker.yml | 14 ++++++++-- docs/docs/administration/updating.md | 4 +++ docs/docs/installation/otp_en.md | 12 ++++----- rel/files/bin/pleroma_ctl | 38 +++++++++++++--------------- 4 files changed, 39 insertions(+), 29 deletions(-) diff --git a/.woodpecker.yml b/.woodpecker.yml index 32db2f1c5..cd1285ecc 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -14,6 +14,14 @@ variables: - stable - refs/tags/v* - refs/tags/stable-* + - &on-stable + when: + event: + - push + - tag + branch: + - stable + - refs/tags/stable-* - &on-point-release when: event: @@ -110,6 +118,8 @@ pipeline: - export SOURCE=akkoma-ubuntu-jammy.zip - export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-ubuntu-jammy.zip - /bin/sh /entrypoint.sh + - export DEST=scaleway:akkoma-updates/$${CI_COMMIT_TAG:-"$CI_COMMIT_BRANCH"}/akkoma-amd64-ubuntu-jammy.zip + - /bin/sh /entrypoint.sh debian-bullseye: image: elixir:1.13.4 @@ -142,7 +152,7 @@ pipeline: # Canonical amd64-musl musl: image: elixir:1.13.4-alpine - <<: *on-release + <<: *on-stable environment: MIX_ENV: prod commands: @@ -157,7 +167,7 @@ pipeline: release-musl: image: akkoma/releaser - <<: *on-release + <<: *on-stable secrets: *scw-secrets commands: - export SOURCE=akkoma-amd64-musl.zip diff --git a/docs/docs/administration/updating.md b/docs/docs/administration/updating.md index 6f7ffcd63..2d9e77075 100644 --- a/docs/docs/administration/updating.md +++ b/docs/docs/administration/updating.md @@ -14,6 +14,10 @@ su akkoma -s $SHELL -lc "./bin/pleroma_ctl update" su akkoma -s $SHELL -lc "./bin/pleroma_ctl migrate" ``` +If you selected an alternate flavour on installation, +you _may_ need to specify `--flavour`, in the same way as +[when installing](../../installation/otp_en#detecting-flavour). + ## For from source installations (using git) 1. Go to the working directory of Akkoma (default is `/opt/akkoma`) diff --git a/docs/docs/installation/otp_en.md b/docs/docs/installation/otp_en.md index 329afe967..3e00d3262 100644 --- a/docs/docs/installation/otp_en.md +++ b/docs/docs/installation/otp_en.md @@ -19,12 +19,12 @@ This is a little more complex than it used to be (thanks ubuntu) Use the following mapping to figure out your flavour: -| distribution | flavour | -| ------------- | ------------ | -| debian stable | amd64 | -| ubuntu focal | amd64 | -| ubuntu jammy | ubuntu-jammy | -| alpine | amd64-musl | +| distribution | flavour | available branches | +| ------------- | ------------------ | ------------------- | +| debian stable | amd64 | develop, stable | +| ubuntu focal | amd64 | develop, stable | +| ubuntu jammy | amd64-ubuntu-jammy | develop, stable | +| alpine | amd64-musl | stable | Other similar distributions will _probably_ work, but if it is not listed above, there is no official support. diff --git a/rel/files/bin/pleroma_ctl b/rel/files/bin/pleroma_ctl index d2949e8fe..e0e6d1b5a 100755 --- a/rel/files/bin/pleroma_ctl +++ b/rel/files/bin/pleroma_ctl @@ -2,28 +2,24 @@ # XXX: This should be removed when elixir's releases get custom command support detect_flavour() { - arch="$(uname -m)" - if [ "$arch" = "x86_64" ]; then - arch="amd64" - elif [ "$arch" = "aarch64" ]; then - arch="arm64" - else - echo "Unsupported arch: $arch" >&2 - exit 1 - fi + arch="amd64" + # Special cases + if grep -qe "VERSION_CODENAME=jammy" /etc/os-release; then + echo "$arch-ubuntu-jammy" + else + if getconf GNU_LIBC_VERSION >/dev/null; then + libc_postfix="" + elif [ "$(ldd 2>&1 | head -c 9)" = "musl libc" ]; then + libc_postfix="-musl" + elif [ "$(find /lib/libc.musl* | wc -l)" ]; then + libc_postfix="-musl" + else + echo "Unsupported libc" >&2 + exit 1 + fi - if getconf GNU_LIBC_VERSION >/dev/null; then - libc_postfix="" - elif [ "$(ldd 2>&1 | head -c 9)" = "musl libc" ]; then - libc_postfix="-musl" - elif [ "$(find /lib/libc.musl* | wc -l)" ]; then - libc_postfix="-musl" - else - echo "Unsupported libc" >&2 - exit 1 - fi - - echo "$arch$libc_postfix" + echo "$arch$libc_postfix" + fi } detect_branch() { From 69099d6f4487f1a513c7dac7f44621942e32a118 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Tue, 20 Sep 2022 12:20:54 +0100 Subject: [PATCH 22/24] ensure we use the same OTP for all releases --- .woodpecker.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.woodpecker.yml b/.woodpecker.yml index cd1285ecc..c97e3eb4f 100644 --- a/.woodpecker.yml +++ b/.woodpecker.yml @@ -95,7 +95,7 @@ pipeline: # Canonical amd64 ubuntu22: - image: hexpm/elixir:1.13.4-erlang-25.0.2-ubuntu-jammy-20220428 + image: hexpm/elixir:1.13.4-erlang-24.3.4.5-ubuntu-jammy-20220428 <<: *on-release environment: MIX_ENV: prod @@ -122,7 +122,7 @@ pipeline: - /bin/sh /entrypoint.sh debian-bullseye: - image: elixir:1.13.4 + image: hexpm/elixir:1.13.4-erlang-24.3.4.5-debian-bullseye-20220801 <<: *on-release environment: MIX_ENV: prod @@ -151,7 +151,7 @@ pipeline: # Canonical amd64-musl musl: - image: elixir:1.13.4-alpine + image: hexpm/elixir:1.13.4-erlang-24.3.4.5-alpine-3.15.6 <<: *on-stable environment: MIX_ENV: prod From 47a793f33ec1a7a15763ef8ce992a8ec7a7aaea5 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Tue, 20 Sep 2022 14:40:32 +0100 Subject: [PATCH 23/24] include requirement to enable HTTP tunnel in tor --- docs/docs/configuration/onion_federation.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/docs/configuration/onion_federation.md b/docs/docs/configuration/onion_federation.md index 077c3eb50..499b4a693 100644 --- a/docs/docs/configuration/onion_federation.md +++ b/docs/docs/configuration/onion_federation.md @@ -14,11 +14,12 @@ apt -yq install tor **WARNING:** Onion instances not using a Tor version supporting V3 addresses will not be able to federate with you. -Create the hidden service for your Akkoma instance in `/etc/tor/torrc`: +Create the hidden service for your Akkoma instance in `/etc/tor/torrc`, with an HTTP tunnel: ``` HiddenServiceDir /var/lib/tor/akkoma_hidden_service/ HiddenServicePort 80 127.0.0.1:8099 HiddenServiceVersion 3 # Remove if Tor version is below 0.3 ( tor --version ) +HTTPTunnelPort 9080 ``` Restart Tor to generate an adress: ``` @@ -35,7 +36,7 @@ Next, edit your Akkoma config. If running in prod, navigate to your Akkoma directory, edit `config/prod.secret.exs` and append this line: ``` -config :pleroma, :http, proxy_url: {:socks5, :localhost, 9050} +config :pleroma, :http, proxy_url: "http://localhost:9080" ``` In your Akkoma directory, assuming you're running prod, run the following: From c6e63aaf6b647f458ecd0e788ca0adb2113a9524 Mon Sep 17 00:00:00 2001 From: floatingghost Date: Thu, 6 Oct 2022 16:22:15 +0000 Subject: [PATCH 24/24] Backend settings sync (#226) Co-authored-by: FloatingGhost Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/226 --- CHANGELOG.md | 9 +- config/config.exs | 3 +- .../akkoma/frontend_setting_profile.ex | 100 +++++++++ lib/pleroma/user.ex | 2 + .../frontend_settings_controller.ex | 96 +++++++++ .../operations/frontend_settings_operation.ex | 133 ++++++++++++ lib/pleroma/web/router.ex | 20 ++ mix.exs | 2 +- ...20911195347_add_user_frontend_profiles.exs | 29 +++ .../akkoma/frontend_setting_profile_test.exs | 196 ++++++++++++++++++ .../frontend_settings_controller_test.exs | 122 +++++++++++ test/support/factory.ex | 11 + 12 files changed, 720 insertions(+), 3 deletions(-) create mode 100644 lib/pleroma/akkoma/frontend_setting_profile.ex create mode 100644 lib/pleroma/web/akkoma_api/controllers/frontend_settings_controller.ex create mode 100644 lib/pleroma/web/api_spec/operations/frontend_settings_operation.ex create mode 100644 priv/repo/migrations/20220911195347_add_user_frontend_profiles.exs create mode 100644 test/pleroma/akkoma/frontend_setting_profile_test.exs create mode 100644 test/pleroma/web/akkoma_api/frontend_settings_controller_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index 104164dec..ece6af0d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,19 @@ 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 +## 2022.10 + +### Added +- Ability to sync frontend profiles between clients, with a name attached +- Status card generation will now use the media summary if it is available ### Changed +- Emoji updated to latest 15.0 draft - **Breaking**: `/api/v1/pleroma/backups` endpoints now requires `read:backups` scope instead of `read:accounts` ### Fixed +- OAuthPlug no longer joins with the database every call and uses the user cache +- Undo activities no longer try to look up by ID, and render correctly - prevent false-errors from meilisearch ## 2022.09 diff --git a/config/config.exs b/config/config.exs index 7fbfb9ad2..d7005770e 100644 --- a/config/config.exs +++ b/config/config.exs @@ -261,7 +261,8 @@ password_reset_token_validity: 60 * 60 * 24, profile_directory: true, privileged_staff: false, - local_bubble: [] + local_bubble: [], + max_frontend_settings_json_chars: 100_000 config :pleroma, :welcome, direct_message: [ diff --git a/lib/pleroma/akkoma/frontend_setting_profile.ex b/lib/pleroma/akkoma/frontend_setting_profile.ex new file mode 100644 index 000000000..18208a7dd --- /dev/null +++ b/lib/pleroma/akkoma/frontend_setting_profile.ex @@ -0,0 +1,100 @@ +defmodule Pleroma.Akkoma.FrontendSettingsProfile do + use Ecto.Schema + + import Ecto.Changeset + import Ecto.Query + alias Pleroma.Repo + alias Pleroma.Config + alias Pleroma.User + + @primary_key false + schema "user_frontend_setting_profiles" do + belongs_to(:user, Pleroma.User, primary_key: true, type: FlakeId.Ecto.CompatType) + field(:frontend_name, :string, primary_key: true) + field(:profile_name, :string, primary_key: true) + field(:settings, :map) + field(:version, :integer) + timestamps() + end + + def changeset(%__MODULE__{} = struct, attrs) do + struct + |> cast(attrs, [:user_id, :frontend_name, :profile_name, :settings, :version]) + |> validate_required([:user_id, :frontend_name, :profile_name, :settings, :version]) + |> validate_length(:frontend_name, min: 1, max: 255) + |> validate_length(:profile_name, min: 1, max: 255) + |> validate_version(struct) + |> validate_number(:version, greater_than: 0) + |> validate_settings_length(Config.get([:instance, :max_frontend_settings_json_chars])) + end + + def create_or_update(%User{} = user, frontend_name, profile_name, settings, version) do + struct = + case get_by_user_and_frontend_name_and_profile_name(user, frontend_name, profile_name) do + nil -> + %__MODULE__{} + + %__MODULE__{} = profile -> + profile + end + + struct + |> changeset(%{ + user_id: user.id, + frontend_name: frontend_name, + profile_name: profile_name, + settings: settings, + version: version + }) + |> Repo.insert_or_update() + end + + def get_all_by_user_and_frontend_name(%User{id: user_id}, frontend_name) do + Repo.all( + from(p in __MODULE__, where: p.user_id == ^user_id and p.frontend_name == ^frontend_name) + ) + end + + def get_by_user_and_frontend_name_and_profile_name( + %User{id: user_id}, + frontend_name, + profile_name + ) do + Repo.one( + from(p in __MODULE__, + where: + p.user_id == ^user_id and p.frontend_name == ^frontend_name and + p.profile_name == ^profile_name + ) + ) + end + + def delete_profile(profile) do + Repo.delete(profile) + end + + defp validate_settings_length( + %Ecto.Changeset{changes: %{settings: settings}} = changeset, + max_length + ) do + settings_json = Jason.encode!(settings) + + if String.length(settings_json) > max_length do + add_error(changeset, :settings, "is too long") + else + changeset + end + end + + defp validate_version(changeset, %{version: nil}), do: changeset + + defp validate_version(%Ecto.Changeset{changes: %{version: version}} = changeset, %{ + version: prev_version + }) do + if version != prev_version + 1 do + add_error(changeset, :version, "must be incremented by 1") + else + changeset + end + end +end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index a36c1c330..700cab2b5 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -165,6 +165,8 @@ defmodule Pleroma.User do has_many(:outgoing_relationships, UserRelationship, foreign_key: :source_id) has_many(:incoming_relationships, UserRelationship, foreign_key: :target_id) + has_many(:frontend_profiles, Pleroma.Akkoma.FrontendSettingsProfile) + for {relationship_type, [ {outgoing_relation, outgoing_relation_target}, diff --git a/lib/pleroma/web/akkoma_api/controllers/frontend_settings_controller.ex b/lib/pleroma/web/akkoma_api/controllers/frontend_settings_controller.ex new file mode 100644 index 000000000..c13ff9096 --- /dev/null +++ b/lib/pleroma/web/akkoma_api/controllers/frontend_settings_controller.ex @@ -0,0 +1,96 @@ +defmodule Pleroma.Web.AkkomaAPI.FrontendSettingsController do + use Pleroma.Web, :controller + + alias Pleroma.Web.Plugs.OAuthScopesPlug + alias Pleroma.Akkoma.FrontendSettingsProfile + + @unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []} + plug( + OAuthScopesPlug, + %{@unauthenticated_access | scopes: ["read:accounts"]} + when action in [ + :list_profiles, + :get_profile + ] + ) + + plug( + OAuthScopesPlug, + %{@unauthenticated_access | scopes: ["write:accounts"]} + when action in [ + :update_profile, + :delete_profile + ] + ) + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.FrontendSettingsOperation + + action_fallback(Pleroma.Web.MastodonAPI.FallbackController) + + @doc "GET /api/v1/akkoma/frontend_settings/:frontend_name/:profile_name" + def get_profile(conn, %{frontend_name: frontend_name, profile_name: profile_name}) do + with %FrontendSettingsProfile{} = profile <- + FrontendSettingsProfile.get_by_user_and_frontend_name_and_profile_name( + conn.assigns.user, + frontend_name, + profile_name + ) do + conn + |> json(%{ + settings: profile.settings, + version: profile.version + }) + else + nil -> {:error, :not_found} + end + end + + @doc "GET /api/v1/akkoma/frontend_settings/:frontend_name" + def list_profiles(conn, %{frontend_name: frontend_name}) do + with profiles <- + FrontendSettingsProfile.get_all_by_user_and_frontend_name( + conn.assigns.user, + frontend_name + ), + data <- + Enum.map(profiles, fn profile -> + %{name: profile.profile_name, version: profile.version} + end) do + json(conn, data) + end + end + + @doc "DELETE /api/v1/akkoma/frontend_settings/:frontend_name/:profile_name" + def delete_profile(conn, %{frontend_name: frontend_name, profile_name: profile_name}) do + with %FrontendSettingsProfile{} = profile <- + FrontendSettingsProfile.get_by_user_and_frontend_name_and_profile_name( + conn.assigns.user, + frontend_name, + profile_name + ), + {:ok, _} <- FrontendSettingsProfile.delete_profile(profile) do + json(conn, %{deleted: "ok"}) + else + nil -> {:error, :not_found} + end + end + + @doc "PUT /api/v1/akkoma/frontend_settings/:frontend_name/:profile_name" + def update_profile(%{body_params: %{settings: settings, version: version}} = conn, %{ + frontend_name: frontend_name, + profile_name: profile_name + }) do + with {:ok, profile} <- + FrontendSettingsProfile.create_or_update( + conn.assigns.user, + frontend_name, + profile_name, + settings, + version + ) do + conn + |> json(profile.settings) + end + end +end diff --git a/lib/pleroma/web/api_spec/operations/frontend_settings_operation.ex b/lib/pleroma/web/api_spec/operations/frontend_settings_operation.ex new file mode 100644 index 000000000..40e81ad55 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/frontend_settings_operation.ex @@ -0,0 +1,133 @@ +defmodule Pleroma.Web.ApiSpec.FrontendSettingsOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + import Pleroma.Web.ApiSpec.Helpers + + @spec open_api_operation(atom) :: Operation.t() + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + @spec list_profiles_operation() :: Operation.t() + def list_profiles_operation() do + %Operation{ + tags: ["Retrieve frontend setting profiles"], + summary: "Frontend Settings Profiles", + description: "List frontend setting profiles", + operationId: "AkkomaAPI.FrontendSettingsController.list_profiles", + parameters: [frontend_name_param()], + security: [%{"oAuth" => ["read:accounts"]}], + responses: %{ + 200 => + Operation.response("Profiles", "application/json", %Schema{ + type: :array, + items: %Schema{ + type: :object, + properties: %{ + name: %Schema{type: :string}, + version: %Schema{type: :integer} + } + } + }) + } + } + end + + @spec get_profile_operation() :: Operation.t() + def get_profile_operation() do + %Operation{ + tags: ["Retrieve frontend setting profile"], + summary: "Frontend Settings Profile", + description: "Get frontend setting profile", + operationId: "AkkomaAPI.FrontendSettingsController.get_profile", + security: [%{"oAuth" => ["read:accounts"]}], + parameters: [frontend_name_param(), profile_name_param()], + responses: %{ + 200 => + Operation.response("Profile", "application/json", %Schema{ + type: :object, + properties: %{ + "version" => %Schema{type: :integer}, + "settings" => %Schema{type: :object, additionalProperties: true} + } + }), + 404 => Operation.response("Not Found", "application/json", %Schema{type: :object}) + } + } + end + + @spec delete_profile_operation() :: Operation.t() + def delete_profile_operation() do + %Operation{ + tags: ["Delete frontend setting profile"], + summary: "Delete frontend Settings Profile", + description: "Delete frontend setting profile", + operationId: "AkkomaAPI.FrontendSettingsController.delete_profile", + security: [%{"oAuth" => ["write:accounts"]}], + parameters: [frontend_name_param(), profile_name_param()], + responses: %{ + 200 => Operation.response("Empty", "application/json", %Schema{type: :object}), + 404 => Operation.response("Not Found", "application/json", %Schema{type: :object}) + } + } + end + + @spec update_profile_operation() :: Operation.t() + def update_profile_operation() do + %Operation{ + tags: ["Update frontend setting profile"], + summary: "Frontend Settings Profile", + description: "Update frontend setting profile", + operationId: "AkkomaAPI.FrontendSettingsController.update_profile_operation", + security: [%{"oAuth" => ["write:accounts"]}], + parameters: [frontend_name_param(), profile_name_param()], + requestBody: profile_body_param(), + responses: %{ + 200 => Operation.response("Settings", "application/json", %Schema{type: :object}), + 422 => Operation.response("Invalid", "application/json", %Schema{type: :object}) + } + } + end + + def frontend_name_param do + Operation.parameter(:frontend_name, :path, :string, "Frontend name", + example: "pleroma-fe", + required: true + ) + end + + def profile_name_param do + Operation.parameter(:profile_name, :path, :string, "Profile name", + example: "mobile", + required: true + ) + end + + def profile_body_param do + request_body( + "Settings", + %Schema{ + title: "Frontend Setting Profile", + type: :object, + required: [:version, :settings], + properties: %{ + version: %Schema{ + type: :integer, + description: "Version of the profile, must increment by 1 each time", + example: 1 + }, + settings: %Schema{ + type: :object, + description: "Settings of the profile", + example: %{ + theme: "dark", + locale: "en" + } + } + } + }, + required: true + ) + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index f722d94f7..838599c4d 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -466,6 +466,26 @@ defmodule Pleroma.Web.Router do scope "/api/v1/akkoma", Pleroma.Web.AkkomaAPI do pipe_through(:authenticated_api) get("/translation/languages", TranslationController, :languages) + + get("/frontend_settings/:frontend_name", FrontendSettingsController, :list_profiles) + + get( + "/frontend_settings/:frontend_name/:profile_name", + FrontendSettingsController, + :get_profile + ) + + put( + "/frontend_settings/:frontend_name/:profile_name", + FrontendSettingsController, + :update_profile + ) + + delete( + "/frontend_settings/:frontend_name/:profile_name", + FrontendSettingsController, + :delete_profile + ) end scope "/api/v1", Pleroma.Web.MastodonAPI do diff --git a/mix.exs b/mix.exs index 19e6fd045..c7e66b158 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("3.2.0"), + version: version("3.3.0"), elixir: "~> 1.12", elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix, :gettext] ++ Mix.compilers(), diff --git a/priv/repo/migrations/20220911195347_add_user_frontend_profiles.exs b/priv/repo/migrations/20220911195347_add_user_frontend_profiles.exs new file mode 100644 index 000000000..c1c449719 --- /dev/null +++ b/priv/repo/migrations/20220911195347_add_user_frontend_profiles.exs @@ -0,0 +1,29 @@ +defmodule Pleroma.Repo.Migrations.AddUserFrontendProfiles do + use Ecto.Migration + + def up do + create_if_not_exists table("user_frontend_setting_profiles", primary_key: false) do + add(:user_id, references(:users, type: :uuid, on_delete: :delete_all), primary_key: true) + add(:frontend_name, :string, primary_key: true) + add(:profile_name, :string, primary_key: true) + add(:version, :integer) + add(:settings, :map) + timestamps() + end + + create_if_not_exists(index(:user_frontend_setting_profiles, [:user_id, :frontend_name])) + + create_if_not_exists( + unique_index(:user_frontend_setting_profiles, [:user_id, :frontend_name, :profile_name]) + ) + end + + def down do + drop_if_exists(table("user_frontend_setting_profiles")) + drop_if_exists(index(:user_frontend_setting_profiles, [:user_id, :frontend_name])) + + drop_if_exists( + unique_index(:user_frontend_setting_profiles, [:user_id, :frontend_name, :profile_name]) + ) + end +end diff --git a/test/pleroma/akkoma/frontend_setting_profile_test.exs b/test/pleroma/akkoma/frontend_setting_profile_test.exs new file mode 100644 index 000000000..4bb1139a8 --- /dev/null +++ b/test/pleroma/akkoma/frontend_setting_profile_test.exs @@ -0,0 +1,196 @@ +defmodule Pleroma.Akkoma.FrontendSettingsProfileTest do + use Pleroma.DataCase, async: true + use Oban.Testing, repo: Pleroma.Repo + alias Pleroma.Akkoma.FrontendSettingsProfile + + import Pleroma.Factory + + describe "changeset/2" do + test "valid" do + user = insert(:user) + frontend_name = "test" + profile_name = "test" + settings = %{"test" => "test"} + struct = %FrontendSettingsProfile{} + + attrs = %{ + user_id: user.id, + frontend_name: frontend_name, + profile_name: profile_name, + settings: settings, + version: 1 + } + + assert %{valid?: true} = FrontendSettingsProfile.changeset(struct, attrs) + end + + test "when settings is too long" do + clear_config([:instance, :max_frontend_settings_json_chars], 10) + user = insert(:user) + frontend_name = "test" + profile_name = "test" + settings = %{"verylong" => "verylongoops"} + struct = %FrontendSettingsProfile{} + + attrs = %{ + user_id: user.id, + frontend_name: frontend_name, + profile_name: profile_name, + settings: settings, + version: 1 + } + + assert %{valid?: false, errors: [settings: {"is too long", _}]} = + FrontendSettingsProfile.changeset(struct, attrs) + end + + test "when frontend name is too short" do + user = insert(:user) + frontend_name = "" + profile_name = "test" + settings = %{"test" => "test"} + struct = %FrontendSettingsProfile{} + + attrs = %{ + user_id: user.id, + frontend_name: frontend_name, + profile_name: profile_name, + settings: settings, + version: 1 + } + + assert %{valid?: false, errors: [frontend_name: {"can't be blank", _}]} = + FrontendSettingsProfile.changeset(struct, attrs) + end + + test "when profile name is too short" do + user = insert(:user) + frontend_name = "test" + profile_name = "" + settings = %{"test" => "test"} + struct = %FrontendSettingsProfile{} + + attrs = %{ + user_id: user.id, + frontend_name: frontend_name, + profile_name: profile_name, + settings: settings, + version: 1 + } + + assert %{valid?: false, errors: [profile_name: {"can't be blank", _}]} = + FrontendSettingsProfile.changeset(struct, attrs) + end + + test "when version is negative" do + user = insert(:user) + frontend_name = "test" + profile_name = "test" + settings = %{"test" => "test"} + struct = %FrontendSettingsProfile{} + + attrs = %{ + user_id: user.id, + frontend_name: frontend_name, + profile_name: profile_name, + settings: settings, + version: -1 + } + + assert %{valid?: false, errors: [version: {"must be greater than %{number}", _}]} = + FrontendSettingsProfile.changeset(struct, attrs) + end + end + + describe "create_or_update/2" do + test "it should create a new record" do + user = insert(:user) + frontend_name = "test" + profile_name = "test" + settings = %{"test" => "test"} + + assert {:ok, %FrontendSettingsProfile{}} = + FrontendSettingsProfile.create_or_update( + user, + frontend_name, + profile_name, + settings, + 1 + ) + end + + test "it should update a record" do + user = insert(:user) + frontend_name = "test" + profile_name = "test" + + insert(:frontend_setting_profile, + user: user, + frontend_name: frontend_name, + profile_name: profile_name, + settings: %{"test" => "test"}, + version: 1 + ) + + settings = %{"test" => "test2"} + + assert {:ok, %FrontendSettingsProfile{settings: ^settings}} = + FrontendSettingsProfile.create_or_update( + user, + frontend_name, + profile_name, + settings, + 2 + ) + end + end + + describe "get_all_by_user_and_frontend_name/2" do + test "it should return all records" do + user = insert(:user) + frontend_name = "test" + + insert(:frontend_setting_profile, + user: user, + frontend_name: frontend_name, + profile_name: "profileA", + settings: %{"test" => "test"}, + version: 1 + ) + + insert(:frontend_setting_profile, + user: user, + frontend_name: frontend_name, + profile_name: "profileB", + settings: %{"test" => "test"}, + version: 1 + ) + + assert [%FrontendSettingsProfile{profile_name: "profileA"}, %{profile_name: "profileB"}] = + FrontendSettingsProfile.get_all_by_user_and_frontend_name(user, frontend_name) + end + end + + describe "get_by_user_and_frontend_name_and_profile_name/3" do + test "it should return a record" do + user = insert(:user) + frontend_name = "test" + profile_name = "profileA" + + insert(:frontend_setting_profile, + user: user, + frontend_name: frontend_name, + profile_name: profile_name, + settings: %{"test" => "test"}, + version: 1 + ) + + assert %FrontendSettingsProfile{profile_name: "profileA"} = + FrontendSettingsProfile.get_by_user_and_frontend_name_and_profile_name( + user, + frontend_name, + profile_name + ) + end + end +end diff --git a/test/pleroma/web/akkoma_api/frontend_settings_controller_test.exs b/test/pleroma/web/akkoma_api/frontend_settings_controller_test.exs new file mode 100644 index 000000000..4909ef3a7 --- /dev/null +++ b/test/pleroma/web/akkoma_api/frontend_settings_controller_test.exs @@ -0,0 +1,122 @@ +defmodule Pleroma.Web.AkkomaAPI.FrontendSettingsControllerTest do + use Pleroma.Web.ConnCase, async: true + + import Pleroma.Factory + alias Pleroma.Akkoma.FrontendSettingsProfile + + describe "GET /api/v1/akkoma/frontend_settings/:frontend_name" do + test "it returns a list of profiles" do + %{conn: conn, user: user} = oauth_access(["read"]) + + insert(:frontend_setting_profile, user: user, frontend_name: "test", profile_name: "test1") + insert(:frontend_setting_profile, user: user, frontend_name: "test", profile_name: "test2") + + response = + conn + |> get("/api/v1/akkoma/frontend_settings/test") + |> json_response_and_validate_schema(200) + + assert response == [ + %{"name" => "test1", "version" => 1}, + %{"name" => "test2", "version" => 1} + ] + end + end + + describe "GET /api/v1/akkoma/frontend_settings/:frontend_name/:profile_name" do + test "it returns 404 if not found" do + %{conn: conn} = oauth_access(["read"]) + + conn + |> get("/api/v1/akkoma/frontend_settings/unknown_frontend/unknown_profile") + |> json_response_and_validate_schema(404) + end + + test "it returns 200 if found" do + %{conn: conn, user: user} = oauth_access(["read"]) + + insert(:frontend_setting_profile, + user: user, + frontend_name: "test", + profile_name: "test1", + settings: %{"test" => "test"} + ) + + response = + conn + |> get("/api/v1/akkoma/frontend_settings/test/test1") + |> json_response_and_validate_schema(200) + + assert response == %{"settings" => %{"test" => "test"}, "version" => 1} + end + end + + describe "PUT /api/v1/akkoma/frontend_settings/:frontend_name/:profile_name" do + test "puts a config" do + %{conn: conn, user: user} = oauth_access(["write"]) + settings = %{"test" => "test2"} + + response = + conn + |> put_req_header("content-type", "application/json") + |> put("/api/v1/akkoma/frontend_settings/test/test1", %{ + "settings" => settings, + "version" => 1 + }) + |> json_response_and_validate_schema(200) + + assert response == settings + + assert %FrontendSettingsProfile{settings: ^settings} = + FrontendSettingsProfile.get_by_user_and_frontend_name_and_profile_name( + user, + "test", + "test1" + ) + end + + test "refuses to overwrite a newer config" do + %{conn: conn, user: user} = oauth_access(["write"]) + + insert(:frontend_setting_profile, + user: user, + frontend_name: "test", + profile_name: "test1", + settings: %{"test" => "test"}, + version: 2 + ) + + conn + |> put_req_header("content-type", "application/json") + |> put("/api/v1/akkoma/frontend_settings/test/test1", %{ + "settings" => %{"test" => "test2"}, + "version" => 1 + }) + |> json_response_and_validate_schema(422) + end + end + + describe "DELETE /api/v1/akkoma/frontend_settings/:frontend_name/:profile_name" do + test "deletes a config" do + %{conn: conn, user: user} = oauth_access(["write"]) + + insert(:frontend_setting_profile, + user: user, + frontend_name: "test", + profile_name: "test1", + settings: %{"test" => "test"}, + version: 2 + ) + + conn + |> delete("/api/v1/akkoma/frontend_settings/test/test1") + |> json_response_and_validate_schema(200) + + assert FrontendSettingsProfile.get_by_user_and_frontend_name_and_profile_name( + user, + "test", + "test1" + ) == nil + end + end +end diff --git a/test/support/factory.ex b/test/support/factory.ex index efcd8039e..54d385bc4 100644 --- a/test/support/factory.ex +++ b/test/support/factory.ex @@ -663,4 +663,15 @@ def announcement_factory(params \\ %{}) do |> Map.merge(params) |> Pleroma.Announcement.add_rendered_properties() end + + def frontend_setting_profile_factory(params \\ %{}) do + %Pleroma.Akkoma.FrontendSettingsProfile{ + user: build(:user), + frontend_name: "akkoma-fe", + profile_name: "default", + settings: %{"test" => "test"}, + version: 1 + } + |> Map.merge(params) + end end