diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 722bf8b60..363b5f3c3 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -710,6 +710,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do |> Map.put(:type, ["Create", "Announce"]) |> Map.put(:user, reading_user) |> Map.put(:actor_id, user.ap_id) + |> Map.put(:actor_local, user.local) |> Map.put(:pinned_object_ids, Map.keys(user.pinned_objects)) params = @@ -1357,41 +1358,48 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_unauthenticated(query, _), do: query - defp restrict_unauthenticated_object(query, nil) do - local = Config.restrict_unauthenticated_access?(:activities, :local) - remote = Config.restrict_unauthenticated_access?(:activities, :remote) - local_object_pattern = Pleroma.Web.Endpoint.url() <> "/%" + defp restrict_activity_and_object_origin(query, local?) do + local_object_prefix = Pleroma.Web.Endpoint.url() <> "/" + + # Keeping the correlated origin checks inside CASE prevents PostgreSQL from choosing a + # global bitmap-and-sort plan before applying the account statuses limit. + from([activity, object: object] in query, + where: + fragment( + "CASE WHEN ? = ? AND starts_with((?->>'id'), ?) = ? THEN true ELSE false END", + activity.local, + ^local?, + object.data, + ^local_object_prefix, + ^local? + ) + ) + end + + defp maybe_restrict_unauthenticated( + query, + %{restrict_unauthenticated: true, user: nil, actor_local: actor_local} + ) + when is_boolean(actor_local) do + local_restricted = Config.restrict_unauthenticated_access?(:activities, :local) + remote_restricted = Config.restrict_unauthenticated_access?(:activities, :remote) + actor_restricted = if actor_local, do: local_restricted, else: remote_restricted cond do - local and remote -> - from([object: object] in query, where: false) + actor_restricted -> + from(activity in query, where: false) - local -> - from([object: object] in query, - where: not fragment("(?->>'id') LIKE ?", object.data, ^local_object_pattern) - ) + local_restricted -> + restrict_activity_and_object_origin(query, false) - remote -> - from([object: object] in query, - where: fragment("(?->>'id') LIKE ?", object.data, ^local_object_pattern) - ) + remote_restricted -> + restrict_activity_and_object_origin(query, true) true -> query end end - defp restrict_unauthenticated_object(query, _), do: query - - defp maybe_restrict_unauthenticated( - query, - %{restrict_unauthenticated: true, user: user} - ) do - query - |> restrict_unauthenticated(user) - |> restrict_unauthenticated_object(user) - end - defp maybe_restrict_unauthenticated(query, _), do: query defp restrict_quote_url(query, %{quote_url: quote_url}) 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 a2ac6382a..e82d795e1 100644 --- a/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/pleroma/web/mastodon_api/controllers/account_controller_test.exs @@ -494,7 +494,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do assert activity_id == activity.id end - test "activity restrictions apply to boosted objects", %{conn: conn} do + test "remote activity restrictions apply to created and boosted objects", %{conn: conn} do clear_config([:restrict_unauthenticated, :activities, :local], false) clear_config([:restrict_unauthenticated, :activities, :remote], true) @@ -502,6 +502,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do remote_user = insert(:user, local: false, domain: "example.com") local_activity = insert(:note_activity, user: local_user) + mixed_create = + insert(:note_activity, local: true, object_local: false, user: local_user) + remote_activity = insert(:note_activity, local: false, object_local: false, user: remote_user) @@ -514,6 +517,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do |> Enum.map(& &1["id"]) assert local_activity.id in status_ids + refute mixed_create.id in status_ids refute announce.id in status_ids authenticated_status_ids = @@ -523,10 +527,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do |> Enum.map(& &1["id"]) assert local_activity.id in authenticated_status_ids + assert mixed_create.id in authenticated_status_ids assert announce.id in authenticated_status_ids end - test "local object restrictions apply to remote boosts", %{conn: conn} do + test "local activity restrictions apply to created and boosted objects", %{conn: conn} do clear_config([:restrict_unauthenticated, :activities, :local], true) clear_config([:restrict_unauthenticated, :activities, :remote], false) @@ -537,6 +542,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do remote_activity = insert(:note_activity, local: false, object_local: false, user: remote_user) + mixed_create = + insert(:note_activity, local: false, object_local: true, user: remote_user) + remote_announce = insert(:announce_activity, note_activity: local_activity, user: remote_user) |> Ecto.Changeset.change(%{ @@ -552,6 +560,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do |> Enum.map(& &1["id"]) assert remote_activity.id in status_ids + refute mixed_create.id in status_ids refute remote_announce.id in status_ids authenticated_status_ids = @@ -561,9 +570,58 @@ defmodule Pleroma.Web.MastodonAPI.AccountControllerTest do |> Enum.map(& &1["id"]) assert remote_activity.id in authenticated_status_ids + assert mixed_create.id in authenticated_status_ids assert remote_announce.id in authenticated_status_ids end + test "restricted local accounts reject activities with mismatched remote origins", %{ + conn: conn + } do + clear_config([:restrict_unauthenticated, :activities, :local], true) + clear_config([:restrict_unauthenticated, :activities, :remote], false) + + local_user = insert(:user) + + mismatched_activity = + insert(:note_activity, local: false, object_local: false, user: local_user) + + assert [] = + build_conn() + |> get("/api/v1/accounts/#{local_user.id}/statuses") + |> json_response_and_validate_schema(200) + + assert [%{"id" => activity_id}] = + conn + |> get("/api/v1/accounts/#{local_user.id}/statuses") + |> json_response_and_validate_schema(200) + + assert activity_id == mismatched_activity.id + end + + test "restricted remote accounts reject activities with mismatched local origins", %{ + conn: conn + } do + clear_config([:restrict_unauthenticated, :activities, :local], false) + clear_config([:restrict_unauthenticated, :activities, :remote], true) + + remote_user = insert(:user, local: false, domain: "example.com") + + mismatched_activity = + insert(:note_activity, local: true, object_local: true, user: remote_user) + + assert [] = + build_conn() + |> get("/api/v1/accounts/#{remote_user.id}/statuses") + |> json_response_and_validate_schema(200) + + assert [%{"id" => activity_id}] = + conn + |> get("/api/v1/accounts/#{remote_user.id}/statuses") + |> json_response_and_validate_schema(200) + + assert activity_id == mismatched_activity.id + end + test "gets a user's statuses without reblogs", %{user: user, conn: conn} do {:ok, %{id: post_id}} = CommonAPI.post(user, %{status: "HI!!!"}) {:ok, _} = CommonAPI.repeat(post_id, user)