From 19eb8264249ff41a516834fc0728cf652cafb6b9 Mon Sep 17 00:00:00 2001 From: foxing Date: Sat, 11 Mar 2023 03:26:48 +0000 Subject: [PATCH 01/35] Show bubble_timeline in the api if any instances are set in it, do not show if none are set --- lib/pleroma/web/mastodon_api/views/instance_view.ex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 2717da99d..d06c80a12 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -67,6 +67,9 @@ def features do "pleroma:api/v1/notifications:include_types_filter", "quote_posting", "editing", + if Config.get([:instance, :local_bubble], []) != [] do + "bubble_timeline" + end, if Config.get([:media_proxy, :enabled]) do "media_proxy" end, From 6c396fcab49f5de8b3f73a8353748d3606687966 Mon Sep 17 00:00:00 2001 From: ilja Date: Mon, 6 Mar 2023 12:04:35 +0100 Subject: [PATCH 02/35] Remove "default" image description When no image description is filled in, Pleroma allowed fallbacks. Those were (based on a setting) either the filename, or a fixed description. Neither are good options for image descriptions imo, so here we remove this. Note that there's two tests removed who supposedly tested something else. But examining closer, they didn't seem to test what they claimed to test, so I removed them rather than try to "fix" them. --- CHANGELOG.md | 1 + config/config.exs | 1 - config/test.exs | 3 +-- docs/docs/configuration/cheatsheet.md | 1 - lib/pleroma/upload.ex | 11 +--------- test/pleroma/upload_test.exs | 17 ++------------ .../web/activity_pub/activity_pub_test.exs | 22 ------------------- 7 files changed, 5 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 859a09e7d..b451f297f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Removed - Possibility of using the `style` parameter on `span` elements. This will break certain MFM parameters. +- Option for "default" image description. ## 2023.02 diff --git a/config/config.exs b/config/config.exs index 5eaa8ce76..4d8fd52c4 100644 --- a/config/config.exs +++ b/config/config.exs @@ -65,7 +65,6 @@ link_name: false, proxy_remote: false, filename_display_max_length: 30, - default_description: nil, base_url: nil config :pleroma, Pleroma.Uploaders.Local, uploads: "uploads" diff --git a/config/test.exs b/config/test.exs index 3056dbd03..4448eeb73 100644 --- a/config/test.exs +++ b/config/test.exs @@ -23,8 +23,7 @@ config :pleroma, Pleroma.Upload, filters: [], - link_name: false, - default_description: :filename + link_name: false config :pleroma, Pleroma.Uploaders.Local, uploads: "test/uploads" diff --git a/docs/docs/configuration/cheatsheet.md b/docs/docs/configuration/cheatsheet.md index 4e84b9a44..1c4d9ec5d 100644 --- a/docs/docs/configuration/cheatsheet.md +++ b/docs/docs/configuration/cheatsheet.md @@ -562,7 +562,6 @@ the source code is here: [kocaptcha](https://github.com/koto-bank/kocaptcha). Th * `proxy_remote`: If you're using a remote uploader, Akkoma will proxy media requests instead of redirecting to it. * `proxy_opts`: Proxy options, see `Pleroma.ReverseProxy` documentation. * `filename_display_max_length`: Set max length of a filename to display. 0 = no limit. Default: 30. -* `default_description`: Sets which default description an image has if none is set explicitly. Options: nil (default) - Don't set a default, :filename - use the filename of the file, a string (e.g. "attachment") - Use this string !!! warning `strip_exif` has been replaced by `Pleroma.Upload.Filter.Mogrify`. diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex index 3b5419db7..2f65540be 100644 --- a/lib/pleroma/upload.ex +++ b/lib/pleroma/upload.ex @@ -65,15 +65,6 @@ defmodule Pleroma.Upload do } defstruct [:id, :name, :tempfile, :content_type, :width, :height, :blurhash, :path] - defp get_description(opts, upload) do - case {opts[:description], Pleroma.Config.get([Pleroma.Upload, :default_description])} do - {description, _} when is_binary(description) -> description - {_, :filename} -> upload.name - {_, str} when is_binary(str) -> str - _ -> "" - end - end - @spec store(source, options :: [option()]) :: {:ok, Map.t()} | {:error, any()} @doc "Store a file. If using a `Plug.Upload{}` as the source, be sure to use `Majic.Plug` to ensure its content_type and filename is correct." def store(upload, opts \\ []) do @@ -82,7 +73,7 @@ def store(upload, opts \\ []) do with {:ok, upload} <- prepare_upload(upload, opts), upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"}, {:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload), - description = get_description(opts, upload), + description = Map.get(opts, :description) || "", {_, true} <- {:description_limit, String.length(description) <= Pleroma.Config.get([:instance, :description_limit])}, diff --git a/test/pleroma/upload_test.exs b/test/pleroma/upload_test.exs index 8f242630f..ad6065b43 100644 --- a/test/pleroma/upload_test.exs +++ b/test/pleroma/upload_test.exs @@ -54,7 +54,7 @@ test "it returns file" do assert result == %{ "id" => result["id"], - "name" => "image.jpg", + "name" => "", "type" => "Document", "mediaType" => "image/jpeg", "url" => [ @@ -154,19 +154,6 @@ test "copies the file to the configured folder with deduping" do "e30397b58d226d6583ab5b8b3c5defb0c682bda5c31ef07a9f57c1c4986e3781.jpg" end - test "copies the file to the configured folder without deduping" do - File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg") - - file = %Plug.Upload{ - content_type: "image/jpeg", - path: Path.absname("test/fixtures/image_tmp.jpg"), - filename: "an [image.jpg" - } - - {:ok, data} = Upload.store(file) - assert data["name"] == "an [image.jpg" - end - test "fixes incorrect content type when base64 is given" do params = %{ img: "data:image/png;base64,#{Base.encode64(File.read!("test/fixtures/image.jpg"))}" @@ -184,7 +171,7 @@ test "adds extension when base64 is given" do } {:ok, data} = Upload.store(params) - assert String.ends_with?(data["name"], ".jpg") + assert String.ends_with?(List.first(data["url"])["href"], ".jpg") end test "copies the file to the configured folder with anonymizing filename" do diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index 17c52fc91..20435d149 100644 --- a/test/pleroma/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -1308,28 +1308,6 @@ test "sets a description if given", %{test_file: file} do assert object.data["name"] == "a cool file" end - test "it sets the default description depending on the configuration", %{test_file: file} do - clear_config([Pleroma.Upload, :default_description]) - - clear_config([Pleroma.Upload, :default_description], nil) - {:ok, %Object{} = object} = ActivityPub.upload(file) - assert object.data["name"] == "" - - clear_config([Pleroma.Upload, :default_description], :filename) - {:ok, %Object{} = object} = ActivityPub.upload(file) - assert object.data["name"] == "an_image.jpg" - - clear_config([Pleroma.Upload, :default_description], "unnamed attachment") - {:ok, %Object{} = object} = ActivityPub.upload(file) - assert object.data["name"] == "unnamed attachment" - end - - test "copies the file to the configured folder", %{test_file: file} do - clear_config([Pleroma.Upload, :default_description], :filename) - {:ok, %Object{} = object} = ActivityPub.upload(file) - assert object.data["name"] == "an_image.jpg" - end - test "works with base64 encoded images" do file = %{ img: data_uri() From 0c77be9308102cb2e4710fbad02035e9dc7125c3 Mon Sep 17 00:00:00 2001 From: flisk Date: Sun, 12 Mar 2023 18:14:05 +0100 Subject: [PATCH 03/35] don't crash on malformed avatar and banner values weird values in href will cause base64 encoding to fail later down the line, so let's make sure the value we're passing on is somewhat sane, or at the very least a binary this fixes #482 --- lib/pleroma/user.ex | 24 ++++++++++++------------ test/pleroma/user_test.exs | 10 ++++++++++ 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index f94202af5..480521984 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -366,21 +366,21 @@ def invisible?(%User{invisible: true}), do: true def invisible?(_), do: false def avatar_url(user, options \\ []) do - case user.avatar do - %{"url" => [%{"href" => href} | _]} -> - href - - _ -> - unless options[:no_default] do - Config.get([:assets, :default_user_avatar], "#{Endpoint.url()}/images/avi.png") - end - end + default = Config.get([:assets, :default_user_avatar], "#{Endpoint.url()}/images/avi.png") + do_optional_url(user.avatar, default, options) end def banner_url(user, options \\ []) do - case user.banner do - %{"url" => [%{"href" => href} | _]} -> href - _ -> !options[:no_default] && "#{Endpoint.url()}/images/banner.png" + do_optional_url(user.banner, "#{Endpoint.url()}/images/banner.png", options) + end + + defp do_optional_url(field, default, options \\ []) do + case field do + %{"url" => [%{"href" => href} | _]} when is_binary(href) -> + href + + _ -> + unless options[:no_default], do: default end end diff --git a/test/pleroma/user_test.exs b/test/pleroma/user_test.exs index a590946c2..12ccc6bf4 100644 --- a/test/pleroma/user_test.exs +++ b/test/pleroma/user_test.exs @@ -2509,6 +2509,16 @@ test "avatar fallback" do assert User.avatar_url(user, no_default: true) == nil end + test "avatar object with nil in href" do + user = insert(:user, avatar: %{"url" => [%{"href" => nil}]}) + assert User.avatar_url(user) != nil + end + + test "banner object with nil in href" do + user = insert(:user, banner: %{"url" => [%{"href" => nil}]}) + assert User.banner_url(user) != nil + end + test "get_host/1" do user = insert(:user, ap_id: "https://lain.com/users/lain", nickname: "lain") assert User.get_host(user) == "lain.com" From 3f76de76dac266ea2909dbd1dc88a9533d972952 Mon Sep 17 00:00:00 2001 From: foxing Date: Sun, 12 Mar 2023 19:13:56 +0000 Subject: [PATCH 04/35] Apply Patch --- lib/pleroma/web/activity_pub/activity_pub.ex | 11 +++++++- .../web/activity_pub/activity_pub_test.exs | 8 ++++++ .../controllers/media_controller_test.exs | 17 +++++++++++ .../mastodon_api/update_credentials_test.exs | 28 +++++++++++++++++++ 4 files changed, 63 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 8e55df0d8..649bf9095 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1502,13 +1502,22 @@ def fetch_activities_bounded( @spec upload(Upload.source(), keyword()) :: {:ok, Object.t()} | {:error, any()} def upload(file, opts \\ []) do - with {:ok, data} <- Upload.store(file, opts) do + with {:ok, data} <- Upload.store(sanitize_upload_file(file), opts) do obj_data = Maps.put_if_present(data, "actor", opts[:actor]) Repo.insert(%Object{data: obj_data}) end end + defp sanitize_upload_file(%Plug.Upload{filename: filename} = upload) when is_binary(filename) do + %Plug.Upload{ + upload + | filename: Path.basename(filename) + } + end + + defp sanitize_upload_file(upload), do: upload + @spec get_actor_url(any()) :: binary() | nil defp get_actor_url(url) when is_binary(url), do: url defp get_actor_url(%{"href" => href}) when is_binary(href), do: href diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index 17c52fc91..e95e4490a 100644 --- a/test/pleroma/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -1303,6 +1303,14 @@ test "returns reblogs for users for whom reblogs have not been muted" do %{test_file: test_file} end + test "strips / from filename", %{test_file: file} do + file = %Plug.Upload{file | filename: "../../../../../nested/bad.jpg"} + {:ok, %Object{} = object} = ActivityPub.upload(file) + [%{"href" => href}] = object.data["url"] + assert Regex.match?(~r"/bad.jpg$", href) + refute Regex.match?(~r"/nested/", href) + end + test "sets a description if given", %{test_file: file} do {:ok, %Object{} = object} = ActivityPub.upload(file, description: "a cool file") assert object.data["name"] == "a cool file" diff --git a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs index 50b9febea..7ff8cff6b 100644 --- a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs @@ -124,6 +124,23 @@ test "/api/v2/media, upload_limit", %{conn: conn, user: user} do assert :ok == File.rm(Path.absname("test/tmp/large_binary.data")) end + + test "Do not allow nested filename", %{conn: conn, image: image} do + image = %Plug.Upload{ + image + | filename: "../../../../../nested/file.jpg" + } + + desc = "Description of the image" + + media = + conn + |> put_req_header("content-type", "multipart/form-data") + |> post("/api/v1/media", %{"file" => image, "description" => desc}) + |> json_response_and_validate_schema(:ok) + + refute Regex.match?(~r"/nested/", media["url"]) + end end describe "Update media description" do diff --git a/test/pleroma/web/mastodon_api/update_credentials_test.exs b/test/pleroma/web/mastodon_api/update_credentials_test.exs index e9b8825bf..4aec31eac 100644 --- a/test/pleroma/web/mastodon_api/update_credentials_test.exs +++ b/test/pleroma/web/mastodon_api/update_credentials_test.exs @@ -396,6 +396,34 @@ test "updates the user's background, upload_limit, returns a HTTP 413", %{ assert :ok == File.rm(Path.absname("test/tmp/large_binary.data")) end + test "Strip / from upload files", %{user: user, conn: conn} do + new_image = %Plug.Upload{ + content_type: "image/jpeg", + path: Path.absname("test/fixtures/image.jpg"), + filename: "../../../../nested/an_image.jpg" + } + + assert user.avatar == %{} + + res = + patch(conn, "/api/v1/accounts/update_credentials", %{ + "avatar" => new_image, + "header" => new_image, + "pleroma_background_image" => new_image + }) + + assert user_response = json_response_and_validate_schema(res, 200) + assert user_response["avatar"] + assert user_response["header"] + assert user_response["pleroma"]["background_image"] + refute Regex.match?(~r"/nested/", user_response["avatar"]) + refute Regex.match?(~r"/nested/", user_response["header"]) + refute Regex.match?(~r"/nested/", user_response["pleroma"]["background_image"]) + + user = User.get_by_id(user.id) + refute user.avatar == %{} + end + test "requires 'write:accounts' permission" do token1 = insert(:oauth_token, scopes: ["read"]) token2 = insert(:oauth_token, scopes: ["write", "follow"]) From 3d964a997033ea65e97cbd08b6549f9cdf445fc9 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sun, 12 Mar 2023 23:24:07 +0000 Subject: [PATCH 05/35] Add frontend preference route --- config/config.exs | 3 + .../frontend_settings_controller.ex | 28 +++++++++ .../operations/frontend_settings_operation.ex | 59 +++++++++++++++++-- lib/pleroma/web/router.ex | 16 +++++ 4 files changed, 102 insertions(+), 4 deletions(-) diff --git a/config/config.exs b/config/config.exs index 5eaa8ce76..8ed3c9cd9 100644 --- a/config/config.exs +++ b/config/config.exs @@ -745,6 +745,9 @@ primary: %{"name" => "pleroma-fe", "ref" => "stable"}, admin: %{"name" => "admin-fe", "ref" => "stable"}, mastodon: %{"name" => "mastodon-fe", "ref" => "akkoma"}, + pickable: [ + "pleroma-fe/stable" + ], swagger: %{ "name" => "swagger-ui", "ref" => "stable", diff --git a/lib/pleroma/web/akkoma_api/controllers/frontend_settings_controller.ex b/lib/pleroma/web/akkoma_api/controllers/frontend_settings_controller.ex index c13ff9096..307d35643 100644 --- a/lib/pleroma/web/akkoma_api/controllers/frontend_settings_controller.ex +++ b/lib/pleroma/web/akkoma_api/controllers/frontend_settings_controller.ex @@ -5,6 +5,16 @@ defmodule Pleroma.Web.AkkomaAPI.FrontendSettingsController do alias Pleroma.Akkoma.FrontendSettingsProfile @unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []} + + plug( + OAuthScopesPlug, + @unauthenticated_access + when action in [ + :available_frontends, + :update_preferred_frontend + ] + ) + plug( OAuthScopesPlug, %{@unauthenticated_access | scopes: ["read:accounts"]} @@ -93,4 +103,22 @@ def update_profile(%{body_params: %{settings: settings, version: version}} = con |> json(profile.settings) end end + + @doc "GET /api/v1/akkoma/preferred_frontend/available" + def available_frontends(conn, _params) do + available = Pleroma.Config.get([:frontends, :pickable]) + + conn + |> json(available) + end + + @doc "PUT /api/v1/akkoma/preferred_frontend" + def update_preferred_frontend( + %{body_params: %{frontend_name: preferred_frontend}} = conn, + _params + ) do + conn + |> put_resp_cookie("preferred_frontend", preferred_frontend) + |> json(%{frontend_name: preferred_frontend}) + 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 index 40e81ad55..867a751b3 100644 --- a/lib/pleroma/web/api_spec/operations/frontend_settings_operation.ex +++ b/lib/pleroma/web/api_spec/operations/frontend_settings_operation.ex @@ -12,7 +12,7 @@ def open_api_operation(action) do @spec list_profiles_operation() :: Operation.t() def list_profiles_operation() do %Operation{ - tags: ["Retrieve frontend setting profiles"], + tags: ["Frontends"], summary: "Frontend Settings Profiles", description: "List frontend setting profiles", operationId: "AkkomaAPI.FrontendSettingsController.list_profiles", @@ -37,7 +37,7 @@ def list_profiles_operation() do @spec get_profile_operation() :: Operation.t() def get_profile_operation() do %Operation{ - tags: ["Retrieve frontend setting profile"], + tags: ["Frontends"], summary: "Frontend Settings Profile", description: "Get frontend setting profile", operationId: "AkkomaAPI.FrontendSettingsController.get_profile", @@ -60,7 +60,7 @@ def get_profile_operation() do @spec delete_profile_operation() :: Operation.t() def delete_profile_operation() do %Operation{ - tags: ["Delete frontend setting profile"], + tags: ["Frontends"], summary: "Delete frontend Settings Profile", description: "Delete frontend setting profile", operationId: "AkkomaAPI.FrontendSettingsController.delete_profile", @@ -76,7 +76,7 @@ def delete_profile_operation() do @spec update_profile_operation() :: Operation.t() def update_profile_operation() do %Operation{ - tags: ["Update frontend setting profile"], + tags: ["Frontends"], summary: "Frontend Settings Profile", description: "Update frontend setting profile", operationId: "AkkomaAPI.FrontendSettingsController.update_profile_operation", @@ -90,6 +90,57 @@ def update_profile_operation() do } end + def available_frontends_operation() do + %Operation{ + tags: ["Frontends"], + summary: "Frontend Settings Profiles", + description: "List frontend setting profiles", + operationId: "AkkomaAPI.FrontendSettingsController.available_frontends", + responses: %{ + 200 => + Operation.response("Frontends", "application/json", %Schema{ + type: :array, + items: %Schema{ + type: :string + } + }) + } + } + end + + def update_preferred_frontend_operation() do + %Operation{ + tags: ["Frontends"], + summary: "Frontend Settings Profiles", + description: "List frontend setting profiles", + operationId: "AkkomaAPI.FrontendSettingsController.available_frontends", + requestBody: + request_body( + "Frontend", + %Schema{ + type: :object, + required: [:frontend_name], + properties: %{ + frontend_name: %Schema{ + type: :string, + description: "Frontend name" + } + } + }, + required: true + ), + responses: %{ + 200 => + Operation.response("Frontends", "application/json", %Schema{ + type: :array, + items: %Schema{ + type: :string + } + }) + } + } + end + def frontend_name_param do Operation.parameter(:frontend_name, :path, :string, "Frontend name", example: "pleroma-fe", diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index faaf3d679..3db8ddab7 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -466,6 +466,22 @@ defmodule Pleroma.Web.Router do put("/statuses/:id/emoji_reactions/:emoji", EmojiReactionController, :create) end + scope "/api/v1/akkoma", Pleroma.Web.AkkomaAPI do + pipe_through(:api) + + get( + "/api/v1/akkoma/preferred_frontend/available", + FrontendSettingsController, + :available_frontends + ) + + put( + "/api/v1/akkoma/preferred_frontend", + FrontendSettingsController, + :update_preferred_frontend + ) + end + scope "/api/v1/akkoma", Pleroma.Web.AkkomaAPI do pipe_through(:authenticated_api) get("/metrics", MetricsController, :show) From 643b8c5f152bb71ef11074edd4a78f1405f360da Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sun, 12 Mar 2023 23:59:10 +0000 Subject: [PATCH 06/35] ensure we send the right files for preferred fe --- .../web/fallback/redirect_controller.ex | 11 ++--- lib/pleroma/web/plugs/frontend_static.ex | 45 ++++++++++++++++--- lib/pleroma/web/plugs/instance_static.ex | 4 +- 3 files changed, 48 insertions(+), 12 deletions(-) diff --git a/lib/pleroma/web/fallback/redirect_controller.ex b/lib/pleroma/web/fallback/redirect_controller.ex index 49f659cf0..2e57fa426 100644 --- a/lib/pleroma/web/fallback/redirect_controller.ex +++ b/lib/pleroma/web/fallback/redirect_controller.ex @@ -20,7 +20,7 @@ def api_not_implemented(conn, _params) do def redirector(conn, _params, code \\ 200) do conn |> put_resp_content_type("text/html") - |> send_file(code, index_file_path()) + |> send_file(code, index_file_path(conn)) end def redirector_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id} = params) do @@ -33,7 +33,7 @@ def redirector_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id} end def redirector_with_meta(conn, params) do - {:ok, index_content} = File.read(index_file_path()) + {:ok, index_content} = File.read(index_file_path(conn)) tags = build_tags(conn, params) preloads = preload_data(conn, params) @@ -53,7 +53,7 @@ def redirector_with_preload(conn, %{"path" => ["pleroma", "admin"]}) do end def redirector_with_preload(conn, params) do - {:ok, index_content} = File.read(index_file_path()) + {:ok, index_content} = File.read(index_file_path(conn)) preloads = preload_data(conn, params) tags = Metadata.build_static_tags(params) title = "#{Pleroma.Config.get([:instance, :name])}" @@ -77,8 +77,9 @@ def empty(conn, _params) do |> text("") end - defp index_file_path do - Pleroma.Web.Plugs.InstanceStatic.file_path("index.html") + defp index_file_path(conn) do + frontend_type = Pleroma.Web.Plugs.FrontendStatic.preferred_or_fallback(conn, :primary) + Pleroma.Web.Plugs.InstanceStatic.file_path("index.html", frontend_type) end defp build_tags(conn, params) do diff --git a/lib/pleroma/web/plugs/frontend_static.ex b/lib/pleroma/web/plugs/frontend_static.ex index 40f51e149..62283353e 100644 --- a/lib/pleroma/web/plugs/frontend_static.ex +++ b/lib/pleroma/web/plugs/frontend_static.ex @@ -5,17 +5,23 @@ defmodule Pleroma.Web.Plugs.FrontendStatic do require Pleroma.Constants + @frontend_cookie_name "preferred_frontend" + @moduledoc """ This is a shim to call `Plug.Static` but with runtime `from` configuration`. It dispatches to the different frontends. """ @behaviour Plug - def file_path(path, frontend_type \\ :primary) do - if configuration = Pleroma.Config.get([:frontends, frontend_type]) do - instance_static_path = Pleroma.Config.get([:instance, :static_dir], "instance/static") + defp instance_static_path do + Pleroma.Config.get([:instance, :static_dir], "instance/static") + end + def file_path(path, frontend_type \\ :primary) + + def file_path(path, frontend_type) when is_atom(frontend_type) do + if configuration = Pleroma.Config.get([:frontends, frontend_type]) do Path.join([ - instance_static_path, + instance_static_path(), "frontends", configuration["name"], configuration["ref"], @@ -26,6 +32,15 @@ def file_path(path, frontend_type \\ :primary) do end end + def file_path(path, frontend_type) when is_binary(frontend_type) do + Path.join([ + instance_static_path(), + "frontends", + frontend_type, + path + ]) + end + def init(opts) do opts |> Keyword.put(:from, "__unconfigured_frontend_static_plug") @@ -38,7 +53,8 @@ def call(conn, opts) do with false <- api_route?(conn.path_info), false <- invalid_path?(conn.path_info), true <- enabled?(opts[:if]), - frontend_type <- Map.get(opts, :frontend_type, :primary), + fallback_frontend_type <- Map.get(opts, :frontend_type, :primary), + frontend_type <- preferred_or_fallback(conn, fallback_frontend_type), path when not is_nil(path) <- file_path("", frontend_type) do call_static(conn, opts, path) else @@ -47,6 +63,24 @@ def call(conn, opts) do end end + def preferred_frontend(conn) do + %{req_cookies: cookies} = + conn + |> Plug.Conn.fetch_cookies() + + Map.get(cookies, @frontend_cookie_name) + end + + def preferred_or_fallback(conn, fallback) do + case preferred_frontend(conn) do + nil -> + fallback + + frontend -> + frontend + end + end + defp enabled?(if_opt) when is_function(if_opt), do: if_opt.() defp enabled?(true), do: true defp enabled?(_), do: false @@ -68,6 +102,7 @@ defp api_route?([h | t]) do defp call_static(conn, opts, from) do opts = Map.put(opts, :from, from) + IO.inspect(opts, label: "opts") Plug.Static.call(conn, opts) end end diff --git a/lib/pleroma/web/plugs/instance_static.ex b/lib/pleroma/web/plugs/instance_static.ex index 723b25679..5f9a6ee83 100644 --- a/lib/pleroma/web/plugs/instance_static.ex +++ b/lib/pleroma/web/plugs/instance_static.ex @@ -12,11 +12,11 @@ defmodule Pleroma.Web.Plugs.InstanceStatic do """ @behaviour Plug - def file_path(path) do + def file_path(path, frontend_type \\ :primary) do instance_path = Path.join(Pleroma.Config.get([:instance, :static_dir], "instance/static/"), path) - frontend_path = Pleroma.Web.Plugs.FrontendStatic.file_path(path, :primary) + frontend_path = Pleroma.Web.Plugs.FrontendStatic.file_path(path, frontend_type) (File.exists?(instance_path) && instance_path) || (frontend_path && File.exists?(frontend_path) && frontend_path) || From ba635e97c8ddcf92946e3d221dbf813189d21ab8 Mon Sep 17 00:00:00 2001 From: foxing Date: Mon, 13 Mar 2023 03:40:20 +0000 Subject: [PATCH 07/35] Use enum empty instead --- lib/pleroma/web/mastodon_api/views/instance_view.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index d06c80a12..2b5354873 100644 --- a/lib/pleroma/web/mastodon_api/views/instance_view.ex +++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex @@ -67,7 +67,7 @@ def features do "pleroma:api/v1/notifications:include_types_filter", "quote_posting", "editing", - if Config.get([:instance, :local_bubble], []) != [] do + if !Enum.empty?(Config.get([:instance, :local_bubble], [])) do "bubble_timeline" end, if Config.get([:media_proxy, :enabled]) do From 9464d505628d9f9dc20e54055d8853bea1d6722b Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 15 Mar 2023 22:13:18 +0000 Subject: [PATCH 08/35] Add publicTimelineVisibility to nodeinfo --- .../web/nodeinfo/nodeinfo_controller.ex | 6 +++- test/pleroma/web/node_info_test.exs | 30 +++++++++++++++++++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index a0dee7c6b..9a76574d5 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -89,7 +89,11 @@ def raw_nodeinfo do features: features, restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]), skipThreadContainment: Config.get([:instance, :skip_thread_containment], false), - localBubbleInstances: Config.get([:instance, :local_bubble], []) + localBubbleInstances: Config.get([:instance, :local_bubble], []), + publicTimelineVisibility: %{ + federated: !Config.restrict_unauthenticated_access?(:timelines, :federated), + local: !Config.restrict_unauthenticated_access?(:timelines, :local) + } } } end diff --git a/test/pleroma/web/node_info_test.exs b/test/pleroma/web/node_info_test.exs index 05a078266..5f001be85 100644 --- a/test/pleroma/web/node_info_test.exs +++ b/test/pleroma/web/node_info_test.exs @@ -292,4 +292,34 @@ test "shows extra information in the mrf_simple_info field for relevant entries" assert response["metadata"]["federation"]["mrf_simple_info"] == expected_config end end + + describe "public timeline visibility" do + test "shows public timeline visibility", %{conn: conn} do + clear_config([:restrict_unauthenticated, :timelines], %{local: false, federated: false}) + response = + conn + |> get("/nodeinfo/2.1.json") + |> json_response(:ok) + + assert response["metadata"]["publicTimelineVisibility"]["local"] == true + assert response["metadata"]["publicTimelineVisibility"]["federated"] == true + + clear_config([:restrict_unauthenticated, :timelines], %{local: true, federated: false}) + response = + conn + |> get("/nodeinfo/2.1.json") + |> json_response(:ok) + assert response["metadata"]["publicTimelineVisibility"]["local"] == false + assert response["metadata"]["publicTimelineVisibility"]["federated"] == true + + clear_config([:restrict_unauthenticated, :timelines], %{local: false, federated: true}) + response = + conn + |> get("/nodeinfo/2.1.json") + |> json_response(:ok) + + assert response["metadata"]["publicTimelineVisibility"]["local"] == true + assert response["metadata"]["publicTimelineVisibility"]["federated"] == false + end + end end From 2c9e02429aaab2b9080b75a193142a4dd191631e Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 15 Mar 2023 22:19:52 +0000 Subject: [PATCH 09/35] mix format --- test/pleroma/web/node_info_test.exs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/pleroma/web/node_info_test.exs b/test/pleroma/web/node_info_test.exs index 5f001be85..ff14db460 100644 --- a/test/pleroma/web/node_info_test.exs +++ b/test/pleroma/web/node_info_test.exs @@ -296,6 +296,7 @@ test "shows extra information in the mrf_simple_info field for relevant entries" describe "public timeline visibility" do test "shows public timeline visibility", %{conn: conn} do clear_config([:restrict_unauthenticated, :timelines], %{local: false, federated: false}) + response = conn |> get("/nodeinfo/2.1.json") @@ -305,14 +306,17 @@ test "shows public timeline visibility", %{conn: conn} do assert response["metadata"]["publicTimelineVisibility"]["federated"] == true clear_config([:restrict_unauthenticated, :timelines], %{local: true, federated: false}) + response = conn |> get("/nodeinfo/2.1.json") |> json_response(:ok) + assert response["metadata"]["publicTimelineVisibility"]["local"] == false assert response["metadata"]["publicTimelineVisibility"]["federated"] == true clear_config([:restrict_unauthenticated, :timelines], %{local: false, federated: true}) + response = conn |> get("/nodeinfo/2.1.json") From 86a5cf3c822e8566925564974d3849039e3b392f Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 15 Mar 2023 22:20:32 +0000 Subject: [PATCH 10/35] Changelog entry --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d620c4f3b..c3b6f775a 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 + +## Added +- Nodeinfo keys for unauthenticated timeline visibility + ## 2023.03 ## Fixed From fe7045632b77576b727281b0fa7a8d87b9b9b44a Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 15 Mar 2023 22:59:58 +0000 Subject: [PATCH 11/35] also put publicVisibility in preloaded nodeinfo --- lib/pleroma/web/nodeinfo/nodeinfo.ex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/nodeinfo/nodeinfo.ex b/lib/pleroma/web/nodeinfo/nodeinfo.ex index bf0d65f45..14e39e6b3 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo.ex @@ -71,7 +71,11 @@ def get_nodeinfo("2.0") do restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]), skipThreadContainment: Config.get([:instance, :skip_thread_containment], false), privilegedStaff: Config.get([:instance, :privileged_staff]), - localBubbleInstances: Config.get([:instance, :local_bubble], []) + localBubbleInstances: Config.get([:instance, :local_bubble], []), + publicTimelineVisibility: %{ + federated: !Config.restrict_unauthenticated_access?(:timelines, :federated), + local: !Config.restrict_unauthenticated_access?(:timelines, :local) + } } } end From 4a5164be9301deb04506bda8dfee5a5bcf309960 Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Thu, 16 Mar 2023 12:53:38 -0400 Subject: [PATCH 12/35] Update required elixir version in mix.exs to 1.14 --- mix.exs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mix.exs b/mix.exs index 098ea15b0..aa1dde667 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ def project do [ app: :pleroma, version: version("3.7.1"), - elixir: "~> 1.12", + elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix] ++ Mix.compilers(), elixirc_options: [warnings_as_errors: warnings_as_errors()], From f22bba6359d14dcd7572e504e1541e28b240347a Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Thu, 16 Mar 2023 12:54:15 -0400 Subject: [PATCH 13/35] Update elixir version in elixir_buildpack.config --- elixir_buildpack.config | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/elixir_buildpack.config b/elixir_buildpack.config index 946408c12..ee9e051a6 100644 --- a/elixir_buildpack.config +++ b/elixir_buildpack.config @@ -1,2 +1,2 @@ -elixir_version=1.9.4 -erlang_version=22.3.4.1 +elixir_version=1.14.3 +erlang_version=25.3 From 3c30666d3f422e6b94e2b81744c6b305f4699aa3 Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Thu, 16 Mar 2023 12:54:38 -0400 Subject: [PATCH 14/35] Update elixir and erlang versions in docs --- docs/docs/installation/generic_dependencies.include | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/installation/generic_dependencies.include b/docs/docs/installation/generic_dependencies.include index 68c61129a..d8cf9f9da 100644 --- a/docs/docs/installation/generic_dependencies.include +++ b/docs/docs/installation/generic_dependencies.include @@ -1,8 +1,8 @@ ## Required dependencies * PostgreSQL 9.6+ -* Elixir 1.12+ (1.13+ recommended) -* Erlang OTP 22.2+ +* Elixir 1.14+ +* Erlang OTP 24+ * git * file / libmagic * gcc (clang might also work) From 63870c2c170e95bb8377805957cffe8ef020d9b1 Mon Sep 17 00:00:00 2001 From: Francis Dinh Date: Thu, 16 Mar 2023 12:55:04 -0400 Subject: [PATCH 15/35] Update base image in Dockerfile to newer elixir version --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 0551a4c9e..c6506c48c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM hexpm/elixir:1.13.4-erlang-24.3.4.5-alpine-3.15.6 +FROM hexpm/elixir:1.14.3-erlang-25.3-alpine-3.17.2 ENV MIX_ENV=prod ENV ERL_EPMD_ADDRESS=127.0.0.1 From dd44387f1addcad910dae3a148b55e53157004c0 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Fri, 17 Mar 2023 15:33:28 +0000 Subject: [PATCH 16/35] Add timeline visibility options --- CHANGELOG.md | 2 + config/config.exs | 5 +- config/description.exs | 6 ++ lib/pleroma/instances/instance.ex | 2 +- .../api_spec/operations/timeline_operation.ex | 3 +- .../controllers/timeline_controller.ex | 46 +++++---- lib/pleroma/web/nodeinfo/nodeinfo.ex | 10 +- .../web/nodeinfo/nodeinfo_controller.ex | 95 +------------------ lib/pleroma/web/router.ex | 2 +- .../controllers/timeline_controller_test.exs | 41 +++++++- .../mastodon_api/views/account_view_test.exs | 4 +- 11 files changed, 90 insertions(+), 126 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c3b6f775a..1bf6253af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ## Added - Nodeinfo keys for unauthenticated timeline visibility +- Option to disable federated timeline +- Option to make the bubble timeline publicly accessible ## 2023.03 diff --git a/config/config.exs b/config/config.exs index 5eaa8ce76..e216caf9d 100644 --- a/config/config.exs +++ b/config/config.exs @@ -261,7 +261,8 @@ privileged_staff: false, local_bubble: [], max_frontend_settings_json_chars: 100_000, - export_prometheus_metrics: true + export_prometheus_metrics: true, + federated_timeline_available: true config :pleroma, :welcome, direct_message: [ @@ -810,7 +811,7 @@ private_instance? = :if_instance_is_private config :pleroma, :restrict_unauthenticated, - timelines: %{local: private_instance?, federated: private_instance?}, + timelines: %{local: private_instance?, federated: private_instance?, bubble: true}, profiles: %{local: private_instance?, remote: private_instance?}, activities: %{local: private_instance?, remote: private_instance?} diff --git a/config/description.exs b/config/description.exs index 2a2d70a7b..f8496760f 100644 --- a/config/description.exs +++ b/config/description.exs @@ -969,6 +969,12 @@ key: :export_prometheus_metrics, type: :boolean, description: "Enable prometheus metrics (at /api/v1/akkoma/metrics)" + }, + %{ + key: :federated_timeline_available, + type: :boolean, + description: + "Let people view the 'firehose' feed of all public statuses from all instances." } ] }, diff --git a/lib/pleroma/instances/instance.ex b/lib/pleroma/instances/instance.ex index 6ddfa5042..5c70748b6 100644 --- a/lib/pleroma/instances/instance.ex +++ b/lib/pleroma/instances/instance.ex @@ -162,7 +162,7 @@ def local do %Instance{ host: Pleroma.Web.Endpoint.host(), favicon: Pleroma.Web.Endpoint.url() <> "/favicon.png", - nodeinfo: Pleroma.Web.Nodeinfo.NodeinfoController.raw_nodeinfo() + nodeinfo: Pleroma.Web.Nodeinfo.Nodeinfo.get_nodeinfo("2.1") } end diff --git a/lib/pleroma/web/api_spec/operations/timeline_operation.ex b/lib/pleroma/web/api_spec/operations/timeline_operation.ex index 3eb6f700b..45c97cab6 100644 --- a/lib/pleroma/web/api_spec/operations/timeline_operation.ex +++ b/lib/pleroma/web/api_spec/operations/timeline_operation.ex @@ -70,7 +70,8 @@ def public_operation do operationId: "TimelineController.public", responses: %{ 200 => Operation.response("Array of Status", "application/json", array_of_statuses()), - 401 => Operation.response("Error", "application/json", ApiError) + 401 => Operation.response("Error", "application/json", ApiError), + 404 => Operation.response("Error", "application/json", ApiError) } } end diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index 2d0e36420..c9960187d 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -16,7 +16,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do alias Pleroma.Web.Plugs.RateLimiter plug(Pleroma.Web.ApiSpec.CastAndValidate) - plug(:skip_public_check when action in [:public, :hashtag]) + plug(:skip_public_check when action in [:public, :hashtag, :bubble]) # TODO: Replace with a macro when there is a Phoenix release with the following commit in it: # https://github.com/phoenixframework/phoenix/commit/2e8c63c01fec4dde5467dbbbf9705ff9e780735e @@ -28,13 +28,13 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do plug(RateLimiter, [name: :timeline, bucket_name: :list_timeline] when action == :list) plug(RateLimiter, [name: :timeline, bucket_name: :bubble_timeline] when action == :bubble) - plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct, :bubble]) + plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action in [:home, :direct]) plug(OAuthScopesPlug, %{scopes: ["read:lists"]} when action == :list) plug( OAuthScopesPlug, %{scopes: ["read:statuses"], fallback: :proceed_unauthenticated} - when action in [:public, :hashtag] + when action in [:public, :hashtag, :bubble] ) defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TimelineOperation @@ -96,21 +96,19 @@ def direct(%{assigns: %{user: user}} = conn, params) do ) end - defp restrict_unauthenticated?(true = _local_only) do - Config.restrict_unauthenticated_access?(:timelines, :local) - end - - defp restrict_unauthenticated?(_) do - Config.restrict_unauthenticated_access?(:timelines, :federated) + defp restrict_unauthenticated?(type) do + Config.restrict_unauthenticated_access?(:timelines, type) end # GET /api/v1/timelines/public def public(%{assigns: %{user: user}} = conn, params) do local_only = params[:local] + timeline_type = if local_only, do: :local, else: :federated - if is_nil(user) and restrict_unauthenticated?(local_only) do - fail_on_bad_auth(conn) - else + with {:enabled, true} <- + {:enabled, local_only || Config.get([:instance, :federated_timeline_available], true)}, + {:authenticated, true} <- + {:authenticated, !(is_nil(user) and restrict_unauthenticated?(timeline_type))} do activities = params |> Map.put(:type, ["Create"]) @@ -131,20 +129,28 @@ def public(%{assigns: %{user: user}} = conn, params) do as: :activity, with_muted: Map.get(params, :with_muted, false) ) + else + {:enabled, false} -> + conn + |> put_status(404) + |> json(%{error: "Federated timeline is disabled"}) + + {:authenticated, false} -> + fail_on_bad_auth(conn) end end # GET /api/v1/timelines/bubble def bubble(%{assigns: %{user: user}} = conn, params) do - bubble_instances = - Enum.uniq( - Config.get([:instance, :local_bubble], []) ++ - [Pleroma.Web.Endpoint.host()] - ) - - if is_nil(user) do + if is_nil(user) and restrict_unauthenticated?(:bubble) do fail_on_bad_auth(conn) else + bubble_instances = + Enum.uniq( + Config.get([:instance, :local_bubble], []) ++ + [Pleroma.Web.Endpoint.host()] + ) + activities = params |> Map.put(:type, ["Create"]) @@ -195,7 +201,7 @@ defp hashtag_fetching(params, user, local_only) do def hashtag(%{assigns: %{user: user}} = conn, params) do local_only = params[:local] - if is_nil(user) and restrict_unauthenticated?(local_only) do + if is_nil(user) and restrict_unauthenticated?(if local_only, do: :local, else: :federated) do fail_on_bad_auth(conn) else activities = hashtag_fetching(params, user, local_only) diff --git a/lib/pleroma/web/nodeinfo/nodeinfo.ex b/lib/pleroma/web/nodeinfo/nodeinfo.ex index 14e39e6b3..532ae53a7 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo.ex @@ -73,9 +73,13 @@ def get_nodeinfo("2.0") do privilegedStaff: Config.get([:instance, :privileged_staff]), localBubbleInstances: Config.get([:instance, :local_bubble], []), publicTimelineVisibility: %{ - federated: !Config.restrict_unauthenticated_access?(:timelines, :federated), - local: !Config.restrict_unauthenticated_access?(:timelines, :local) - } + federated: + !Config.restrict_unauthenticated_access?(:timelines, :federated) && + Config.get([:instance, :federated_timeline_available], true), + local: !Config.restrict_unauthenticated_access?(:timelines, :local), + bubble: !Config.restrict_unauthenticated_access?(:timelines, :bubble) + }, + federatedTimelineAvailable: Config.get([:instance, :federated_timeline_available], true) } } end diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index 9a76574d5..4c5a36895 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -11,6 +11,7 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do alias Pleroma.Web.Federator.Publisher alias Pleroma.Web.MastodonAPI.InstanceView alias Pleroma.Web.Endpoint + alias Pleroma.Web.Nodeinfo.Nodeinfo def schemas(conn, _params) do response = %{ @@ -29,105 +30,15 @@ def schemas(conn, _params) do json(conn, response) end - # returns a nodeinfo 2.0 map, since 2.1 just adds a repository field - # under software. - def raw_nodeinfo do - stats = Stats.get_stats() - - staff_accounts = - User.all_superusers() - |> Enum.map(fn u -> u.ap_id end) - |> Enum.filter(fn u -> not Enum.member?(Config.get([:instance, :staff_transparency]), u) end) - - features = InstanceView.features() - federation = InstanceView.federation() - - %{ - version: "2.0", - software: %{ - name: Pleroma.Application.name() |> String.downcase(), - version: Pleroma.Application.version() - }, - protocols: Publisher.gather_nodeinfo_protocol_names(), - services: %{ - inbound: [], - outbound: [] - }, - openRegistrations: Config.get([:instance, :registrations_open]), - usage: %{ - users: %{ - total: Map.get(stats, :user_count, 0) - }, - localPosts: Map.get(stats, :status_count, 0) - }, - metadata: %{ - nodeName: Config.get([:instance, :name]), - nodeDescription: Config.get([:instance, :description]), - private: !Config.get([:instance, :public], true), - suggestions: %{ - enabled: false - }, - staffAccounts: staff_accounts, - federation: federation, - pollLimits: Config.get([:instance, :poll_limits]), - postFormats: Config.get([:instance, :allowed_post_formats]), - uploadLimits: %{ - general: Config.get([:instance, :upload_limit]), - avatar: Config.get([:instance, :avatar_upload_limit]), - banner: Config.get([:instance, :banner_upload_limit]), - background: Config.get([:instance, :background_upload_limit]) - }, - fieldsLimits: %{ - maxFields: Config.get([:instance, :max_account_fields]), - maxRemoteFields: Config.get([:instance, :max_remote_account_fields]), - nameLength: Config.get([:instance, :account_field_name_length]), - valueLength: Config.get([:instance, :account_field_value_length]) - }, - accountActivationRequired: Config.get([:instance, :account_activation_required], false), - invitesEnabled: Config.get([:instance, :invites_enabled], false), - mailerEnabled: Config.get([Pleroma.Emails.Mailer, :enabled], false), - features: features, - restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]), - skipThreadContainment: Config.get([:instance, :skip_thread_containment], false), - localBubbleInstances: Config.get([:instance, :local_bubble], []), - publicTimelineVisibility: %{ - federated: !Config.restrict_unauthenticated_access?(:timelines, :federated), - local: !Config.restrict_unauthenticated_access?(:timelines, :local) - } - } - } - end - # Schema definition: https://github.com/jhass/nodeinfo/blob/master/schemas/2.0/schema.json # and https://github.com/jhass/nodeinfo/blob/master/schemas/2.1/schema.json - def nodeinfo(conn, %{"version" => "2.0"}) do + def nodeinfo(conn, %{"version" => version}) when version in ["2.0", "2.1"] do conn |> put_resp_header( "content-type", "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8" ) - |> json(raw_nodeinfo()) - end - - def nodeinfo(conn, %{"version" => "2.1"}) do - raw_response = raw_nodeinfo() - - updated_software = - raw_response - |> Map.get(:software) - |> Map.put(:repository, Pleroma.Application.repository()) - - response = - raw_response - |> Map.put(:software, updated_software) - |> Map.put(:version, "2.1") - - conn - |> put_resp_header( - "content-type", - "application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.1#; charset=utf-8" - ) - |> json(response) + |> json(Nodeinfo.get_nodeinfo(version)) end def nodeinfo(conn, _) do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index faaf3d679..24ca5c37b 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -598,7 +598,6 @@ defmodule Pleroma.Web.Router do get("/timelines/home", TimelineController, :home) get("/timelines/direct", TimelineController, :direct) get("/timelines/list/:list_id", TimelineController, :list) - get("/timelines/bubble", TimelineController, :bubble) get("/announcements", AnnouncementController, :index) post("/announcements/:id/dismiss", AnnouncementController, :mark_read) @@ -653,6 +652,7 @@ defmodule Pleroma.Web.Router do get("/timelines/public", TimelineController, :public) get("/timelines/tag/:tag", TimelineController, :hashtag) + get("/timelines/bubble", TimelineController, :bubble) get("/polls/:id", PollController, :show) diff --git a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs index aa9006681..fcc7a204e 100644 --- a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs @@ -408,6 +408,26 @@ test "should not return local-only posts for anonymous users" do assert [] = result end + + test "should return 404 if disabled" do + clear_config([:instance, :federated_timeline_available], false) + + result = + build_conn() + |> get("/api/v1/timelines/public") + |> json_response_and_validate_schema(404) + + assert %{"error" => "Federated timeline is disabled"} = result + end + + test "should not return 404 if local is specified" do + clear_config([:instance, :federated_timeline_available], false) + + result = + build_conn() + |> get("/api/v1/timelines/public?local=true") + |> json_response_and_validate_schema(200) + end end defp local_and_remote_activities do @@ -1036,9 +1056,8 @@ test "with `%{local: true, federated: false}`, forbids unauthenticated access to end describe "bubble" do - setup do: oauth_access(["read:statuses"]) - - test "filtering", %{conn: conn, user: user} do + test "filtering" do + %{conn: conn, user: user} = oauth_access(["read:statuses"]) clear_config([:instance, :local_bubble], []) # our endpoint host has a port in it so let's set the AP ID local_user = insert(:user, %{ap_id: "https://localhost/users/user"}) @@ -1060,7 +1079,7 @@ test "filtering", %{conn: conn, user: user} do assert local_activity.id in one_instance - # If we have others, also include theirs + # If we have others, also include theirs clear_config([:instance, :local_bubble], ["example.com"]) two_instances = @@ -1072,6 +1091,20 @@ test "filtering", %{conn: conn, user: user} do assert local_activity.id in two_instances assert remote_activity.id in two_instances end + + test "restrict_unauthenticated with bubble timeline", %{conn: conn} do + clear_config([:restrict_unauthenticated, :timelines, :bubble], true) + + conn + |> get("/api/v1/timelines/bubble") + |> json_response_and_validate_schema(:unauthorized) + + clear_config([:restrict_unauthenticated, :timelines, :bubble], false) + + conn + |> get("/api/v1/timelines/bubble") + |> json_response_and_validate_schema(200) + end end defp create_remote_activity(user) do diff --git a/test/pleroma/web/mastodon_api/views/account_view_test.exs b/test/pleroma/web/mastodon_api/views/account_view_test.exs index c9036d67d..6ef89f799 100644 --- a/test/pleroma/web/mastodon_api/views/account_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/account_view_test.exs @@ -269,8 +269,8 @@ test "Represent a Service(bot) account" do } with_mock( - Pleroma.Web.Nodeinfo.NodeinfoController, - raw_nodeinfo: fn -> %{version: "2.0"} end + Pleroma.Web.Nodeinfo.Nodeinfo, + get_nodeinfo: fn _ -> %{version: "2.0"} end ) do assert expected == AccountView.render("show.json", %{user: user, skip_visibility_check: true}) From f94e8a3713e5cadb67b53ea05a2bc38eb562c2f5 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Sat, 18 Mar 2023 20:49:43 +0000 Subject: [PATCH 17/35] add bubble visibility to description --- config/description.exs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/config/description.exs b/config/description.exs index f8496760f..6520bc29a 100644 --- a/config/description.exs +++ b/config/description.exs @@ -2999,6 +2999,11 @@ key: :federated, type: :boolean, description: "Disallow viewing the whole known network timeline." + }, + %{ + key: :bubble, + type: :boolean, + description: "Disallow viewing the bubble timeline." } ] }, From 4bbe9c8f5ca0482a75c09335892c788c8de06a45 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Mon, 27 Mar 2023 10:03:12 +0100 Subject: [PATCH 18/35] Ship with hehe --- config/custom_emoji.txt | 2 ++ mix.lock | 14 +++++++------- priv/static/emoji/hehe.png | Bin 0 -> 15993 bytes priv/static/emoji/nothehe.png | Bin 0 -> 13628 bytes 4 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 priv/static/emoji/hehe.png create mode 100644 priv/static/emoji/nothehe.png diff --git a/config/custom_emoji.txt b/config/custom_emoji.txt index e69de29bb..7b2e51265 100644 --- a/config/custom_emoji.txt +++ b/config/custom_emoji.txt @@ -0,0 +1,2 @@ +hehe, /emoji/hehe.png, Akkoma +nothehe, /emoji/nothehe.png, Akkoma diff --git a/mix.lock b/mix.lock index 369d1ed25..bee2c1585 100644 --- a/mix.lock +++ b/mix.lock @@ -27,7 +27,7 @@ "earmark": {:hex, :earmark, "1.4.37", "56ce845c543393aa3f9b294c818c3d783452a4a67e4ab18c4303a954a8b59363", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}], "hexpm", "d86d5e12868db86d5321b00e62a4bbcb4150346e4acc9a90a041fb188a5cb106"}, "earmark_parser": {:hex, :earmark_parser, "1.4.31", "a93921cdc6b9b869f519213d5bc79d9e218ba768d7270d46fdcf1c01bacff9e2", [:mix], [], "hexpm", "317d367ee0335ef037a87e46c91a2269fef6306413f731e8ec11fc45a7efd059"}, "eblurhash": {:hex, :eblurhash, "1.2.2", "7da4255aaea984b31bb71155f673257353b0e0554d0d30dcf859547e74602582", [:rebar3], [], "hexpm", "8c20ca00904de023a835a9dcb7b7762fed32264c85a80c3cafa85288e405044c"}, - "ecto": {:hex, :ecto, "3.9.4", "3ee68e25dbe0c36f980f1ba5dd41ee0d3eb0873bccae8aeaf1a2647242bffa35", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "de5f988c142a3aa4ec18b85a4ec34a2390b65b24f02385c1144252ff6ff8ee75"}, + "ecto": {:hex, :ecto, "3.9.5", "9f0aa7ae44a1577b651c98791c6988cd1b69b21bc724e3fd67090b97f7604263", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "d4f3115d8cbacdc0bfa4b742865459fb1371d0715515842a1fb17fe31920b74c"}, "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"}, "ecto_psql_extras": {:hex, :ecto_psql_extras, "0.7.10", "e14d400930f401ca9f541b3349212634e44027d7f919bbb71224d7ac0d0e8acd", [:mix], [{:ecto_sql, "~> 3.4", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.15.7 or ~> 0.16.0", [hex: :postgrex, repo: "hexpm", optional: false]}, {:table_rex, "~> 3.1.1", [hex: :table_rex, repo: "hexpm", optional: false]}], "hexpm", "505e8cd81e4f17c090be0f99e92b1b3f0fd915f98e76965130b8ccfb891e7088"}, "ecto_sql": {:hex, :ecto_sql, "3.9.2", "34227501abe92dba10d9c3495ab6770e75e79b836d114c41108a4bf2ce200ad5", [:mix], [{:db_connection, "~> 2.4.1 or ~> 2.5", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.9.2", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.6.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.16.0 or ~> 1.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:tds, "~> 2.1.1 or ~> 2.2", [hex: :tds, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1eb5eeb4358fdbcd42eac11c1fbd87e3affd7904e639d77903c1358b2abd3f70"}, @@ -38,7 +38,7 @@ "ex_aws": {:hex, :ex_aws, "2.1.9", "dc4865ecc20a05190a34a0ac5213e3e5e2b0a75a0c2835e923ae7bfeac5e3c31", [:mix], [{:configparser_ex, "~> 4.0", [hex: :configparser_ex, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: true]}, {:jsx, "~> 3.0", [hex: :jsx, repo: "hexpm", optional: true]}, {:sweet_xml, "~> 0.6", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "3e6c776703c9076001fbe1f7c049535f042cb2afa0d2cbd3b47cbc4e92ac0d10"}, "ex_aws_s3": {:hex, :ex_aws_s3, "2.4.0", "ce8decb6b523381812798396bc0e3aaa62282e1b40520125d1f4eff4abdff0f4", [:mix], [{:ex_aws, "~> 2.0", [hex: :ex_aws, repo: "hexpm", optional: false]}, {:sweet_xml, ">= 0.0.0", [hex: :sweet_xml, repo: "hexpm", optional: true]}], "hexpm", "85dda6e27754d94582869d39cba3241d9ea60b6aa4167f9c88e309dc687e56bb"}, "ex_const": {:hex, :ex_const, "0.2.4", "d06e540c9d834865b012a17407761455efa71d0ce91e5831e86881b9c9d82448", [:mix], [], "hexpm", "96fd346610cc992b8f896ed26a98be82ac4efb065a0578f334a32d60a3ba9767"}, - "ex_doc": {:hex, :ex_doc, "0.29.2", "dfa97532ba66910b2a3016a4bbd796f41a86fc71dd5227e96f4c8581fdf0fdf0", [:mix], [{:earmark_parser, "~> 1.4.19", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "6b5d7139eda18a753e3250e27e4a929f8d2c880dd0d460cb9986305dea3e03af"}, + "ex_doc": {:hex, :ex_doc, "0.29.3", "f07444bcafb302db86e4f02d8bbcd82f2e881a0dcf4f3e4740e4b8128b9353f7", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "3dc6787d7b08801ec3b51e9bd26be5e8826fbf1a17e92d1ebc252e1a1c75bfe1"}, "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, "ex_syslogger": {:hex, :ex_syslogger, "1.5.2", "72b6aa2d47a236e999171f2e1ec18698740f40af0bd02c8c650bf5f1fd1bac79", [:mix], [{:poison, ">= 1.5.0", [hex: :poison, repo: "hexpm", optional: true]}, {:syslog, "~> 1.1.0", [hex: :syslog, repo: "hexpm", optional: false]}], "hexpm", "ab9fab4136dbc62651ec6f16fa4842f10cf02ab4433fa3d0976c01be99398399"}, "excoveralls": {:hex, :excoveralls, "0.15.1", "83c8cf7973dd9d1d853dce37a2fb98aaf29b564bf7d01866e409abf59dac2c0e", [:mix], [{:hackney, "~> 1.16", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f8416bd90c0082d56a2178cf46c837595a06575f70a5624f164a1ffe37de07e7"}, @@ -86,14 +86,14 @@ "phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"}, "phoenix_html": {:hex, :phoenix_html, "3.3.1", "4788757e804a30baac6b3fc9695bf5562465dd3f1da8eb8460ad5b404d9a2178", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "bed1906edd4906a15fd7b412b85b05e521e1f67c9a85418c55999277e553d0d3"}, "phoenix_live_dashboard": {:hex, :phoenix_live_dashboard, "0.7.2", "97cc4ff2dba1ebe504db72cb45098cb8e91f11160528b980bd282cc45c73b29c", [:mix], [{:ecto, "~> 3.6.2 or ~> 3.7", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_mysql_extras, "~> 0.5", [hex: :ecto_mysql_extras, repo: "hexpm", optional: true]}, {:ecto_psql_extras, "~> 0.7", [hex: :ecto_psql_extras, repo: "hexpm", optional: true]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:phoenix_live_view, "~> 0.18.3", [hex: :phoenix_live_view, repo: "hexpm", optional: false]}, {:telemetry_metrics, "~> 0.6 or ~> 1.0", [hex: :telemetry_metrics, repo: "hexpm", optional: false]}], "hexpm", "0e5fdf063c7a3b620c566a30fcf68b7ee02e5e46fe48ee46a6ec3ba382dc05b7"}, - "phoenix_live_view": {:hex, :phoenix_live_view, "0.18.17", "74938b02f3c531bed3f87fe1ea39af6b5b2d26ab1405e77e76b8ef5df9ffa8a1", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "f4b5710e19a29b8dc93b7af4bab4739c067a3cb759af01ffc3057165453dce38"}, + "phoenix_live_view": {:hex, :phoenix_live_view, "0.18.18", "1f38fbd7c363723f19aad1a04b5490ff3a178e37daaf6999594d5f34796c47fc", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix, "~> 1.6.15 or ~> 1.7.0", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 3.3", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 2.0", [hex: :phoenix_view, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a5810d0472f3189ede6d2a95bda7f31c6113156b91784a3426cb0ab6a6d85214"}, "phoenix_pubsub": {:hex, :phoenix_pubsub, "2.1.1", "ba04e489ef03763bf28a17eb2eaddc2c20c6d217e2150a61e3298b0f4c2012b5", [:mix], [], "hexpm", "81367c6d1eea5878ad726be80808eb5a787a23dee699f96e72b1109c57cdd8d9"}, "phoenix_swoosh": {:hex, :phoenix_swoosh, "0.3.4", "615f8f393135de7e0cbb4bd00ba238b1e0cd324b0d90efbaee613c2f02ca5e5c", [:mix], [{:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:phoenix, "~> 1.4", [hex: :phoenix, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: false]}, {:swoosh, "~> 1.0", [hex: :swoosh, repo: "hexpm", optional: false]}], "hexpm", "3971221846232021ab5e3c7489fd62ec5bfd6a2e01cae10a317ccf6fb350571c"}, "phoenix_template": {:hex, :phoenix_template, "1.0.1", "85f79e3ad1b0180abb43f9725973e3b8c2c3354a87245f91431eec60553ed3ef", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}], "hexpm", "157dc078f6226334c91cb32c1865bf3911686f8bcd6bcff86736f6253e6993ee"}, "phoenix_view": {:hex, :phoenix_view, "2.0.2", "6bd4d2fd595ef80d33b439ede6a19326b78f0f1d8d62b9a318e3d9c1af351098", [:mix], [{:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:phoenix_template, "~> 1.0", [hex: :phoenix_template, repo: "hexpm", optional: false]}], "hexpm", "a929e7230ea5c7ee0e149ffcf44ce7cf7f4b6d2bfe1752dd7c084cdff152d36f"}, - "plug": {:hex, :plug, "1.14.0", "ba4f558468f69cbd9f6b356d25443d0b796fbdc887e03fa89001384a9cac638f", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "bf020432c7d4feb7b3af16a0c2701455cbbbb95e5b6866132cb09eb0c29adc14"}, - "plug_cowboy": {:hex, :plug_cowboy, "2.6.0", "d1cf12ff96a1ca4f52207c5271a6c351a4733f413803488d75b70ccf44aebec2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "073cf20b753ce6682ed72905cd62a2d4bd9bad1bf9f7feb02a1b8e525bd94fa6"}, - "plug_crypto": {:hex, :plug_crypto, "1.2.4", "34c380ef387cc7e8d537854ddd4b7096c79a4397d53587cb80419c782b03fdbc", [:mix], [], "hexpm", "4de415f03faec94d9da9be8c12cb51e9c98cbf66d732b6df669d4562d8e91acc"}, + "plug": {:hex, :plug, "1.14.2", "cff7d4ec45b4ae176a227acd94a7ab536d9b37b942c8e8fa6dfc0fff98ff4d80", [:mix], [{:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_crypto, "~> 1.1.1 or ~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4.3 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "842fc50187e13cf4ac3b253d47d9474ed6c296a8732752835ce4a86acdf68d13"}, + "plug_cowboy": {:hex, :plug_cowboy, "2.6.1", "9a3bbfceeb65eff5f39dab529e5cd79137ac36e913c02067dba3963a26efe9b2", [:mix], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:cowboy_telemetry, "~> 0.3", [hex: :cowboy_telemetry, repo: "hexpm", optional: false]}, {:plug, "~> 1.14", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "de36e1a21f451a18b790f37765db198075c25875c64834bcc82d90b309eb6613"}, + "plug_crypto": {:hex, :plug_crypto, "1.2.5", "918772575e48e81e455818229bf719d4ab4181fcbf7f85b68a35620f78d89ced", [:mix], [], "hexpm", "26549a1d6345e2172eb1c233866756ae44a9609bd33ee6f99147ab3fd87fd842"}, "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "79fd4fcf34d110605c26560cbae8f23c603ec4158c08298bd4360fdea90bb5cf"}, "poison": {:hex, :poison, "5.0.0", "d2b54589ab4157bbb82ec2050757779bfed724463a544b6e20d79855a9e43b24", [:mix], [{:decimal, "~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "11dc6117c501b80c62a7594f941d043982a1bd05a1184280c0d9166eb4d8d3fc"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm", "dad79704ce5440f3d5a3681c8590b9dc25d1a561e8f5a9c995281012860901e3"}, @@ -117,7 +117,7 @@ "telemetry_poller": {:hex, :telemetry_poller, "0.5.1", "21071cc2e536810bac5628b935521ff3e28f0303e770951158c73eaaa01e962a", [:rebar3], [{:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "4cab72069210bc6e7a080cec9afffad1b33370149ed5d379b81c7c5f0c663fd4"}, "temple": {:git, "https://akkoma.dev/AkkomaGang/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]}, "tesla": {:hex, :tesla, "1.4.4", "bb89aa0c9745190930366f6a2ac612cdf2d0e4d7fff449861baa7875afd797b2", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "d5503a49f9dec1b287567ea8712d085947e247cb11b06bc54adb05bfde466457"}, - "timex": {:hex, :timex, "3.7.9", "790cdfc4acfce434e442f98c02ea6d84d0239073bfd668968f82ac63e9a6788d", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "64691582e5bb87130f721fc709acfb70f24405833998fabf35be968984860ce1"}, + "timex": {:hex, :timex, "3.7.11", "bb95cb4eb1d06e27346325de506bcc6c30f9c6dea40d1ebe390b262fad1862d1", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.20", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8b9024f7efbabaf9bd7aa04f65cf8dcd7c9818ca5737677c7b76acbc6a94d1aa"}, "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"}, "tzdata": {:hex, :tzdata, "1.1.1", "20c8043476dfda8504952d00adac41c6eda23912278add38edc140ae0c5bcc46", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a69cec8352eafcd2e198dea28a34113b60fdc6cb57eb5ad65c10292a6ba89787"}, "ueberauth": {:hex, :ueberauth, "0.10.5", "806adb703df87e55b5615cf365e809f84c20c68aa8c08ff8a416a5a6644c4b02", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "3efd1f31d490a125c7ed453b926f7c31d78b97b8a854c755f5c40064bf3ac9e1"}, diff --git a/priv/static/emoji/hehe.png b/priv/static/emoji/hehe.png new file mode 100644 index 0000000000000000000000000000000000000000..c02592d97604befbb241ef16c2016d84bc2bb89d GIT binary patch literal 15993 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 Dnw$bX literal 0 HcmV?d00001 diff --git a/priv/static/emoji/nothehe.png b/priv/static/emoji/nothehe.png new file mode 100644 index 0000000000000000000000000000000000000000..427bb6d61f1f4a24e4b89ed48f440a3038c2b594 GIT binary patch literal 13628 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 Date: Tue, 28 Mar 2023 12:44:52 +0100 Subject: [PATCH 19/35] add selection UI --- config/description.exs | 6 ++++++ .../controllers/frontend_switcher.ex | 20 +++++++++++++++++++ .../web/akkoma_api/views/frontend_switcher.ex | 3 +++ lib/pleroma/web/plugs/frontend_static.ex | 8 ++++++-- lib/pleroma/web/router.ex | 7 +++++++ .../frontend_switcher/switch.html.eex | 7 +++++++ 6 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 lib/pleroma/web/akkoma_api/controllers/frontend_switcher.ex create mode 100644 lib/pleroma/web/akkoma_api/views/frontend_switcher.ex create mode 100644 lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex diff --git a/config/description.exs b/config/description.exs index 2a2d70a7b..75fd23566 100644 --- a/config/description.exs +++ b/config/description.exs @@ -3148,6 +3148,12 @@ description: "A map containing available frontends and parameters for their installation.", children: frontend_options + }, + %{ + key: :pickable, + type: {:list, :string}, + description: + "A list containing all frontends users can pick as their preference, format is :name/:ref, e.g pleroma-fe/stable." } ] }, diff --git a/lib/pleroma/web/akkoma_api/controllers/frontend_switcher.ex b/lib/pleroma/web/akkoma_api/controllers/frontend_switcher.ex new file mode 100644 index 000000000..2095db4b5 --- /dev/null +++ b/lib/pleroma/web/akkoma_api/controllers/frontend_switcher.ex @@ -0,0 +1,20 @@ +defmodule Pleroma.Web.AkkomaAPI.FrontendSwitcherController do + use Pleroma.Web, :controller + alias Pleroma.Config + + @doc "GET /akkoma/frontend" + def switch(conn, _params) do + pickable = Config.get([:frontends, :pickable], []) + + conn + |> put_view(Pleroma.Web.AkkomaAPI.FrontendSwitcherView) + |> render("switch.html", choices: pickable) + end + + @doc "POST /akkoma/frontend" + def do_switch(conn, params) do + conn + |> put_resp_cookie("preferred_frontend", params["frontend"]) + |> html("") + end +end diff --git a/lib/pleroma/web/akkoma_api/views/frontend_switcher.ex b/lib/pleroma/web/akkoma_api/views/frontend_switcher.ex new file mode 100644 index 000000000..1564c9e44 --- /dev/null +++ b/lib/pleroma/web/akkoma_api/views/frontend_switcher.ex @@ -0,0 +1,3 @@ +defmodule Pleroma.Web.AkkomaAPI.FrontendSwitcherView do + use Pleroma.Web, :view +end diff --git a/lib/pleroma/web/plugs/frontend_static.ex b/lib/pleroma/web/plugs/frontend_static.ex index 62283353e..91dfc77c3 100644 --- a/lib/pleroma/web/plugs/frontend_static.ex +++ b/lib/pleroma/web/plugs/frontend_static.ex @@ -50,6 +50,7 @@ def init(opts) do end def call(conn, opts) do + IO.inspect("OPTS: #{inspect(opts)}") with false <- api_route?(conn.path_info), false <- invalid_path?(conn.path_info), true <- enabled?(opts[:if]), @@ -71,16 +72,19 @@ def preferred_frontend(conn) do Map.get(cookies, @frontend_cookie_name) end - def preferred_or_fallback(conn, fallback) do + # Only override primary frontend + def preferred_or_fallback(conn, :primary) do case preferred_frontend(conn) do nil -> - fallback + :primary frontend -> frontend end end + def preferred_or_fallback(conn, fallback), do: fallback + defp enabled?(if_opt) when is_function(if_opt), do: if_opt.() defp enabled?(true), do: true defp enabled?(_), do: false diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 3db8ddab7..d02ae3460 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -466,6 +466,13 @@ defmodule Pleroma.Web.Router do put("/statuses/:id/emoji_reactions/:emoji", EmojiReactionController, :create) end + scope "/akkoma/", Pleroma.Web.AkkomaAPI do + pipe_through(:browser) + + get("/frontend", FrontendSwitcherController, :switch) + post("/frontend", FrontendSwitcherController, :do_switch) + end + scope "/api/v1/akkoma", Pleroma.Web.AkkomaAPI do pipe_through(:api) diff --git a/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex b/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex new file mode 100644 index 000000000..0692ddfb8 --- /dev/null +++ b/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex @@ -0,0 +1,7 @@ +

Switch Frontend

+ +<%= form_for @conn, Routes.frontend_switcher_path(@conn, :do_switch), fn f -> %> + <%= select(f, :frontend, @choices) %> + + <%= submit do: "submit" %> +<% end %> From 1d94f2a424e64082854c9ccf1e2086e314e07852 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 29 Mar 2023 01:59:19 +0100 Subject: [PATCH 20/35] Remove indexer plugin --- config/config.exs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config/config.exs b/config/config.exs index e216caf9d..ffb62a28f 100644 --- a/config/config.exs +++ b/config/config.exs @@ -572,8 +572,7 @@ database_prune: 1 ], plugins: [ - Oban.Plugins.Pruner, - {Oban.Plugins.Reindexer, schedule: "@weekly"} + Oban.Plugins.Pruner ], crontab: [ {"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker}, From 3f340cbc43948f3ce0e8bca23142be9cd2454960 Mon Sep 17 00:00:00 2001 From: sadposter Date: Wed, 29 Mar 2023 03:31:56 +0100 Subject: [PATCH 21/35] Only even attempt to fetch local activities by object_id TODO: PLEASE FOR THE LOVE OF KANATAN CACHE THIS --- lib/pleroma/activity.ex | 8 ++++++++ lib/pleroma/web/o_status/o_status_controller.ex | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index c5b514742..0a376be04 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -277,6 +277,14 @@ def get_create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do def get_create_by_object_ap_id_with_object(_), do: nil + def get_local_create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do + ap_id + |> create_by_object_ap_id_with_object() + |> where(local: true) + |> Repo.one() + end + + @spec create_by_id_with_object(String.t()) :: t() | nil def create_by_id_with_object(id) do get_by_id_with_opts(id, preload: [:object], filter: [type: "Create"]) diff --git a/lib/pleroma/web/o_status/o_status_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex index 7731d847f..79db112df 100644 --- a/lib/pleroma/web/o_status/o_status_controller.ex +++ b/lib/pleroma/web/o_status/o_status_controller.ex @@ -36,7 +36,7 @@ def object(%{assigns: %{format: format}} = conn, _params) def object(conn, _params) do with id <- Endpoint.url() <> conn.request_path, {_, %Activity{} = activity} <- - {:activity, Activity.get_create_by_object_ap_id_with_object(id)}, + {:activity, Activity.get_local_create_by_object_ap_id_with_object(id)}, {_, true} <- {:public?, Visibility.is_public?(activity)}, {_, false} <- {:local_public?, Visibility.is_local_public?(activity)} do redirect(conn, to: "/notice/#{activity.id}") From 0151ca1d52d383e4fa7a510a810975fbb09ee6f3 Mon Sep 17 00:00:00 2001 From: sadposter Date: Wed, 29 Mar 2023 03:32:30 +0100 Subject: [PATCH 22/35] Revert "Remove indexer plugin" This reverts commit 1d94f2a424e64082854c9ccf1e2086e314e07852. --- config/config.exs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config/config.exs b/config/config.exs index ffb62a28f..e216caf9d 100644 --- a/config/config.exs +++ b/config/config.exs @@ -572,7 +572,8 @@ database_prune: 1 ], plugins: [ - Oban.Plugins.Pruner + Oban.Plugins.Pruner, + {Oban.Plugins.Reindexer, schedule: "@weekly"} ], crontab: [ {"0 0 * * 0", Pleroma.Workers.Cron.DigestEmailsWorker}, From d85d1e128a0ca836e8976202781902383aea3d89 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 29 Mar 2023 11:44:03 +0100 Subject: [PATCH 23/35] we don't actually need the object on redirect --- lib/pleroma/activity.ex | 4 ++-- lib/pleroma/web/o_status/o_status_controller.ex | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 0a376be04..925612d3c 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -277,9 +277,9 @@ def get_create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do def get_create_by_object_ap_id_with_object(_), do: nil - def get_local_create_by_object_ap_id_with_object(ap_id) when is_binary(ap_id) do + def get_local_create_by_object_ap_id(ap_id) when is_binary(ap_id) do ap_id - |> create_by_object_ap_id_with_object() + |> create_by_object_ap_id() |> where(local: true) |> Repo.one() end diff --git a/lib/pleroma/web/o_status/o_status_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex index 79db112df..95a22895e 100644 --- a/lib/pleroma/web/o_status/o_status_controller.ex +++ b/lib/pleroma/web/o_status/o_status_controller.ex @@ -36,7 +36,7 @@ def object(%{assigns: %{format: format}} = conn, _params) def object(conn, _params) do with id <- Endpoint.url() <> conn.request_path, {_, %Activity{} = activity} <- - {:activity, Activity.get_local_create_by_object_ap_id_with_object(id)}, + {:activity, Activity.get_local_create_by_object_ap_id(id)}, {_, true} <- {:public?, Visibility.is_public?(activity)}, {_, false} <- {:local_public?, Visibility.is_local_public?(activity)} do redirect(conn, to: "/notice/#{activity.id}") From 66d162bb9edf79455558bdf7e267c08f8c3cf964 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 29 Mar 2023 12:01:16 +0100 Subject: [PATCH 24/35] Add debug logs to timeline rendering to assist debugging --- lib/pleroma/activity.ex | 1 - lib/pleroma/user.ex | 2 +- .../controllers/timeline_controller.ex | 27 +++++++++++++++++++ .../web/mastodon_api/views/status_view.ex | 13 +++++++-- 4 files changed, 39 insertions(+), 4 deletions(-) diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 925612d3c..c94667fb9 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -284,7 +284,6 @@ def get_local_create_by_object_ap_id(ap_id) when is_binary(ap_id) do |> Repo.one() end - @spec create_by_id_with_object(String.t()) :: t() | nil def create_by_id_with_object(id) do get_by_id_with_opts(id, preload: [:object], filter: [type: "Create"]) diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 480521984..ead37ccca 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -374,7 +374,7 @@ def banner_url(user, options \\ []) do do_optional_url(user.banner, "#{Endpoint.url()}/images/banner.png", options) end - defp do_optional_url(field, default, options \\ []) do + defp do_optional_url(field, default, options) do case field do %{"url" => [%{"href" => href} | _]} when is_binary(href) -> href diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index c9960187d..1d4e734a4 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -37,10 +37,16 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do when action in [:public, :hashtag, :bubble] ) + require Logger + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TimelineOperation # GET /api/v1/timelines/home def home(%{assigns: %{user: user}} = conn, params) do + %{nickname: nickname} = user + + Logger.debug("TimelineController.home: #{nickname}") + followed_hashtags = user |> User.followed_hashtags() @@ -58,11 +64,15 @@ def home(%{assigns: %{user: user}} = conn, params) do |> Map.put(:followed_hashtags, followed_hashtags) |> Map.delete(:local) + Logger.debug("TimelineController.home: #{nickname} - fetching activities") + activities = [user.ap_id | User.following(user)] |> ActivityPub.fetch_activities(params) |> Enum.reverse() + Logger.debug("TimelineController.home: #{nickname} - rendering") + conn |> add_link_headers(activities) |> render("index.json", @@ -75,6 +85,8 @@ def home(%{assigns: %{user: user}} = conn, params) do # GET /api/v1/timelines/direct def direct(%{assigns: %{user: user}} = conn, params) do + Logger.debug("TimelineController.direct: #{user.nickname}") + params = params |> Map.put(:type, "Create") @@ -82,11 +94,15 @@ def direct(%{assigns: %{user: user}} = conn, params) do |> Map.put(:user, user) |> Map.put(:visibility, "direct") + Logger.debug("TimelineController.direct: #{user.nickname} - fetching activities") + activities = [user.ap_id] |> ActivityPub.fetch_activities_query(params) |> Pagination.fetch_paginated(params) + Logger.debug("TimelineController.direct: #{user.nickname} - rendering") + conn |> add_link_headers(activities) |> render("index.json", @@ -102,6 +118,7 @@ defp restrict_unauthenticated?(type) do # GET /api/v1/timelines/public def public(%{assigns: %{user: user}} = conn, params) do + Logger.debug("TimelineController.public") local_only = params[:local] timeline_type = if local_only, do: :local, else: :federated @@ -109,6 +126,8 @@ def public(%{assigns: %{user: user}} = conn, params) do {:enabled, local_only || Config.get([:instance, :federated_timeline_available], true)}, {:authenticated, true} <- {:authenticated, !(is_nil(user) and restrict_unauthenticated?(timeline_type))} do + Logger.debug("TimelineController.public: fetching activities") + activities = params |> Map.put(:type, ["Create"]) @@ -121,6 +140,8 @@ def public(%{assigns: %{user: user}} = conn, params) do |> Map.put(:includes_local_public, not is_nil(user)) |> ActivityPub.fetch_public_activities() + Logger.debug("TimelineController.public: rendering") + conn |> add_link_headers(activities, %{"local" => local_only}) |> render("index.json", @@ -142,6 +163,8 @@ def public(%{assigns: %{user: user}} = conn, params) do # GET /api/v1/timelines/bubble def bubble(%{assigns: %{user: user}} = conn, params) do + Logger.debug("TimelineController.bubble") + if is_nil(user) and restrict_unauthenticated?(:bubble) do fail_on_bad_auth(conn) else @@ -151,6 +174,8 @@ def bubble(%{assigns: %{user: user}} = conn, params) do [Pleroma.Web.Endpoint.host()] ) + Logger.debug("TimelineController.bubble: fetching activities") + activities = params |> Map.put(:type, ["Create"]) @@ -160,6 +185,8 @@ def bubble(%{assigns: %{user: user}} = conn, params) do |> Map.put(:instance, bubble_instances) |> ActivityPub.fetch_public_activities() + Logger.debug("TimelineController.bubble: rendering") + conn |> add_link_headers(activities) |> render("index.json", diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 3868da8d9..47d1616c4 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -21,6 +21,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.MediaProxy alias Pleroma.Web.PleromaAPI.EmojiReactionController + require Logger import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1, visible_for_user?: 2] @@ -87,6 +88,7 @@ defp reblogged?(activity, %User{ap_id: ap_id}) do defp reblogged?(_activity, _user), do: false def render("index.json", opts) do + Logger.debug("Rendering index") reading_user = opts[:for] # To do: check AdminAPIControllerTest on the reasons behind nil activities in the list activities = Enum.filter(opts.activities, & &1) @@ -136,8 +138,10 @@ def render("index.json", opts) do def render( "show.json", - %{activity: %{data: %{"type" => "Announce", "object" => _object}} = activity} = opts + %{activity: %{id: id, data: %{"type" => "Announce", "object" => _object}} = activity} = + opts ) do + Logger.debug("Rendering reblog #{id}") user = CommonAPI.get_user(activity.data["actor"]) created_at = Utils.to_masto_date(activity.data["published"]) object = Object.normalize(activity, fetch: false) @@ -209,7 +213,9 @@ def render( } end - def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do + def render("show.json", %{activity: %{id: id, data: %{"object" => _object}} = activity} = opts) do + Logger.debug("Rendering status #{id}") + with %Object{} = object <- Object.normalize(activity, fetch: false) do user = CommonAPI.get_user(activity.data["actor"]) user_follower_address = user.follower_address @@ -428,6 +434,7 @@ def render("show.json", _) do end def render("history.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do + Logger.debug("Rendering history for #{activity.id}") object = Object.normalize(activity, fetch: false) hashtags = Object.hashtags(object) @@ -614,6 +621,8 @@ def render("attachment_meta.json", %{ def render("attachment_meta.json", _), do: nil def render("context.json", %{activity: activity, activities: activities, user: user}) do + Logger.debug("Rendering context for #{activity.id}") + %{ancestors: ancestors, descendants: descendants} = activities |> Enum.reverse() From 2a8c1f41924e3f7092ffea0fb407c2906f705242 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Wed, 29 Mar 2023 14:11:00 +0100 Subject: [PATCH 25/35] Add extra diagnostic tasks in --- lib/mix/tasks/pleroma/diagnostics.ex | 42 ++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/lib/mix/tasks/pleroma/diagnostics.ex b/lib/mix/tasks/pleroma/diagnostics.ex index 3914540ca..3b6063723 100644 --- a/lib/mix/tasks/pleroma/diagnostics.ex +++ b/lib/mix/tasks/pleroma/diagnostics.ex @@ -82,4 +82,46 @@ def run(["user_timeline", nickname, reading_nickname]) do Ecto.Adapters.SQL.explain(Repo, :all, query, analyze: true, timeout: :infinity) |> IO.puts() end + + def run(["notifications", nickname]) do + start_pleroma() + user = Repo.get_by!(User, nickname: nickname) + account_ap_id = user.ap_id + options = %{account_ap_id: user.ap_id} + + query = + user + |> Pleroma.Notification.for_user_query(options) + |> where([n, a], a.actor == ^account_ap_id) + |> limit(20) + + Ecto.Adapters.SQL.explain(Repo, :all, query, analyze: true, timeout: :infinity) + |> IO.puts() + end + + def run(["known_network", nickname]) do + start_pleroma() + user = Repo.get_by!(User, nickname: nickname) + + params = + %{} + |> Map.put(:type, ["Create"]) + |> Map.put(:local_only, false) + |> Map.put(:blocking_user, user) + |> Map.put(:muting_user, user) + |> Map.put(:reply_filtering_user, user) + # Restricts unfederated content to authenticated users + |> Map.put(:includes_local_public, not is_nil(user)) + |> Map.put(:restrict_unlisted, true) + + query = + Pleroma.Web.ActivityPub.ActivityPub.fetch_activities_query( + [Pleroma.Constants.as_public()], + params + ) + |> limit(20) + + Ecto.Adapters.SQL.explain(Repo, :all, query, analyze: true, timeout: :infinity) + |> IO.puts() + end end From d8d9edee98ef4cf89fedf8028236b496b699e080 Mon Sep 17 00:00:00 2001 From: ilja Date: Mon, 3 Apr 2023 11:18:28 +0200 Subject: [PATCH 26/35] Add YunoHost to installation guides --- README.md | 3 +++ docs/docs/installation/yunohost_en.md | 9 +++++++++ 2 files changed, 12 insertions(+) create mode 100644 docs/docs/installation/yunohost_en.md diff --git a/README.md b/README.md index 8d35212aa..e4aa25715 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,9 @@ If your platform is not supported, or you just want to be able to edit the sourc ### Docker Docker installation is supported via [this setup](https://docs.akkoma.dev/stable/installation/docker_en/) +### Packages +Akkoma is packaged for [YunoHost](https://yunohost.org) and can be found and installed from the [YunoHost app catalogue](https://yunohost.org/#/apps). + ### Compilation Troubleshooting If you ever encounter compilation issues during the updating of Akkoma, you can try these commands and see if they fix things: diff --git a/docs/docs/installation/yunohost_en.md b/docs/docs/installation/yunohost_en.md new file mode 100644 index 000000000..0d3adb4fe --- /dev/null +++ b/docs/docs/installation/yunohost_en.md @@ -0,0 +1,9 @@ +# Installing on Yunohost + +[YunoHost](https://yunohost.org) is a server operating system aimed at self-hosting. The YunoHost community maintains a package of Akkoma which allows you to install Akkoma on YunoHost. You can install it via the normal way through the admin web interface, or through the CLI. More information can be found at [the repo of the package](https://github.com/YunoHost-Apps/akkoma_ynh). + +## Questions + +Questions and problems related to the YunoHost parts can be done through the [YunoHost channels](https://yunohost.org/en/help). + +For questions about Akkoma, check out the [Akkoma community channels](../../#community-channels). From 1b2c24a19e86434f748a4f3cba6fb688253efb15 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Fri, 14 Apr 2023 15:20:55 +0100 Subject: [PATCH 27/35] fix tests --- lib/pleroma/web/plugs/frontend_static.ex | 4 +--- test/pleroma/web/plugs/frontend_static_plug_test.exs | 1 + 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/plugs/frontend_static.ex b/lib/pleroma/web/plugs/frontend_static.ex index 91dfc77c3..1d6861456 100644 --- a/lib/pleroma/web/plugs/frontend_static.ex +++ b/lib/pleroma/web/plugs/frontend_static.ex @@ -50,7 +50,6 @@ def init(opts) do end def call(conn, opts) do - IO.inspect("OPTS: #{inspect(opts)}") with false <- api_route?(conn.path_info), false <- invalid_path?(conn.path_info), true <- enabled?(opts[:if]), @@ -83,7 +82,7 @@ def preferred_or_fallback(conn, :primary) do end end - def preferred_or_fallback(conn, fallback), do: fallback + def preferred_or_fallback(_conn, fallback), do: fallback defp enabled?(if_opt) when is_function(if_opt), do: if_opt.() defp enabled?(true), do: true @@ -106,7 +105,6 @@ defp api_route?([h | t]) do defp call_static(conn, opts, from) do opts = Map.put(opts, :from, from) - IO.inspect(opts, label: "opts") Plug.Static.call(conn, opts) end end diff --git a/test/pleroma/web/plugs/frontend_static_plug_test.exs b/test/pleroma/web/plugs/frontend_static_plug_test.exs index 66e6ba4ca..815e888ee 100644 --- a/test/pleroma/web/plugs/frontend_static_plug_test.exs +++ b/test/pleroma/web/plugs/frontend_static_plug_test.exs @@ -83,6 +83,7 @@ test "api routes are detected correctly" do "main", "ostatus_subscribe", "oauth", + "akkoma", "objects", "activities", "notice", From a079ec3a3cdfd42d2cbd51c7698c2c87828e5778 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Fri, 14 Apr 2023 16:36:40 +0100 Subject: [PATCH 28/35] in dev, allow dev FE --- lib/pleroma/web/plugs/http_security_plug.ex | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/plugs/http_security_plug.ex b/lib/pleroma/web/plugs/http_security_plug.ex index b1f1ada94..570aeefff 100644 --- a/lib/pleroma/web/plugs/http_security_plug.ex +++ b/lib/pleroma/web/plugs/http_security_plug.ex @@ -8,6 +8,8 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlug do require Logger + @mix_env Mix.env() + def init(opts), do: opts def call(conn, _options) do @@ -114,7 +116,12 @@ defp csp_string(conn) do style_src = "style-src 'self' '#{nonce_tag}'" font_src = "font-src 'self'" - script_src = "script-src 'self' '#{nonce_tag}'" + script_src = "script-src 'self' '#{nonce_tag}' " + script_src = if @mix_env == :dev do + "script-src 'self' 'unsafe-eval' 'unsafe-inline'" + else + script_src + end report = if report_uri, do: ["report-uri ", report_uri, ";report-to csp-endpoint"] insecure = if scheme == "https", do: "upgrade-insecure-requests" From 9e8e7cc13ebd2ec2ecbf90ae10c3532f77af7a81 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Fri, 14 Apr 2023 16:55:48 +0100 Subject: [PATCH 29/35] Add note telling people to refresh --- .../web/templates/akkoma_api/frontend_switcher/switch.html.eex | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex b/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex index 0692ddfb8..a0b0a2361 100644 --- a/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex +++ b/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex @@ -1,7 +1,10 @@

Switch Frontend

+

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

+ <%= form_for @conn, Routes.frontend_switcher_path(@conn, :do_switch), fn f -> %> <%= select(f, :frontend, @choices) %> <%= submit do: "submit" %> <% end %> + From ba59fdcd54986b0ebc31a577cb1c767e55a43e7f Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Fri, 14 Apr 2023 16:56:51 +0100 Subject: [PATCH 30/35] add changelog entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bf6253af..a277ac08c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Nodeinfo keys for unauthenticated timeline visibility - Option to disable federated timeline - Option to make the bubble timeline publicly accessible +- Ability to swap between installed standard frontends + - *mastodon frontends are still not counted as standard frontends due to the complexity in serving them correctly*. ## 2023.03 From f12d3cce39e6bdfbe4aa8364ba1051a6b72155fc Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Fri, 14 Apr 2023 17:42:40 +0100 Subject: [PATCH 31/35] ensure only pickable frontends can be returned --- lib/pleroma/web/plugs/frontend_static.ex | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/plugs/frontend_static.ex b/lib/pleroma/web/plugs/frontend_static.ex index 1d6861456..41b8ba46b 100644 --- a/lib/pleroma/web/plugs/frontend_static.ex +++ b/lib/pleroma/web/plugs/frontend_static.ex @@ -78,7 +78,11 @@ def preferred_or_fallback(conn, :primary) do :primary frontend -> - frontend + if Enum.member?(Pleroma.Config.get([:frontends, :pickable], []), frontend) do + frontend + else + :primary + end end end From d2b0d864718c676af9909ee45fac60d5f232bf6a Mon Sep 17 00:00:00 2001 From: Atsuko Karagi Date: Tue, 4 Apr 2023 17:41:13 +0200 Subject: [PATCH 32/35] HTTP signatures respect allowlist federation --- lib/pleroma/web/activity_pub/publisher.ex | 19 +++++++-- ...mapped_signature_to_identity_plug_test.exs | 41 +++++++++++++++++++ 2 files changed, 57 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/activity_pub/publisher.ex b/lib/pleroma/web/activity_pub/publisher.ex index b187d3a48..3071c1b77 100644 --- a/lib/pleroma/web/activity_pub/publisher.ex +++ b/lib/pleroma/web/activity_pub/publisher.ex @@ -108,15 +108,28 @@ defp blocked_instances do Config.get([:mrf_simple, :reject], []) end + defp allowed_instances do + Config.get([:mrf_simple, :accept]) + end + def should_federate?(url) do %{host: host} = URI.parse(url) - quarantined_instances = - blocked_instances() + with allowed <- allowed_instances(), + false <- Enum.empty?(allowed) do + allowed |> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples() |> Pleroma.Web.ActivityPub.MRF.subdomains_regex() + |> Pleroma.Web.ActivityPub.MRF.subdomain_match?(host) + else + _ -> + quarantined_instances = + blocked_instances() + |> Pleroma.Web.ActivityPub.MRF.instance_list_from_tuples() + |> Pleroma.Web.ActivityPub.MRF.subdomains_regex() - !Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host) + not Pleroma.Web.ActivityPub.MRF.subdomain_match?(quarantined_instances, host) + end end @spec recipients(User.t(), Activity.t()) :: list(User.t()) | [] diff --git a/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs b/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs index 21c574ba3..c42b82810 100644 --- a/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs +++ b/test/pleroma/web/plugs/mapped_signature_to_identity_plug_test.exs @@ -69,6 +69,47 @@ test "it considers a mapped identity to be invalid when the associated instance assert %{valid_signature: false} == conn.assigns end + test "allowlist federation: it considers a mapped identity to be valid when the associated instance is allowed" do + clear_config([:activitypub, :authorized_fetch_mode], true) + + clear_config([:mrf_simple, :accept], [ + {"mastodon.example.org", "anime is allowed"} + ]) + + on_exit(fn -> + Pleroma.Config.put([:activitypub, :authorized_fetch_mode], false) + Pleroma.Config.put([:mrf_simple, :accept], []) + end) + + conn = + build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"}) + |> set_signature("http://mastodon.example.org/users/admin") + |> MappedSignatureToIdentityPlug.call(%{}) + + assert conn.assigns[:valid_signature] + refute is_nil(conn.assigns.user) + end + + test "allowlist federation: it considers a mapped identity to be invalid when the associated instance is not allowed" do + clear_config([:activitypub, :authorized_fetch_mode], true) + + clear_config([:mrf_simple, :accept], [ + {"misskey.example.org", "anime is allowed"} + ]) + + on_exit(fn -> + Pleroma.Config.put([:activitypub, :authorized_fetch_mode], false) + Pleroma.Config.put([:mrf_simple, :accept], []) + end) + + conn = + build_conn(:post, "/doesntmattter", %{"actor" => "http://mastodon.example.org/users/admin"}) + |> set_signature("http://mastodon.example.org/users/admin") + |> MappedSignatureToIdentityPlug.call(%{}) + + assert %{valid_signature: false} == conn.assigns + end + @tag skip: "known breakage; the testsuite presently depends on it" test "it considers a mapped identity to be invalid when the identity cannot be found" do conn = From 1fa3c0b485fd60df8ed78531435dbf58db375b03 Mon Sep 17 00:00:00 2001 From: Atsuko Karagi Date: Sat, 8 Apr 2023 18:06:58 +0200 Subject: [PATCH 33/35] Remove support for outdated Create format --- .../activity_pub/activity_pub_controller.ex | 21 -------------- .../activity_pub_controller_test.exs | 29 ------------------- 2 files changed, 50 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index c07f91b2e..5d7297c23 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -293,33 +293,12 @@ def inbox(%{assigns: %{valid_signature: false}} = conn, _params) do |> json("Invalid HTTP Signature") end - # POST /relay/inbox -or- POST /internal/fetch/inbox - def inbox(conn, %{"type" => "Create"} = params) do - if FederatingPlug.federating?() do - post_inbox_relayed_create(conn, params) - else - conn - |> put_status(:bad_request) - |> json("Not federating") - end - end - def inbox(conn, _params) do conn |> put_status(:bad_request) |> json("error, missing HTTP Signature") end - defp post_inbox_relayed_create(conn, params) do - Logger.debug( - "Signature missing or not from author, relayed Create message, fetching object from source" - ) - - Fetcher.fetch_object_from_id(params["object"]["id"]) - - json(conn, "ok") - end - defp represent_service_actor(%User{} = user, conn) do conn |> put_resp_content_type("application/activity+json") diff --git a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs index 2008ebf04..0d4a7ec2e 100644 --- a/test/pleroma/web/activity_pub/activity_pub_controller_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_controller_test.exs @@ -662,35 +662,6 @@ test "accept follow activity", %{conn: conn} do assert_receive {:mix_shell, :info, ["https://relay.mastodon.host/actor"]} end - @tag capture_log: true - test "without valid signature, " <> - "it only accepts Create activities and requires enabled federation", - %{conn: conn} do - data = File.read!("test/fixtures/mastodon-post-activity.json") |> Jason.decode!() - non_create_data = File.read!("test/fixtures/mastodon-announce.json") |> Jason.decode!() - - conn = put_req_header(conn, "content-type", "application/activity+json") - - clear_config([:instance, :federating], false) - - conn - |> post("/inbox", data) - |> json_response(403) - - conn - |> post("/inbox", non_create_data) - |> json_response(403) - - clear_config([:instance, :federating], true) - - ret_conn = post(conn, "/inbox", data) - assert "ok" == json_response(ret_conn, 200) - - conn - |> post("/inbox", non_create_data) - |> json_response(400) - end - test "accepts Add/Remove activities", %{conn: conn} do object_id = "c61d6733-e256-4fe1-ab13-1e369789423f" From 522221f7fb76859b23840bb0c9de0018dae41cbf Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Fri, 14 Apr 2023 17:56:34 +0100 Subject: [PATCH 34/35] Mix format --- config/description.exs | 8 ++++---- lib/mix/tasks/pleroma/diagnostics.ex | 4 ++-- .../web/activity_pub/activity_pub_controller.ex | 1 - lib/pleroma/web/nodeinfo/nodeinfo_controller.ex | 5 ----- lib/pleroma/web/plugs/http_security_plug.ex | 12 +++++++----- .../controllers/timeline_controller_test.exs | 7 +++---- 6 files changed, 16 insertions(+), 21 deletions(-) diff --git a/config/description.exs b/config/description.exs index d329f8afa..bd20cb239 100644 --- a/config/description.exs +++ b/config/description.exs @@ -3161,10 +3161,10 @@ children: frontend_options }, %{ - key: :pickable, - type: {:list, :string}, - description: - "A list containing all frontends users can pick as their preference, format is :name/:ref, e.g pleroma-fe/stable." + key: :pickable, + type: {:list, :string}, + description: + "A list containing all frontends users can pick as their preference, format is :name/:ref, e.g pleroma-fe/stable." } ] }, diff --git a/lib/mix/tasks/pleroma/diagnostics.ex b/lib/mix/tasks/pleroma/diagnostics.ex index 3b6063723..87be38b78 100644 --- a/lib/mix/tasks/pleroma/diagnostics.ex +++ b/lib/mix/tasks/pleroma/diagnostics.ex @@ -120,8 +120,8 @@ def run(["known_network", nickname]) do params ) |> limit(20) - + Ecto.Adapters.SQL.explain(Repo, :all, query, analyze: true, timeout: :infinity) - |> IO.puts() + |> IO.puts() end end diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 5d7297c23..4e6842d85 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -8,7 +8,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do alias Pleroma.Activity alias Pleroma.Delivery alias Pleroma.Object - alias Pleroma.Object.Fetcher alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.InternalFetchActor diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index 4c5a36895..ea2d86f92 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -5,11 +5,6 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do use Pleroma.Web, :controller - alias Pleroma.Config - alias Pleroma.Stats - alias Pleroma.User - alias Pleroma.Web.Federator.Publisher - alias Pleroma.Web.MastodonAPI.InstanceView alias Pleroma.Web.Endpoint alias Pleroma.Web.Nodeinfo.Nodeinfo diff --git a/lib/pleroma/web/plugs/http_security_plug.ex b/lib/pleroma/web/plugs/http_security_plug.ex index 570aeefff..d7cff7343 100644 --- a/lib/pleroma/web/plugs/http_security_plug.ex +++ b/lib/pleroma/web/plugs/http_security_plug.ex @@ -117,11 +117,13 @@ defp csp_string(conn) do font_src = "font-src 'self'" script_src = "script-src 'self' '#{nonce_tag}' " - script_src = if @mix_env == :dev do - "script-src 'self' 'unsafe-eval' 'unsafe-inline'" - else - script_src - end + + script_src = + if @mix_env == :dev do + "script-src 'self' 'unsafe-eval' 'unsafe-inline'" + else + script_src + end report = if report_uri, do: ["report-uri ", report_uri, ";report-to csp-endpoint"] insecure = if scheme == "https", do: "upgrade-insecure-requests" diff --git a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs index fcc7a204e..eed12234f 100644 --- a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs @@ -423,10 +423,9 @@ test "should return 404 if disabled" do test "should not return 404 if local is specified" do clear_config([:instance, :federated_timeline_available], false) - result = - build_conn() - |> get("/api/v1/timelines/public?local=true") - |> json_response_and_validate_schema(200) + build_conn() + |> get("/api/v1/timelines/public?local=true") + |> json_response_and_validate_schema(200) end end From 963d29ad8cd2c32fe408d608bd644e1db3ebbda1 Mon Sep 17 00:00:00 2001 From: FloatingGhost Date: Fri, 14 Apr 2023 18:00:59 +0100 Subject: [PATCH 35/35] 2023.04 Release --- CHANGELOG.md | 7 ++++++- mix.exs | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f481aad5..9ef9d4e97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,7 +4,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). -## Unreleased +## 2023.04 ## Added - Nodeinfo keys for unauthenticated timeline visibility @@ -13,6 +13,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Ability to swap between installed standard frontends - *mastodon frontends are still not counted as standard frontends due to the complexity in serving them correctly*. +### Upgrade Notes +- Elixir 1.14 is now required. If your distribution does not package this, you can + use [asdf](https://asdf-vm.com/). At time of writing, elixir 1.14.3 / erlang 25.3 + is confirmed to work. + ## 2023.03 ## Fixed diff --git a/mix.exs b/mix.exs index aa1dde667..6544529bd 100644 --- a/mix.exs +++ b/mix.exs @@ -4,7 +4,7 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("3.7.1"), + version: version("3.7.2"), elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix] ++ Mix.compilers(),