From 1f0be71ea433971de874a71ba1dafd101f4301b6 Mon Sep 17 00:00:00 2001 From: KokaKiwi Date: Sun, 24 Feb 2019 18:45:29 +0100 Subject: [PATCH 1/7] Make activity announceable by its author. --- lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- lib/pleroma/web/activity_pub/visibility.ex | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 95f994c17..c58b48443 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -346,7 +346,7 @@ def announce( local \\ true, public \\ true ) do - with true <- is_public?(object), + with true <- is_announceable?(object, user), announce_data <- make_announce_data(user, object, activity_id, public), {:ok, activity} <- insert(announce_data, local), {:ok, object} <- add_announce_to_object(activity, object), diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex index dfb166b65..021efd30f 100644 --- a/lib/pleroma/web/activity_pub/visibility.ex +++ b/lib/pleroma/web/activity_pub/visibility.ex @@ -27,6 +27,10 @@ def is_private?(activity) do end end + def is_announceable?(activity, user) do + is_public?(activity) || activity.data["actor"] == user.ap_id + end + def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true def is_direct?(%Object{data: %{"directMessage" => true}}), do: true From fe538973ddbdb7b3216e8da1defaa57adb63e890 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 1 Oct 2019 17:49:52 +0200 Subject: [PATCH 2/7] Ensure self-announces do not widen the audience of the original post --- lib/pleroma/web/activity_pub/activity_pub.ex | 2 +- lib/pleroma/web/activity_pub/visibility.ex | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index c58b48443..c52efb578 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -346,7 +346,7 @@ def announce( local \\ true, public \\ true ) do - with true <- is_announceable?(object, user), + with true <- is_announceable?(object, user, public), announce_data <- make_announce_data(user, object, activity_id, public), {:ok, activity} <- insert(announce_data, local), {:ok, object} <- add_announce_to_object(activity, object), diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex index 021efd30f..270d0fa02 100644 --- a/lib/pleroma/web/activity_pub/visibility.ex +++ b/lib/pleroma/web/activity_pub/visibility.ex @@ -27,8 +27,9 @@ def is_private?(activity) do end end - def is_announceable?(activity, user) do - is_public?(activity) || activity.data["actor"] == user.ap_id + def is_announceable?(activity, user, public \\ true) do + is_public?(activity) || + (!public && is_private?(activity) && activity.data["actor"] == user.ap_id) end def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true From e0b654e202554f001a5de3df4ccb8021fd3a517a Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 1 Oct 2019 17:51:27 +0200 Subject: [PATCH 3/7] Add tests --- test/web/activity_pub/activity_pub_test.exs | 33 +++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/web/activity_pub/activity_pub_test.exs b/test/web/activity_pub/activity_pub_test.exs index a203d1d30..f29497847 100644 --- a/test/web/activity_pub/activity_pub_test.exs +++ b/test/web/activity_pub/activity_pub_test.exs @@ -839,6 +839,39 @@ test "adds an announce activity to the db" do end end + describe "announcing a private object" do + test "adds an announce activity to the db if the audience is not widened" do + user = insert(:user) + {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) + object = Object.normalize(note_activity) + + {:ok, announce_activity, object} = ActivityPub.announce(user, object, nil, true, false) + + assert announce_activity.data["to"] == [User.ap_followers(user)] + + assert announce_activity.data["object"] == object.data["id"] + assert announce_activity.data["actor"] == user.ap_id + assert announce_activity.data["context"] == object.data["context"] + end + + test "does not add an announce activity to the db if the audience is widened" do + user = insert(:user) + {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) + object = Object.normalize(note_activity) + + assert {:error, _} = ActivityPub.announce(user, object, nil, true, true) + end + + test "does not add an announce activity to the db if the announcer is not the author" do + user = insert(:user) + announcer = insert(:user) + {:ok, note_activity} = CommonAPI.post(user, %{"status" => ".", "visibility" => "private"}) + object = Object.normalize(note_activity) + + assert {:error, _} = ActivityPub.announce(announcer, object, nil, true, false) + end + end + describe "unannouncing an object" do test "unannouncing a previously announced object" do note_activity = insert(:note_activity) From 4c1f158f5de95581f1489be32614e0e75bc77ba4 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 1 Oct 2019 18:38:23 +0200 Subject: [PATCH 4/7] Allow users to announce privately, including own private notes --- lib/pleroma/web/common_api/common_api.ex | 15 ++++++++++++--- .../mastodon_api/controllers/status_controller.ex | 4 ++-- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 2ec017ff8..677a53ddf 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -76,11 +76,12 @@ def delete(activity_id, user) do end end - def repeat(id_or_ap_id, user) do + def repeat(id_or_ap_id, user, params \\ %{}) do with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id), object <- Object.normalize(activity), - nil <- Utils.get_existing_announce(user.ap_id, object) do - ActivityPub.announce(user, object) + nil <- Utils.get_existing_announce(user.ap_id, object), + public <- get_announce_visibility(object, params) do + ActivityPub.announce(user, object, nil, true, public) else _ -> {:error, dgettext("errors", "Could not repeat")} end @@ -169,6 +170,14 @@ defp normalize_and_validate_choices(choices, object) do end end + def get_announce_visibility(_, %{"visibility" => visibility}) + when visibility in ~w{public unlisted private direct}, + do: visibility in ~w(public unlisted) + + def get_announce_visibility(object, _) do + Visibility.is_public?(object) + end + def get_visibility(_, _, %Participation{}), do: {"direct", "direct"} def get_visibility(%{"visibility" => visibility}, in_reply_to, _) diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index fb6fd7676..51456d453 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -125,8 +125,8 @@ def delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do end @doc "POST /api/v1/statuses/:id/reblog" - def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do - with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user), + def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id} = params) do + with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user, params), %Activity{} = announce <- Activity.normalize(announce.data) do try_render(conn, "show.json", %{activity: announce, for: user, as: :activity}) end From 7d5a9f3f6d393c744364148568cfb9b0249789fc Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 1 Oct 2019 19:08:25 +0200 Subject: [PATCH 5/7] Add tests for privately announcing statuses via API --- test/web/common_api/common_api_test.exs | 12 ++++++++++++ .../controllers/status_controller_test.exs | 18 ++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index 0f4a5eb25..2d3c41e82 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -231,6 +231,18 @@ test "repeating a status" do {:ok, %Activity{}, _} = CommonAPI.repeat(activity.id, user) end + test "repeating a status privately" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) + + {:ok, %Activity{} = announce_activity, _} = + CommonAPI.repeat(activity.id, user, %{"visibility" => "private"}) + + assert Visibility.is_private?(announce_activity) + end + test "favoriting a status" do user = insert(:user) other_user = insert(:user) diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs index b194feae6..727a233e7 100644 --- a/test/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/web/mastodon_api/controllers/status_controller_test.exs @@ -547,6 +547,24 @@ test "reblogs and returns the reblogged status", %{conn: conn} do assert to_string(activity.id) == id end + test "reblogs privately and returns the reblogged status", %{conn: conn} do + activity = insert(:note_activity) + user = insert(:user) + + conn = + conn + |> assign(:user, user) + |> post("/api/v1/statuses/#{activity.id}/reblog", %{"visibility" => "private"}) + + assert %{ + "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 0}, + "reblogged" => true, + "visibility" => "private" + } = json_response(conn, 200) + + assert to_string(activity.id) == id + end + test "reblogged status for another user", %{conn: conn} do activity = insert(:note_activity) user1 = insert(:user) From 43e3db0951c34859932f20d8c82284343a82fcf1 Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 1 Oct 2019 19:28:51 +0200 Subject: [PATCH 6/7] Fix returned visibility of announces in MastodonAPI --- lib/pleroma/web/mastodon_api/views/status_view.ex | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 3262427ec..9b8dd3086 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -125,7 +125,7 @@ def render( pinned: pinned?(activity, user), sensitive: false, spoiler_text: "", - visibility: "public", + visibility: get_visibility(activity), media_attachments: reblogged[:media_attachments] || [], mentions: mentions, tags: reblogged[:tags] || [], From 427d0c2a007db6c8424c64a8f3504420e5203bef Mon Sep 17 00:00:00 2001 From: Thibaut Girka Date: Tue, 1 Oct 2019 21:40:35 +0200 Subject: [PATCH 7/7] Store private announcements in object.data["announcements"], filter them on display --- lib/pleroma/web/activity_pub/utils.ex | 2 +- .../controllers/status_controller.ex | 14 +++++++++++++- .../controllers/status_controller_test.exs | 19 ++++++++++++++++++- 3 files changed, 32 insertions(+), 3 deletions(-) diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 2ba182f4e..0828591ee 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -494,7 +494,7 @@ def make_unlike_data( @spec add_announce_to_object(Activity.t(), Object.t()) :: {:ok, Object.t()} | {:error, Ecto.Changeset.t()} def add_announce_to_object( - %Activity{data: %{"actor" => actor, "cc" => [Pleroma.Constants.as_public()]}}, + %Activity{data: %{"actor" => actor}}, object ) do announcements = take_announcements(object) diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex index 51456d453..79cced163 100644 --- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -242,7 +242,19 @@ def favourited_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do with %Activity{} = activity <- Activity.get_by_id_with_object(id), {:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)}, - %Object{data: %{"announcements" => announces}} <- Object.normalize(activity) do + %Object{data: %{"announcements" => announces, "id" => ap_id}} <- + Object.normalize(activity) do + announces = + "Announce" + |> Activity.Queries.by_type() + |> Ecto.Query.where([a], a.actor in ^announces) + # this is to use the index + |> Activity.Queries.by_object_id(ap_id) + |> Repo.all() + |> Enum.filter(&Visibility.visible_for_user?(&1, user)) + |> Enum.map(& &1.actor) + |> Enum.uniq() + users = User |> Ecto.Query.where([u], u.ap_id in ^announces) diff --git a/test/web/mastodon_api/controllers/status_controller_test.exs b/test/web/mastodon_api/controllers/status_controller_test.exs index 727a233e7..b648ad6ff 100644 --- a/test/web/mastodon_api/controllers/status_controller_test.exs +++ b/test/web/mastodon_api/controllers/status_controller_test.exs @@ -557,7 +557,7 @@ test "reblogs privately and returns the reblogged status", %{conn: conn} do |> post("/api/v1/statuses/#{activity.id}/reblog", %{"visibility" => "private"}) assert %{ - "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 0}, + "reblog" => %{"id" => id, "reblogged" => true, "reblogs_count" => 1}, "reblogged" => true, "visibility" => "private" } = json_response(conn, 200) @@ -1167,6 +1167,23 @@ test "does not return users who have reblogged the status but are blocked", %{ assert Enum.empty?(response) end + test "does not return users who have reblogged the status privately", %{ + conn: %{assigns: %{user: user}} = conn, + activity: activity + } do + other_user = insert(:user) + + {:ok, _, _} = CommonAPI.repeat(activity.id, other_user, %{"visibility" => "private"}) + + response = + conn + |> assign(:user, user) + |> get("/api/v1/statuses/#{activity.id}/reblogged_by") + |> json_response(:ok) + + assert Enum.empty?(response) + end + test "does not fail on an unauthenticated request", %{conn: conn, activity: activity} do other_user = insert(:user) {:ok, _, _} = CommonAPI.repeat(activity.id, other_user)