diff --git a/CHANGELOG.md b/CHANGELOG.md index c12deb72b..07a4496b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,20 @@ 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/). +## 2023.04 + +## Added +- 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*. + +### 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 @@ -19,6 +33,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/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 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/config/config.exs b/config/config.exs index 5eaa8ce76..95c576385 100644 --- a/config/config.exs +++ b/config/config.exs @@ -65,7 +65,6 @@ config :pleroma, Pleroma.Upload, link_name: false, proxy_remote: false, filename_display_max_length: 30, - default_description: nil, base_url: nil config :pleroma, Pleroma.Uploaders.Local, uploads: "uploads" @@ -261,7 +260,8 @@ config :pleroma, :instance, 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: [ @@ -745,6 +745,9 @@ config :pleroma, :frontends, 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", @@ -810,7 +813,7 @@ config :pleroma, :majic_pool, size: 2 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/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/config/description.exs b/config/description.exs index 2a2d70a7b..bd20cb239 100644 --- a/config/description.exs +++ b/config/description.exs @@ -969,6 +969,12 @@ config :pleroma, :config_description, [ 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." } ] }, @@ -2993,6 +2999,11 @@ config :pleroma, :config_description, [ key: :federated, type: :boolean, description: "Disallow viewing the whole known network timeline." + }, + %{ + key: :bubble, + type: :boolean, + description: "Disallow viewing the bubble timeline." } ] }, @@ -3148,6 +3159,12 @@ config :pleroma, :config_description, [ 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/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, :auth, oauth_consumer_strategies: [] 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/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) 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). 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 diff --git a/lib/mix/tasks/pleroma/diagnostics.ex b/lib/mix/tasks/pleroma/diagnostics.ex index 3914540ca..87be38b78 100644 --- a/lib/mix/tasks/pleroma/diagnostics.ex +++ b/lib/mix/tasks/pleroma/diagnostics.ex @@ -82,4 +82,46 @@ defmodule Mix.Tasks.Pleroma.Diagnostics 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 diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index c5b514742..c94667fb9 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -277,6 +277,13 @@ defmodule Pleroma.Activity do def get_create_by_object_ap_id_with_object(_), do: nil + def get_local_create_by_object_ap_id(ap_id) when is_binary(ap_id) do + ap_id + |> create_by_object_ap_id() + |> 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/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 @@ defmodule Pleroma.Instances.Instance 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/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 @@ defmodule Pleroma.Upload 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/lib/pleroma/user.ex b/lib/pleroma/user.ex index f94202af5..ead37ccca 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -366,21 +366,21 @@ defmodule Pleroma.User do 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/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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do @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/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index c07f91b2e..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 @@ -293,33 +292,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController 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/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 @@ defmodule Pleroma.Web.ActivityPub.Publisher 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/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 @@ defmodule Pleroma.Web.AkkomaAPI.FrontendSettingsController do |> 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/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/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 @@ defmodule Pleroma.Web.ApiSpec.FrontendSettingsOperation 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 @@ defmodule Pleroma.Web.ApiSpec.FrontendSettingsOperation 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 @@ defmodule Pleroma.Web.ApiSpec.FrontendSettingsOperation 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 @@ defmodule Pleroma.Web.ApiSpec.FrontendSettingsOperation 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 @@ defmodule Pleroma.Web.ApiSpec.FrontendSettingsOperation 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/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 @@ defmodule Pleroma.Web.ApiSpec.TimelineOperation 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/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 @@ defmodule Pleroma.Web.Fallback.RedirectController 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 @@ defmodule Pleroma.Web.Fallback.RedirectController do 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 @@ defmodule Pleroma.Web.Fallback.RedirectController 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 @@ defmodule Pleroma.Web.Fallback.RedirectController 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/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index 2d0e36420..1d4e734a4 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,19 +28,25 @@ 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] ) + 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 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController 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 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController 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 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController 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", @@ -96,21 +112,22 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController 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 + Logger.debug("TimelineController.public") local_only = params[:local] + timeline_type = if local_only, do: :local, else: :federated + + 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 + Logger.debug("TimelineController.public: fetching activities") - if is_nil(user) and restrict_unauthenticated?(local_only) do - fail_on_bad_auth(conn) - else activities = params |> Map.put(:type, ["Create"]) @@ -123,6 +140,8 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController 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", @@ -131,20 +150,32 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController 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()] - ) + Logger.debug("TimelineController.bubble") - 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()] + ) + + Logger.debug("TimelineController.bubble: fetching activities") + activities = params |> Map.put(:type, ["Create"]) @@ -154,6 +185,8 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do |> Map.put(:instance, bubble_instances) |> ActivityPub.fetch_public_activities() + Logger.debug("TimelineController.bubble: rendering") + conn |> add_link_headers(activities) |> render("index.json", @@ -195,7 +228,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController 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/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex index 2717da99d..2b5354873 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 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do "pleroma:api/v1/notifications:include_types_filter", "quote_posting", "editing", + if !Enum.empty?(Config.get([:instance, :local_bubble], [])) do + "bubble_timeline" + end, if Config.get([:media_proxy, :enabled]) do "media_proxy" end, 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 @@ defmodule Pleroma.Web.MastodonAPI.StatusView 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 @@ defmodule Pleroma.Web.MastodonAPI.StatusView 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 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do } 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 @@ defmodule Pleroma.Web.MastodonAPI.StatusView 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 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do 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() diff --git a/lib/pleroma/web/nodeinfo/nodeinfo.ex b/lib/pleroma/web/nodeinfo/nodeinfo.ex index bf0d65f45..532ae53a7 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo.ex @@ -71,7 +71,15 @@ defmodule Pleroma.Web.Nodeinfo.Nodeinfo 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) && + 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 a0dee7c6b..ea2d86f92 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -5,12 +5,8 @@ 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 def schemas(conn, _params) do response = %{ @@ -29,101 +25,15 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController 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], []) - } - } - 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/o_status/o_status_controller.ex b/lib/pleroma/web/o_status/o_status_controller.ex index 7731d847f..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 @@ defmodule Pleroma.Web.OStatus.OStatusController do 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(id)}, {_, true} <- {:public?, Visibility.is_public?(activity)}, {_, false} <- {:local_public?, Visibility.is_local_public?(activity)} do redirect(conn, to: "/notice/#{activity.id}") diff --git a/lib/pleroma/web/plugs/frontend_static.ex b/lib/pleroma/web/plugs/frontend_static.ex index 40f51e149..41b8ba46b 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 @@ defmodule Pleroma.Web.Plugs.FrontendStatic 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 @@ defmodule Pleroma.Web.Plugs.FrontendStatic 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,31 @@ defmodule Pleroma.Web.Plugs.FrontendStatic do end end + def preferred_frontend(conn) do + %{req_cookies: cookies} = + conn + |> Plug.Conn.fetch_cookies() + + Map.get(cookies, @frontend_cookie_name) + end + + # Only override primary frontend + def preferred_or_fallback(conn, :primary) do + case preferred_frontend(conn) do + nil -> + :primary + + frontend -> + if Enum.member?(Pleroma.Config.get([:frontends, :pickable], []), frontend) do + frontend + else + :primary + end + 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/plugs/http_security_plug.ex b/lib/pleroma/web/plugs/http_security_plug.ex index b1f1ada94..d7cff7343 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,14 @@ defmodule Pleroma.Web.Plugs.HTTPSecurityPlug 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" 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) || diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index faaf3d679..7550eefdf 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -466,6 +466,29 @@ 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) + + 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) @@ -598,7 +621,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 +675,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/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..a0b0a2361 --- /dev/null +++ b/lib/pleroma/web/templates/akkoma_api/frontend_switcher/switch.html.eex @@ -0,0 +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 %> + diff --git a/mix.exs b/mix.exs index 098ea15b0..6544529bd 100644 --- a/mix.exs +++ b/mix.exs @@ -4,8 +4,8 @@ defmodule Pleroma.Mixfile do def project do [ app: :pleroma, - version: version("3.7.1"), - elixir: "~> 1.12", + version: version("3.7.2"), + elixir: "~> 1.14", elixirc_paths: elixirc_paths(Mix.env()), compilers: [:phoenix] ++ Mix.compilers(), elixirc_options: [warnings_as_errors: warnings_as_errors()], 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 000000000..c02592d97 Binary files /dev/null and b/priv/static/emoji/hehe.png differ diff --git a/priv/static/emoji/nothehe.png b/priv/static/emoji/nothehe.png new file mode 100644 index 000000000..427bb6d61 Binary files /dev/null and b/priv/static/emoji/nothehe.png differ 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 @@ defmodule Pleroma.UploadTest do assert result == %{ "id" => result["id"], - "name" => "image.jpg", + "name" => "", "type" => "Document", "mediaType" => "image/jpeg", "url" => [ @@ -154,19 +154,6 @@ defmodule Pleroma.UploadTest 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 @@ defmodule Pleroma.UploadTest 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/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 @@ defmodule Pleroma.UserTest 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" 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 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubControllerTest 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" diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index 17c52fc91..b65575f01 100644 --- a/test/pleroma/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -1303,33 +1303,19 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest 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" 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() 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 @@ defmodule Pleroma.Web.MastodonAPI.MediaControllerTest 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/controllers/timeline_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs index aa9006681..eed12234f 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,25 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest 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) + + 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 +1055,8 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do 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 +1078,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest 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 +1090,20 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest 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/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 @@ defmodule Pleroma.Web.MastodonAPI.UpdateCredentialsTest do 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"]) 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 @@ defmodule Pleroma.Web.MastodonAPI.AccountViewTest 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}) diff --git a/test/pleroma/web/node_info_test.exs b/test/pleroma/web/node_info_test.exs index 05a078266..ff14db460 100644 --- a/test/pleroma/web/node_info_test.exs +++ b/test/pleroma/web/node_info_test.exs @@ -292,4 +292,38 @@ defmodule Pleroma.Web.NodeInfoTest do 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 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 @@ defmodule Pleroma.Web.Plugs.FrontendStaticPlugTest do "main", "ostatus_subscribe", "oauth", + "akkoma", "objects", "activities", "notice", 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 @@ defmodule Pleroma.Web.Plugs.MappedSignatureToIdentityPlugTest do 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 =