diff --git a/changelog.d/bookmark-folders.ignore b/changelog.d/bookmark-folders.ignore new file mode 100644 index 000000000..8705ac00b --- /dev/null +++ b/changelog.d/bookmark-folders.ignore @@ -0,0 +1 @@ +Various bookmark folders-related improvements diff --git a/changelog.d/boost-visibilities.add b/changelog.d/boost-visibilities.add new file mode 100644 index 000000000..317d9840d --- /dev/null +++ b/changelog.d/boost-visibilities.add @@ -0,0 +1 @@ +Allow fine-grained announce visibilities diff --git a/changelog.d/credo-aliases-sort-fixes.skip b/changelog.d/credo-aliases-sort-fixes.skip new file mode 100644 index 000000000..e69de29bb diff --git a/changelog.d/elixir-1.19-cherrypicks.change b/changelog.d/elixir-1.19-cherrypicks.change new file mode 100644 index 000000000..7e56be008 --- /dev/null +++ b/changelog.d/elixir-1.19-cherrypicks.change @@ -0,0 +1 @@ +No-op code correctness improvements detected by Elixir 1.19 compiler diff --git a/changelog.d/exclusive-lists.add b/changelog.d/exclusive-lists.add new file mode 100644 index 000000000..bbd722f07 --- /dev/null +++ b/changelog.d/exclusive-lists.add @@ -0,0 +1 @@ +Support lists `exclusive` param diff --git a/changelog.d/gopher-genserver-crash-on-boot.fix b/changelog.d/gopher-genserver-crash-on-boot.fix new file mode 100644 index 000000000..3b51662be --- /dev/null +++ b/changelog.d/gopher-genserver-crash-on-boot.fix @@ -0,0 +1 @@ +Gopher: Fix Ranch listener not being stopped properly on Pleroma restart when database configuration is enabled diff --git a/changelog.d/hubzilla-alsoknownas.fix b/changelog.d/hubzilla-alsoknownas.fix new file mode 100644 index 000000000..2a2969807 --- /dev/null +++ b/changelog.d/hubzilla-alsoknownas.fix @@ -0,0 +1 @@ +Fix fetching Hubzilla Actors with alsoKnownAs as string diff --git a/changelog.d/live-dashboard-redirect.fix b/changelog.d/live-dashboard-redirect.fix new file mode 100644 index 000000000..10588d89e --- /dev/null +++ b/changelog.d/live-dashboard-redirect.fix @@ -0,0 +1 @@ +Fix /phoenix/live_dashboard redirect not working when user added a path segment diff --git a/changelog.d/missing-static-file.fix b/changelog.d/missing-static-file.fix new file mode 100644 index 000000000..c7ef805aa --- /dev/null +++ b/changelog.d/missing-static-file.fix @@ -0,0 +1 @@ +Fix 404 error codes for missing static files diff --git a/changelog.d/old-migrations.fix b/changelog.d/old-migrations.fix new file mode 100644 index 000000000..49566c896 --- /dev/null +++ b/changelog.d/old-migrations.fix @@ -0,0 +1 @@ +Correct old migrations for expiring activities and user access tokens. diff --git a/changelog.d/rate-limiter-hardening.fix b/changelog.d/rate-limiter-hardening.fix new file mode 100644 index 000000000..a3af8fcc4 --- /dev/null +++ b/changelog.d/rate-limiter-hardening.fix @@ -0,0 +1 @@ +Stop the rate limiter from crashing when run with wrong settings. diff --git a/changelog.d/restore-embeds.fix b/changelog.d/restore-embeds.fix new file mode 100644 index 000000000..5a2a1c4fe --- /dev/null +++ b/changelog.d/restore-embeds.fix @@ -0,0 +1 @@ +Restore embeds route diff --git a/changelog.d/reverseproxy-recursive-redirect.fix b/changelog.d/reverseproxy-recursive-redirect.fix new file mode 100644 index 000000000..744109fd6 --- /dev/null +++ b/changelog.d/reverseproxy-recursive-redirect.fix @@ -0,0 +1 @@ +ReverseProxy: Recursively follow redirects until redirect_limit is reached diff --git a/changelog.d/search-indexing.change b/changelog.d/search-indexing.change new file mode 100644 index 000000000..766934f3f --- /dev/null +++ b/changelog.d/search-indexing.change @@ -0,0 +1 @@ +Filter indexable activities before inserting indexing jobs into the queue. diff --git a/changelog.d/twitter-api.skip b/changelog.d/twitter-api.skip new file mode 100644 index 000000000..e69de29bb diff --git a/changelog.d/user-view.ignore b/changelog.d/user-view.ignore new file mode 100644 index 000000000..37e9a7e09 --- /dev/null +++ b/changelog.d/user-view.ignore @@ -0,0 +1 @@ +Avoid code duplication in UserView diff --git a/docs/development/API/differences_in_mastoapi_responses.md b/docs/development/API/differences_in_mastoapi_responses.md index 358d05bf4..194c98e3f 100644 --- a/docs/development/API/differences_in_mastoapi_responses.md +++ b/docs/development/API/differences_in_mastoapi_responses.md @@ -74,7 +74,7 @@ Pleroma does not process remote images and therefore cannot include fields such The `GET /api/v1/bookmarks` endpoint accepts optional parameter `folder_id` for bookmark folder ID. -The `POST /api/v1/statuses/:id/bookmark` endpoint accepts optional parameter `folder_id` for bookmark folder ID. +The `POST /api/v1/statuses/:id/bookmark` endpoint accepts optional parameter `folder_id` for bookmark folder ID. Bookmarking an already bookmarked post will update the folder association, or remove it if `folder_id` is omitted or `null`. ## Accounts diff --git a/lib/pleroma/bookmark.ex b/lib/pleroma/bookmark.ex index 1a2a63b82..1c78d495f 100644 --- a/lib/pleroma/bookmark.ex +++ b/lib/pleroma/bookmark.ex @@ -19,7 +19,7 @@ defmodule Pleroma.Bookmark do schema "bookmarks" do belongs_to(:user, User, type: FlakeId.Ecto.CompatType) belongs_to(:activity, Activity, type: FlakeId.Ecto.CompatType) - belongs_to(:folder, BookmarkFolder, type: FlakeId.Ecto.CompatType) + belongs_to(:folder, BookmarkFolder, type: FlakeId.Ecto.Type) timestamps() end @@ -38,7 +38,7 @@ defmodule Pleroma.Bookmark do |> validate_required([:user_id, :activity_id]) |> unique_constraint(:activity_id, name: :bookmarks_user_id_activity_id_index) |> Repo.insert( - on_conflict: [set: [folder_id: folder_id]], + on_conflict: [set: [folder_id: folder_id, updated_at: NaiveDateTime.utc_now()]], conflict_target: [:user_id, :activity_id] ) end @@ -76,11 +76,4 @@ defmodule Pleroma.Bookmark do |> Repo.one() |> Repo.delete() end - - def set_folder(bookmark, folder_id) do - bookmark - |> cast(%{folder_id: folder_id}, [:folder_id]) - |> validate_required([:folder_id]) - |> Repo.update() - end end diff --git a/lib/pleroma/bookmark_folder.ex b/lib/pleroma/bookmark_folder.ex index 14d37e197..65856bb29 100644 --- a/lib/pleroma/bookmark_folder.ex +++ b/lib/pleroma/bookmark_folder.ex @@ -14,7 +14,7 @@ defmodule Pleroma.BookmarkFolder do alias Pleroma.User @type t :: %__MODULE__{} - @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true} + @primary_key {:id, FlakeId.Ecto.Type, autogenerate: true} schema "bookmark_folders" do field(:name, :string) diff --git a/lib/pleroma/config_db.ex b/lib/pleroma/config_db.ex index e9990fa35..2c3df773b 100644 --- a/lib/pleroma/config_db.ex +++ b/lib/pleroma/config_db.ex @@ -10,6 +10,7 @@ defmodule Pleroma.ConfigDB do import Pleroma.Web.Gettext alias __MODULE__ + alias Pleroma.EctoType.Config.RateLimit alias Pleroma.Repo @type t :: %__MODULE__{} @@ -60,8 +61,59 @@ defmodule Pleroma.ConfigDB do |> cast(params, [:key, :group, :value]) |> validate_required([:key, :group, :value]) |> unique_constraint(:key, name: :config_group_key_index) + |> validate_rate_limit() end + defp validate_rate_limit(changeset) do + group = get_field(changeset, :group) + key = get_field(changeset, :key) + + if group == :pleroma and key == :rate_limit do + value = get_field(changeset, :value) + + case normalize_rate_limit(value) do + {:ok, normalized_value} -> + put_change(changeset, :value, normalized_value) + + {:error, {limiter_name, reason}} -> + add_error( + changeset, + :value, + "invalid :rate_limit value for #{inspect(limiter_name)}: #{reason}" + ) + end + else + changeset + end + end + + defp normalize_rate_limit(nil), do: {:ok, nil} + + defp normalize_rate_limit(%{} = value), do: normalize_rate_limit(Map.to_list(value)) + + defp normalize_rate_limit(value) when is_list(value) do + if Keyword.keyword?(value) do + value + |> Enum.reduce_while({:ok, []}, fn {limiter_name, limiter_value}, {:ok, acc} -> + case RateLimit.cast_with_error(limiter_value) do + {:ok, normalized_limiter_value} -> + {:cont, {:ok, [{limiter_name, normalized_limiter_value} | acc]}} + + {:error, reason} -> + {:halt, {:error, {limiter_name, reason}}} + end + end) + |> case do + {:ok, acc} -> {:ok, Enum.reverse(acc)} + {:error, _} = error -> error + end + else + {:error, {:rate_limit, "must be a keyword list"}} + end + end + + defp normalize_rate_limit(_), do: {:error, {:rate_limit, "must be a keyword list"}} + defp create(params) do %ConfigDB{} |> changeset(params) diff --git a/lib/pleroma/ecto_type/config/rate_limit.ex b/lib/pleroma/ecto_type/config/rate_limit.ex new file mode 100644 index 000000000..0518ffd7e --- /dev/null +++ b/lib/pleroma/ecto_type/config/rate_limit.ex @@ -0,0 +1,71 @@ +# Pleroma: A lightweight social networking server +# Copyright © Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.EctoType.Config.RateLimit do + @moduledoc false + + use Ecto.Type + + @type t :: + nil + | {non_neg_integer(), non_neg_integer()} + | [{non_neg_integer(), non_neg_integer()}] + + @impl true + def type, do: :term + + @impl true + def cast(value) do + case cast_with_error(value) do + {:ok, normalized} -> {:ok, normalized} + {:error, _reason} -> :error + end + end + + @impl true + def load(value), do: cast(value) + + @impl true + def dump(value), do: cast(value) + + @spec cast_with_error(term()) :: {:ok, t()} | {:error, String.t()} + def cast_with_error(nil), do: {:ok, nil} + + def cast_with_error({scale, limit}) do + with {:ok, scale} <- parse_integer(scale, "scale"), + {:ok, limit} <- parse_integer(limit, "limit"), + true <- scale >= 1 and limit >= 1 do + {:ok, {scale, limit}} + else + false -> {:error, "scale and limit must be >= 1"} + {:error, reason} -> {:error, reason} + end + end + + def cast_with_error([{_, _} = unauth, {_, _} = auth]) do + with {:ok, unauth} <- cast_with_error(unauth), + {:ok, auth} <- cast_with_error(auth) do + {:ok, [unauth, auth]} + else + {:error, reason} -> {:error, reason} + end + end + + def cast_with_error(_), + do: + {:error, "must be a {scale, limit} tuple, a [{scale, limit}, {scale, limit}] list, or nil"} + + defp parse_integer(value, _label) when is_integer(value), do: {:ok, value} + + defp parse_integer(value, label) when is_binary(value) do + value = String.trim(value) + + case Integer.parse(value) do + {number, ""} -> {:ok, number} + _ -> {:error, "#{label} must be an integer"} + end + end + + defp parse_integer(_value, label), do: {:error, "#{label} must be an integer"} +end diff --git a/lib/pleroma/gopher/server.ex b/lib/pleroma/gopher/server.ex index add3ba925..7b6985efe 100644 --- a/lib/pleroma/gopher/server.ex +++ b/lib/pleroma/gopher/server.ex @@ -21,10 +21,13 @@ defmodule Pleroma.Gopher.Server do def init([ip, port]) do Logger.info("Starting gopher server on #{port}") + Process.flag(:trap_exit, true) + + listener = :gopher {:ok, _pid} = :ranch.start_listener( - :gopher, + listener, :ranch_tcp, %{ num_acceptors: 100, @@ -35,7 +38,11 @@ defmodule Pleroma.Gopher.Server do [] ) - {:ok, %{ip: ip, port: port}} + {:ok, %{ip: ip, port: port, listener: listener}} + end + + def terminate(_reason, state) do + :ranch.stop_listener(state.listener) end end diff --git a/lib/pleroma/list.ex b/lib/pleroma/list.ex index b446b91a0..cd23bd0a9 100644 --- a/lib/pleroma/list.ex +++ b/lib/pleroma/list.ex @@ -17,13 +17,14 @@ defmodule Pleroma.List do field(:title, :string) field(:following, {:array, :string}, default: []) field(:ap_id, :string) + field(:exclusive, :boolean, default: false) timestamps() end - def title_changeset(list, attrs \\ %{}) do + def update_changeset(list, attrs \\ %{}) do list - |> cast(attrs, [:title]) + |> cast(attrs, [:title, :exclusive]) |> validate_required([:title]) end @@ -91,14 +92,14 @@ defmodule Pleroma.List do |> Repo.all() end - def rename(%Pleroma.List{} = list, title) do + def update(%Pleroma.List{} = list, params) do list - |> title_changeset(%{title: title}) + |> update_changeset(params) |> Repo.update() end - def create(title, %User{} = creator) do - changeset = title_changeset(%Pleroma.List{user_id: creator.id}, %{title: title}) + def create(params, %User{} = creator) do + changeset = update_changeset(%Pleroma.List{user_id: creator.id}, params) if changeset.valid? do Repo.transaction(fn -> @@ -149,4 +150,14 @@ defmodule Pleroma.List do end def member?(_, _), do: false + + def get_exclusive_list_members(%User{id: user_id}) do + Pleroma.List + |> where([l], l.user_id == ^user_id) + |> where([l], l.exclusive == true) + |> select([l], l.following) + |> Repo.all() + |> List.flatten() + |> Enum.uniq() + end end diff --git a/lib/pleroma/marker.ex b/lib/pleroma/marker.ex index 68b054e4d..fab24d183 100644 --- a/lib/pleroma/marker.ex +++ b/lib/pleroma/marker.ex @@ -78,7 +78,7 @@ defmodule Pleroma.Marker do defp get_marker(user, timeline) do case Repo.find_resource(get_query(user, timeline)) do - {:ok, marker} -> %__MODULE__{marker | user: user} + {:ok, %__MODULE__{} = marker} -> %{marker | user: user} _ -> %__MODULE__{timeline: timeline, user_id: user.id} end end diff --git a/lib/pleroma/mfa/changeset.ex b/lib/pleroma/mfa/changeset.ex index 3ec3cfe91..2045c3a7c 100644 --- a/lib/pleroma/mfa/changeset.ex +++ b/lib/pleroma/mfa/changeset.ex @@ -8,7 +8,8 @@ defmodule Pleroma.MFA.Changeset do alias Pleroma.User def disable(%Ecto.Changeset{} = changeset, force \\ false) do - settings = + %Settings{} = + settings = changeset |> Ecto.Changeset.apply_changes() |> MFA.fetch_settings() @@ -20,20 +21,20 @@ defmodule Pleroma.MFA.Changeset do end end - def disable_totp(%User{multi_factor_authentication_settings: settings} = user) do + def disable_totp(%User{multi_factor_authentication_settings: %Settings{} = settings} = user) do user |> put_change(%Settings{settings | totp: %Settings.TOTP{}}) end - def confirm_totp(%User{multi_factor_authentication_settings: settings} = user) do - totp_settings = %Settings.TOTP{settings.totp | confirmed: true} + def confirm_totp(%User{multi_factor_authentication_settings: %Settings{} = settings} = user) do + totp_settings = %Settings.TOTP{(%Settings.TOTP{} = settings.totp) | confirmed: true} user |> put_change(%Settings{settings | totp: totp_settings, enabled: true}) end def setup_totp(%User{} = user, attrs) do - mfa_settings = MFA.fetch_settings(user) + %Settings{} = mfa_settings = MFA.fetch_settings(user) totp_settings = %Settings.TOTP{} @@ -46,7 +47,7 @@ defmodule Pleroma.MFA.Changeset do def cast_backup_codes(%User{} = user, codes) do user |> put_change(%Settings{ - user.multi_factor_authentication_settings + (%Settings{} = user.multi_factor_authentication_settings) | backup_codes: codes }) end diff --git a/lib/pleroma/reverse_proxy/client/hackney.ex b/lib/pleroma/reverse_proxy/client/hackney.ex index 7e1fca80d..4c81a6225 100644 --- a/lib/pleroma/reverse_proxy/client/hackney.ex +++ b/lib/pleroma/reverse_proxy/client/hackney.ex @@ -4,6 +4,9 @@ defmodule Pleroma.ReverseProxy.Client.Hackney do @behaviour Pleroma.ReverseProxy.Client + @redirect_limit 5 + + require Logger # In-app redirect handler to avoid Hackney redirect bugs: # - https://github.com/benoitc/hackney/issues/527 (relative/protocol-less redirects can crash Hackney) @@ -31,26 +34,15 @@ defmodule Pleroma.ReverseProxy.Client.Hackney do if opts[:follow_redirect] != false do {_state, req_opts} = Access.get_and_update(opts, :follow_redirect, fn a -> {a, false} end) + env = %{method: method, headers: headers, body: body, req_opts: req_opts} res = :hackney.request(method, url, headers, body, req_opts) case res do {:ok, code, resp_headers, _client} when code in @redirect_statuses -> - :hackney.request( - method, - absolute_redirect_url(url, resp_headers), - headers, - body, - req_opts - ) + redirect(url, resp_headers, env, @redirect_limit) {:ok, code, resp_headers} when code in @redirect_statuses -> - :hackney.request( - method, - absolute_redirect_url(url, resp_headers), - headers, - body, - req_opts - ) + redirect(url, resp_headers, env, @redirect_limit) _ -> res @@ -71,4 +63,32 @@ defmodule Pleroma.ReverseProxy.Client.Hackney do @impl true def close(ref), do: :hackney.close(ref) + + defp redirect(url, resp_headers, env, limit) when limit == 0 do + new_url = absolute_redirect_url(url, resp_headers) + + Logger.debug( + "#{__MODULE__}: Handling redirect #{url} -> #{new_url}; redirect limit was reached - returning response after final redirect" + ) + + :hackney.request(env.method, new_url, env.headers, env.body, env.req_opts) + end + + defp redirect(url, resp_headers, env, limit) do + new_url = absolute_redirect_url(url, resp_headers) + Logger.debug("#{__MODULE__}: handling redirect #{url} -> #{new_url}; limit = #{limit}") + + res = :hackney.request(env.method, new_url, env.headers, env.body, env.req_opts) + + case res do + {:ok, code, new_resp_headers, _client} when code in @redirect_statuses -> + redirect(new_url, new_resp_headers, env, limit - 1) + + {:ok, code, new_resp_headers} when code in @redirect_statuses -> + redirect(new_url, new_resp_headers, env, limit - 1) + + _ -> + res + end + end end diff --git a/lib/pleroma/search.ex b/lib/pleroma/search.ex index 30b3ba958..8477873f9 100644 --- a/lib/pleroma/search.ex +++ b/lib/pleroma/search.ex @@ -1,11 +1,28 @@ defmodule Pleroma.Search do + alias Pleroma.Activity + alias Pleroma.Object + alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Workers.SearchIndexingWorker - def add_to_index(%Pleroma.Activity{id: activity_id}) do - SearchIndexingWorker.new(%{"op" => "add_to_index", "activity" => activity_id}) - |> Oban.insert() + @spec add_to_index(Activity.t()) :: {:ok, Oban.Job.t() | :noop} | {:error, Oban.Job.changeset()} + def add_to_index(%Activity{id: activity_id, object: %Object{} = object} = activity) do + with {_, true} <- {:indexable, indexable?(activity)}, + {_, "public"} <- {:visibility, Visibility.get_visibility(object)} do + SearchIndexingWorker.new(%{"op" => "add_to_index", "activity" => activity_id}) + |> Oban.insert() + else + _ -> {:ok, :noop} + end end + def add_to_index(%Activity{id: activity_id}) do + case Activity.get_by_id_with_object(activity_id) do + %Activity{} = preloaded -> add_to_index(preloaded) + _ -> :ok + end + end + + @spec remove_from_index(Object.t()) :: {:ok, Oban.Job.t()} | {:error, Oban.Job.changeset()} def remove_from_index(%Pleroma.Object{id: object_id}) do SearchIndexingWorker.new(%{"op" => "remove_from_index", "object" => object_id}) |> Oban.insert() @@ -20,4 +37,7 @@ defmodule Pleroma.Search do search_module = Pleroma.Config.get([Pleroma.Search, :module]) search_module.healthcheck_endpoints() end + + defp indexable?(%Activity{data: %{"type" => "Create"}}), do: true + defp indexable?(_), do: false end diff --git a/lib/pleroma/search/meilisearch.ex b/lib/pleroma/search/meilisearch.ex index cafae8099..4541ef14a 100644 --- a/lib/pleroma/search/meilisearch.ex +++ b/lib/pleroma/search/meilisearch.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Search.Meilisearch do alias Pleroma.Activity alias Pleroma.Config.Getting, as: Config + alias Pleroma.Object import Pleroma.Search.DatabaseSearch import Ecto.Query @@ -156,28 +157,23 @@ defmodule Pleroma.Search.Meilisearch do end @impl true - def add_to_index(activity) do - maybe_search_data = object_to_search_data(activity.object) + def add_to_index(%Activity{object: %Object{} = object} = activity) do + search_data = object_to_search_data(object) - if activity.data["type"] == "Create" and maybe_search_data do - result = - meili_put( - "/indexes/objects/documents", - [maybe_search_data] - ) + result = + meili_put( + "/indexes/objects/documents", + [search_data] + ) - with {:ok, %{"status" => "enqueued"}} <- result do - # Added successfully - :ok - else - _ -> - # There was an error, report it - Logger.error("Failed to add activity #{activity.id} to index: #{inspect(result)}") - {:error, result} - end - else - # The post isn't something we can search, that's ok + with {:ok, %{"status" => "enqueued"}} <- result do + # Added successfully :ok + else + _ -> + # There was an error, report it + Logger.error("Failed to add activity #{activity.id} to index: #{inspect(result)}") + {:error, result} end end diff --git a/lib/pleroma/search/qdrant_search.ex b/lib/pleroma/search/qdrant_search.ex index 5142a273f..06f5b1983 100644 --- a/lib/pleroma/search/qdrant_search.ex +++ b/lib/pleroma/search/qdrant_search.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Search.QdrantSearch do alias Pleroma.Activity alias Pleroma.Config.Getting, as: Config + alias Pleroma.Object alias __MODULE__.OpenAIClient alias __MODULE__.QdrantClient @@ -82,23 +83,18 @@ defmodule Pleroma.Search.QdrantSearch do end @impl true - def add_to_index(activity) do - # This will only index public or unlisted notes - maybe_search_data = object_to_search_data(activity.object) + def add_to_index(%Activity{object: %Object{} = object} = activity) do + search_data = object_to_search_data(object) - if activity.data["type"] == "Create" and maybe_search_data do - with {:ok, embedding} <- get_embedding(maybe_search_data.content), - {:ok, %{status: 200}} <- - QdrantClient.put( - "/collections/posts/points", - build_index_payload(activity, embedding) - ) do - :ok - else - e -> {:error, e} - end - else + with {:ok, embedding} <- get_embedding(search_data.content), + {:ok, %{status: 200}} <- + QdrantClient.put( + "/collections/posts/points", + build_index_payload(activity, embedding) + ) do :ok + else + e -> {:error, e} end end diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex index 06d8005bc..350ff6cb3 100644 --- a/lib/pleroma/upload.ex +++ b/lib/pleroma/upload.ex @@ -93,7 +93,7 @@ defmodule Pleroma.Upload do def store(upload, opts \\ []) do opts = get_opts(opts) - with {:ok, upload} <- prepare_upload(upload, opts), + with {:ok, %__MODULE__{} = 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(upload), diff --git a/lib/pleroma/utils.ex b/lib/pleroma/utils.ex index 73001c987..61d122a47 100644 --- a/lib/pleroma/utils.ex +++ b/lib/pleroma/utils.ex @@ -17,7 +17,7 @@ defmodule Pleroma.Utils do dir |> File.ls!() |> Enum.map(&Path.join(dir, &1)) - |> Kernel.ParallelCompiler.compile() + |> Kernel.ParallelCompiler.compile(return_diagnostics: true) end @doc """ diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 44e9a22e5..071d634db 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -1618,6 +1618,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp normalize_image(urls) when is_list(urls), do: urls |> List.first() |> normalize_image() defp normalize_image(_), do: nil + defp normalize_also_known_as(urls) when is_list(urls), do: urls + defp normalize_also_known_as(url) when is_binary(url), do: [url] + defp normalize_also_known_as(nil), do: [] + defp maybe_put_description(map, %{"name" => description}) when is_binary(description) do Map.put(map, "name", description) end @@ -1693,7 +1697,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do featured_address: featured_address, bio: data["summary"] || "", actor_type: actor_type, - also_known_as: Map.get(data, "alsoKnownAs", []), + also_known_as: normalize_also_known_as(data["alsoKnownAs"]), public_key: public_key, inbox: data["inbox"], shared_inbox: shared_inbox, diff --git a/lib/pleroma/web/activity_pub/builder.ex b/lib/pleroma/web/activity_pub/builder.ex index 167c769a9..3b208ab77 100644 --- a/lib/pleroma/web/activity_pub/builder.ex +++ b/lib/pleroma/web/activity_pub/builder.ex @@ -332,21 +332,18 @@ defmodule Pleroma.Web.ActivityPub.Builder do @spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()} def announce(actor, object, options \\ []) do - public? = Keyword.get(options, :public, false) + visibility = Keyword.get(options, :visibility, "public") - to = - cond do - actor.ap_id == Relay.ap_id() -> - [actor.follower_address] - - public? and Visibility.local_public?(object) -> - [actor.follower_address, object.data["actor"], Utils.as_local_public()] - - public? -> - [actor.follower_address, object.data["actor"], Pleroma.Constants.as_public()] - - true -> - [actor.follower_address, object.data["actor"]] + {to, cc} = + if actor.ap_id == Relay.ap_id() do + {[actor.follower_address], []} + else + Pleroma.Web.CommonAPI.Utils.get_to_and_cc_for_visibility( + visibility, + actor.follower_address, + nil, + [object.data["actor"]] + ) end {:ok, @@ -355,6 +352,7 @@ defmodule Pleroma.Web.ActivityPub.Builder do "actor" => actor.ap_id, "object" => object.data["id"], "to" => to, + "cc" => cc, "context" => object.data["context"], "type" => "Announce", "published" => Utils.make_date() diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index c7fb2a1d7..8847692c8 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -35,32 +35,14 @@ defmodule Pleroma.Web.ActivityPub.UserView do def render("endpoints.json", _), do: %{} def render("service.json", %{user: user}) do - {:ok, _, public_key} = Keys.keys_from_pem(user.keys) - public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) - public_key = :public_key.pem_encode([public_key]) - - endpoints = render("endpoints.json", %{user: user}) - - %{ - "id" => user.ap_id, + Map.merge(common_actor_fields(user), %{ "type" => "Application", - "following" => "#{user.ap_id}/following", - "followers" => "#{user.ap_id}/followers", - "inbox" => "#{user.ap_id}/inbox", - "outbox" => "#{user.ap_id}/outbox", "name" => "Pleroma", "summary" => "An internal service actor for this Pleroma instance. No user-serviceable parts inside.", - "url" => user.ap_id, "manuallyApprovesFollowers" => false, - "publicKey" => %{ - "id" => "#{user.ap_id}#main-key", - "owner" => user.ap_id, - "publicKeyPem" => public_key - }, - "endpoints" => endpoints, "invisible" => User.invisible?(user) - } + }) |> Map.merge(Utils.make_json_ld_header()) end @@ -77,13 +59,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do end def render("user.json", %{user: user}) do - {:ok, _, public_key} = Keys.keys_from_pem(user.keys) - public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) - public_key = :public_key.pem_encode([public_key]) user = User.sanitize_html(user) - endpoints = render("endpoints.json", %{user: user}) - emoji_tags = Transmogrifier.take_emoji_tags(user) fields = Enum.map(user.fields, &Map.put(&1, "type", "PropertyValue")) @@ -102,25 +79,9 @@ defmodule Pleroma.Web.ActivityPub.UserView do do: Date.to_iso8601(user.birthday), else: nil - %{ - "id" => user.ap_id, - "type" => user.actor_type, - "following" => "#{user.ap_id}/following", - "followers" => "#{user.ap_id}/followers", - "inbox" => "#{user.ap_id}/inbox", - "outbox" => "#{user.ap_id}/outbox", + Map.merge(common_actor_fields(user), %{ "featured" => "#{user.ap_id}/collections/featured", "preferredUsername" => user.nickname, - "name" => user.name, - "summary" => user.bio, - "url" => user.ap_id, - "manuallyApprovesFollowers" => user.is_locked, - "publicKey" => %{ - "id" => "#{user.ap_id}#main-key", - "owner" => user.ap_id, - "publicKeyPem" => public_key - }, - "endpoints" => endpoints, "attachment" => fields, "tag" => emoji_tags, # Note: key name is indeed "discoverable" (not an error) @@ -130,7 +91,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do "vcard:bday" => birthday, "webfinger" => "acct:#{User.full_nickname(user)}", "published" => Pleroma.Web.CommonAPI.Utils.to_masto_date(user.inserted_at) - } + }) |> Map.merge( maybe_make_image( &User.avatar_url/2, @@ -309,6 +270,33 @@ defmodule Pleroma.Web.ActivityPub.UserView do |> Map.merge(Utils.make_json_ld_header()) end + defp common_actor_fields(%User{} = user) do + endpoints = render("endpoints.json", %{user: user}) + + {:ok, _, public_key} = Keys.keys_from_pem(user.keys) + public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key) + public_key = :public_key.pem_encode([public_key]) + + %{ + "id" => user.ap_id, + "type" => user.actor_type, + "following" => "#{user.ap_id}/following", + "followers" => "#{user.ap_id}/followers", + "inbox" => "#{user.ap_id}/inbox", + "outbox" => "#{user.ap_id}/outbox", + "name" => user.name, + "summary" => user.bio, + "url" => user.ap_id, + "manuallyApprovesFollowers" => user.is_locked, + "endpoints" => endpoints, + "publicKey" => %{ + "id" => "#{user.ap_id}#main-key", + "owner" => user.ap_id, + "publicKeyPem" => public_key + } + } + end + defp maybe_put_total_items(map, false, _total), do: map defp maybe_put_total_items(map, true, total) do diff --git a/lib/pleroma/web/api_spec/cast_and_validate.ex b/lib/pleroma/web/api_spec/cast_and_validate.ex index 672d1c4a1..95bd4d9cf 100644 --- a/lib/pleroma/web/api_spec/cast_and_validate.ex +++ b/lib/pleroma/web/api_spec/cast_and_validate.ex @@ -106,7 +106,14 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do OpenApiSpex.cast_and_validate(spec, operation, conn, content_type, cast_opts) end - defp cast_and_validate(spec, operation, conn, content_type, false = _strict, cast_opts) do + defp cast_and_validate( + spec, + operation, + %Conn{} = conn, + content_type, + false = _strict, + cast_opts + ) do case OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) do {:ok, conn} -> {:ok, conn} diff --git a/lib/pleroma/web/api_spec/operations/list_operation.ex b/lib/pleroma/web/api_spec/operations/list_operation.ex index 7d876ae2d..d2e803178 100644 --- a/lib/pleroma/web/api_spec/operations/list_operation.ex +++ b/lib/pleroma/web/api_spec/operations/list_operation.ex @@ -36,7 +36,7 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do summary: "Create a list", description: "Fetch the list with the given ID. Used for verifying the title of a list.", operationId: "ListController.create", - requestBody: create_update_request(), + requestBody: create_request(), security: [%{"oAuth" => ["write:lists"]}], responses: %{ 200 => Operation.response("List", "application/json", List), @@ -68,7 +68,7 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do description: "Change the title of a list", operationId: "ListController.update", parameters: [id_param()], - requestBody: create_update_request(), + requestBody: update_request(), security: [%{"oAuth" => ["write:lists"]}], responses: %{ 200 => Operation.response("List", "application/json", List), @@ -164,14 +164,15 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do ) end - defp create_update_request do + defp create_request do request_body( "Parameters", %Schema{ - description: "POST body for creating or updating a List", + description: "POST body for creating a List", type: :object, properties: %{ - title: %Schema{type: :string, description: "List title"} + title: %Schema{type: :string, description: "List title"}, + exclusive: %Schema{type: :boolean, description: "Whether members of the list should be removed from the “Home” feed"} }, required: [:title] }, @@ -179,6 +180,21 @@ defmodule Pleroma.Web.ApiSpec.ListOperation do ) end + defp update_request do + request_body( + "Parameters", + %Schema{ + description: "PUT body for updating a List", + type: :object, + properties: %{ + title: %Schema{type: :string, description: "List title"}, + exclusive: %Schema{type: :boolean, description: "Whether members of the list should be removed from the “Home” feed"} + } + }, + required: true + ) + end + defp add_remove_accounts_request(required) when is_boolean(required) do request_body( "Parameters", diff --git a/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex b/lib/pleroma/web/api_spec/operations/pleroma_util_operation.ex similarity index 84% rename from lib/pleroma/web/api_spec/operations/twitter_util_operation.ex rename to lib/pleroma/web/api_spec/operations/pleroma_util_operation.ex index 724d873c0..4bb4f112c 100644 --- a/lib/pleroma/web/api_spec/operations/twitter_util_operation.ex +++ b/lib/pleroma/web/api_spec/operations/pleroma_util_operation.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do +defmodule Pleroma.Web.ApiSpec.PleromaUtilOperation do alias OpenApiSpex.Operation alias OpenApiSpex.Schema alias Pleroma.Web.ApiSpec.Schemas.ApiError @@ -19,7 +19,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do %Operation{ tags: ["Custom emojis"], summary: "List all custom emojis", - operationId: "UtilController.emoji", + operationId: "PleromaAPI.UtilController.emoji", parameters: [], responses: %{ 200 => @@ -48,7 +48,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do %Operation{ tags: ["Others"], summary: "Dump frontend configurations", - operationId: "UtilController.frontend_configurations", + operationId: "PleromaAPI.UtilController.frontend_configurations", parameters: [], responses: %{ 200 => @@ -70,7 +70,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do tags: ["Account credentials"], summary: "Change account password", security: [%{"oAuth" => ["write:accounts"]}], - operationId: "UtilController.change_password", + operationId: "PleromaAPI.UtilController.change_password", requestBody: request_body("Parameters", change_password_request(), required: true), responses: %{ 200 => @@ -106,7 +106,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do tags: ["Account credentials"], summary: "Change account email", security: [%{"oAuth" => ["write:accounts"]}], - operationId: "UtilController.change_email", + operationId: "PleromaAPI.UtilController.change_email", requestBody: request_body("Parameters", change_email_request(), required: true), responses: %{ 200 => @@ -141,7 +141,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do tags: ["Settings"], summary: "Update Notification Settings", security: [%{"oAuth" => ["write:accounts"]}], - operationId: "UtilController.update_notification_settings", + operationId: "PleromaAPI.UtilController.update_notification_settings", parameters: [ Operation.parameter( :block_from_strangers, @@ -173,7 +173,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do tags: ["Account credentials"], summary: "Disable Account", security: [%{"oAuth" => ["write:accounts"]}], - operationId: "UtilController.disable_account", + operationId: "PleromaAPI.UtilController.disable_account", parameters: [ Operation.parameter(:password, :query, :string, "Password") ], @@ -193,7 +193,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do tags: ["Account credentials"], summary: "Delete Account", security: [%{"oAuth" => ["write:accounts"]}], - operationId: "UtilController.delete_account", + operationId: "PleromaAPI.UtilController.delete_account", parameters: [ Operation.parameter(:password, :query, :string, "Password") ], @@ -212,7 +212,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do def captcha_operation do %Operation{ summary: "Get a captcha", - operationId: "UtilController.captcha", + operationId: "PleromaAPI.UtilController.captcha", tags: ["Others"], parameters: [], responses: %{ @@ -226,7 +226,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do tags: ["Account credentials"], summary: "Move account", security: [%{"oAuth" => ["write:accounts"]}], - operationId: "UtilController.move_account", + operationId: "PleromaAPI.UtilController.move_account", requestBody: request_body("Parameters", move_account_request(), required: true), responses: %{ 200 => @@ -262,7 +262,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do tags: ["Account credentials"], summary: "List account aliases", security: [%{"oAuth" => ["read:accounts"]}], - operationId: "UtilController.list_aliases", + operationId: "PleromaAPI.UtilController.list_aliases", responses: %{ 200 => Operation.response("Success", "application/json", %Schema{ @@ -286,7 +286,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do tags: ["Account credentials"], summary: "Add an alias to this account", security: [%{"oAuth" => ["write:accounts"]}], - operationId: "UtilController.add_alias", + operationId: "PleromaAPI.UtilController.add_alias", requestBody: request_body("Parameters", add_alias_request(), required: true), responses: %{ 200 => @@ -326,7 +326,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do tags: ["Account credentials"], summary: "Delete an alias from this account", security: [%{"oAuth" => ["write:accounts"]}], - operationId: "UtilController.delete_alias", + operationId: "PleromaAPI.UtilController.delete_alias", requestBody: request_body("Parameters", delete_alias_request(), required: true), responses: %{ 200 => @@ -366,7 +366,7 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do tags: ["Others"], summary: "Quick status check on the instance", security: [%{"oAuth" => ["write:accounts"]}], - operationId: "UtilController.healthcheck", + operationId: "PleromaAPI.UtilController.healthcheck", parameters: [], responses: %{ 200 => Operation.response("Healthy", "application/json", %Schema{type: :object}), @@ -376,52 +376,6 @@ defmodule Pleroma.Web.ApiSpec.TwitterUtilOperation do } end - def remote_subscribe_operation do - %Operation{ - tags: ["Remote interaction"], - summary: "Remote Subscribe", - operationId: "UtilController.remote_subscribe", - parameters: [], - responses: %{200 => Operation.response("Web Page", "test/html", %Schema{type: :string})} - } - end - - def remote_interaction_operation do - %Operation{ - tags: ["Remote interaction"], - summary: "Remote interaction", - operationId: "UtilController.remote_interaction", - requestBody: request_body("Parameters", remote_interaction_request(), required: true), - responses: %{ - 200 => - Operation.response("Remote interaction URL", "application/json", %Schema{type: :object}) - } - } - end - - defp remote_interaction_request do - %Schema{ - title: "RemoteInteractionRequest", - description: "POST body for remote interaction", - type: :object, - required: [:ap_id, :profile], - properties: %{ - ap_id: %Schema{type: :string, description: "Profile or status ActivityPub ID"}, - profile: %Schema{type: :string, description: "Remote profile webfinger"} - } - } - end - - def show_subscribe_form_operation do - %Operation{ - tags: ["Remote interaction"], - summary: "Show remote subscribe form", - operationId: "UtilController.show_subscribe_form", - parameters: [], - responses: %{200 => Operation.response("Web Page", "test/html", %Schema{type: :string})} - } - end - defp delete_account_request do %Schema{ title: "AccountDeleteRequest", diff --git a/lib/pleroma/web/api_spec/operations/remote_interaction_operation.ex b/lib/pleroma/web/api_spec/operations/remote_interaction_operation.ex new file mode 100644 index 000000000..54edbcf32 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/remote_interaction_operation.ex @@ -0,0 +1,99 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.RemoteInteractionOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def remote_interaction_operation do + %Operation{ + tags: ["Remote interaction"], + summary: "Remote interaction", + operationId: "RemoteInteractionController.remote_interaction", + requestBody: request_body("Parameters", remote_interaction_request(), required: true), + responses: %{ + 200 => + Operation.response("Remote interaction URL", "application/json", %Schema{type: :object}) + } + } + end + + defp remote_interaction_request do + %Schema{ + title: "RemoteInteractionRequest", + description: "POST body for remote interaction", + type: :object, + required: [:ap_id, :profile], + properties: %{ + ap_id: %Schema{type: :string, description: "Profile or status ActivityPub ID"}, + profile: %Schema{type: :string, description: "Remote profile webfinger"} + } + } + end + + def follow_operation do + %Operation{ + tags: ["Remote interaction"], + summary: "Display follow form", + operationId: "RemoteInteractionController.follow", + parameters: [], + responses: %{ + 200 => Operation.response("Web Page", "text/html", %Schema{type: :string}), + 302 => Operation.response("Redirect to the status page", nil, nil) + } + } + end + + def do_follow_operation do + %Operation{ + tags: ["Remote interaction"], + summary: "Perform follow activity", + operationId: "RemoteInteractionController.do_follow", + parameters: [], + responses: %{ + 200 => Operation.response("Web page", "text/html", %Schema{type: :string}), + 302 => Operation.response("Redirect to the account page", nil, nil) + } + } + end + + def authorize_interaction_operation do + %Operation{ + tags: ["Remote interaction"], + summary: "Authorize remote interaction", + operationId: "RemoteInteractionController.authorize_interaction", + parameters: [], + responses: %{ + 302 => Operation.response("Redirect to remote_interaction path", nil, nil) + } + } + end + + def show_subscribe_form_operation do + %Operation{ + tags: ["Remote interaction"], + summary: "Show remote subscribe form", + operationId: "RemoteInteractionController.show_subscribe_form", + parameters: [], + responses: %{200 => Operation.response("Web Page", "text/html", %Schema{type: :string})} + } + end + + def remote_subscribe_operation do + %Operation{ + tags: ["Remote interaction"], + summary: "Remote Subscribe", + operationId: "RemoteInteractionController.remote_subscribe", + parameters: [], + responses: %{200 => Operation.response("Web Page", "text/html", %Schema{type: :string})} + } + end +end diff --git a/lib/pleroma/web/api_spec/render_error.ex b/lib/pleroma/web/api_spec/render_error.ex index 3539af6e4..2ba76f250 100644 --- a/lib/pleroma/web/api_spec/render_error.ex +++ b/lib/pleroma/web/api_spec/render_error.ex @@ -17,11 +17,11 @@ defmodule Pleroma.Web.ApiSpec.RenderError do def call(conn, errors) do errors = Enum.map(errors, fn - %{name: nil, reason: :invalid_enum} = err -> - %OpenApiSpex.Cast.Error{err | name: err.value} + %OpenApiSpex.Cast.Error{name: nil, reason: :invalid_enum} = err -> + %{err | name: err.value} - %{name: nil} = err -> - %OpenApiSpex.Cast.Error{err | name: List.last(err.path)} + %OpenApiSpex.Cast.Error{name: nil} = err -> + %{err | name: List.last(err.path)} err -> err diff --git a/lib/pleroma/web/api_spec/schemas/bookmark_folder.ex b/lib/pleroma/web/api_spec/schemas/bookmark_folder.ex index e8b4f43b7..f5ce9e8a1 100644 --- a/lib/pleroma/web/api_spec/schemas/bookmark_folder.ex +++ b/lib/pleroma/web/api_spec/schemas/bookmark_folder.ex @@ -15,12 +15,18 @@ defmodule Pleroma.Web.ApiSpec.Schemas.BookmarkFolder do properties: %{ id: FlakeID, name: %Schema{type: :string, description: "Folder name"}, - emoji: %Schema{type: :string, description: "Folder emoji", nullable: true} + emoji: %Schema{type: :string, description: "Folder emoji", nullable: true}, + emoji_url: %Schema{ + type: :string, + description: "URL of the folder emoji if it's a custom emoji, null otherwise", + nullable: true + } }, example: %{ "id" => "9toJCu5YZW7O7gfvH6", "name" => "Read later", - "emoji" => nil + "emoji" => nil, + "emoji_url" => nil } }) end diff --git a/lib/pleroma/web/api_spec/schemas/list.ex b/lib/pleroma/web/api_spec/schemas/list.ex index e57de7917..5df674894 100644 --- a/lib/pleroma/web/api_spec/schemas/list.ex +++ b/lib/pleroma/web/api_spec/schemas/list.ex @@ -13,7 +13,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.List do type: :object, properties: %{ id: %Schema{type: :string, description: "The internal database ID of the list"}, - title: %Schema{type: :string, description: "The user-defined title of the list"} + title: %Schema{type: :string, description: "The user-defined title of the list"}, + exclusive: %Schema{ + type: :boolean, + description: "Whether members of the list should be removed from the “Home” feed" + } }, example: %{ "id" => "12249", diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex index 04181ad8f..cb9d521b3 100644 --- a/lib/pleroma/web/common_api.ex +++ b/lib/pleroma/web/common_api.ex @@ -222,8 +222,8 @@ defmodule Pleroma.Web.CommonAPI do with %Activity{data: %{"type" => "Create"}} = activity <- Activity.get_by_id(id), object = %Object{} <- Object.normalize(activity, fetch: false), {_, nil} <- {:existing_announce, Utils.get_existing_announce(user.ap_id, object)}, - public = public_announce?(object, params), - {:ok, announce, _} <- Builder.announce(user, object, public: public), + visibility = announce_visibility(object, params), + {:ok, announce, _} <- Builder.announce(user, object, visibility: visibility), {:ok, activity, _} <- Pipeline.common_pipeline(announce, local: true) do {:ok, activity} else @@ -407,13 +407,11 @@ defmodule Pleroma.Web.CommonAPI do end end - defp public_announce?(_, %{visibility: visibility}) - when visibility in ~w{public unlisted private direct}, - do: visibility in ~w(public unlisted) + def announce_visibility(_, %{visibility: visibility}) + when visibility in ~w{public unlisted private direct local}, + do: visibility - defp public_announce?(object, _) do - Visibility.public?(object) - end + def announce_visibility(object, _), do: Visibility.get_visibility(object) @spec get_visibility(map(), map() | nil, Participation.t() | nil) :: {String.t() | nil, String.t() | nil} diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex index c0b98508c..16489663a 100644 --- a/lib/pleroma/web/common_api/activity_draft.ex +++ b/lib/pleroma/web/common_api/activity_draft.ex @@ -88,7 +88,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do |> validate() end - defp listen_object(draft) do + defp listen_object(%__MODULE__{} = draft) do object = draft.params |> Map.take([:album, :artist, :title, :length]) @@ -99,34 +99,34 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do |> Map.put("cc", draft.cc) |> Map.put("actor", draft.user.ap_id) - %__MODULE__{draft | object: object} + %{draft | object: object} end - defp put_params(draft, params) do + defp put_params(%__MODULE__{} = draft, params) do params = Map.put_new(params, :in_reply_to_status_id, params[:in_reply_to_id]) - %__MODULE__{draft | params: params} + %{draft | params: params} end - defp status(%{params: %{status: status}} = draft) do - %__MODULE__{draft | status: String.trim(status)} + defp status(%__MODULE__{params: %{status: status}} = draft) do + %{draft | status: String.trim(status)} end - defp summary(%{params: params} = draft) do - %__MODULE__{draft | summary: Map.get(params, :spoiler_text, "")} + defp summary(%__MODULE__{params: params} = draft) do + %{draft | summary: Map.get(params, :spoiler_text, "")} end - defp full_payload(%{status: status, summary: summary} = draft) do + defp full_payload(%__MODULE__{status: status, summary: summary} = draft) do full_payload = String.trim(status <> summary) case Utils.validate_character_limit(full_payload, draft.attachments) do - :ok -> %__MODULE__{draft | full_payload: full_payload} + :ok -> %{draft | full_payload: full_payload} {:error, message} -> add_error(draft, message) end end - defp attachments(%{params: params} = draft) do + defp attachments(%__MODULE__{params: params} = draft) do attachments = Utils.attachments_from_ids(params, draft.user) - draft = %__MODULE__{draft | attachments: attachments} + draft = %{draft | attachments: attachments} case Utils.validate_attachments_count(attachments) do :ok -> draft @@ -134,9 +134,10 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do end end - defp in_reply_to(%{params: %{in_reply_to_status_id: ""}} = draft), do: draft + defp in_reply_to(%__MODULE__{params: %{in_reply_to_status_id: ""}} = draft), do: draft - defp in_reply_to(%{params: %{in_reply_to_status_id: id}} = draft) when is_binary(id) do + defp in_reply_to(%__MODULE__{params: %{in_reply_to_status_id: id}} = draft) + when is_binary(id) do # If a post was deleted all its activities (except the newly added Delete) are purged too, # thus lookup by Create db ID will yield nil just as if it never existed in the first place. # @@ -148,7 +149,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do with %Activity{} = activity <- Activity.get_by_id(id), true <- Visibility.visible_for_user?(activity, draft.user), {_, type} when type in ["Create", "Announce"] <- {:type, activity.data["type"]} do - %__MODULE__{draft | in_reply_to: activity} + %{draft | in_reply_to: activity} else nil -> add_error(draft, dgettext("errors", "Cannot reply to a deleted status")) @@ -166,40 +167,43 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do end end - defp in_reply_to(%{params: %{in_reply_to_status_id: %Activity{} = in_reply_to}} = draft) do - %__MODULE__{draft | in_reply_to: in_reply_to} + defp in_reply_to( + %__MODULE__{params: %{in_reply_to_status_id: %Activity{} = in_reply_to}} = draft + ) do + %{draft | in_reply_to: in_reply_to} end defp in_reply_to(draft), do: draft - defp quote_post(%{params: %{quoted_status_id: id}} = draft) when not_empty_string(id) do + defp quote_post(%__MODULE__{params: %{quoted_status_id: id}} = draft) + when not_empty_string(id) do case Activity.get_by_id_with_object(id) do %Activity{} = activity -> - %__MODULE__{draft | quote_post: activity} + %{draft | quote_post: activity} _ -> draft end end - defp quote_post(%{params: %{quote_id: id}} = draft) when not_empty_string(id) do + defp quote_post(%__MODULE__{params: %{quote_id: id}} = draft) when not_empty_string(id) do quote_post(%{draft | params: Map.put(draft.params, :quoted_status_id, id)}) end defp quote_post(draft), do: draft - defp in_reply_to_conversation(draft) do + defp in_reply_to_conversation(%__MODULE__{} = draft) do in_reply_to_conversation = Participation.get(draft.params[:in_reply_to_conversation_id]) - %__MODULE__{draft | in_reply_to_conversation: in_reply_to_conversation} + %{draft | in_reply_to_conversation: in_reply_to_conversation} end - defp visibility(%{params: params} = draft) do + defp visibility(%__MODULE__{params: params} = draft) do case CommonAPI.get_visibility(params, draft.in_reply_to, draft.in_reply_to_conversation) do {visibility, "direct"} when visibility != "direct" -> add_error(draft, dgettext("errors", "The message visibility must be direct")) {visibility, _} -> - %__MODULE__{draft | visibility: visibility} + %{draft | visibility: visibility} end end @@ -215,7 +219,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do false end - defp quoting_visibility(%{quote_post: %Activity{}} = draft) do + defp quoting_visibility(%__MODULE__{quote_post: %Activity{}} = draft) do with %Object{} = object <- Object.normalize(draft.quote_post, fetch: false), true <- can_quote?(draft, object, Visibility.get_visibility(object)) do draft @@ -226,24 +230,24 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do defp quoting_visibility(draft), do: draft - defp expires_at(draft) do + defp expires_at(%__MODULE__{} = draft) do case CommonAPI.check_expiry_date(draft.params[:expires_in]) do - {:ok, expires_at} -> %__MODULE__{draft | expires_at: expires_at} + {:ok, expires_at} -> %{draft | expires_at: expires_at} {:error, message} -> add_error(draft, message) end end - defp poll(draft) do + defp poll(%__MODULE__{} = draft) do case Utils.make_poll_data(draft.params) do {:ok, {poll, poll_emoji}} -> - %__MODULE__{draft | extra: poll, emoji: Map.merge(draft.emoji, poll_emoji)} + %{draft | extra: poll, emoji: Map.merge(draft.emoji, poll_emoji)} {:error, message} -> add_error(draft, message) end end - defp content(%{mentions: mentions} = draft) do + defp content(%__MODULE__{mentions: mentions} = draft) do {content_html, mentioned_users, tags} = Utils.make_content_html(draft) mentioned_ap_ids = @@ -254,25 +258,25 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do |> Kernel.++(mentioned_ap_ids) |> Utils.get_addressed_users(draft.params[:to]) - %__MODULE__{draft | content_html: content_html, mentions: mentions, tags: tags} + %{draft | content_html: content_html, mentions: mentions, tags: tags} end - defp to_and_cc(draft) do + defp to_and_cc(%__MODULE__{} = draft) do {to, cc} = Utils.get_to_and_cc(draft) - %__MODULE__{draft | to: to, cc: cc} + %{draft | to: to, cc: cc} end - defp context(draft) do + defp context(%__MODULE__{} = draft) do context = Utils.make_context(draft.in_reply_to, draft.in_reply_to_conversation) - %__MODULE__{draft | context: context} + %{draft | context: context} end - defp sensitive(draft) do + defp sensitive(%__MODULE__{} = draft) do sensitive = draft.params[:sensitive] - %__MODULE__{draft | sensitive: sensitive} + %{draft | sensitive: sensitive} end - defp language(draft) do + defp language(%__MODULE__{} = draft) do language = with language <- draft.params[:language], true <- good_locale_code?(language) do @@ -281,10 +285,10 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do _ -> LanguageDetector.detect(draft.content_html <> " " <> draft.summary) end - %__MODULE__{draft | language: language} + %{draft | language: language} end - defp object(draft) do + defp object(%__MODULE__{} = draft) do emoji = Map.merge(Pleroma.Emoji.Formatter.get_emoji_map(draft.full_payload), draft.emoji) # Sometimes people create posts with subject containing emoji, @@ -325,15 +329,15 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do |> Map.put("generator", draft.params[:generator]) |> Map.put("language", draft.language) - %__MODULE__{draft | object: object} + %{draft | object: object} end - defp preview?(draft) do + defp preview?(%__MODULE__{} = draft) do preview? = Pleroma.Web.Utils.Params.truthy_param?(draft.params[:preview]) - %__MODULE__{draft | preview?: preview?} + %{draft | preview?: preview?} end - defp changes(draft) do + defp changes(%__MODULE__{} = draft) do direct? = draft.visibility == "direct" additional = %{"cc" => draft.cc, "directMessage" => direct?} @@ -353,14 +357,14 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do } |> Utils.maybe_add_list_data(draft.user, draft.visibility) - %__MODULE__{draft | changes: changes} + %{draft | changes: changes} end defp with_valid(%{valid?: true} = draft, func), do: func.(draft) defp with_valid(draft, _func), do: draft - defp add_error(draft, message) do - %__MODULE__{draft | valid?: false, errors: [message | draft.errors]} + defp add_error(%__MODULE__{} = draft, message) do + %{draft | valid?: false, errors: [message | draft.errors]} end defp validate(%{valid?: true} = draft), do: {:ok, draft} diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 91bf9c502..32572a721 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -75,48 +75,70 @@ defmodule Pleroma.Web.CommonAPI.Utils do {Enum.map(participation.recipients, & &1.ap_id), []} end - def get_to_and_cc(%{visibility: visibility} = draft) when visibility in ["public", "local"] do - to = - case visibility do - "public" -> [Pleroma.Constants.as_public() | draft.mentions] - "local" -> [Utils.as_local_public() | draft.mentions] + def get_to_and_cc(%{visibility: visibility} = draft) do + # If the OP is a DM already, add the implicit actor + mentions = + if visibility == "direct" && draft.in_reply_to && Visibility.direct?(draft.in_reply_to) do + Enum.uniq([draft.in_reply_to.data["actor"] | draft.mentions]) + else + draft.mentions end - cc = [draft.user.follower_address] - - if draft.in_reply_to do - {Enum.uniq([draft.in_reply_to.data["actor"] | to]), cc} - else - {to, cc} - end + get_to_and_cc_for_visibility( + visibility, + draft.user.follower_address, + draft.in_reply_to && draft.in_reply_to.data["actor"], + mentions + ) end - def get_to_and_cc(%{visibility: "unlisted"} = draft) do - to = [draft.user.follower_address | draft.mentions] - cc = [Pleroma.Constants.as_public()] + def get_to_and_cc_for_visibility("public", follower_collection, parent_actor, mentions) do + scope_addr = Pleroma.Constants.as_public() - if draft.in_reply_to do - {Enum.uniq([draft.in_reply_to.data["actor"] | to]), cc} - else - {to, cc} - end + to = + if parent_actor, + do: Enum.uniq([parent_actor, scope_addr | mentions]), + else: [scope_addr | mentions] + + {to, [follower_collection]} end - def get_to_and_cc(%{visibility: "private"} = draft) do - {to, cc} = get_to_and_cc(struct(draft, visibility: "direct")) - {[draft.user.follower_address | to], cc} + def get_to_and_cc_for_visibility("local", follower_collection, parent_actor, mentions) do + recipients = + if parent_actor, + do: Enum.uniq([parent_actor | mentions]), + else: mentions + + to = [ + Utils.as_local_public() + | Enum.filter(recipients, fn addr -> + String.starts_with?(addr, Pleroma.Web.Endpoint.url() <> "/") + end) + ] + + {to, [follower_collection]} end - def get_to_and_cc(%{visibility: "direct"} = draft) do - # If the OP is a DM already, add the implicit actor. - if draft.in_reply_to && Visibility.direct?(draft.in_reply_to) do - {Enum.uniq([draft.in_reply_to.data["actor"] | draft.mentions]), []} - else - {draft.mentions, []} - end + def get_to_and_cc_for_visibility("unlisted", follower_collection, parent_actor, mentions) do + to = + if parent_actor, + do: Enum.uniq([parent_actor, follower_collection | mentions]), + else: [follower_collection | mentions] + + {to, [Pleroma.Constants.as_public()]} end - def get_to_and_cc(%{visibility: {:list, _}, mentions: mentions}), do: {mentions, []} + def get_to_and_cc_for_visibility("private", follower_collection, _, mentions) do + {[follower_collection | mentions], []} + end + + def get_to_and_cc_for_visibility("direct", _, _, mentions) do + {mentions, []} + end + + def get_to_and_cc_for_visibility({:list, _}, _, _, mentions) do + {mentions, []} + end def get_addressed_users(_, to) when is_list(to) do User.get_ap_ids_by_nicknames(to) diff --git a/lib/pleroma/web/embed_controller.ex b/lib/pleroma/web/embed_controller.ex index 2ca4501a6..8420f17a5 100644 --- a/lib/pleroma/web/embed_controller.ex +++ b/lib/pleroma/web/embed_controller.ex @@ -20,6 +20,7 @@ defmodule Pleroma.Web.EmbedController do conn |> delete_resp_header("x-frame-options") |> delete_resp_header("content-security-policy") + |> put_layout({Pleroma.Web.LayoutView, :embed}) |> render("show.html", activity: activity, author: User.sanitize_html(author), diff --git a/lib/pleroma/web/fallback/redirect_controller.ex b/lib/pleroma/web/fallback/redirect_controller.ex index d75a95fb3..c7f80ad77 100644 --- a/lib/pleroma/web/fallback/redirect_controller.ex +++ b/lib/pleroma/web/fallback/redirect_controller.ex @@ -29,9 +29,18 @@ defmodule Pleroma.Web.Fallback.RedirectController do ) end - def live_dashboard(conn, _params) do + def live_dashboard(conn, %{"path" => path}) do + query_params = conn.query_string + + redirect_path = + if query_params == "" do + "/pleroma/live_dashboard/#{path}" + else + "/pleroma/live_dashboard/#{path}?#{query_params}" + end + conn - |> redirect(to: "/pleroma/live_dashboard") + |> redirect(to: redirect_path) end def redirector(conn, _params, code \\ 200) do diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex index 6dc731ed4..6d5851029 100644 --- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex @@ -26,7 +26,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do alias Pleroma.Web.OAuth.OAuthController alias Pleroma.Web.Plugs.OAuthScopesPlug alias Pleroma.Web.Plugs.RateLimiter - alias Pleroma.Web.TwitterAPI.TwitterAPI + alias Pleroma.Web.Registration alias Pleroma.Web.Utils.Params plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) @@ -111,8 +111,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do _params ) do with :ok <- validate_email_param(params), - :ok <- TwitterAPI.validate_captcha(app, params), - {:ok, user} <- TwitterAPI.register_user(params), + :ok <- Registration.validate_captcha(app, params), + {:ok, user} <- Registration.register_user(params), {_, {:ok, token}} <- {:login, OAuthController.login(user, app, app.scopes)} do OAuthController.after_token_exchange(conn, %{user: user, token: token}) diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex index fbb54a171..653b5fc29 100644 --- a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex @@ -7,7 +7,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do import Pleroma.Web.ControllerHelper, only: [json_response: 3] - alias Pleroma.Web.TwitterAPI.TwitterAPI + alias Pleroma.Web.Registration action_fallback(Pleroma.Web.MastodonAPI.FallbackController) @@ -17,7 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do def password_reset(conn, params) do nickname_or_email = params["email"] || params["nickname"] - TwitterAPI.password_reset(nickname_or_email) + Registration.password_reset(nickname_or_email) json_response(conn, :no_content, "") end diff --git a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex index 3bfc365a5..048012ae6 100644 --- a/lib/pleroma/web/mastodon_api/controllers/list_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/list_controller.ex @@ -28,27 +28,27 @@ defmodule Pleroma.Web.MastodonAPI.ListController do # POST /api/v1/lists def create( - %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: %{title: title}}}} = + %{assigns: %{user: user}, private: %{open_api_spex: %{body_params: params}}} = conn, _ ) do - with {:ok, %Pleroma.List{} = list} <- Pleroma.List.create(title, user) do + with {:ok, %Pleroma.List{} = list} <- Pleroma.List.create(params, user) do render(conn, "show.json", list: list) end end - # GET /api/v1/lists/:idOB + # GET /api/v1/lists/:id def show(%{assigns: %{list: list}} = conn, _) do render(conn, "show.json", list: list) end # PUT /api/v1/lists/:id def update( - %{assigns: %{list: list}, private: %{open_api_spex: %{body_params: %{title: title}}}} = + %{assigns: %{list: list}, private: %{open_api_spex: %{body_params: params}}} = conn, _ ) do - with {:ok, list} <- Pleroma.List.rename(list, title) do + with {:ok, list} <- Pleroma.List.update(list, params) do render(conn, "show.json", list: list) end end diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index db2b61b3c..26e38aac8 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -81,10 +81,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do plug(OAuthScopesPlug, %{scopes: ["write:accounts"]} when action in [:pin, :unpin]) - # Note: scope not present in Mastodon: read:bookmarks plug(OAuthScopesPlug, %{scopes: ["read:bookmarks"]} when action == :bookmarks) - # Note: scope not present in Mastodon: write:bookmarks plug( OAuthScopesPlug, %{scopes: ["write:bookmarks"]} when action in [:bookmark, :unbookmark] diff --git a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex index 5ee74a80e..99a5b6957 100644 --- a/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex @@ -45,6 +45,10 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do |> User.followed_hashtags() |> Enum.map(& &1.id) + excluded_list_members = + user + |> Pleroma.List.get_exclusive_list_members() + params = params |> Map.put(:type, ["Create", "Announce"]) @@ -58,7 +62,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineController do |> Map.delete(:local) activities = - [user.ap_id | User.following(user)] + [user.ap_id | User.following(user) -- excluded_list_members] |> ActivityPub.fetch_activities(params) |> Enum.reverse() diff --git a/lib/pleroma/web/mastodon_api/views/list_view.ex b/lib/pleroma/web/mastodon_api/views/list_view.ex index a7ae7c5f7..740f458d4 100644 --- a/lib/pleroma/web/mastodon_api/views/list_view.ex +++ b/lib/pleroma/web/mastodon_api/views/list_view.ex @@ -13,7 +13,8 @@ defmodule Pleroma.Web.MastodonAPI.ListView do def render("show.json", %{list: list}) do %{ id: to_string(list.id), - title: list.title + title: list.title, + exclusive: list.exclusive } end end diff --git a/lib/pleroma/web/twitter_api/controllers/password_controller.ex b/lib/pleroma/web/o_auth/password_controller.ex similarity index 90% rename from lib/pleroma/web/twitter_api/controllers/password_controller.ex rename to lib/pleroma/web/o_auth/password_controller.ex index e5482de9d..b209e7564 100644 --- a/lib/pleroma/web/twitter_api/controllers/password_controller.ex +++ b/lib/pleroma/web/o_auth/password_controller.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.PasswordController do +defmodule Pleroma.Web.OAuth.PasswordController do @moduledoc """ The module contains functions for password reset. """ @@ -16,7 +16,7 @@ defmodule Pleroma.Web.TwitterAPI.PasswordController do alias Pleroma.PasswordResetToken alias Pleroma.Repo alias Pleroma.User - alias Pleroma.Web.TwitterAPI.TwitterAPI + alias Pleroma.Web.Registration plug(Pleroma.Web.Plugs.RateLimiter, [name: :request] when action == :request) @@ -24,7 +24,7 @@ defmodule Pleroma.Web.TwitterAPI.PasswordController do def request(conn, params) do nickname_or_email = params["email"] || params["nickname"] - TwitterAPI.password_reset(nickname_or_email) + Registration.password_reset(nickname_or_email) json_response(conn, :no_content, "") end diff --git a/lib/pleroma/web/twitter_api/views/password_view.ex b/lib/pleroma/web/o_auth/password_view.ex similarity index 83% rename from lib/pleroma/web/twitter_api/views/password_view.ex rename to lib/pleroma/web/o_auth/password_view.ex index 55790941f..0b85c76e8 100644 --- a/lib/pleroma/web/twitter_api/views/password_view.ex +++ b/lib/pleroma/web/o_auth/password_view.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.PasswordView do +defmodule Pleroma.Web.OAuth.PasswordView do use Pleroma.Web, :view import Phoenix.HTML.Form alias Pleroma.Web.Gettext diff --git a/lib/pleroma/web/twitter_api/controller.ex b/lib/pleroma/web/o_auth/token_controller.ex similarity index 94% rename from lib/pleroma/web/twitter_api/controller.ex rename to lib/pleroma/web/o_auth/token_controller.ex index 6db3d6067..d2313a9cc 100644 --- a/lib/pleroma/web/twitter_api/controller.ex +++ b/lib/pleroma/web/o_auth/token_controller.ex @@ -2,13 +2,13 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.Controller do +defmodule Pleroma.Web.OAuth.TokenController do use Pleroma.Web, :controller alias Pleroma.User alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.OAuth.TokenView alias Pleroma.Web.Plugs.OAuthScopesPlug - alias Pleroma.Web.TwitterAPI.TokenView require Logger diff --git a/lib/pleroma/web/twitter_api/views/token_view.ex b/lib/pleroma/web/o_auth/token_view.ex similarity index 81% rename from lib/pleroma/web/twitter_api/views/token_view.ex rename to lib/pleroma/web/o_auth/token_view.ex index 36776ce3b..f9894399a 100644 --- a/lib/pleroma/web/twitter_api/views/token_view.ex +++ b/lib/pleroma/web/o_auth/token_view.ex @@ -2,12 +2,12 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.TokenView do +defmodule Pleroma.Web.OAuth.TokenView do use Pleroma.Web, :view def render("index.json", %{tokens: tokens}) do tokens - |> render_many(Pleroma.Web.TwitterAPI.TokenView, "show.json") + |> render_many(Pleroma.Web.OAuth.TokenView, "show.json") |> Enum.filter(&Enum.any?/1) end diff --git a/lib/pleroma/web/pleroma_api/controllers/bookmark_folder_controller.ex b/lib/pleroma/web/pleroma_api/controllers/bookmark_folder_controller.ex index 6d6e2e940..55f323a16 100644 --- a/lib/pleroma/web/pleroma_api/controllers/bookmark_folder_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/bookmark_folder_controller.ex @@ -10,10 +10,8 @@ defmodule Pleroma.Web.PleromaAPI.BookmarkFolderController do plug(Pleroma.Web.ApiSpec.CastAndValidate) - # Note: scope not present in Mastodon: read:bookmarks plug(OAuthScopesPlug, %{scopes: ["read:bookmarks"]} when action == :index) - # Note: scope not present in Mastodon: write:bookmarks plug( OAuthScopesPlug, %{scopes: ["write:bookmarks"]} when action in [:create, :update, :delete] diff --git a/lib/pleroma/web/pleroma_api/controllers/password_controller.ex b/lib/pleroma/web/pleroma_api/controllers/password_controller.ex new file mode 100644 index 000000000..444477245 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/password_controller.ex @@ -0,0 +1,52 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.PasswordController do + @moduledoc """ + The module contains functions for password reset. + """ + + use Pleroma.Web, :controller + + require Logger + + import Pleroma.Web.ControllerHelper, only: [json_response: 3] + + alias Pleroma.PasswordResetToken + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Web.Registration + + plug(Pleroma.Web.Plugs.RateLimiter, [name: :request] when action == :request) + + @doc "POST /auth/password" + def request(conn, params) do + nickname_or_email = params["email"] || params["nickname"] + + Registration.password_reset(nickname_or_email) + + json_response(conn, :no_content, "") + end + + def reset(conn, %{"token" => token}) do + with %{used: false} = token <- Repo.get_by(PasswordResetToken, %{token: token}), + false <- PasswordResetToken.expired?(token), + %User{} = user <- User.get_cached_by_id(token.user_id) do + render(conn, "reset.html", %{ + token: token, + user: user + }) + else + _e -> render(conn, "invalid_token.html") + end + end + + def do_reset(conn, %{"data" => data}) do + with {:ok, _} <- PasswordResetToken.reset_password(data["token"], data) do + render(conn, "reset_success.html") + else + _e -> render(conn, "reset_failed.html") + end + end +end diff --git a/lib/pleroma/web/pleroma_api/controllers/token_controller.ex b/lib/pleroma/web/pleroma_api/controllers/token_controller.ex new file mode 100644 index 000000000..10eeb907e --- /dev/null +++ b/lib/pleroma/web/pleroma_api/controllers/token_controller.ex @@ -0,0 +1,59 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.TokenController do + use Pleroma.Web, :controller + + alias Pleroma.User + alias Pleroma.Web.OAuth.Token + alias Pleroma.Web.PleromaAPI.TokenView + alias Pleroma.Web.Plugs.OAuthScopesPlug + + require Logger + + plug(:skip_auth when action == :confirm_email) + plug(:skip_plug, OAuthScopesPlug when action in [:oauth_tokens, :revoke_token]) + + action_fallback(:errors) + + def confirm_email(conn, %{"user_id" => uid, "token" => token}) do + with %User{} = user <- User.get_cached_by_id(uid), + true <- user.local and !user.is_confirmed and user.confirmation_token == token, + {:ok, _} <- User.confirm(user) do + redirect(conn, to: "/") + end + end + + def oauth_tokens(%{assigns: %{user: user}} = conn, _params) do + with oauth_tokens <- Token.get_user_tokens(user) do + conn + |> put_view(TokenView) + |> render("index.json", %{tokens: oauth_tokens}) + end + end + + def revoke_token(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do + Token.delete_user_token(user, id) + + json_reply(conn, 201, "") + end + + defp errors(conn, {:param_cast, _}) do + conn + |> put_status(400) + |> json("Invalid parameters") + end + + defp errors(conn, _) do + conn + |> put_status(500) + |> json("Something went wrong") + end + + defp json_reply(conn, status, json) do + conn + |> put_resp_content_type("application/json") + |> send_resp(status, json) + end +end diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/pleroma_api/controllers/util_controller.ex similarity index 65% rename from lib/pleroma/web/twitter_api/controllers/util_controller.ex rename to lib/pleroma/web/pleroma_api/controllers/util_controller.ex index 1c072f98a..882e9476e 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/util_controller.ex @@ -2,12 +2,11 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.UtilController do +defmodule Pleroma.Web.PleromaAPI.UtilController do use Pleroma.Web, :controller require Logger - alias Pleroma.Activity alias Pleroma.Config alias Pleroma.Emoji alias Pleroma.Healthcheck @@ -17,19 +16,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do alias Pleroma.Web.Auth.WrapperAuthenticator, as: Authenticator alias Pleroma.Web.CommonAPI alias Pleroma.Web.Plugs.OAuthScopesPlug - alias Pleroma.Web.WebFinger - plug( - Pleroma.Web.ApiSpec.CastAndValidate, - [replace_params: false] - when action != :remote_subscribe and action != :show_subscribe_form - ) - - plug( - Pleroma.Web.Plugs.FederatingPlug - when action == :remote_subscribe - when action == :show_subscribe_form - ) + plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false) plug( OAuthScopesPlug, @@ -54,125 +42,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do ] ) - defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.TwitterUtilOperation - - def show_subscribe_form(conn, %{"nickname" => nick}) do - with %User{} = user <- User.get_cached_by_nickname(nick), - avatar = User.avatar_url(user) do - conn - |> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false}) - else - _e -> - render(conn, "subscribe.html", %{ - nickname: nick, - avatar: nil, - error: - Pleroma.Web.Gettext.dpgettext( - "static_pages", - "remote follow error message - user not found", - "Could not find user" - ) - }) - end - end - - def show_subscribe_form(conn, %{"status_id" => id}) do - with %Activity{} = activity <- Activity.get_by_id(id), - {:ok, ap_id} <- get_ap_id(activity), - %User{} = user <- User.get_cached_by_ap_id(activity.actor), - avatar = User.avatar_url(user) do - conn - |> render("status_interact.html", %{ - status_link: ap_id, - status_id: id, - nickname: user.nickname, - avatar: avatar, - error: false - }) - else - _e -> - render(conn, "status_interact.html", %{ - status_id: id, - avatar: nil, - error: - Pleroma.Web.Gettext.dpgettext( - "static_pages", - "status interact error message - status not found", - "Could not find status" - ) - }) - end - end - - def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do - show_subscribe_form(conn, %{"nickname" => nick}) - end - - def remote_subscribe(conn, %{"status_id" => id, "profile" => _}) do - show_subscribe_form(conn, %{"status_id" => id}) - end - - def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profile}}) do - with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile), - %User{ap_id: ap_id} <- User.get_cached_by_nickname(nick) do - conn - |> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id)) - else - _e -> - render(conn, "subscribe.html", %{ - nickname: nick, - avatar: nil, - error: - Pleroma.Web.Gettext.dpgettext( - "static_pages", - "remote follow error message - unknown error", - "Something went wrong." - ) - }) - end - end - - def remote_subscribe(conn, %{"status" => %{"status_id" => id, "profile" => profile}}) do - with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile), - %Activity{} = activity <- Activity.get_by_id(id), - {:ok, ap_id} <- get_ap_id(activity) do - conn - |> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id)) - else - _e -> - render(conn, "status_interact.html", %{ - status_id: id, - avatar: nil, - error: - Pleroma.Web.Gettext.dpgettext( - "static_pages", - "status interact error message - unknown error", - "Something went wrong." - ) - }) - end - end - - def remote_interaction( - %{private: %{open_api_spex: %{body_params: %{ap_id: ap_id, profile: profile}}}} = conn, - _params - ) do - with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile) do - conn - |> json(%{url: String.replace(template, "{uri}", ap_id)}) - else - _e -> json(conn, %{error: "Couldn't find user"}) - end - end - - defp get_ap_id(activity) do - object = Pleroma.Object.normalize(activity, fetch: false) - - case object do - %{data: %{"id" => ap_id}} -> {:ok, ap_id} - _ -> {:no_ap_id, nil} - end - end + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaUtilOperation def frontend_configurations(conn, _params) do render(conn, "frontend_configurations.json") diff --git a/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex b/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex index 29028781f..ddffa25d3 100644 --- a/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex +++ b/lib/pleroma/web/pleroma_api/views/bookmark_folder_view.ex @@ -9,11 +9,13 @@ defmodule Pleroma.Web.PleromaAPI.BookmarkFolderView do alias Pleroma.Emoji def render("show.json", %{folder: %BookmarkFolder{} = folder}) do + {emoji, emoji_url} = get_emoji(folder.emoji) + %{ id: folder.id |> to_string(), name: folder.name, - emoji: folder.emoji, - emoji_url: get_emoji_url(folder.emoji) + emoji: emoji, + emoji_url: emoji_url } end @@ -21,20 +23,15 @@ defmodule Pleroma.Web.PleromaAPI.BookmarkFolderView do render_many(folders, __MODULE__, "show.json", Map.delete(opts, :folders)) end - defp get_emoji_url(nil) do - nil - end + defp get_emoji(nil), do: {nil, nil} - defp get_emoji_url(emoji) do + defp get_emoji(emoji) do if Emoji.unicode?(emoji) do - nil + {emoji, nil} else - emoji = Emoji.get(emoji) - - if emoji != nil do - Emoji.local_url(emoji.file) - else - nil + case Emoji.get(emoji) do + nil -> {nil, nil} + emoji_data -> {emoji, Emoji.local_url(emoji_data.file)} end end end diff --git a/lib/pleroma/web/pleroma_api/views/util_view.ex b/lib/pleroma/web/pleroma_api/views/util_view.ex new file mode 100644 index 000000000..b5e07c006 --- /dev/null +++ b/lib/pleroma/web/pleroma_api/views/util_view.ex @@ -0,0 +1,13 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.UtilView do + use Pleroma.Web, :view + alias Pleroma.Config + + def render("frontend_configurations.json", _) do + Config.get(:frontend_configurations, %{}) + |> Enum.into(%{}) + end +end diff --git a/lib/pleroma/web/plugs/instance_static.ex b/lib/pleroma/web/plugs/instance_static.ex index d2a674d39..948188287 100644 --- a/lib/pleroma/web/plugs/instance_static.ex +++ b/lib/pleroma/web/plugs/instance_static.ex @@ -4,7 +4,7 @@ defmodule Pleroma.Web.Plugs.InstanceStatic do require Pleroma.Constants - import Plug.Conn, only: [put_resp_header: 3] + import Plug.Conn, only: [put_resp_header: 3, put_status: 2, send_resp: 3, halt: 1] @moduledoc """ This is a shim to call `Plug.Static` but with runtime `from` configuration. @@ -51,10 +51,37 @@ defmodule Pleroma.Web.Plugs.InstanceStatic do |> Map.put(:from, from) |> Map.put(:content_types, false) - conn = set_content_type(conn, conn.request_path) + conn = + conn + |> set_content_type(conn.request_path) + |> Plug.Static.call(opts) - # Call Plug.Static with our sanitized content-type - Plug.Static.call(conn, opts) + if conn.halted do + conn + else + path = String.trim_leading(conn.request_path, "/") + + if not File.exists?(file_path(path)) do + conn + |> put_status(:not_found) + |> send_404() + |> halt() + else + conn + end + end + end + + defp send_404(conn) do + if String.ends_with?(String.downcase(conn.request_path), ".json") do + conn + |> put_resp_header("content-type", "application/json") + |> send_resp(404, Jason.encode!(%{error: "not found"})) + else + conn + |> put_resp_header("content-type", "text/plain") + |> send_resp(404, "Not found") + end end defp set_content_type(conn, "/emoji/" <> filepath) do diff --git a/lib/pleroma/web/plugs/rate_limiter.ex b/lib/pleroma/web/plugs/rate_limiter.ex index aa79dbf6b..0ebb73bbc 100644 --- a/lib/pleroma/web/plugs/rate_limiter.ex +++ b/lib/pleroma/web/plugs/rate_limiter.ex @@ -67,6 +67,8 @@ defmodule Pleroma.Web.Plugs.RateLimiter do import Plug.Conn alias Pleroma.Config + alias Pleroma.Config.Holder + alias Pleroma.EctoType.Config.RateLimit alias Pleroma.User alias Pleroma.Web.Plugs.RateLimiter.LimiterSupervisor @@ -143,7 +145,7 @@ defmodule Pleroma.Web.Plugs.RateLimiter do def action_settings(plug_opts) do with limiter_name when is_atom(limiter_name) <- plug_opts[:name], - limits when not is_nil(limits) <- Config.get([:rate_limit, limiter_name]) do + {:ok, limits} <- fetch_and_normalize_limits(limiter_name) do bucket_name_root = Keyword.get(plug_opts, :bucket_name, limiter_name) %{ @@ -151,6 +153,72 @@ defmodule Pleroma.Web.Plugs.RateLimiter do limits: limits, opts: plug_opts } + else + :disabled -> nil + end + end + + defp fetch_and_normalize_limits(limiter_name) do + limits = Config.get([:rate_limit, limiter_name]) + + case normalize_limits(limits) do + {:ok, limits} -> + {:ok, limits} + + :disabled -> + :disabled + + :error -> + default_limits = + Holder.default_config(:pleroma, :rate_limit) + |> get_default_limits(limiter_name) + + case normalize_limits(default_limits) do + {:ok, normalized_limits} -> + warn_invalid_limits_once(limiter_name, limits) + {:ok, normalized_limits} + + _ -> + warn_invalid_limits_once(limiter_name, limits) + :disabled + end + end + end + + defp get_default_limits(%{} = rate_limit, limiter_name), do: Map.get(rate_limit, limiter_name) + + defp get_default_limits(rate_limit, limiter_name) when is_list(rate_limit) do + if Keyword.keyword?(rate_limit) do + Keyword.get(rate_limit, limiter_name) + else + nil + end + end + + defp get_default_limits(_, _), do: nil + + @invalid_limits_warned_key {__MODULE__, :invalid_limits_warned} + + defp warn_invalid_limits_once(limiter_name, limits) do + warned = :persistent_term.get(@invalid_limits_warned_key, MapSet.new()) + + if MapSet.member?(warned, limiter_name) do + :ok + else + :persistent_term.put(@invalid_limits_warned_key, MapSet.put(warned, limiter_name)) + + Logger.warning( + "Invalid rate limiter config for #{inspect(limiter_name)}: #{inspect(limits)}. Falling back to defaults or disabling this limiter." + ) + end + end + + defp normalize_limits(nil), do: :disabled + + defp normalize_limits(limits) do + case RateLimit.cast(limits) do + {:ok, normalized_limits} -> {:ok, normalized_limits} + :error -> :error end end diff --git a/lib/pleroma/web/preload/providers/instance.ex b/lib/pleroma/web/preload/providers/instance.ex index 6183f7b70..f2a60393f 100644 --- a/lib/pleroma/web/preload/providers/instance.ex +++ b/lib/pleroma/web/preload/providers/instance.ex @@ -5,9 +5,9 @@ defmodule Pleroma.Web.Preload.Providers.Instance do alias Pleroma.Web.MastodonAPI.InstanceView alias Pleroma.Web.Nodeinfo.Nodeinfo + alias Pleroma.Web.PleromaAPI.UtilView alias Pleroma.Web.Plugs.InstanceStatic alias Pleroma.Web.Preload.Providers.Provider - alias Pleroma.Web.TwitterAPI.UtilView @behaviour Provider @instance_url "/api/v1/instance" diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/registration.ex similarity index 98% rename from lib/pleroma/web/twitter_api/twitter_api.ex rename to lib/pleroma/web/registration.ex index ef2eb75f4..df71300ce 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/registration.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.TwitterAPI do +defmodule Pleroma.Web.Registration do import Pleroma.Web.Gettext alias Pleroma.Emails.Mailer diff --git a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex b/lib/pleroma/web/remote_interaction/remote_interaction_controller.ex similarity index 57% rename from lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex rename to lib/pleroma/web/remote_interaction/remote_interaction_controller.ex index 38ebc8c5d..90f15cdb6 100644 --- a/lib/pleroma/web/twitter_api/controllers/remote_follow_controller.ex +++ b/lib/pleroma/web/remote_interaction/remote_interaction_controller.ex @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do +defmodule Pleroma.Web.RemoteInteraction.RemoteInteractionController do use Pleroma.Web, :controller require Logger @@ -14,18 +14,27 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do alias Pleroma.Web.Auth.TOTPAuthenticator alias Pleroma.Web.Auth.WrapperAuthenticator alias Pleroma.Web.CommonAPI + alias Pleroma.Web.WebFinger @status_types ["Article", "Event", "Note", "Video", "Page", "Question"] + plug( + Pleroma.Web.ApiSpec.CastAndValidate, + [replace_params: false] + when action == :remote_interaction + ) + plug(Pleroma.Web.Plugs.FederatingPlug) # Note: follower can submit the form (with password auth) not being signed in (having no token) plug( Pleroma.Web.Plugs.OAuthScopesPlug, %{fallback: :proceed_unauthenticated, scopes: ["follow", "write:follows"]} - when action in [:do_follow] + when action == :do_follow ) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.RemoteInteractionOperation + # GET /ostatus_subscribe # def follow(%{assigns: %{user: user}} = conn, %{"acct" => acct}) do @@ -125,7 +134,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do # def authorize_interaction(conn, %{"uri" => uri}) do conn - |> redirect(to: Routes.remote_follow_path(conn, :follow, %{acct: uri})) + |> redirect(to: Routes.remote_interaction_path(conn, :follow, %{acct: uri})) end defp handle_follow_error(conn, {:mfa_token, followee, _} = _) do @@ -162,4 +171,122 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowController do Logger.debug("Remote follow failed with error #{inspect(error)}") render(conn, "followed.html", %{error: "Something went wrong."}) end + + def show_subscribe_form(conn, %{"nickname" => nick}) do + with %User{} = user <- User.get_cached_by_nickname(nick), + avatar = User.avatar_url(user) do + conn + |> render("subscribe.html", %{nickname: nick, avatar: avatar, error: false}) + else + _e -> + render(conn, "subscribe.html", %{ + nickname: nick, + avatar: nil, + error: + Pleroma.Web.Gettext.dpgettext( + "static_pages", + "remote follow error message - user not found", + "Could not find user" + ) + }) + end + end + + def show_subscribe_form(conn, %{"status_id" => id}) do + with %Activity{} = activity <- Activity.get_by_id(id), + {:ok, ap_id} <- get_ap_id(activity), + %User{} = user <- User.get_cached_by_ap_id(activity.actor), + avatar = User.avatar_url(user) do + conn + |> render("status_interact.html", %{ + status_link: ap_id, + status_id: id, + nickname: user.nickname, + avatar: avatar, + error: false + }) + else + _e -> + render(conn, "status_interact.html", %{ + status_id: id, + avatar: nil, + error: + Pleroma.Web.Gettext.dpgettext( + "static_pages", + "status interact error message - status not found", + "Could not find status" + ) + }) + end + end + + def remote_subscribe(conn, %{"nickname" => nick, "profile" => _}) do + show_subscribe_form(conn, %{"nickname" => nick}) + end + + def remote_subscribe(conn, %{"status_id" => id, "profile" => _}) do + show_subscribe_form(conn, %{"status_id" => id}) + end + + def remote_subscribe(conn, %{"user" => %{"nickname" => nick, "profile" => profile}}) do + with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile), + %User{ap_id: ap_id} <- User.get_cached_by_nickname(nick) do + conn + |> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id)) + else + _e -> + render(conn, "subscribe.html", %{ + nickname: nick, + avatar: nil, + error: + Pleroma.Web.Gettext.dpgettext( + "static_pages", + "remote follow error message - unknown error", + "Something went wrong." + ) + }) + end + end + + def remote_subscribe(conn, %{"status" => %{"status_id" => id, "profile" => profile}}) do + with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile), + %Activity{} = activity <- Activity.get_by_id(id), + {:ok, ap_id} <- get_ap_id(activity) do + conn + |> Phoenix.Controller.redirect(external: String.replace(template, "{uri}", ap_id)) + else + _e -> + render(conn, "status_interact.html", %{ + status_id: id, + avatar: nil, + error: + Pleroma.Web.Gettext.dpgettext( + "static_pages", + "status interact error message - unknown error", + "Something went wrong." + ) + }) + end + end + + def remote_interaction( + %{private: %{open_api_spex: %{body_params: %{ap_id: ap_id, profile: profile}}}} = conn, + _params + ) do + with {:ok, %{"subscribe_address" => template}} <- WebFinger.finger(profile) do + conn + |> json(%{url: String.replace(template, "{uri}", ap_id)}) + else + _e -> json(conn, %{error: "Couldn't find user"}) + end + end + + defp get_ap_id(activity) do + object = Pleroma.Object.normalize(activity, fetch: false) + + case object do + %{data: %{"id" => ap_id}} -> {:ok, ap_id} + _ -> {:no_ap_id, nil} + end + end end diff --git a/lib/pleroma/web/twitter_api/views/remote_follow_view.ex b/lib/pleroma/web/remote_interaction/remote_interaction_view.ex similarity index 75% rename from lib/pleroma/web/twitter_api/views/remote_follow_view.ex rename to lib/pleroma/web/remote_interaction/remote_interaction_view.ex index 8902261b0..6e7f40749 100644 --- a/lib/pleroma/web/twitter_api/views/remote_follow_view.ex +++ b/lib/pleroma/web/remote_interaction/remote_interaction_view.ex @@ -2,9 +2,11 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.RemoteFollowView do +defmodule Pleroma.Web.RemoteInteraction.RemoteInteractionView do use Pleroma.Web, :view + import Phoenix.HTML import Phoenix.HTML.Form + import Phoenix.HTML.Link alias Pleroma.Web.Gettext def avatar_url(user) do diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 20ac1c67b..c91ca8c97 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -226,23 +226,28 @@ defmodule Pleroma.Web.Router do plug(Pleroma.Web.Plugs.StaticFEPlug) end - scope "/api/v1/pleroma", Pleroma.Web.TwitterAPI do + scope "/api/v1/pleroma", Pleroma.Web.OAuth do pipe_through(:pleroma_api) get("/password_reset/:token", PasswordController, :reset, as: :reset_password) post("/password_reset", PasswordController, :do_reset, as: :reset_password) - get("/emoji", UtilController, :emoji) - get("/captcha", UtilController, :captcha) - get("/healthcheck", UtilController, :healthcheck) - post("/remote_interaction", UtilController, :remote_interaction) end scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do pipe_through(:pleroma_api) + get("/emoji", UtilController, :emoji) + get("/captcha", UtilController, :captcha) + get("/healthcheck", UtilController, :healthcheck) get("/federation_status", InstancesController, :show) end + scope "/api/v1/pleroma", Pleroma.Web.RemoteInteraction do + pipe_through(:pleroma_api) + + post("/remote_interaction", RemoteInteractionController, :remote_interaction) + end + scope "/api/v1/pleroma", Pleroma.Web do pipe_through(:pleroma_api) post("/uploader_callback/:upload_path", UploaderController, :callback) @@ -484,18 +489,18 @@ defmodule Pleroma.Web.Router do end end - scope "/", Pleroma.Web.TwitterAPI do + scope "/", Pleroma.Web.RemoteInteraction do pipe_through(:pleroma_html) - post("/main/ostatus", UtilController, :remote_subscribe) - get("/main/ostatus", UtilController, :show_subscribe_form) - get("/ostatus_subscribe", RemoteFollowController, :follow) - post("/ostatus_subscribe", RemoteFollowController, :do_follow) + post("/main/ostatus", RemoteInteractionController, :remote_subscribe) + get("/main/ostatus", RemoteInteractionController, :show_subscribe_form) + get("/ostatus_subscribe", RemoteInteractionController, :follow) + post("/ostatus_subscribe", RemoteInteractionController, :do_follow) - get("/authorize_interaction", RemoteFollowController, :authorize_interaction) + get("/authorize_interaction", RemoteInteractionController, :authorize_interaction) end - scope "/api/pleroma", Pleroma.Web.TwitterAPI do + scope "/api/pleroma", Pleroma.Web.PleromaAPI do pipe_through(:authenticated_api) post("/change_email", UtilController, :change_email) @@ -853,7 +858,7 @@ defmodule Pleroma.Web.Router do scope "/api", Pleroma.Web do pipe_through(:config) - get("/pleroma/frontend_configurations", TwitterAPI.UtilController, :frontend_configurations) + get("/pleroma/frontend_configurations", PleromaAPI.UtilController, :frontend_configurations) end scope "/api", Pleroma.Web do @@ -861,7 +866,7 @@ defmodule Pleroma.Web.Router do get( "/account/confirm_email/:user_id/:token", - TwitterAPI.Controller, + OAuth.TokenController, :confirm_email, as: :confirm_email ) @@ -873,11 +878,11 @@ defmodule Pleroma.Web.Router do get("/openapi", OpenApiSpex.Plug.RenderSpec, []) end - scope "/api", Pleroma.Web, as: :authenticated_twitter_api do + scope "/api", Pleroma.Web, as: :authenticated_pleroma_api do pipe_through(:authenticated_api) - get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens) - delete("/oauth_tokens/:id", TwitterAPI.Controller, :revoke_token) + get("/oauth_tokens", OAuth.TokenController, :oauth_tokens) + delete("/oauth_tokens/:id", OAuth.TokenController, :revoke_token) end scope "/", Pleroma.Web do @@ -1026,7 +1031,9 @@ defmodule Pleroma.Web.Router do scope "/", Pleroma.Web do pipe_through(:pleroma_html) - post("/auth/password", TwitterAPI.PasswordController, :request) + post("/auth/password", OAuth.PasswordController, :request) + + get("/embed/:id", EmbedController, :show) end scope "/proxy/", Pleroma.Web do @@ -1088,7 +1095,7 @@ defmodule Pleroma.Web.Router do get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta) match(:*, "/api/pleroma/*path", LegacyPleromaApiRerouterPlug, []) get("/api/*path", RedirectController, :api_not_implemented) - get("/phoenix/live_dashboard", RedirectController, :live_dashboard) + get("/phoenix/live_dashboard/*path", RedirectController, :live_dashboard) get("/*path", RedirectController, :redirector_with_preload) options("/*path", RedirectController, :empty) diff --git a/lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex b/lib/pleroma/web/templates/o_auth/password/invalid_token.html.eex similarity index 100% rename from lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex rename to lib/pleroma/web/templates/o_auth/password/invalid_token.html.eex diff --git a/lib/pleroma/web/templates/twitter_api/password/reset.html.eex b/lib/pleroma/web/templates/o_auth/password/reset.html.eex similarity index 100% rename from lib/pleroma/web/templates/twitter_api/password/reset.html.eex rename to lib/pleroma/web/templates/o_auth/password/reset.html.eex diff --git a/lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex b/lib/pleroma/web/templates/o_auth/password/reset_failed.html.eex similarity index 100% rename from lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex rename to lib/pleroma/web/templates/o_auth/password/reset_failed.html.eex diff --git a/lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex b/lib/pleroma/web/templates/o_auth/password/reset_success.html.eex similarity index 100% rename from lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex rename to lib/pleroma/web/templates/o_auth/password/reset_success.html.eex diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex b/lib/pleroma/web/templates/remote_interaction/remote_interaction/follow.html.eex similarity index 83% rename from lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex rename to lib/pleroma/web/templates/remote_interaction/remote_interaction/follow.html.eex index e2d251fac..00cc7d383 100644 --- a/lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex +++ b/lib/pleroma/web/templates/remote_interaction/remote_interaction/follow.html.eex @@ -4,7 +4,7 @@

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

<%= @followee.nickname %>

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

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

<%= @followee.nickname %>

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

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

<%= @followee.nickname %>

-<%= form_for @conn, Routes.remote_follow_path(@conn, :do_follow), [as: "mfa"], fn f -> %> +<%= form_for @conn, Routes.remote_interaction_path(@conn, :do_follow), [as: "mfa"], fn f -> %> <%= text_input f, :code, placeholder: Gettext.dpgettext("static_pages", "placeholder text for auth code entry", "Authentication code"), required: true %>
<%= hidden_input f, :id, value: @followee.id %> diff --git a/lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex b/lib/pleroma/web/templates/remote_interaction/remote_interaction/followed.html.eex similarity index 100% rename from lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex rename to lib/pleroma/web/templates/remote_interaction/remote_interaction/followed.html.eex diff --git a/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex b/lib/pleroma/web/templates/remote_interaction/remote_interaction/status_interact.html.eex similarity index 88% rename from lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex rename to lib/pleroma/web/templates/remote_interaction/remote_interaction/status_interact.html.eex index d77174967..b3d590186 100644 --- a/lib/pleroma/web/templates/twitter_api/util/status_interact.html.eex +++ b/lib/pleroma/web/templates/remote_interaction/remote_interaction/status_interact.html.eex @@ -2,7 +2,7 @@

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

<% else %>

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

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

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

<% else %>

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

- <%= form_for @conn, Routes.util_path(@conn, :remote_subscribe), [as: "user"], fn f -> %> + <%= form_for @conn, Routes.remote_interaction_path(@conn, :remote_subscribe), [as: "user"], fn f -> %> <%= hidden_input f, :nickname, value: @nickname %> <%= text_input f, :profile, placeholder: Gettext.dpgettext("static_pages", "placeholder text for account id", "Your account ID, e.g. lain@quitter.se") %> <%= submit Gettext.dpgettext("static_pages", "remote follow authorization button for following with a remote account", "Follow") %> diff --git a/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex b/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex index a14ca305e..85bfd7b3a 100644 --- a/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex +++ b/lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex @@ -2,7 +2,7 @@

<%= link instance_name(), to: "/" %>

-
+ diff --git a/lib/pleroma/web/twitter_api/views/util_view.ex b/lib/pleroma/web/twitter_api/views/util_view.ex deleted file mode 100644 index 31b7c0c0c..000000000 --- a/lib/pleroma/web/twitter_api/views/util_view.ex +++ /dev/null @@ -1,31 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2022 Pleroma Authors -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.TwitterAPI.UtilView do - use Pleroma.Web, :view - import Phoenix.HTML - import Phoenix.HTML.Form - import Phoenix.HTML.Link - alias Pleroma.Config - alias Pleroma.Web.Endpoint - alias Pleroma.Web.Gettext - - def status_net_config(instance) do - """ - - - #{Keyword.get(instance, :name)} - #{Endpoint.url()} - #{Keyword.get(instance, :limit)} - #{!Keyword.get(instance, :registrations_open)} - - - """ - end - - def render("frontend_configurations.json", _) do - Config.get(:frontend_configurations, %{}) - |> Enum.into(%{}) - end -end diff --git a/priv/repo/migrations/20200825061316_move_activity_expirations_to_oban.exs b/priv/repo/migrations/20200825061316_move_activity_expirations_to_oban.exs index f15876180..b512313c7 100644 --- a/priv/repo/migrations/20200825061316_move_activity_expirations_to_oban.exs +++ b/priv/repo/migrations/20200825061316_move_activity_expirations_to_oban.exs @@ -23,10 +23,12 @@ defmodule Pleroma.Repo.Migrations.MoveActivityExpirationsToOban do |> Pleroma.Repo.stream() |> Stream.each(fn expiration -> with {:ok, expires_at} <- DateTime.from_naive(expiration.scheduled_at, "Etc/UTC") do - Pleroma.Workers.PurgeExpiredActivity.enqueue(%{ - activity_id: FlakeId.to_string(expiration.activity_id), - expires_at: expires_at - }) + Pleroma.Workers.PurgeExpiredActivity.enqueue( + %{ + activity_id: FlakeId.to_string(expiration.activity_id) + }, + scheduled_at: expires_at + ) end end) |> Stream.run() diff --git a/priv/repo/migrations/20200907092050_move_tokens_expiration_into_oban.exs b/priv/repo/migrations/20200907092050_move_tokens_expiration_into_oban.exs index c140bc66a..0a55e4f71 100644 --- a/priv/repo/migrations/20200907092050_move_tokens_expiration_into_oban.exs +++ b/priv/repo/migrations/20200907092050_move_tokens_expiration_into_oban.exs @@ -21,7 +21,7 @@ defmodule Pleroma.Repo.Migrations.MoveTokensExpirationIntoOban do from(t in Pleroma.Web.OAuth.Token, where: t.valid_until > ^NaiveDateTime.utc_now()) |> Pleroma.Repo.stream() |> Stream.each(fn token -> - Pleroma.Workers.PurgeExpiredToken.enqueue(%{ + enqueue(%{ token_id: token.id, valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), mod: Pleroma.Web.OAuth.Token @@ -33,7 +33,7 @@ defmodule Pleroma.Repo.Migrations.MoveTokensExpirationIntoOban do from(t in Pleroma.MFA.Token, where: t.valid_until > ^NaiveDateTime.utc_now()) |> Pleroma.Repo.stream() |> Stream.each(fn token -> - Pleroma.Workers.PurgeExpiredToken.enqueue(%{ + enqueue(%{ token_id: token.id, valid_until: DateTime.from_naive!(token.valid_until, "Etc/UTC"), mod: Pleroma.MFA.Token @@ -41,4 +41,14 @@ defmodule Pleroma.Repo.Migrations.MoveTokensExpirationIntoOban do end) |> Stream.run() end + + @spec enqueue(%{token_id: integer(), valid_until: DateTime.t()}) :: + {:ok, Oban.Job.t()} | {:error, Ecto.Changeset.t()} + defp enqueue(args) do + {scheduled_at, args} = Map.pop(args, :valid_until) + + args + |> Pleroma.Workers.PurgeExpiredToken.new(scheduled_at: scheduled_at) + |> Oban.insert() + end end diff --git a/priv/repo/migrations/20260218000000_add_exclusive_to_lists.exs b/priv/repo/migrations/20260218000000_add_exclusive_to_lists.exs new file mode 100644 index 000000000..253a8acb7 --- /dev/null +++ b/priv/repo/migrations/20260218000000_add_exclusive_to_lists.exs @@ -0,0 +1,9 @@ +defmodule Pleroma.Repo.Migrations.AddExclusiveToLists do + use Ecto.Migration + + def change do + alter table(:lists) do + add(:exclusive, :boolean, default: false) + end + end +end diff --git a/test/fixtures/users_mock/hubzilla-actor-alsoknownas-string.json b/test/fixtures/users_mock/hubzilla-actor-alsoknownas-string.json new file mode 100644 index 000000000..086db73b1 --- /dev/null +++ b/test/fixtures/users_mock/hubzilla-actor-alsoknownas-string.json @@ -0,0 +1 @@ +{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1","https://purl.archive.org/socialweb/webfinger",{"zot":"https://hub.netzgemeinde.eu/apschema#","contextHistory":"https://w3id.org/fep/171b/contextHistory","schema":"http://schema.org#","ostatus":"http://ostatus.org#","diaspora":"https://diasporafoundation.org/ns/","litepub":"http://litepub.social/ns#","toot":"http://joinmastodon.org/ns#","commentPolicy":"zot:commentPolicy","Bookmark":"zot:Bookmark","Category":"zot:Category","Emoji":"toot:Emoji","directMessage":"litepub:directMessage","PropertyValue":"schema:PropertyValue","value":"schema:value","uuid":"schema:identifier","conversation":"ostatus:conversation","manuallyApprovesFollowers":"as:manuallyApprovesFollowers","Hashtag":"as:Hashtag","quoteUrl":"as:quoteUrl","quoteUri":"http://fedibird.com/ns#quoteUri"}],"type":"Person","manuallyApprovesFollowers":true,"id":"https://hub.netzgemeinde.eu/channel/jupiter_rowland","preferredUsername":"jupiter_rowland","name":"Jupiter Rowland","updated":"0001-01-01T00:00:00Z","icon":{"type":"Image","mediaType":"image/png","updated":"2024-10-15T21:10:02Z","url":"https://hub.netzgemeinde.eu/photo/profile/l/1035?rev=1729019402","height":300,"width":300},"url":"https://hub.netzgemeinde.eu/channel/jupiter_rowland","publicKey":{"id":"https://hub.netzgemeinde.eu/channel/jupiter_rowland","owner":"https://hub.netzgemeinde.eu/channel/jupiter_rowland","signatureAlgorithm":"http://www.w3.org/2001/04/xmldsig-more#rsa-sha256","publicKeyPem":"-----BEGIN PUBLIC KEY-----\nMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAmDB9nkcdhjzcfSPQG5q3\nxRVAYaWa+pKC38NhhRMbmd7+P8+8be3HUuX97bwhLdSA3+IRMz9JmX8bqqtKPK8A\nKxWFdUxoZKefwAAVpT+R89hHvi6Ib56Tp5lNlUSTIg3QXfm2gyRc3ehYbI9i6+Wr\nuFgCzdYT9bIeLqhZdFabPP4NORnyPgBQtktXcQUoDOKSaaKuitxFP1dhM9Dco3uX\nLcpQOLdY6yct5J+1Y6+/GUZgtO2pjMtkoEo6Ro0+Wlo7xfvdPnk5moljDVzPFoQE\nhUao5amorPhm1/iEpQXI0eEUW8IXdObFS8gyQJLmS30AjMkaWfwM9HFGmUmn8CSw\nKGBaKDN2C93fvmLOLpaoIRqgVTHBxfv3bN/CtvTRP83/eWvmhnlYo0fmE49tj2ZH\neCHCvRPJ+XM44WntbrUmwJ61+6nO/Io7qe7zmLm+0ew1VD9xTWAd96isW8HEmhcu\nO2iP2GALb0PqE7mgQmV1x/WAYB40+29C03UINHAnZY+nvBW6xd0wAOFiYXqjJF/4\nxEOWPvwcwOtltX+dTerFBC2KzLQQVk/CK2JdKtD2ssiYrvdC/IqPQzVMz3EAtVoP\n8sq/lzffCD3T+zhhnLhgxu7p9JNjRq83jMOOB3DUSd5izO+u6TBn5lasfoa7Eh+X\nQezGvMXhRQR4mnzrSLwZZ1ECAwEAAQ==\n-----END PUBLIC KEY-----\n"},"tag":[{"type":"PropertyValue","name":"Protocol","value":"zot6"},{"type":"PropertyValue","name":"Protocol","value":"activitypub"}],"outbox":"https://hub.netzgemeinde.eu/outbox/jupiter_rowland","webfinger":"jupiter_rowland@hub.netzgemeinde.eu","inbox":"https://hub.netzgemeinde.eu/inbox/jupiter_rowland","followers":"https://hub.netzgemeinde.eu/followers/jupiter_rowland","following":"https://hub.netzgemeinde.eu/following/jupiter_rowland","endpoints":{"sharedInbox":"https://hub.netzgemeinde.eu/inbox"},"discoverable":true,"assertionMethod":[{"id":"https://hub.netzgemeinde.eu/channel/jupiter_rowland#z6Mkw5vE2YnuCwke9VY6Xn1jnaXqLgDCKoJsRE2PjDmAUw9w","type":"Multikey","controller":"https://hub.netzgemeinde.eu/channel/jupiter_rowland","publicKeyMultibase":"z6Mkw5vE2YnuCwke9VY6Xn1jnaXqLgDCKoJsRE2PjDmAUw9w"}],"copiedTo":"https://hub.hubzilla.de/channel/jupiter_rowland","alsoKnownAs":"https://hub.hubzilla.de/channel/jupiter_rowland","image":{"type":"Image","mediaType":"image/jpeg","url":"https://hub.netzgemeinde.eu/photo/5bd87e54-e328-47d3-a8ed-7a33547ad882-7"},"summary":"An avatar roaming the decentralised and federated 3-D virtual worlds based on OpenSimulator, a free and open-source server-side re-implementation of Second Life. Mostly talking about OpenSim, sometimes about other virtual worlds, occasionally about the Fediverse beyond Mastodon. No, the Fediverse is not only Mastodon.

If you're looking for real-life people posting about real-life topics, go look somewhere else. This channel is never about real life.

Even if you see me on Mastodon, I'm not on Mastodon myself. I'm on Hubzilla which is neither a Mastodon instance nor a Mastodon fork. In fact, it's older and much more powerful than Mastodon. And it has always been connected to Mastodon.

I regularly write posts with way more than 500 characters. If that disturbs you, block me now, but don't complain. I'm not on Mastodon, I don't have a character limit here.

I rather give too many content warnings than too few. But I have absolutely no means of blanking out pictures for Mastodon users.

I always describe my images, no matter how long it takes. My posts with image descriptions tend to be my longest. Don't go looking for my image descriptions in the alt-text; they're always in the post text which is always hidden behind a content warning due to being over 500 characters long.

If you follow me, and I "follow" you back, I don't actually follow you and receive your posts. Unless you've got something to say that's interesting to me within the scope of this channel, or I know you from OpenSim, I'll most likely deny you the permission to send me your posts. I only "follow" you back because Hubzilla requires me to do that to allow you to follow me. But I do let you send me your comments and direct messages. If you boost a lot of uninteresting stuff, I'll block you boosts.

My "birthday" isn't my actual birthday but my rezday. My first avatar has been around since that day.

If you happen to know German, maybe my "homepage" is something for you, a blog which, much like this channel, is about OpenSim and generally virtual worlds.

#OpenSim #OpenSimulator #VirtualWorlds #Metaverse #SocialVR  #fedi22","proof":{"type":"DataIntegrityProof","cryptosuite":"eddsa-jcs-2022","created":"2026-02-12T17:18:56Z","verificationMethod":"https://hub.netzgemeinde.eu/channel/jupiter_rowland#z6Mkw5vE2YnuCwke9VY6Xn1jnaXqLgDCKoJsRE2PjDmAUw9w","proofPurpose":"assertionMethod","proofValue":"z65hrf1X7c2kZgiyAvBnZ1pd16LGRaLYN4zt6kLdPQtYoYpxuVYgL7zZGFEh3h6gcsq8f6FL5XDjXVNivwg9eJL3u"},"signature":{"@context":["https://www.w3.org/ns/activitystreams","https://w3id.org/security/v1"],"type":"RsaSignature2017","nonce":"87c52f614ebea65c8ea41d99487e8f8c8883bd5bc65b2538438df3870c532823","creator":"https://hub.netzgemeinde.eu/channel/jupiter_rowland","created":"2026-02-12T17:18:56Z","signatureValue":"FtiD/BaezB1SFiYj18hfAuyLgYQ0c+Mjc9CXiB+ikd8re/uXV5jr+kMgpYzY+nUB3vpU03XnWVi+lHkJ55vzcTjfbUi7puspF7e88yjIFJcHhs+fc9/+bzBbL2PSQ026bTgH3N1Hj2I7qQ3NcmvKmDF/OovWDks/HPGFiiS9uUezZHRF5be7KfUgEJjESk+5zKGkr7yG4M+f23xaScGxT+/uGK69/RT7QbgSiF2A1IiD2gtwVqxWe9eP//HeoXPCT+O+MdKLWtSZwwzfboO/iwNehMtSAAq+POBW7emHTcM1FEZD6abEQAQwpTq28qAnjuYNCU4TzBdKg1q4akhqnTWEyqpscS9xVUUv/iRiyXvWh9WtcGSCvcMb6avUsxGmdk0nO8hv2zigR/s/ZZJhjXUrID+/bwP2o9Uie6ibdwmt1bCHR8U6z7NCQXEJXw9qZuZYpiTFeuNSftICVRuE/NIYpSOPyqERlRnjU6AX6qEQXHQsOnvhB8BUKcRXpWv0xuzWN3Sr2MZyul8A98nvla9Uwqh9BYn70nRcpRGfiY3m41N4kn5WbPw5PezOB8RSFZjji963oum8tu0/NjzKQ/5UOh3aPO+h+AJsnjau2KOeRHLeChalOOLrt3KFalkyGxLzxt5o/OcRvyM37nnih14lqeTNo60PmR/l/QpiwBs="}} \ No newline at end of file diff --git a/test/pleroma/config_db_test.exs b/test/pleroma/config_db_test.exs index d68e4e6fa..9d70e6d54 100644 --- a/test/pleroma/config_db_test.exs +++ b/test/pleroma/config_db_test.exs @@ -174,6 +174,22 @@ defmodule Pleroma.ConfigDBTest do assert updated1.value == [groups: [c: 3, d: 4], key: [a: 1, b: 2]] assert updated2.value == [mascots: [c: 3, d: 4], key: [a: 1, b: 2]] end + + test "rejects invalid :rate_limit values (e.g. empty-string scale from AdminFE)" do + assert {:error, _changeset} = + ConfigDB.update_or_create(%{ + group: ":pleroma", + key: ":rate_limit", + value: [ + %{ + "tuple" => [ + ":statuses_actions", + [%{"tuple" => ["", 0]}, %{"tuple" => ["", ""]}] + ] + } + ] + }) + end end describe "delete/1" do @@ -273,24 +289,28 @@ defmodule Pleroma.ConfigDBTest do end test "sigil" do - assert ConfigDB.to_elixir_types("~r[comp[lL][aA][iI][nN]er]") == ~r/comp[lL][aA][iI][nN]er/ + assert ConfigDB.to_elixir_types("~r[comp[lL][aA][iI][nN]er]").source == + ~r/comp[lL][aA][iI][nN]er/.source end test "link sigil" do - assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/") == ~r/https:\/\/example.com/ + assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/").source == + ~r/https:\/\/example.com/.source end test "link sigil with um modifiers" do - assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/um") == - ~r/https:\/\/example.com/um + assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/um").source == + ~r/https:\/\/example.com/um.source end test "link sigil with i modifier" do - assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/i") == ~r/https:\/\/example.com/i + assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/i").source == + ~r/https:\/\/example.com/i.source end test "link sigil with s modifier" do - assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/s") == ~r/https:\/\/example.com/s + assert ConfigDB.to_elixir_types("~r/https:\/\/example.com/s").source == + ~r/https:\/\/example.com/s.source end test "raise if valid delimiter not found" do @@ -460,11 +480,11 @@ defmodule Pleroma.ConfigDBTest do test "complex keyword with sigil" do assert ConfigDB.to_elixir_types([ %{"tuple" => [":federated_timeline_removal", []]}, - %{"tuple" => [":reject", ["~r/comp[lL][aA][iI][nN]er/"]]}, + %{"tuple" => [":reject", [~r/comp[lL][aA][iI][nN]er/.source]]}, %{"tuple" => [":replace", []]} ]) == [ federated_timeline_removal: [], - reject: [~r/comp[lL][aA][iI][nN]er/], + reject: [~r/comp[lL][aA][iI][nN]er/.source], replace: [] ] end diff --git a/test/pleroma/http/hackney_follow_redirect_regression_test.exs b/test/pleroma/http/hackney_follow_redirect_regression_test.exs index 71fda4479..4fef26af6 100644 --- a/test/pleroma/http/hackney_follow_redirect_regression_test.exs +++ b/test/pleroma/http/hackney_follow_redirect_regression_test.exs @@ -88,6 +88,49 @@ defmodule Pleroma.HTTP.HackneyFollowRedirectRegressionTest do Pleroma.ReverseProxy.Client.Hackney.close(ref) end + test "hackney reverse proxy follows nested redirects via proxy", %{ + tls_server: tls_server, + proxy: proxy + } do + url = "#{tls_server.base_url}/nested_redirect" + + opts = [ + pool: :media, + proxy: proxy.proxy_url, + insecure: true, + connect_timeout: 1_000, + recv_timeout: 1_000, + follow_redirect: true + ] + + assert {:ok, 200, _headers, ref} = + Pleroma.ReverseProxy.Client.Hackney.request(:get, url, [], "", opts) + + assert collect_body(ref) == "ok" + Pleroma.ReverseProxy.Client.Hackney.close(ref) + end + + test "hackney reverse proxy stops following redirects after limit is reached", %{ + tls_server: tls_server, + proxy: proxy + } do + url = "#{tls_server.base_url}/infinite_redirect" + + opts = [ + pool: :media, + proxy: proxy.proxy_url, + insecure: true, + connect_timeout: 1_000, + recv_timeout: 1_000, + follow_redirect: true + ] + + assert {:ok, 302, _headers, ref} = + Pleroma.ReverseProxy.Client.Hackney.request(:get, url, [], "", opts) + + Pleroma.ReverseProxy.Client.Hackney.close(ref) + end + defp collect_body(ref, acc \\ "") do case Pleroma.ReverseProxy.Client.Hackney.stream_body(ref) do :done -> acc @@ -148,6 +191,12 @@ defmodule Pleroma.HTTP.HackneyFollowRedirectRegressionTest do {:ok, data} <- recv_ssl_headers(ssl_socket), {:ok, path} <- parse_path(data) do case path do + "/infinite_redirect" -> + send_ssl_response(ssl_socket, 302, "Found", [{"Location", "/infinite_redirect"}], "") + + "/nested_redirect" -> + send_ssl_response(ssl_socket, 302, "Found", [{"Location", "/redirect"}], "") + "/redirect" -> send_ssl_response(ssl_socket, 302, "Found", [{"Location", "/final"}], "") diff --git a/test/pleroma/integration/mastodon_websocket_test.exs b/test/pleroma/integration/mastodon_websocket_test.exs index 88f32762d..47f6f5f76 100644 --- a/test/pleroma/integration/mastodon_websocket_test.exs +++ b/test/pleroma/integration/mastodon_websocket_test.exs @@ -363,7 +363,7 @@ defmodule Pleroma.Integration.MastodonWebsocketTest do test "accepts the 'list' stream", %{token: token, user: user} do posting_user = insert(:user) - {:ok, list} = Pleroma.List.create("test", user) + {:ok, list} = Pleroma.List.create(%{title: "test"}, user) Pleroma.List.follow(list, posting_user) assert {:ok, _} = start_socket("?stream=list&access_token=#{token.token}&list=#{list.id}") diff --git a/test/pleroma/list_test.exs b/test/pleroma/list_test.exs index a68146b0d..d44310689 100644 --- a/test/pleroma/list_test.exs +++ b/test/pleroma/list_test.exs @@ -10,22 +10,23 @@ defmodule Pleroma.ListTest do test "creating a list" do user = insert(:user) - {:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", user) - %Pleroma.List{title: title} = Pleroma.List.get(list.id, user) + {:ok, %Pleroma.List{} = list} = Pleroma.List.create(%{title: "title"}, user) + %Pleroma.List{title: title, exclusive: exclusive} = Pleroma.List.get(list.id, user) assert title == "title" + assert exclusive == false end test "validates title" do user = insert(:user) - assert {:error, changeset} = Pleroma.List.create("", user) + assert {:error, changeset} = Pleroma.List.create(%{title: ""}, user) assert changeset.errors == [title: {"can't be blank", [validation: :required]}] end test "getting a list not belonging to the user" do user = insert(:user) other_user = insert(:user) - {:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", user) + {:ok, %Pleroma.List{} = list} = Pleroma.List.create(%{title: "title"}, user) ret = Pleroma.List.get(list.id, other_user) assert is_nil(ret) end @@ -33,7 +34,7 @@ defmodule Pleroma.ListTest do test "adding an user to a list" do user = insert(:user) other_user = insert(:user) - {:ok, list} = Pleroma.List.create("title", user) + {:ok, list} = Pleroma.List.create(%{title: "title"}, user) {:ok, %{following: following}} = Pleroma.List.follow(list, other_user) assert [other_user.follower_address] == following end @@ -41,7 +42,7 @@ defmodule Pleroma.ListTest do test "removing an user from a list" do user = insert(:user) other_user = insert(:user) - {:ok, list} = Pleroma.List.create("title", user) + {:ok, list} = Pleroma.List.create(%{title: "title"}, user) {:ok, %{following: _following}} = Pleroma.List.follow(list, other_user) {:ok, %{following: following}} = Pleroma.List.unfollow(list, other_user) assert [] == following @@ -49,14 +50,27 @@ defmodule Pleroma.ListTest do test "renaming a list" do user = insert(:user) - {:ok, list} = Pleroma.List.create("title", user) - {:ok, %{title: title}} = Pleroma.List.rename(list, "new") + {:ok, list} = Pleroma.List.create(%{title: "title"}, user) + {:ok, %{title: title}} = Pleroma.List.update(list, %{title: "new"}) assert "new" == title end + test "updating a list exclusivity" do + user = insert(:user) + + {:ok, %{exclusive: exclusive} = list} = + Pleroma.List.create(%{title: "title", exclusive: true}, user) + + assert exclusive == true + {:ok, %{exclusive: exclusive} = list} = Pleroma.List.update(list, %{exclusive: false}) + assert exclusive == false + {:ok, %{exclusive: exclusive}} = Pleroma.List.update(list, %{exclusive: true}) + assert exclusive == true + end + test "deleting a list" do user = insert(:user) - {:ok, list} = Pleroma.List.create("title", user) + {:ok, list} = Pleroma.List.create(%{title: "title"}, user) {:ok, list} = Pleroma.List.delete(list) assert is_nil(Repo.get(Pleroma.List, list.id)) end @@ -65,7 +79,7 @@ defmodule Pleroma.ListTest do user = insert(:user) other_user = insert(:user) third_user = insert(:user) - {:ok, list} = Pleroma.List.create("title", user) + {:ok, list} = Pleroma.List.create(%{title: "title"}, user) {:ok, list} = Pleroma.List.follow(list, other_user) {:ok, list} = Pleroma.List.follow(list, third_user) {:ok, following} = Pleroma.List.get_following(list) @@ -76,9 +90,9 @@ defmodule Pleroma.ListTest do test "getting all lists by an user" do user = insert(:user) other_user = insert(:user) - {:ok, list_one} = Pleroma.List.create("title", user) - {:ok, list_two} = Pleroma.List.create("other title", user) - {:ok, list_three} = Pleroma.List.create("third title", other_user) + {:ok, list_one} = Pleroma.List.create(%{title: "title"}, user) + {:ok, list_two} = Pleroma.List.create(%{title: "other title"}, user) + {:ok, list_three} = Pleroma.List.create(%{title: "third title"}, other_user) lists = Pleroma.List.for_user(user, %{}) assert list_one in lists assert list_two in lists @@ -88,9 +102,9 @@ defmodule Pleroma.ListTest do test "getting all lists the user is a member of" do user = insert(:user) other_user = insert(:user) - {:ok, list_one} = Pleroma.List.create("title", user) - {:ok, list_two} = Pleroma.List.create("other title", user) - {:ok, list_three} = Pleroma.List.create("third title", other_user) + {:ok, list_one} = Pleroma.List.create(%{title: "title"}, user) + {:ok, list_two} = Pleroma.List.create(%{title: "other title"}, user) + {:ok, list_three} = Pleroma.List.create(%{title: "third title"}, other_user) {:ok, list_one} = Pleroma.List.follow(list_one, other_user) {:ok, list_two} = Pleroma.List.follow(list_two, other_user) {:ok, list_three} = Pleroma.List.follow(list_three, user) @@ -106,8 +120,8 @@ defmodule Pleroma.ListTest do not_owner = insert(:user) member_1 = insert(:user) member_2 = insert(:user) - {:ok, owned_list} = Pleroma.List.create("owned", owner) - {:ok, not_owned_list} = Pleroma.List.create("not owned", not_owner) + {:ok, owned_list} = Pleroma.List.create(%{title: "owned"}, owner) + {:ok, not_owned_list} = Pleroma.List.create(%{title: "not owned"}, not_owner) {:ok, owned_list} = Pleroma.List.follow(owned_list, member_1) {:ok, owned_list} = Pleroma.List.follow(owned_list, member_2) {:ok, not_owned_list} = Pleroma.List.follow(not_owned_list, member_1) @@ -123,14 +137,14 @@ defmodule Pleroma.ListTest do test "get by ap_id" do user = insert(:user) - {:ok, list} = Pleroma.List.create("foo", user) + {:ok, list} = Pleroma.List.create(%{title: "foo"}, user) assert Pleroma.List.get_by_ap_id(list.ap_id) == list end test "memberships" do user = insert(:user) member = insert(:user) - {:ok, list} = Pleroma.List.create("foo", user) + {:ok, list} = Pleroma.List.create(%{title: "foo"}, user) {:ok, list} = Pleroma.List.follow(list, member) assert Pleroma.List.memberships(member) == [list.ap_id] @@ -140,7 +154,7 @@ defmodule Pleroma.ListTest do user = insert(:user) member = insert(:user) - {:ok, list} = Pleroma.List.create("foo", user) + {:ok, list} = Pleroma.List.create(%{title: "foo"}, user) {:ok, list} = Pleroma.List.follow(list, member) assert Pleroma.List.member?(list, member) diff --git a/test/pleroma/marker_test.exs b/test/pleroma/marker_test.exs index 819dde9be..7f573ac7a 100644 --- a/test/pleroma/marker_test.exs +++ b/test/pleroma/marker_test.exs @@ -36,11 +36,12 @@ defmodule Pleroma.MarkerTest do insert(:notification, user: user, activity: insert(:note_activity)) insert(:notification, user: user, activity: insert(:note_activity)) insert(:marker, timeline: "home", user: user) + %Marker{} = refreshed_marker = refresh_record(marker) assert Marker.get_markers( user, ["notifications"] - ) == [%Marker{refresh_record(marker) | unread_count: 2}] + ) == [%{refreshed_marker | unread_count: 2}] end end diff --git a/test/pleroma/repo_test.exs b/test/pleroma/repo_test.exs index 9c0f5d028..721175bda 100644 --- a/test/pleroma/repo_test.exs +++ b/test/pleroma/repo_test.exs @@ -24,7 +24,8 @@ defmodule Pleroma.RepoTest do describe "get_assoc/2" do test "get assoc from preloaded data" do user = %User{name: "Agent Smith"} - token = %Pleroma.Web.OAuth.Token{insert(:oauth_token) | user: user} + %Pleroma.Web.OAuth.Token{} = token = insert(:oauth_token) + token = %{token | user: user} assert Repo.get_assoc(token, :user) == {:ok, user} end diff --git a/test/pleroma/search/meilisearch_test.exs b/test/pleroma/search/meilisearch_test.exs index eea454323..ff32491c5 100644 --- a/test/pleroma/search/meilisearch_test.exs +++ b/test/pleroma/search/meilisearch_test.exs @@ -74,29 +74,6 @@ defmodule Pleroma.Search.MeilisearchTest do assert_received("posted_to_meilisearch") end - test "doesn't index posts that are not public" do - user = insert(:user) - - Enum.each(["private", "direct"], fn visibility -> - {:ok, activity} = - CommonAPI.post(user, %{ - status: "guys i just don't wanna leave the swamp", - visibility: visibility - }) - - args = %{"op" => "add_to_index", "activity" => activity.id} - - Config - |> expect(:get, fn - [Pleroma.Search, :module], nil -> - Meilisearch - end) - - assert_enqueued(worker: SearchIndexingWorker, args: args) - assert :ok = perform_job(SearchIndexingWorker, args) - end) - end - test "deletes posts from index when deleted locally" do user = insert(:user) diff --git a/test/pleroma/search_test.exs b/test/pleroma/search_test.exs new file mode 100644 index 000000000..d777bcda2 --- /dev/null +++ b/test/pleroma/search_test.exs @@ -0,0 +1,69 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2021 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.SearchTest do + use Pleroma.DataCase, async: true + use Oban.Testing, repo: Pleroma.Repo + + import Pleroma.Factory + + alias Pleroma.Web.CommonAPI + alias Pleroma.Workers.SearchIndexingWorker + + test "indexes posts that are public" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + status: "Well this is a story all about how my life got flipped turned upside down", + visibility: "public" + }) + + args = %{"op" => "add_to_index", "activity" => activity.id} + + assert_enqueued(worker: SearchIndexingWorker, args: args) + end + + test "doesn't index posts that are not public" do + user = insert(:user) + + Enum.each(["private", "direct"], fn visibility -> + {:ok, activity} = + CommonAPI.post(user, %{ + status: "guys i just don't wanna leave the swamp", + visibility: visibility + }) + + args = %{"op" => "add_to_index", "activity" => activity.id} + + refute_enqueued(worker: SearchIndexingWorker, args: args) + end) + end + + test "Indexes appropriate activity types" do + user = insert(:user) + + {:ok, activity} = + CommonAPI.post(user, %{ + status: "I'm my own hype man", + visibility: "public" + }) + + args = %{"op" => "add_to_index", "activity" => activity.id} + + assert_enqueued(worker: SearchIndexingWorker, args: args) + + {:ok, fav_activity} = CommonAPI.favorite(activity.id, user) + + args = %{"op" => "add_to_index", "activity" => fav_activity.id} + + refute_enqueued(worker: SearchIndexingWorker, args: args) + + {:ok, repeat_activity} = CommonAPI.repeat(activity.id, user) + + args = %{"op" => "add_to_index", "activity" => repeat_activity.id} + + refute_enqueued(worker: SearchIndexingWorker, args: args) + end +end diff --git a/test/pleroma/web/activity_pub/activity_pub_test.exs b/test/pleroma/web/activity_pub/activity_pub_test.exs index 73f53db56..9aafc41a5 100644 --- a/test/pleroma/web/activity_pub/activity_pub_test.exs +++ b/test/pleroma/web/activity_pub/activity_pub_test.exs @@ -499,6 +499,35 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do "https://queef.in/storage/banner.gif" end + test "works with alsoKnownAs as string" do + user_id = "https://hub.netzgemeinde.eu/channel/jupiter_rowland" + + user_data = + "test/fixtures/users_mock/hubzilla-actor-alsoknownas-string.json" + |> File.read!() + + user_data_decoded = + user_data + |> Jason.decode!() + + Tesla.Mock.mock(fn + %{ + method: :get, + url: ^user_id + } -> + %Tesla.Env{ + status: 200, + body: user_data, + headers: [{"content-type", "application/activity+json"}] + } + end) + + {:ok, user} = ActivityPub.make_user_from_ap_id(user_id) + + assert is_list(user.also_known_as) + assert user.also_known_as == [user_data_decoded["alsoKnownAs"]] + end + test "it fetches the appropriate tag-restricted posts" do user = insert(:user) @@ -1495,8 +1524,8 @@ 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"} + test "strips / from filename", %{test_file: %Plug.Upload{} = file} do + file = %{file | filename: "../../../../../nested/bad.jpg"} {:ok, %Object{} = object} = ActivityPub.upload(file) [%{"href" => href}] = object.data["url"] assert Regex.match?(~r"/bad.jpg$", href) @@ -1754,13 +1783,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do test "fetch_activities/2 returns activities addressed to a list " do user = insert(:user) member = insert(:user) - {:ok, list} = Pleroma.List.create("foo", user) + {:ok, list} = Pleroma.List.create(%{title: "foo"}, user) {:ok, list} = Pleroma.List.follow(list, member) - {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"}) + {:ok, %Activity{} = activity} = + CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"}) activity = Repo.preload(activity, :bookmark) - activity = %Activity{activity | thread_muted?: !!activity.thread_muted?} + activity = %{activity | thread_muted?: !!activity.thread_muted?} assert ActivityPub.fetch_activities([], %{user: user}) == [activity] end @@ -1960,7 +1990,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert User.following?(follower, old_user) assert User.following?(follower_move_opted_out, old_user) - assert {:ok, activity} = ActivityPub.move(old_user, new_user) + assert {:ok, %Activity{} = activity} = ActivityPub.move(old_user, new_user) assert %Activity{ actor: ^old_ap_id, @@ -1992,7 +2022,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubTest do assert User.following?(follower_move_opted_out, old_user) refute User.following?(follower_move_opted_out, new_user) - activity = %Activity{activity | object: nil} + activity = %{activity | object: nil} assert [%Notification{activity: ^activity}] = Notification.for_user(follower) diff --git a/test/pleroma/web/activity_pub/mrf_test.exs b/test/pleroma/web/activity_pub/mrf_test.exs index 25548e3da..401d4ebb3 100644 --- a/test/pleroma/web/activity_pub/mrf_test.exs +++ b/test/pleroma/web/activity_pub/mrf_test.exs @@ -10,18 +10,25 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do alias Pleroma.Web.ActivityPub.MRF + defp regexes_match!([],[]), do: true + + defp regexes_match!([authority | authority_rest], [checked | checked_rest]) do + authority.source == checked.source and regexes_match!(authority_rest, checked_rest) + end + + defp regexes_match!(_, _), do: false + test "subdomains_regex/1" do - assert MRF.subdomains_regex(["unsafe.tld", "*.unsafe.tld"]) == [ - ~r/^unsafe.tld$/i, - ~r/^(.*\.)*unsafe.tld$/i - ] + regexes = MRF.subdomains_regex(["unsafe.tld", "*.unsafe.tld"]) + + assert regexes_match!(regexes, [~r/^unsafe.tld$/i, ~r/^(.*\.)*unsafe.tld$/i]) end describe "subdomain_match/2" do test "common domains" do regexes = MRF.subdomains_regex(["unsafe.tld", "unsafe2.tld"]) - assert regexes == [~r/^unsafe.tld$/i, ~r/^unsafe2.tld$/i] + assert regexes_match!(regexes, [~r/^unsafe.tld$/i, ~r/^unsafe2.tld$/i]) assert MRF.subdomain_match?(regexes, "unsafe.tld") assert MRF.subdomain_match?(regexes, "unsafe2.tld") @@ -32,7 +39,7 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do test "wildcard domains with one subdomain" do regexes = MRF.subdomains_regex(["*.unsafe.tld"]) - assert regexes == [~r/^(.*\.)*unsafe.tld$/i] + assert regexes_match!(regexes, [~r/^(.*\.)*unsafe.tld$/i]) assert MRF.subdomain_match?(regexes, "unsafe.tld") assert MRF.subdomain_match?(regexes, "sub.unsafe.tld") @@ -43,7 +50,7 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do test "wildcard domains with two subdomains" do regexes = MRF.subdomains_regex(["*.unsafe.tld"]) - assert regexes == [~r/^(.*\.)*unsafe.tld$/i] + assert regexes_match!(regexes, [~r/^(.*\.)*unsafe.tld$/i]) assert MRF.subdomain_match?(regexes, "unsafe.tld") assert MRF.subdomain_match?(regexes, "sub.sub.unsafe.tld") @@ -54,7 +61,7 @@ defmodule Pleroma.Web.ActivityPub.MRFTest do test "matches are case-insensitive" do regexes = MRF.subdomains_regex(["UnSafe.TLD", "UnSAFE2.Tld"]) - assert regexes == [~r/^UnSafe.TLD$/i, ~r/^UnSAFE2.Tld$/i] + assert regexes_match!(regexes, [~r/^UnSafe.TLD$/i, ~r/^UnSAFE2.Tld$/i]) assert MRF.subdomain_match?(regexes, "UNSAFE.TLD") assert MRF.subdomain_match?(regexes, "UNSAFE2.TLD") diff --git a/test/pleroma/web/activity_pub/object_validators/announce_validation_test.exs b/test/pleroma/web/activity_pub/object_validators/announce_validation_test.exs index 5b2fcb26d..1ca4f04f6 100644 --- a/test/pleroma/web/activity_pub/object_validators/announce_validation_test.exs +++ b/test/pleroma/web/activity_pub/object_validators/announce_validation_test.exs @@ -86,23 +86,32 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidationTest do object = Object.normalize(post_activity, fetch: false) # Another user can't announce it - {:ok, announce, []} = Builder.announce(announcer, object, public: false) + {:ok, announce, []} = Builder.announce(announcer, object, visibility: "private") {:error, cng} = ObjectValidator.validate(announce, []) assert {:actor, {"can not announce this object", []}} in cng.errors - # The actor of the object can announce it - {:ok, announce, []} = Builder.announce(user, object, public: false) + # The actor of the object can announce it with a restrictive scope + {:ok, announce, []} = Builder.announce(user, object, visibility: "private") + assert {:ok, _, _} = ObjectValidator.validate(announce, []) + {:ok, announce, []} = Builder.announce(user, object, visibility: "direct") assert {:ok, _, _} = ObjectValidator.validate(announce, []) # The actor of the object can not announce it publicly - {:ok, announce, []} = Builder.announce(user, object, public: true) + {:ok, announce, []} = Builder.announce(user, object, visibility: "public") + {:error, cng1} = ObjectValidator.validate(announce, []) - {:error, cng} = ObjectValidator.validate(announce, []) + {:ok, announce, []} = Builder.announce(user, object, visibility: "unlisted") + {:error, cng2} = ObjectValidator.validate(announce, []) - assert {:actor, {"can not announce this object publicly", []}} in cng.errors + {:ok, announce, []} = Builder.announce(user, object, visibility: "local") + {:error, cng3} = ObjectValidator.validate(announce, []) + + for cng <- [cng1, cng2, cng3] do + assert {:actor, {"can not announce this object publicly", []}} in cng.errors + end end end end diff --git a/test/pleroma/web/activity_pub/publisher_test.exs b/test/pleroma/web/activity_pub/publisher_test.exs index 7f07ee3a7..447ffda1b 100644 --- a/test/pleroma/web/activity_pub/publisher_test.exs +++ b/test/pleroma/web/activity_pub/publisher_test.exs @@ -334,7 +334,7 @@ defmodule Pleroma.Web.ActivityPub.PublisherTest do test "activity with BCC is published to a list member." do actor = insert(:user) - {:ok, list} = Pleroma.List.create("list", actor) + {:ok, list} = Pleroma.List.create(%{title: "list"}, actor) list_member = insert(:user, %{local: false}) Pleroma.List.follow(list, list_member) diff --git a/test/pleroma/web/activity_pub/side_effects_test.exs b/test/pleroma/web/activity_pub/side_effects_test.exs index 4a18cab68..6d20c591c 100644 --- a/test/pleroma/web/activity_pub/side_effects_test.exs +++ b/test/pleroma/web/activity_pub/side_effects_test.exs @@ -784,13 +784,15 @@ defmodule Pleroma.Web.ActivityPub.SideEffectsTest do {:ok, post} = CommonAPI.post(poster, %{status: "hey"}) {:ok, private_post} = CommonAPI.post(poster, %{status: "hey", visibility: "private"}) - {:ok, announce_data, _meta} = Builder.announce(user, post.object, public: true) + {:ok, announce_data, _meta} = Builder.announce(user, post.object, visibility: "public") {:ok, private_announce_data, _meta} = - Builder.announce(user, private_post.object, public: false) + Builder.announce(user, private_post.object, visibility: "private") {:ok, relay_announce_data, _meta} = - Builder.announce(Pleroma.Web.ActivityPub.Relay.get_actor(), post.object, public: true) + Builder.announce(Pleroma.Web.ActivityPub.Relay.get_actor(), post.object, + visibility: "public" + ) {:ok, announce, _meta} = ActivityPub.persist(announce_data, local: true) {:ok, private_announce, _meta} = ActivityPub.persist(private_announce_data, local: true) diff --git a/test/pleroma/web/activity_pub/transmogrifier_test.exs b/test/pleroma/web/activity_pub/transmogrifier_test.exs index f0a64e781..c1e01557d 100644 --- a/test/pleroma/web/activity_pub/transmogrifier_test.exs +++ b/test/pleroma/web/activity_pub/transmogrifier_test.exs @@ -581,7 +581,7 @@ defmodule Pleroma.Web.ActivityPub.TransmogrifierTest do test "it strips BCC field" do user = insert(:user) - {:ok, list} = Pleroma.List.create("foo", user) + {:ok, list} = Pleroma.List.create(%{title: "foo"}, user) {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"}) diff --git a/test/pleroma/web/activity_pub/visibility_test.exs b/test/pleroma/web/activity_pub/visibility_test.exs index fd3dc83a1..f92f2df16 100644 --- a/test/pleroma/web/activity_pub/visibility_test.exs +++ b/test/pleroma/web/activity_pub/visibility_test.exs @@ -17,7 +17,7 @@ defmodule Pleroma.Web.ActivityPub.VisibilityTest do following = insert(:user) unrelated = insert(:user) {:ok, following, user} = Pleroma.User.follow(following, user) - {:ok, list} = Pleroma.List.create("foo", user) + {:ok, list} = Pleroma.List.create(%{title: "foo"}, user) Pleroma.List.follow(list, unrelated) diff --git a/test/pleroma/web/common_api/utils_test.exs b/test/pleroma/web/common_api/utils_test.exs index 27b1da1e3..d0cbc3111 100644 --- a/test/pleroma/web/common_api/utils_test.exs +++ b/test/pleroma/web/common_api/utils_test.exs @@ -647,7 +647,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do describe "maybe_add_list_data/3" do test "adds list params when found user list" do user = insert(:user) - {:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", user) + {:ok, %Pleroma.List{} = list} = Pleroma.List.create(%{title: "title"}, user) assert Utils.maybe_add_list_data(%{additional: %{}, object: %{}}, user, {:list, list.id}) == %{ @@ -658,7 +658,7 @@ defmodule Pleroma.Web.CommonAPI.UtilsTest do test "returns original params when list not found" do user = insert(:user) - {:ok, %Pleroma.List{} = list} = Pleroma.List.create("title", insert(:user)) + {:ok, %Pleroma.List{} = list} = Pleroma.List.create(%{title: "title"}, insert(:user)) assert Utils.maybe_add_list_data(%{additional: %{}, object: %{}}, user, {:list, list.id}) == %{additional: %{}, object: %{}} diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs index 4eb057712..017fac696 100644 --- a/test/pleroma/web/common_api_test.exs +++ b/test/pleroma/web/common_api_test.exs @@ -759,7 +759,7 @@ defmodule Pleroma.Web.CommonAPITest do test "it allows to address a list" do user = insert(:user) - {:ok, list} = Pleroma.List.create("foo", user) + {:ok, list} = Pleroma.List.create(%{title: "foo"}, user) {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"}) diff --git a/test/pleroma/web/fallback_test.exs b/test/pleroma/web/fallback_test.exs index 6d0ba3d2a..bc3052959 100644 --- a/test/pleroma/web/fallback_test.exs +++ b/test/pleroma/web/fallback_test.exs @@ -79,6 +79,12 @@ defmodule Pleroma.Web.FallbackTest do test "GET /phoenix/live_dashboard -> /pleroma/live_dashboard", %{conn: conn} do assert redirected_to(get(conn, "/phoenix/live_dashboard")) =~ "/pleroma/live_dashboard" + assert redirected_to(get(conn, "/phoenix/live_dashboard/")) =~ "/pleroma/live_dashboard/" + end + + test "GET /phoenix/live_dashboard/* -> /pleroma/live_dashboard/*", %{conn: conn} do + assert redirected_to(get(conn, "/phoenix/live_dashboard/ecto_stats?nav=diagnose")) =~ + "/pleroma/live_dashboard/ecto_stats?nav=diagnose" end test "OPTIONS /*path", %{conn: conn} do diff --git a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs index 14a8eb9a0..67f292506 100644 --- a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs @@ -1815,7 +1815,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do test "returns lists to which the account belongs" do %{user: user, conn: conn} = oauth_access(["read:lists"]) other_user = insert(:user) - assert {:ok, %Pleroma.List{id: _list_id} = list} = Pleroma.List.create("Test List", user) + assert {:ok, %Pleroma.List{id: _list_id} = list} = Pleroma.List.create(%{title: "Test List"}, user) {:ok, %{following: _following}} = Pleroma.List.follow(list, other_user) assert [%{"id" => _list_id, "title" => "Test List"}] = diff --git a/test/pleroma/web/mastodon_api/controllers/list_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/list_controller_test.exs index 430b8b89d..bfda48be4 100644 --- a/test/pleroma/web/mastodon_api/controllers/list_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/list_controller_test.exs @@ -56,7 +56,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do %{user: user, conn: conn} = oauth_access(["write:lists"]) other_user = insert(:user) third_user = insert(:user) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) assert %{} == conn @@ -77,7 +77,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do other_user = insert(:user) third_user = insert(:user) fourth_user = insert(:user) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) {:ok, list} = Pleroma.List.follow(list, other_user) {:ok, list} = Pleroma.List.follow(list, third_user) {:ok, list} = Pleroma.List.follow(list, fourth_user) @@ -98,7 +98,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do %{user: user, conn: conn} = oauth_access(["write:lists"]) other_user = insert(:user) third_user = insert(:user) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) {:ok, list} = Pleroma.List.follow(list, other_user) {:ok, list} = Pleroma.List.follow(list, third_user) @@ -115,7 +115,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do test "listing users in a list" do %{user: user, conn: conn} = oauth_access(["read:lists"]) other_user = insert(:user) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) {:ok, list} = Pleroma.List.follow(list, other_user) conn = @@ -129,7 +129,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do test "retrieving a list" do %{user: user, conn: conn} = oauth_access(["read:lists"]) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) conn = conn @@ -150,7 +150,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do test "renaming a list" do %{user: user, conn: conn} = oauth_access(["write:lists"]) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) assert %{"title" => "newname"} = conn @@ -161,7 +161,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do test "validates title when renaming a list" do %{user: user, conn: conn} = oauth_access(["write:lists"]) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) conn = conn @@ -175,7 +175,7 @@ defmodule Pleroma.Web.MastodonAPI.ListControllerTest do test "deleting a list" do %{user: user, conn: conn} = oauth_access(["write:lists"]) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) conn = delete(conn, "/api/v1/lists/#{list.id}") 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 ae86078d7..fc083fd0e 100644 --- a/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/media_controller_test.exs @@ -129,8 +129,8 @@ 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{ + test "Do not allow nested filename", %{conn: conn, image: %Plug.Upload{} = image} do + image = %{ image | filename: "../../../../../nested/file.jpg" } diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs index 298e92366..11e96a6ac 100644 --- a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -3068,7 +3068,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do |> json_response_and_validate_schema(:ok) {:ok, a_expires_at, 0} = DateTime.from_iso8601(a_expires_at) - assert DateTime.diff(expires_at, a_expires_at) == 0 + + assert DateTime.diff( + DateTime.truncate(expires_at, :second), + DateTime.truncate(a_expires_at, :second) + ) == 0 %{conn: conn} = oauth_access(["read:statuses"]) 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 4d646509c..b685f4ff8 100644 --- a/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/timeline_controller_test.exs @@ -149,6 +149,31 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do |> get("/api/v1/timelines/home?remote=true&local=true") |> json_response_and_validate_schema(200) == [] end + + test "the home timeline excludes posts from users in exclusive lists", %{ + user: user, + conn: conn + } do + other_user1 = insert(:user) + other_user2 = insert(:user) + + {:ok, user, other_user1} = User.follow(user, other_user1) + {:ok, user, other_user2} = User.follow(user, other_user2) + + {:ok, list} = Pleroma.List.create(%{title: "foo", exclusive: true}, user) + {:ok, _list} = Pleroma.List.follow(list, other_user1) + + {:ok, _activity} = CommonAPI.post(other_user1, %{status: "hi"}) + {:ok, %{id: activity2_id}} = CommonAPI.post(other_user2, %{status: "hi too"}) + + response = + conn + |> assign(:user, user) + |> get("/api/v1/timelines/home") + |> json_response_and_validate_schema(200) + + assert [%{"id" => ^activity2_id}] = response + end end describe "public" do @@ -606,7 +631,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do {:ok, activity_two} = CommonAPI.post(other_user, %{status: "Marisa is stupid."}) {:ok, _} = CommonAPI.repeat(activity_one.id, other_user) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) {:ok, list} = Pleroma.List.follow(list, other_user) conn = get(conn, "/api/v1/timelines/list/#{list.id}") @@ -618,7 +643,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do test "works with pagination", %{user: user, conn: conn} do other_user = insert(:user) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) {:ok, list} = Pleroma.List.follow(list, other_user) Enum.each(1..30, fn i -> @@ -644,7 +669,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do other_user = insert(:user) {:ok, _activity_one} = CommonAPI.post(user, %{status: "Marisa is cute."}) {:ok, activity_two} = CommonAPI.post(other_user, %{status: "Marisa is cute."}) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) {:ok, list} = Pleroma.List.follow(list, other_user) conn = get(conn, "/api/v1/timelines/list/#{list.id}") @@ -667,7 +692,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do visibility: "private" }) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) {:ok, list} = Pleroma.List.follow(list, other_user) conn = get(conn, "/api/v1/timelines/list/#{list.id}") @@ -685,7 +710,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do {:ok, _} = CommonAPI.react_with_emoji(activity.id, user3, "🎅") User.mute(user, user3) - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) {:ok, list} = Pleroma.List.follow(list, user2) result = @@ -716,7 +741,7 @@ defmodule Pleroma.Web.MastodonAPI.TimelineControllerTest do end test "filtering", %{user: user, conn: conn} do - {:ok, list} = Pleroma.List.create("name", user) + {:ok, list} = Pleroma.List.create(%{title: "name"}, user) local_user = insert(:user) {:ok, local_activity} = CommonAPI.post(local_user, %{status: "Marisa is stupid."}) diff --git a/test/pleroma/web/mastodon_api/views/list_view_test.exs b/test/pleroma/web/mastodon_api/views/list_view_test.exs index bbf87bab2..ae0593b6b 100644 --- a/test/pleroma/web/mastodon_api/views/list_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/list_view_test.exs @@ -10,11 +10,12 @@ defmodule Pleroma.Web.MastodonAPI.ListViewTest do test "show" do user = insert(:user) title = "mortal enemies" - {:ok, list} = Pleroma.List.create(title, user) + {:ok, list} = Pleroma.List.create(%{title: title}, user) expected = %{ id: to_string(list.id), - title: title + title: title, + exclusive: false } assert expected == ListView.render("show.json", %{list: list}) @@ -23,10 +24,13 @@ defmodule Pleroma.Web.MastodonAPI.ListViewTest do test "index" do user = insert(:user) - {:ok, list} = Pleroma.List.create("my list", user) - {:ok, list2} = Pleroma.List.create("cofe", user) + {:ok, list} = Pleroma.List.create(%{title: "my list", exclusive: false}, user) + {:ok, list2} = Pleroma.List.create(%{title: "cofe", exclusive: true}, user) - assert [%{id: _, title: "my list"}, %{id: _, title: "cofe"}] = + assert [ + %{id: _, title: "my list", exclusive: false}, + %{id: _, title: "cofe", exclusive: true} + ] = ListView.render("index.json", lists: [list, list2]) end end diff --git a/test/pleroma/web/mastodon_api/views/status_view_test.exs b/test/pleroma/web/mastodon_api/views/status_view_test.exs index 73cab817b..76123cd0f 100644 --- a/test/pleroma/web/mastodon_api/views/status_view_test.exs +++ b/test/pleroma/web/mastodon_api/views/status_view_test.exs @@ -909,7 +909,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusViewTest do test "visibility/list" do user = insert(:user) - {:ok, list} = Pleroma.List.create("foo", user) + {:ok, list} = Pleroma.List.create(%{title: "foo"}, user) {:ok, activity} = CommonAPI.post(user, %{status: "foobar", visibility: "list:#{list.id}"}) diff --git a/test/pleroma/web/twitter_api/password_controller_test.exs b/test/pleroma/web/o_auth/password_controller_test.exs similarity index 99% rename from test/pleroma/web/twitter_api/password_controller_test.exs rename to test/pleroma/web/o_auth/password_controller_test.exs index 26cca1345..bb61b24fd 100644 --- a/test/pleroma/web/twitter_api/password_controller_test.exs +++ b/test/pleroma/web/o_auth/password_controller_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.PasswordControllerTest do +defmodule Pleroma.Web.OAuth.PasswordControllerTest do use Pleroma.Web.ConnCase alias Pleroma.Config diff --git a/test/pleroma/web/twitter_api/controller_test.exs b/test/pleroma/web/o_auth/token_controller_test.exs similarity index 97% rename from test/pleroma/web/twitter_api/controller_test.exs rename to test/pleroma/web/o_auth/token_controller_test.exs index 494be9ec7..5c64cb394 100644 --- a/test/pleroma/web/twitter_api/controller_test.exs +++ b/test/pleroma/web/o_auth/token_controller_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.ControllerTest do +defmodule Pleroma.Web.OAuth.TokenControllerTest do use Pleroma.Web.ConnCase, async: true alias Pleroma.Repo diff --git a/test/pleroma/web/twitter_api/util_controller_test.exs b/test/pleroma/web/pleroma_api/controllers/util_controller_test.exs similarity index 85% rename from test/pleroma/web/twitter_api/util_controller_test.exs rename to test/pleroma/web/pleroma_api/controllers/util_controller_test.exs index d06ae71aa..6e817df56 100644 --- a/test/pleroma/web/twitter_api/util_controller_test.exs +++ b/test/pleroma/web/pleroma_api/controllers/util_controller_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do +defmodule Pleroma.Web.PleromaAPI.UtilControllerTest do use Pleroma.Web.ConnCase use Oban.Testing, repo: Pleroma.Repo @@ -182,153 +182,6 @@ defmodule Pleroma.Web.TwitterAPI.UtilControllerTest do end end - describe "POST /main/ostatus - remote_subscribe/2" do - setup do: clear_config([:instance, :federating], true) - - test "renders subscribe form", %{conn: conn} do - user = insert(:user) - - response = - conn - |> post("/main/ostatus", %{"nickname" => user.nickname, "profile" => ""}) - |> response(:ok) - - refute response =~ "Could not find user" - assert response =~ "Remotely follow #{user.nickname}" - end - - test "renders subscribe form with error when user not found", %{conn: conn} do - response = - conn - |> post("/main/ostatus", %{"nickname" => "nickname", "profile" => ""}) - |> response(:ok) - - assert response =~ "Could not find user" - refute response =~ "Remotely follow" - end - - test "it redirect to webfinger url", %{conn: conn} do - user = insert(:user) - user2 = insert(:user, ap_id: "shp@social.heldscal.la") - - conn = - conn - |> post("/main/ostatus", %{ - "user" => %{"nickname" => user.nickname, "profile" => user2.ap_id} - }) - - assert redirected_to(conn) == - "https://social.heldscal.la/main/ostatussub?profile=#{user.ap_id}" - end - - test "it renders form with error when user not found", %{conn: conn} do - user2 = insert(:user, ap_id: "shp@social.heldscal.la") - - response = - conn - |> post("/main/ostatus", %{"user" => %{"nickname" => "jimm", "profile" => user2.ap_id}}) - |> response(:ok) - - assert response =~ "Something went wrong." - end - end - - describe "POST /main/ostatus - remote_subscribe/2 - with statuses" do - setup do: clear_config([:instance, :federating], true) - - test "renders subscribe form", %{conn: conn} do - user = insert(:user) - status = insert(:note_activity, %{user: user}) - status_id = status.id - - assert is_binary(status_id) - - response = - conn - |> post("/main/ostatus", %{"status_id" => status_id, "profile" => ""}) - |> response(:ok) - - refute response =~ "Could not find status" - assert response =~ "Interacting with" - end - - test "renders subscribe form with error when status not found", %{conn: conn} do - response = - conn - |> post("/main/ostatus", %{"status_id" => "somerandomid", "profile" => ""}) - |> response(:ok) - - assert response =~ "Could not find status" - refute response =~ "Interacting with" - end - - test "it redirect to webfinger url", %{conn: conn} do - user = insert(:user) - status = insert(:note_activity, %{user: user}) - status_id = status.id - status_ap_id = status.data["object"] - - assert is_binary(status_id) - assert is_binary(status_ap_id) - - user2 = insert(:user, ap_id: "shp@social.heldscal.la") - - conn = - conn - |> post("/main/ostatus", %{ - "status" => %{"status_id" => status_id, "profile" => user2.ap_id} - }) - - assert redirected_to(conn) == - "https://social.heldscal.la/main/ostatussub?profile=#{status_ap_id}" - end - - test "it renders form with error when status not found", %{conn: conn} do - user2 = insert(:user, ap_id: "shp@social.heldscal.la") - - response = - conn - |> post("/main/ostatus", %{ - "status" => %{"status_id" => "somerandomid", "profile" => user2.ap_id} - }) - |> response(:ok) - - assert response =~ "Something went wrong." - end - end - - describe "GET /main/ostatus - show_subscribe_form/2" do - setup do: clear_config([:instance, :federating], true) - - test "it works with users", %{conn: conn} do - user = insert(:user) - - response = - conn - |> get("/main/ostatus", %{"nickname" => user.nickname}) - |> response(:ok) - - refute response =~ "Could not find user" - assert response =~ "Remotely follow #{user.nickname}" - end - - test "it works with statuses", %{conn: conn} do - user = insert(:user) - status = insert(:note_activity, %{user: user}) - status_id = status.id - - assert is_binary(status_id) - - response = - conn - |> get("/main/ostatus", %{"status_id" => status_id}) - |> response(:ok) - - refute response =~ "Could not find status" - assert response =~ "Interacting with" - end - end - test "it returns new captcha", %{conn: conn} do with_mock Pleroma.Captcha, new: fn -> "test_captcha" end do diff --git a/test/pleroma/web/plugs/instance_static_test.exs b/test/pleroma/web/plugs/instance_static_test.exs index b5a5a3334..017c49d1e 100644 --- a/test/pleroma/web/plugs/instance_static_test.exs +++ b/test/pleroma/web/plugs/instance_static_test.exs @@ -137,4 +137,47 @@ defmodule Pleroma.Web.Plugs.InstanceStaticTest do # It should be preserved because "image" is in the allowed_mime_types list assert content_type == "image/jpeg" end + + describe "404s for missing files in static-only paths" do + test "returns 404 for non-existent static-only JSON files" do + conn = get(build_conn(), "/static/non-existent.json") + + assert conn.status == 404 + assert ["application/json"] = get_resp_header(conn, "content-type") + assert Jason.decode!(conn.resp_body) == %{"error" => "not found"} + end + + test "returns 404 for non-existent static-only non-JSON files" do + conn = get(build_conn(), "/static/non-existent.txt") + + assert conn.status == 404 + assert conn.resp_body == "Not found" + assert ["text/plain"] = get_resp_header(conn, "content-type") + end + + test "returns 404 for non-existent .css files" do + conn = get(build_conn(), "/static/non-existent.css") + + assert conn.status == 404 + assert conn.resp_body == "Not found" + # Verifies that we forced text/plain for the error body, even though the path was .css + assert ["text/plain"] = get_resp_header(conn, "content-type") + end + + test "returns 404 for non-existent files without an extension" do + conn = get(build_conn(), "/static/non-existent") + + assert conn.status == 404 + assert conn.resp_body == "Not found" + assert ["text/plain"] = get_resp_header(conn, "content-type") + end + + test "returns 200 (falls through to SPA) for non-static-only paths" do + # /some-route is NOT in static_only_files, so it should still fall through to the SPA. + conn = get(build_conn(), "/some-route") + + assert conn.status == 200 + assert ["text/html; charset=utf-8"] = get_resp_header(conn, "content-type") + end + end end diff --git a/test/pleroma/web/plugs/rate_limiter_test.exs b/test/pleroma/web/plugs/rate_limiter_test.exs index 19cee8aee..10c93fa73 100644 --- a/test/pleroma/web/plugs/rate_limiter_test.exs +++ b/test/pleroma/web/plugs/rate_limiter_test.exs @@ -268,6 +268,23 @@ defmodule Pleroma.Web.Plugs.RateLimiterTest do refute {:err, :not_found} == RateLimiter.inspect_bucket(conn, limiter_name, opts) end + test "doesn't crash if rate limit scale is invalid (e.g. broken DB config)" do + limiter_name = :test_invalid_rate_limit_config + + clear_config([:rate_limit, limiter_name], [{"", 0}, {"", ""}]) + clear_config([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) + + opts = RateLimiter.init(name: limiter_name) + + conn = %{build_conn(:get, "/") | remote_ip: {127, 0, 0, 1}} + + conn_limited = RateLimiter.call(conn, opts) + + refute conn_limited.status == Conn.Status.code(:too_many_requests) + refute conn_limited.resp_body + refute conn_limited.halted + end + def expire_ttl(%{remote_ip: remote_ip} = _conn, bucket_name_root) do bucket_name = "anon:#{bucket_name_root}" |> String.to_atom() key_name = "ip::#{remote_ip |> Tuple.to_list() |> Enum.join(".")}" diff --git a/test/pleroma/web/twitter_api/twitter_api_test.exs b/test/pleroma/web/registration_test.exs similarity index 90% rename from test/pleroma/web/twitter_api/twitter_api_test.exs rename to test/pleroma/web/registration_test.exs index b3cd80146..896e1a600 100644 --- a/test/pleroma/web/twitter_api/twitter_api_test.exs +++ b/test/pleroma/web/registration_test.exs @@ -2,14 +2,14 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do +defmodule Pleroma.Web.RegistrationTest do use Pleroma.DataCase import Pleroma.Factory alias Pleroma.Repo alias Pleroma.Tests.ObanHelpers alias Pleroma.User alias Pleroma.UserInviteToken - alias Pleroma.Web.TwitterAPI.TwitterAPI + alias Pleroma.Web.Registration setup_all do Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) @@ -25,7 +25,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :confirm => "bear" } - {:ok, user} = TwitterAPI.register_user(data) + {:ok, user} = Registration.register_user(data) assert user == User.get_cached_by_nickname("lain") end @@ -40,7 +40,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :confirm => "bear" } - {:ok, user} = TwitterAPI.register_user(data) + {:ok, user} = Registration.register_user(data) assert user == User.get_cached_by_nickname("lain") end @@ -57,7 +57,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :confirm => "bear" } - {:ok, user} = TwitterAPI.register_user(data) + {:ok, user} = Registration.register_user(data) ObanHelpers.perform_all() refute user.is_confirmed @@ -89,7 +89,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :reason => "I love anime" } - {:ok, user} = TwitterAPI.register_user(data) + {:ok, user} = Registration.register_user(data) ObanHelpers.perform_all() refute user.is_approved @@ -125,7 +125,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :confirm => "bear" } - {:ok, user1} = TwitterAPI.register_user(data1) + {:ok, user1} = Registration.register_user(data1) data2 = %{ :username => "lain", @@ -136,7 +136,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :confirm => "bear" } - {:ok, user2} = TwitterAPI.register_user(data2) + {:ok, user2} = Registration.register_user(data2) expected_text = ~s(@john test) @@ -160,7 +160,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :token => invite.token } - {:ok, user} = TwitterAPI.register_user(data) + {:ok, user} = Registration.register_user(data) assert user == User.get_cached_by_nickname("vinny") @@ -179,7 +179,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :token => "DudeLetMeInImAFairy" } - {:error, msg} = TwitterAPI.register_user(data) + {:error, msg} = Registration.register_user(data) assert msg == "Invalid token" refute User.get_cached_by_nickname("GrimReaper") @@ -199,7 +199,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :token => invite.token } - {:error, msg} = TwitterAPI.register_user(data) + {:error, msg} = Registration.register_user(data) assert msg == "Expired token" refute User.get_cached_by_nickname("GrimReaper") @@ -221,7 +221,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do check_fn = fn invite -> data = Map.put(data, :token, invite.token) - {:ok, user} = TwitterAPI.register_user(data) + {:ok, user} = Registration.register_user(data) assert user == User.get_cached_by_nickname("vinny") end @@ -254,7 +254,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do data = Map.put(data, "token", invite.token) - {:error, msg} = TwitterAPI.register_user(data) + {:error, msg} = Registration.register_user(data) assert msg == "Expired token" refute User.get_cached_by_nickname("vinny") @@ -282,7 +282,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :token => invite.token } - {:ok, user} = TwitterAPI.register_user(data) + {:ok, user} = Registration.register_user(data) assert user == User.get_cached_by_nickname("vinny") invite = Repo.get_by(UserInviteToken, token: invite.token) @@ -298,7 +298,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :token => invite.token } - {:error, msg} = TwitterAPI.register_user(data) + {:error, msg} = Registration.register_user(data) assert msg == "Expired token" refute User.get_cached_by_nickname("GrimReaper") @@ -321,7 +321,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :token => invite.token } - {:ok, user} = TwitterAPI.register_user(data) + {:ok, user} = Registration.register_user(data) assert user == User.get_cached_by_nickname("vinny") invite = Repo.get_by(UserInviteToken, token: invite.token) @@ -343,7 +343,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :token => invite.token } - {:ok, user} = TwitterAPI.register_user(data) + {:ok, user} = Registration.register_user(data) assert user == User.get_cached_by_nickname("vinny") invite = Repo.get_by(UserInviteToken, token: invite.token) @@ -359,7 +359,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :token => invite.token } - {:error, msg} = TwitterAPI.register_user(data) + {:error, msg} = Registration.register_user(data) assert msg == "Expired token" refute User.get_cached_by_nickname("GrimReaper") @@ -379,7 +379,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :token => invite.token } - {:error, msg} = TwitterAPI.register_user(data) + {:error, msg} = Registration.register_user(data) assert msg == "Expired token" refute User.get_cached_by_nickname("GrimReaper") @@ -401,7 +401,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :token => invite.token } - {:error, msg} = TwitterAPI.register_user(data) + {:error, msg} = Registration.register_user(data) assert msg == "Expired token" refute User.get_cached_by_nickname("GrimReaper") @@ -416,7 +416,7 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do :bio => "close the world." } - {:error, error} = TwitterAPI.register_user(data) + {:error, error} = Registration.register_user(data) assert is_binary(error) refute User.get_cached_by_nickname("lain") diff --git a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs b/test/pleroma/web/remote_interaction/remote_interaction_controller_test.exs similarity index 68% rename from test/pleroma/web/twitter_api/remote_follow_controller_test.exs rename to test/pleroma/web/remote_interaction/remote_interaction_controller_test.exs index f762b1356..9df86c0a2 100644 --- a/test/pleroma/web/twitter_api/remote_follow_controller_test.exs +++ b/test/pleroma/web/remote_interaction/remote_interaction_controller_test.exs @@ -2,7 +2,7 @@ # Copyright © 2017-2022 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do +defmodule Pleroma.Web.RemoteInteraction.RemoteInteractionControllerTest do use Pleroma.Web.ConnCase alias Pleroma.MFA @@ -16,6 +16,11 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do import Mox import Pleroma.Factory + setup do + Tesla.Mock.mock(fn env -> apply(HttpRequestMock, :request, [env]) end) + :ok + end + setup_all do: clear_config([:instance, :federating], true) setup do: clear_config([:user, :deny_follow_blocked]) @@ -49,7 +54,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do assert conn |> get( - remote_follow_path(conn, :follow, %{ + remote_interaction_path(conn, :follow, %{ acct: "https://mastodon.social/users/emelie/statuses/101849165031453009" }) ) @@ -78,7 +83,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn - |> get(remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"})) + |> get(remote_interaction_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"})) |> html_response(200) assert response =~ "Log in to follow" @@ -109,7 +114,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn |> assign(:user, user) - |> get(remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"})) + |> get(remote_interaction_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"})) |> html_response(200) assert response =~ "Remote follow" @@ -130,7 +135,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do conn |> assign(:user, user) |> get( - remote_follow_path(conn, :follow, %{ + remote_interaction_path(conn, :follow, %{ acct: "https://mastodon.social/users/not_found" }) ) @@ -152,7 +157,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do conn |> assign(:user, user) |> assign(:token, read_token) - |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) + |> post(remote_interaction_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) |> response(200) assert response =~ "Error following account" @@ -167,7 +172,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do conn |> assign(:user, user) |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:follows"])) - |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) + |> post(remote_interaction_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) assert redirected_to(conn) == "/users/#{user2.id}" end @@ -179,7 +184,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn |> assign(:user, user) - |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) + |> post(remote_interaction_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) |> response(200) assert response =~ "Error following account" @@ -195,7 +200,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn |> assign(:user, user) - |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) + |> post(remote_interaction_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) |> response(200) assert response =~ "Error following account" @@ -207,7 +212,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn |> assign(:user, user) - |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => "jimm"}}) + |> post(remote_interaction_path(conn, :do_follow), %{"user" => %{"id" => "jimm"}}) |> response(200) assert response =~ "Error following account" @@ -222,7 +227,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do conn |> assign(:user, refresh_record(user)) |> assign(:token, insert(:oauth_token, user: user, scopes: ["write:follows"])) - |> post(remote_follow_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) + |> post(remote_interaction_path(conn, :do_follow), %{"user" => %{"id" => user2.id}}) assert redirected_to(conn) == "/users/#{user2.id}" end @@ -244,7 +249,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn - |> post(remote_follow_path(conn, :do_follow), %{ + |> post(remote_interaction_path(conn, :do_follow), %{ "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} }) |> response(200) @@ -272,7 +277,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn - |> post(remote_follow_path(conn, :do_follow), %{ + |> post(remote_interaction_path(conn, :do_follow), %{ "authorization" => %{"name" => user.nickname, "password" => "test1", "id" => user2.id} }) |> response(200) @@ -300,7 +305,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do conn = conn |> post( - remote_follow_path(conn, :do_follow), + remote_interaction_path(conn, :do_follow), %{ "mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id} } @@ -329,7 +334,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn |> post( - remote_follow_path(conn, :do_follow), + remote_interaction_path(conn, :do_follow), %{ "mfa" => %{"code" => otp_token, "token" => token, "id" => user2.id} } @@ -348,7 +353,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do conn = conn - |> post(remote_follow_path(conn, :do_follow), %{ + |> post(remote_interaction_path(conn, :do_follow), %{ "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} }) @@ -361,7 +366,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn - |> post(remote_follow_path(conn, :do_follow), %{ + |> post(remote_interaction_path(conn, :do_follow), %{ "authorization" => %{"name" => user.nickname, "password" => "test", "id" => "jimm"} }) |> response(200) @@ -374,7 +379,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn - |> post(remote_follow_path(conn, :do_follow), %{ + |> post(remote_interaction_path(conn, :do_follow), %{ "authorization" => %{"name" => "jimm", "password" => "test", "id" => user.id} }) |> response(200) @@ -388,7 +393,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn - |> post(remote_follow_path(conn, :do_follow), %{ + |> post(remote_interaction_path(conn, :do_follow), %{ "authorization" => %{"name" => user.nickname, "password" => "42", "id" => user2.id} }) |> response(200) @@ -404,7 +409,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do response = conn - |> post(remote_follow_path(conn, :do_follow), %{ + |> post(remote_interaction_path(conn, :do_follow), %{ "authorization" => %{"name" => user.nickname, "password" => "test", "id" => user2.id} }) |> response(200) @@ -423,7 +428,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do avatar: %{"url" => [%{"href" => "https://remote.org/avatar.png"}]} }) - avatar_url = Pleroma.Web.TwitterAPI.RemoteFollowView.avatar_url(user) + avatar_url = Pleroma.Web.RemoteInteraction.RemoteInteractionView.avatar_url(user) assert avatar_url == "https://remote.org/avatar.png" end @@ -440,7 +445,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do avatar: %{"url" => [%{"href" => "https://remote.org/avatar.png"}]} }) - avatar_url = Pleroma.Web.TwitterAPI.RemoteFollowView.avatar_url(user) + avatar_url = Pleroma.Web.RemoteInteraction.RemoteInteractionView.avatar_url(user) url = Pleroma.Web.Endpoint.url() assert String.starts_with?(avatar_url, url) @@ -455,7 +460,7 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do avatar: %{"url" => [%{"href" => "#{Pleroma.Web.Endpoint.url()}/localuser/avatar.png"}]} }) - avatar_url = Pleroma.Web.TwitterAPI.RemoteFollowView.avatar_url(user) + avatar_url = Pleroma.Web.RemoteInteraction.RemoteInteractionView.avatar_url(user) assert avatar_url == "#{Pleroma.Web.Endpoint.url()}/localuser/avatar.png" end @@ -485,13 +490,160 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowControllerTest do conn = conn |> get( - remote_follow_path(conn, :authorize_interaction, %{ + remote_interaction_path(conn, :authorize_interaction, %{ uri: "https://mastodon.social/users/emelie" }) ) assert redirected_to(conn) == - remote_follow_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"}) + remote_interaction_path(conn, :follow, %{acct: "https://mastodon.social/users/emelie"}) + end + end + + describe "POST /main/ostatus - remote_subscribe/2" do + setup do: clear_config([:instance, :federating], true) + + test "renders subscribe form", %{conn: conn} do + user = insert(:user) + + response = + conn + |> post("/main/ostatus", %{"nickname" => user.nickname, "profile" => ""}) + |> response(:ok) + + refute response =~ "Could not find user" + assert response =~ "Remotely follow #{user.nickname}" + end + + test "renders subscribe form with error when user not found", %{conn: conn} do + response = + conn + |> post("/main/ostatus", %{"nickname" => "nickname", "profile" => ""}) + |> response(:ok) + + assert response =~ "Could not find user" + refute response =~ "Remotely follow" + end + + test "it redirect to webfinger url", %{conn: conn} do + user = insert(:user) + user2 = insert(:user, ap_id: "shp@social.heldscal.la") + + conn = + conn + |> post("/main/ostatus", %{ + "user" => %{"nickname" => user.nickname, "profile" => user2.ap_id} + }) + + assert redirected_to(conn) == + "https://social.heldscal.la/main/ostatussub?profile=#{user.ap_id}" + end + + test "it renders form with error when user not found", %{conn: conn} do + user2 = insert(:user, ap_id: "shp@social.heldscal.la") + + response = + conn + |> post("/main/ostatus", %{"user" => %{"nickname" => "jimm", "profile" => user2.ap_id}}) + |> response(:ok) + + assert response =~ "Something went wrong." + end + end + + describe "POST /main/ostatus - remote_subscribe/2 - with statuses" do + setup do: clear_config([:instance, :federating], true) + + test "renders subscribe form", %{conn: conn} do + user = insert(:user) + status = insert(:note_activity, %{user: user}) + status_id = status.id + + assert is_binary(status_id) + + response = + conn + |> post("/main/ostatus", %{"status_id" => status_id, "profile" => ""}) + |> response(:ok) + + refute response =~ "Could not find status" + assert response =~ "Interacting with" + end + + test "renders subscribe form with error when status not found", %{conn: conn} do + response = + conn + |> post("/main/ostatus", %{"status_id" => "somerandomid", "profile" => ""}) + |> response(:ok) + + assert response =~ "Could not find status" + refute response =~ "Interacting with" + end + + test "it redirect to webfinger url", %{conn: conn} do + user = insert(:user) + status = insert(:note_activity, %{user: user}) + status_id = status.id + status_ap_id = status.data["object"] + + assert is_binary(status_id) + assert is_binary(status_ap_id) + + user2 = insert(:user, ap_id: "shp@social.heldscal.la") + + conn = + conn + |> post("/main/ostatus", %{ + "status" => %{"status_id" => status_id, "profile" => user2.ap_id} + }) + + assert redirected_to(conn) == + "https://social.heldscal.la/main/ostatussub?profile=#{status_ap_id}" + end + + test "it renders form with error when status not found", %{conn: conn} do + user2 = insert(:user, ap_id: "shp@social.heldscal.la") + + response = + conn + |> post("/main/ostatus", %{ + "status" => %{"status_id" => "somerandomid", "profile" => user2.ap_id} + }) + |> response(:ok) + + assert response =~ "Something went wrong." + end + end + + describe "GET /main/ostatus - show_subscribe_form/2" do + setup do: clear_config([:instance, :federating], true) + + test "it works with users", %{conn: conn} do + user = insert(:user) + + response = + conn + |> get("/main/ostatus", %{"nickname" => user.nickname}) + |> response(:ok) + + refute response =~ "Could not find user" + assert response =~ "Remotely follow #{user.nickname}" + end + + test "it works with statuses", %{conn: conn} do + user = insert(:user) + status = insert(:note_activity, %{user: user}) + status_id = status.id + + assert is_binary(status_id) + + response = + conn + |> get("/main/ostatus", %{"status_id" => status_id}) + |> response(:ok) + + refute response =~ "Could not find status" + assert response =~ "Interacting with" end end end diff --git a/test/pleroma/web/streamer_test.exs b/test/pleroma/web/streamer_test.exs index f5008f6b9..b26bd1847 100644 --- a/test/pleroma/web/streamer_test.exs +++ b/test/pleroma/web/streamer_test.exs @@ -220,7 +220,7 @@ defmodule Pleroma.Web.StreamerTest do } do %{token: read_lists_token} = oauth_access(["read:lists"], user: user) %{token: invalid_token} = oauth_access(["irrelevant:scope"], user: user) - {:ok, list} = List.create("Test", user) + {:ok, list} = List.create(%{title: "Test"}, user) assert {:error, _} = Streamer.get_topic("list:#{list.id}", user, read_oauth_token) @@ -233,7 +233,7 @@ defmodule Pleroma.Web.StreamerTest do test "disallows list stream that are not owned by the user", %{user: user, token: oauth_token} do another_user = insert(:user) - {:ok, list} = List.create("Test", another_user) + {:ok, list} = List.create(%{title: "Test"}, another_user) assert {:error, _} = Streamer.get_topic("list:#{list.id}", user, oauth_token) assert {:error, _} = Streamer.get_topic("list", user, oauth_token, %{"list" => list.id}) @@ -803,7 +803,7 @@ defmodule Pleroma.Web.StreamerTest do {:ok, user_a, user_b} = User.follow(user_a, user_b) - {:ok, list} = List.create("Test", user_a) + {:ok, list} = List.create(%{title: "Test"}, user_a) {:ok, list} = List.follow(list, user_b) Streamer.get_topic_and_add_socket("list", user_a, user_a_token, %{"list" => list.id}) @@ -820,7 +820,7 @@ defmodule Pleroma.Web.StreamerTest do test "it doesn't send unwanted private posts to list", %{user: user_a, token: user_a_token} do user_b = insert(:user) - {:ok, list} = List.create("Test", user_a) + {:ok, list} = List.create(%{title: "Test"}, user_a) {:ok, list} = List.follow(list, user_b) Streamer.get_topic_and_add_socket("list", user_a, user_a_token, %{"list" => list.id}) @@ -839,7 +839,7 @@ defmodule Pleroma.Web.StreamerTest do {:ok, user_a, user_b} = User.follow(user_a, user_b) - {:ok, list} = List.create("Test", user_a) + {:ok, list} = List.create(%{title: "Test"}, user_a) {:ok, list} = List.follow(list, user_b) Streamer.get_topic_and_add_socket("list", user_a, user_a_token, %{"list" => list.id})