logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://anongit.hacktivis.me/git/pleroma.git/
commit: 6c73ebe484c22300af5ee06f9c1c095787713aeb
parent 0127a1062164b5dac43381e25188c5100497194d
Author: Lain Soykaf <lain@lain.com>
Date:   Mon, 29 Dec 2025 09:47:54 +0400

Merge branch 'phnt/mastoapi-misattribution-3381' into release/2.10-sec

Diffstat:

Achangelog.d/mastoapi-misatrribution.fix1+
Achangelog.d/restrict-unauthenticated-bypass.fix1+
Mlib/pleroma/web/activity_pub/visibility.ex16++++++++++++++++
Mlib/pleroma/web/mastodon_api/views/status_view.ex2+-
Mtest/pleroma/activity_test.exs16++++++++++------
Mtest/pleroma/web/mastodon_api/controllers/status_controller_test.exs590+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----
Mtest/support/factory.ex69++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
7 files changed, 651 insertions(+), 44 deletions(-)

diff --git a/changelog.d/mastoapi-misatrribution.fix b/changelog.d/mastoapi-misatrribution.fix @@ -0,0 +1 @@ +MastodonAPI: Fix misattribution of statuses when fetched via non-Announce Activity ID diff --git a/changelog.d/restrict-unauthenticated-bypass.fix b/changelog.d/restrict-unauthenticated-bypass.fix @@ -0,0 +1 @@ +Fix bypass of the restrict unauthenticated setting by requesting local Activities diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex @@ -73,6 +73,22 @@ defmodule Pleroma.Web.ActivityPub.Visibility do |> Pleroma.List.member?(user) end + def visible_for_user?(%Activity{object: %Object{} = object} = activity, nil) do + activity_visibility? = restrict_unauthenticated_access?(activity) + activity_public? = public?(activity) and not local_public?(activity) + object_visibility? = restrict_unauthenticated_access?(object) + object_public? = public?(object) and not local_public?(object) + + # Activity could be local, but object might not (Announce/Like) + cond do + activity_visibility? or object_visibility? -> + false + + true -> + activity_public? and object_public? + end + end + def visible_for_user?(%{__struct__: module} = message, nil) when module in [Activity, Object] do if restrict_unauthenticated_access?(message), diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -240,7 +240,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} = opts) do object = Object.normalize(activity, fetch: false) - user = CommonAPI.get_user(activity.data["actor"]) + user = CommonAPI.get_user(object.data["actor"]) user_follower_address = user.follower_address like_count = object.data["like_count"] || 0 diff --git a/test/pleroma/activity_test.exs b/test/pleroma/activity_test.exs @@ -261,23 +261,27 @@ defmodule Pleroma.ActivityTest do test "add_by_params_query/3" do user = insert(:user) - note = insert(:note_activity, user: user) + note_activity = insert(:note_activity, user: user) - insert(:add_activity, user: user, note: note) - insert(:add_activity, user: user, note: note) + insert(:add_activity, user: user, note_activity: note_activity) + insert(:add_activity, user: user, note_activity: note_activity) insert(:add_activity, user: user) - assert Repo.aggregate(Activity, :count, :id) == 4 + assert Repo.aggregate(Activity, :count, :id) == 5 add_query = - Activity.add_by_params_query(note.data["object"], user.ap_id, user.featured_address) + Activity.add_by_params_query( + note_activity.data["object"], + user.ap_id, + user.featured_address + ) assert Repo.aggregate(add_query, :count, :id) == 2 Repo.delete_all(add_query) assert Repo.aggregate(add_query, :count, :id) == 0 - assert Repo.aggregate(Activity, :count, :id) == 2 + assert Repo.aggregate(Activity, :count, :id) == 3 end describe "associated_object_id() sql function" do diff --git a/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/status_controller_test.exs @@ -827,9 +827,17 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do end defp local_and_remote_activities do + remote_user = insert(:user, local: false, domain: "example.com") + announce_user = insert(:user) local = insert(:note_activity) - remote = insert(:note_activity, local: false) - {:ok, local: local, remote: remote} + remote = insert(:note_activity, local: false, object_local: false, user: remote_user) + remote_note = insert(:note_activity, local: false, object_local: false) + + local_activity_remote_object = + insert(:announce_activity, note_activity: remote_note, user: announce_user) + + {:ok, + local: local, remote: remote, local_activity_remote_object: local_activity_remote_object} end defp local_and_remote_context_activities do @@ -872,7 +880,64 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do {:ok, job} = Pleroma.Web.Federator.incoming_ap_doc(params) {:ok, remote_activity} = ObanHelpers.perform(job) - %{locals: [id1, id2], remote: remote_activity.id, context: context} + {:ok, %{id: local_to_local_react_id}} = CommonAPI.react_with_emoji(id1, local_user_2, "🦊") + + {:ok, %{id: local_to_remote_react_id}} = + CommonAPI.react_with_emoji(remote_activity.id, local_user_1, "🦊") + + {:ok, %{id: remote_to_local_react_id}} = CommonAPI.react_with_emoji(id1, remote_user, "🦊") + + {:ok, %{id: remote_to_remote_react_id}} = + CommonAPI.react_with_emoji(remote_activity.id, remote_user, "🦊") + + %{ + locals: [id1, id2], + remote: remote_activity.id, + local_interactions: [local_to_local_react_id, local_to_remote_react_id], + remote_interactions: [remote_to_local_react_id, remote_to_remote_react_id], + context: context + } + end + + defp extract_activity_ids_from_response(list) when is_list(list) do + list + |> Enum.map(& &1["id"]) + end + + defp all_ids_included?(checked, authority) when is_list(checked) and is_list(authority) do + set1 = MapSet.new(checked) + set2 = MapSet.new(authority) + MapSet.equal?(set1, set2) + end + + defp local_interactions_to_remote do + interacted_user = insert(:user, local: false, domain: "example.com") + interacting_user = insert(:user) + + announced_post = + insert(:note_activity, local: false, object_local: false, user: interacted_user) + + emoji_reacted_post = + insert(:note_activity, local: false, object_local: false, user: interacted_user) + + favorited_post = + insert(:note_activity, local: false, object_local: false, user: interacted_user) + + announce = insert(:announce_activity, note_activity: announced_post, user: interacting_user) + + emoji_react = + insert(:emoji_react_activity, note_activity: emoji_reacted_post, user: interacting_user) + + {:ok, favorite} = CommonAPI.favorite(favorited_post.id, interacting_user) + + { + :ok, + announce: announce, + emoji_react: emoji_react, + favorite: favorite, + interacted: interacted_user, + interacter: interacting_user + } end describe "status with restrict unauthenticated activities for local and remote" do @@ -882,7 +947,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true) - test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do + test "if user is unauthenticated", %{ + conn: conn, + local: local, + remote: remote, + local_activity_remote_object: local_activity_remote_object + } do res_conn = get(conn, "/api/v1/statuses/#{local.id}") assert json_response_and_validate_schema(res_conn, :not_found) == %{ @@ -894,15 +964,28 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do assert json_response_and_validate_schema(res_conn, :not_found) == %{ "error" => "Record not found" } + + res_conn = get(conn, "/api/v1/statuses/#{local_activity_remote_object.id}") + + assert json_response_and_validate_schema(res_conn, :not_found) == %{ + "error" => "Record not found" + } end - test "if user is authenticated", %{local: local, remote: remote} do + test "if user is authenticated", %{ + local: local, + remote: remote, + local_activity_remote_object: local_activity_remote_object + } do %{conn: conn} = oauth_access(["read"]) res_conn = get(conn, "/api/v1/statuses/#{local.id}") assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200) res_conn = get(conn, "/api/v1/statuses/#{remote.id}") assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200) + + res_conn = get(conn, "/api/v1/statuses/#{local_activity_remote_object.id}") + assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200) end end @@ -911,24 +994,42 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do setup do: clear_config([:restrict_unauthenticated, :activities, :local], true) - test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do + test "if user is unauthenticated", %{ + conn: conn, + local: local, + remote: remote, + local_activity_remote_object: local_activity_remote_object + } do res_conn = get(conn, "/api/v1/statuses/#{local.id}") assert json_response_and_validate_schema(res_conn, :not_found) == %{ "error" => "Record not found" } + res_conn = get(conn, "/api/v1/statuses/#{local_activity_remote_object.id}") + + assert json_response_and_validate_schema(res_conn, :not_found) == %{ + "error" => "Record not found" + } + res_conn = get(conn, "/api/v1/statuses/#{remote.id}") assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200) end - test "if user is authenticated", %{local: local, remote: remote} do + test "if user is authenticated", %{ + local: local, + remote: remote, + local_activity_remote_object: local_activity_remote_object + } do %{conn: conn} = oauth_access(["read"]) res_conn = get(conn, "/api/v1/statuses/#{local.id}") assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200) res_conn = get(conn, "/api/v1/statuses/#{remote.id}") assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200) + + res_conn = get(conn, "/api/v1/statuses/#{local_activity_remote_object.id}") + assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200) end end @@ -937,7 +1038,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true) - test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do + test "if user is unauthenticated", %{ + conn: conn, + local: local, + remote: remote, + local_activity_remote_object: local_activity_remote_object + } do res_conn = get(conn, "/api/v1/statuses/#{local.id}") assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200) @@ -946,15 +1052,163 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do assert json_response_and_validate_schema(res_conn, :not_found) == %{ "error" => "Record not found" } + + res_conn = get(conn, "/api/v1/statuses/#{local_activity_remote_object.id}") + + assert json_response_and_validate_schema(res_conn, :not_found) == %{ + "error" => "Record not found" + } end - test "if user is authenticated", %{local: local, remote: remote} do + test "if user is authenticated", %{ + local: local, + remote: remote, + local_activity_remote_object: local_activity_remote_object + } do %{conn: conn} = oauth_access(["read"]) res_conn = get(conn, "/api/v1/statuses/#{local.id}") assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200) res_conn = get(conn, "/api/v1/statuses/#{remote.id}") assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200) + + res_conn = get(conn, "/api/v1/statuses/#{local_activity_remote_object.id}") + assert %{"id" => _} = json_response_and_validate_schema(res_conn, 200) + end + end + + # Note: Activities of type Flag,Follow,Delete,Accept,Reject,Undo return 404 + describe "status has correct attribution when fetching as" do + setup do: local_interactions_to_remote() + + test "Add/Remove activity", %{conn: conn, interacter: user} do + add = insert(:add_activity, user: user) + add_id = add.id + remove = insert(:remove_activity, user: user) + remove_id = remove.id + user_id = user.id + + # Schema validation fails: + # null value where integer expected at /pleroma/conversation_id + result1 = + conn + |> get("/api/v1/statuses/#{add_id}") + |> json_response(200) + + assert match?(%{"id" => ^add_id, "account" => %{"id" => ^user_id}}, result1) + + result2 = + conn + |> get("/api/v1/statuses/#{remove_id}") + |> json_response(200) + + assert match?(%{"id" => ^remove_id, "account" => %{"id" => ^user_id}}, result2) + end + + test "Announce activity", %{ + conn: conn, + announce: activity, + interacter: interacter, + interacted: user + } do + announce_id = activity.id + announced_activity = Pleroma.Activity.get_create_by_object_ap_id(activity.data["object"]) + announced_id = announced_activity.id + interacter_ap_id = interacter.id + user_id = user.id + + result = + conn + |> get("/api/v1/statuses/#{activity.id}") + |> json_response_and_validate_schema(200) + + assert match?( + %{ + "id" => ^announce_id, + "account" => %{"id" => ^interacter_ap_id}, + "reblog" => %{"account" => %{"id" => ^user_id}, "id" => ^announced_id} + }, + result + ) + end + + test "Create activity", %{conn: conn, interacted: user} do + note_activity = insert(:note_activity, local: false, object_local: false, user: user) + note_activity_id = note_activity.id + user_id = user.id + + result = + conn + |> get("/api/v1/statuses/#{note_activity_id}") + |> json_response_and_validate_schema(200) + + assert match?(%{"id" => ^note_activity_id, "account" => %{"id" => ^user_id}}, result) + end + + test "EmojiReact activity", %{conn: conn, emoji_react: activity, interacted: user} do + emoji_react_id = activity.id + user_id = user.id + + result = + conn + |> get("/api/v1/statuses/#{emoji_react_id}") + |> json_response_and_validate_schema(200) + + assert match?(%{"id" => ^emoji_react_id, "account" => %{"id" => ^user_id}}, result) + end + + test "Like activity", %{conn: conn, favorite: activity, interacted: user} do + like_id = activity.id + user_id = user.id + + result = + conn + |> get("/api/v1/statuses/#{like_id}") + |> json_response_and_validate_schema(200) + + assert match?(%{"id" => ^like_id, "account" => %{"id" => ^user_id}}, result) + end + + test "Update activity" do + %{conn: conn, user: user} = oauth_access(["write:statuses"]) + user_id = user.id + + {:ok, activity} = CommonAPI.post(user, %{status: "This will be edited"}) + {:ok, updated_activity} = CommonAPI.update(activity, user, %{status: "edited"}) + + activity_id = activity.id + updated_activity_id = updated_activity.id + + result1 = + conn + |> get("/api/v1/statuses/#{activity_id}") + |> json_response_and_validate_schema(200) + + # Even though we ask for the original Create activity, updated post is served + assert match?( + %{ + "id" => ^activity_id, + "account" => %{"id" => ^user_id}, + "content" => "edited" + }, + result1 + ) + + # Schema validation fails: + # null value where integer expected at /pleroma/conversation_id + result2 = + conn + |> get("/api/v1/statuses/#{updated_activity_id}") + |> json_response(200) + + assert match?( + %{ + "id" => ^updated_activity_id, + "account" => %{"id" => ^user_id}, + "content" => "edited" + }, + result2 + ) end end @@ -1014,18 +1268,35 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true) - test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do - res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}") + test "if user is unauthenticated", %{ + conn: conn, + local: local, + remote: remote, + local_activity_remote_object: local_activity_remote_object + } do + res_conn = + get( + conn, + "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}&id[]=#{local_activity_remote_object.id}" + ) assert json_response_and_validate_schema(res_conn, 200) == [] end - test "if user is authenticated", %{local: local, remote: remote} do + test "if user is authenticated", %{ + local: local, + remote: remote, + local_activity_remote_object: local_activity_remote_object + } do %{conn: conn} = oauth_access(["read"]) - res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}") + res_conn = + get( + conn, + "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}&id[]=#{local_activity_remote_object.id}" + ) - assert length(json_response_and_validate_schema(res_conn, 200)) == 2 + assert length(json_response_and_validate_schema(res_conn, 200)) == 3 end end @@ -1034,19 +1305,36 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do setup do: clear_config([:restrict_unauthenticated, :activities, :local], true) - test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do - res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}") + test "if user is unauthenticated", %{ + conn: conn, + local: local, + remote: remote, + local_activity_remote_object: local_activity_remote_object + } do + res_conn = + get( + conn, + "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}&id[]=#{local_activity_remote_object.id}" + ) remote_id = remote.id assert [%{"id" => ^remote_id}] = json_response_and_validate_schema(res_conn, 200) end - test "if user is authenticated", %{local: local, remote: remote} do + test "if user is authenticated", %{ + local: local, + remote: remote, + local_activity_remote_object: local_activity_remote_object + } do %{conn: conn} = oauth_access(["read"]) - res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}") + res_conn = + get( + conn, + "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}&id[]=#{local_activity_remote_object.id}" + ) - assert length(json_response_and_validate_schema(res_conn, 200)) == 2 + assert length(json_response_and_validate_schema(res_conn, 200)) == 3 end end @@ -1055,22 +1343,40 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do setup do: clear_config([:restrict_unauthenticated, :activities, :remote], true) - test "if user is unauthenticated", %{conn: conn, local: local, remote: remote} do - res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}") + test "if user is unauthenticated", %{ + conn: conn, + local: local, + remote: remote, + local_activity_remote_object: local_activity_remote_object + } do + res_conn = + get( + conn, + "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}&id[]=#{local_activity_remote_object.id}" + ) local_id = local.id assert [%{"id" => ^local_id}] = json_response_and_validate_schema(res_conn, 200) end - test "if user is authenticated", %{local: local, remote: remote} do + test "if user is authenticated", %{ + local: local, + remote: remote, + local_activity_remote_object: local_activity_remote_object + } do %{conn: conn} = oauth_access(["read"]) - res_conn = get(conn, "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}") + res_conn = + get( + conn, + "/api/v1/statuses?id[]=#{local.id}&id[]=#{remote.id}&id[]=#{local_activity_remote_object.id}" + ) - assert length(json_response_and_validate_schema(res_conn, 200)) == 2 + assert length(json_response_and_validate_schema(res_conn, 200)) == 3 end end + # Note: Context route on EmojiReact/Announce activities puts everything into the ancestors field describe "getting status contexts restricted unauthenticated for local and remote" do setup do: local_and_remote_context_activities() @@ -1096,6 +1402,40 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do } end + test "if user is unauthenticated Activity interactions", %{ + conn: conn, + local_interactions: [local_to_local, local_to_remote], + remote_interactions: [remote_to_local, remote_to_remote] + } do + res_conn = get(conn, "/api/v1/statuses/#{local_to_local}/context") + + assert json_response_and_validate_schema(res_conn, 200) == %{ + "ancestors" => [], + "descendants" => [] + } + + res_conn = get(conn, "/api/v1/statuses/#{local_to_remote}/context") + + assert json_response_and_validate_schema(res_conn, 200) == %{ + "ancestors" => [], + "descendants" => [] + } + + res_conn = get(conn, "/api/v1/statuses/#{remote_to_local}/context") + + assert json_response_and_validate_schema(res_conn, 200) == %{ + "ancestors" => [], + "descendants" => [] + } + + res_conn = get(conn, "/api/v1/statuses/#{remote_to_remote}/context") + + assert json_response_and_validate_schema(res_conn, 200) == %{ + "ancestors" => [], + "descendants" => [] + } + end + test "if user is authenticated", %{locals: [post_id, reply_id], remote: remote_reply_id} do %{conn: conn} = oauth_access(["read"]) res_conn = get(conn, "/api/v1/statuses/#{post_id}/context") @@ -1129,6 +1469,47 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do assert post_id in ancestor_ids assert remote_reply_id in descendant_ids end + + test "if user is authenticated Activity interactions", %{ + locals: [post_id, reply_id], + remote: remote_reply_id, + local_interactions: [local_to_local, local_to_remote], + remote_interactions: [remote_to_local, remote_to_remote] + } do + %{conn: conn} = oauth_access(["read"]) + all_ids = [post_id, reply_id, remote_reply_id] + res_conn = get(conn, "/api/v1/statuses/#{local_to_local}/context") + + %{"ancestors" => ancestors1, "descendants" => []} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids1 = extract_activity_ids_from_response(ancestors1) + assert all_ids_included?(ancestor_ids1, all_ids) + + res_conn = get(conn, "/api/v1/statuses/#{local_to_remote}/context") + + %{"ancestors" => ancestors2, "descendants" => []} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids2 = extract_activity_ids_from_response(ancestors2) + assert all_ids_included?(ancestor_ids2, all_ids) + + res_conn = get(conn, "/api/v1/statuses/#{remote_to_local}/context") + + %{"ancestors" => ancestors3, "descendants" => []} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids3 = extract_activity_ids_from_response(ancestors3) + assert all_ids_included?(ancestor_ids3, all_ids) + + res_conn = get(conn, "/api/v1/statuses/#{remote_to_remote}/context") + + %{"ancestors" => ancestors4, "descendants" => []} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids4 = extract_activity_ids_from_response(ancestors4) + assert all_ids_included?(ancestor_ids4, all_ids) + end end describe "getting status contexts restricted unauthenticated for local" do @@ -1178,6 +1559,45 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do assert remote_reply_id in descendant_ids end + test "if user is unauthenticated Activity interactions", %{ + conn: conn, + remote: remote_reply_id, + local_interactions: [local_to_local, local_to_remote], + remote_interactions: [remote_to_local, remote_to_remote] + } do + res_conn = get(conn, "/api/v1/statuses/#{local_to_local}/context") + + %{"ancestors" => ancestors1, "descendants" => []} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids1 = extract_activity_ids_from_response(ancestors1) + assert all_ids_included?(ancestor_ids1, [remote_reply_id]) + + res_conn = get(conn, "/api/v1/statuses/#{local_to_remote}/context") + + %{"ancestors" => ancestors2, "descendants" => []} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids2 = extract_activity_ids_from_response(ancestors2) + assert all_ids_included?(ancestor_ids2, [remote_reply_id]) + + res_conn = get(conn, "/api/v1/statuses/#{remote_to_local}/context") + + %{"ancestors" => ancestors3, "descendants" => []} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids3 = extract_activity_ids_from_response(ancestors3) + assert all_ids_included?(ancestor_ids3, [remote_reply_id]) + + res_conn = get(conn, "/api/v1/statuses/#{remote_to_remote}/context") + + %{"ancestors" => ancestors4, "descendants" => []} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids4 = extract_activity_ids_from_response(ancestors4) + assert all_ids_included?(ancestor_ids4, [remote_reply_id]) + end + test "if user is authenticated", %{locals: [post_id, reply_id], remote: remote_reply_id} do %{conn: conn} = oauth_access(["read"]) res_conn = get(conn, "/api/v1/statuses/#{post_id}/context") @@ -1211,6 +1631,47 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do assert post_id in ancestor_ids assert remote_reply_id in descendant_ids end + + test "if user is authenticated Activity interactions", %{ + locals: [post_id, reply_id], + remote: remote_reply_id, + local_interactions: [local_to_local, local_to_remote], + remote_interactions: [remote_to_local, remote_to_remote] + } do + %{conn: conn} = oauth_access(["read"]) + all_ids = [post_id, reply_id, remote_reply_id] + res_conn = get(conn, "/api/v1/statuses/#{local_to_local}/context") + + %{"ancestors" => ancestors1, "descendants" => []} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids1 = extract_activity_ids_from_response(ancestors1) + assert all_ids_included?(ancestor_ids1, all_ids) + + res_conn = get(conn, "/api/v1/statuses/#{local_to_remote}/context") + + %{"ancestors" => ancestors2, "descendants" => []} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids2 = extract_activity_ids_from_response(ancestors2) + assert all_ids_included?(ancestor_ids2, all_ids) + + res_conn = get(conn, "/api/v1/statuses/#{remote_to_local}/context") + + %{"ancestors" => ancestors3, "descendants" => []} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids3 = extract_activity_ids_from_response(ancestors3) + assert all_ids_included?(ancestor_ids3, all_ids) + + res_conn = get(conn, "/api/v1/statuses/#{remote_to_remote}/context") + + %{"ancestors" => ancestors4, "descendants" => []} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids4 = extract_activity_ids_from_response(ancestors4) + assert all_ids_included?(ancestor_ids4, all_ids) + end end describe "getting status contexts restricted unauthenticated for remote" do @@ -1260,6 +1721,46 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do assert remote_reply_id not in descendant_ids end + test "if user is unauthenticated Activity interactions", %{ + conn: conn, + locals: [post_id, reply_id], + local_interactions: [local_to_local, local_to_remote], + remote_interactions: [remote_to_local, remote_to_remote] + } do + res_conn = get(conn, "/api/v1/statuses/#{local_to_local}/context") + all_ids = [post_id, reply_id] + + %{"ancestors" => ancestors1, "descendants" => []} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids1 = extract_activity_ids_from_response(ancestors1) + assert all_ids_included?(ancestor_ids1, all_ids) + + res_conn = get(conn, "/api/v1/statuses/#{local_to_remote}/context") + + %{"ancestors" => ancestors2, "descendants" => []} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids2 = extract_activity_ids_from_response(ancestors2) + assert all_ids_included?(ancestor_ids2, all_ids) + + res_conn = get(conn, "/api/v1/statuses/#{remote_to_local}/context") + + %{"ancestors" => ancestors3, "descendants" => []} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids3 = extract_activity_ids_from_response(ancestors3) + assert all_ids_included?(ancestor_ids3, all_ids) + + res_conn = get(conn, "/api/v1/statuses/#{remote_to_remote}/context") + + %{"ancestors" => ancestors4, "descendants" => []} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids4 = extract_activity_ids_from_response(ancestors4) + assert all_ids_included?(ancestor_ids4, all_ids) + end + test "if user is authenticated", %{locals: [post_id, reply_id], remote: remote_reply_id} do %{conn: conn} = oauth_access(["read"]) res_conn = get(conn, "/api/v1/statuses/#{post_id}/context") @@ -1293,6 +1794,47 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do assert post_id in ancestor_ids assert remote_reply_id in descendant_ids end + + test "if user is authenticated Activity interactions", %{ + locals: [post_id, reply_id], + remote: remote_reply_id, + local_interactions: [local_to_local, local_to_remote], + remote_interactions: [remote_to_local, remote_to_remote] + } do + %{conn: conn} = oauth_access(["read"]) + all_ids = [post_id, reply_id, remote_reply_id] + res_conn = get(conn, "/api/v1/statuses/#{local_to_local}/context") + + %{"ancestors" => ancestors1, "descendants" => []} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids1 = extract_activity_ids_from_response(ancestors1) + assert all_ids_included?(ancestor_ids1, all_ids) + + res_conn = get(conn, "/api/v1/statuses/#{local_to_remote}/context") + + %{"ancestors" => ancestors2, "descendants" => []} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids2 = extract_activity_ids_from_response(ancestors2) + assert all_ids_included?(ancestor_ids2, all_ids) + + res_conn = get(conn, "/api/v1/statuses/#{remote_to_local}/context") + + %{"ancestors" => ancestors3, "descendants" => []} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids3 = extract_activity_ids_from_response(ancestors3) + assert all_ids_included?(ancestor_ids3, all_ids) + + res_conn = get(conn, "/api/v1/statuses/#{remote_to_remote}/context") + + %{"ancestors" => ancestors4, "descendants" => []} = + json_response_and_validate_schema(res_conn, 200) + + ancestor_ids4 = extract_activity_ids_from_response(ancestors4) + assert all_ids_included?(ancestor_ids4, all_ids) + end end describe "deleting a status" do diff --git a/test/support/factory.ex b/test/support/factory.ex @@ -102,11 +102,19 @@ defmodule Pleroma.Factory do user = attrs[:user] || insert(:user) + object_id = + if attrs[:object_local] == false do + # Must not match our Endpoint URL in the test env + "https://example.com/objects/#{Ecto.UUID.generate()}" + else + Pleroma.Web.ActivityPub.Utils.generate_object_id() + end + data = %{ "type" => "Note", "content" => text, "source" => text, - "id" => Pleroma.Web.ActivityPub.Utils.generate_object_id(), + "id" => object_id, "actor" => user.ap_id, "to" => ["https://www.w3.org/ns/activitystreams#Public"], "published" => DateTime.utc_now() |> DateTime.to_iso8601(), @@ -297,27 +305,27 @@ defmodule Pleroma.Factory do featured_collection_activity(attrs, "Add") end - def remove_activity_factor(attrs \\ %{}) do + def remove_activity_factory(attrs \\ %{}) do featured_collection_activity(attrs, "Remove") end defp featured_collection_activity(attrs, type) do user = attrs[:user] || insert(:user) - note = attrs[:note] || insert(:note, user: user) + note_activity = attrs[:note_activity] || insert(:note_activity, user: user) data_attrs = attrs |> Map.get(:data_attrs, %{}) |> Map.put(:type, type) - attrs = Map.drop(attrs, [:user, :note, :data_attrs]) + attrs = Map.drop(attrs, [:user, :note_activity, :data_attrs]) data = %{ "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(), "target" => user.featured_address, - "object" => note.data["object"], - "actor" => note.data["actor"], + "object" => note_activity.data["object"], + "actor" => note_activity.data["actor"], "type" => "Add", "to" => [Pleroma.Constants.as_public()], "cc" => [user.follower_address] @@ -361,14 +369,25 @@ defmodule Pleroma.Factory do def note_activity_factory(attrs \\ %{}) do user = attrs[:user] || insert(:user) - note = attrs[:note] || insert(:note, user: user) + object_local = if attrs[:object_local] == false, do: false, else: true + note = attrs[:note] || insert(:note, user: user, object_local: object_local) + + activity_id = + if attrs[:local] == false do + # Same domain as in note Object factory, it doesn't make sense + # to create mismatched Create Activities with an ID coming from + # a different domain than the Object + "https://example.com/activities/#{Ecto.UUID.generate()}" + else + Pleroma.Web.ActivityPub.Utils.generate_activity_id() + end data_attrs = attrs[:data_attrs] || %{} - attrs = Map.drop(attrs, [:user, :note, :data_attrs]) + attrs = Map.drop(attrs, [:user, :note, :data_attrs, :object_local]) data = %{ - "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(), + "id" => activity_id, "type" => "Create", "actor" => note.data["actor"], "to" => note.data["to"], @@ -408,15 +427,17 @@ defmodule Pleroma.Factory do def announce_activity_factory(attrs \\ %{}) do note_activity = attrs[:note_activity] || insert(:note_activity) + object = Object.normalize(note_activity, fetch: false) user = attrs[:user] || insert(:user) data = %{ + "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(), "type" => "Announce", - "actor" => note_activity.actor, - "object" => note_activity.data["id"], - "to" => [user.follower_address, note_activity.data["actor"]], + "actor" => user.ap_id, + "object" => object.data["id"], + "to" => [user.follower_address, object.data["actor"]], "cc" => ["https://www.w3.org/ns/activitystreams#Public"], - "context" => note_activity.data["context"] + "context" => object.data["context"] } %Pleroma.Activity{ @@ -426,6 +447,28 @@ defmodule Pleroma.Factory do } end + def emoji_react_activity_factory(attrs \\ %{}) do + note_activity = attrs[:note_activity] || insert(:note_activity) + object = Object.normalize(note_activity, fetch: false) + user = attrs[:user] || insert(:user) + + data = %{ + "id" => Pleroma.Web.ActivityPub.Utils.generate_activity_id(), + "actor" => user.ap_id, + "type" => "EmojiReact", + "object" => object.data["id"], + "to" => [user.follower_address, object.data["actor"]], + "cc" => ["https://www.w3.org/ns/activitystreams#Public"], + "published_at" => DateTime.utc_now() |> DateTime.to_iso8601(), + "context" => object.data["context"], + "content" => "😀" + } + + %Pleroma.Activity{ + data: data + } + end + def like_activity_factory(attrs \\ %{}) do note_activity = attrs[:note_activity] || insert(:note_activity) object = Object.normalize(note_activity, fetch: false)