logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://anongit.hacktivis.me/git/pleroma.git/
commit: fe7108cbc28581cf7242c73867a06a68a96ac14b
parent 73a3f06f7169b4de7d9237c7d258e3fd21aeaacf
Author: Phantasm <phantasm@centrum.cz>
Date:   Thu, 11 Dec 2025 22:37:51 +0100

MastoAPI: Unify pin/bookmark/mute/fav not visible responses to 404

Also adds more tests for these interactions.

Diffstat:

Mlib/pleroma/web/api_spec/operations/status_operation.ex18++++++------------
Mlib/pleroma/web/common_api.ex5++++-
Mlib/pleroma/web/mastodon_api/controllers/status_controller.ex17+++++++++++++++++
Mtest/pleroma/web/common_api_test.exs2+-
Mtest/pleroma/web/mastodon_api/controllers/status_controller_test.exs60++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 88 insertions(+), 14 deletions(-)

diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do alias Pleroma.Web.ApiSpec.AccountOperation alias Pleroma.Web.ApiSpec.Schemas.Account alias Pleroma.Web.ApiSpec.Schemas.ApiError + alias Pleroma.Web.ApiSpec.Schemas.ApiNotFoundError alias Pleroma.Web.ApiSpec.Schemas.Attachment alias Pleroma.Web.ApiSpec.Schemas.BooleanLike alias Pleroma.Web.ApiSpec.Schemas.Emoji @@ -289,7 +290,8 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do } }), responses: %{ - 200 => status_response() + 200 => status_response(), + 404 => Operation.response("Not found", "application/json", ApiNotFoundError) } } end @@ -303,7 +305,8 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do operationId: "StatusController.unbookmark", parameters: [id_param()], responses: %{ - 200 => status_response() + 200 => status_response(), + 404 => Operation.response("Not found", "application/json", ApiNotFoundError) } } end @@ -339,16 +342,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do responses: %{ 200 => status_response(), 400 => Operation.response("Error", "application/json", ApiError), - 404 => - Operation.response( - "Not Found", - "application/json", - %Schema{ - allOf: [ApiError], - title: "Error", - example: %{"error" => "Record not found"} - } - ) + 404 => Operation.response("Not found", "application/json", ApiError) } } end diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex @@ -300,6 +300,7 @@ defmodule Pleroma.Web.CommonAPI do with {_, %Activity{data: %{"type" => "Create"}} = activity} <- {:find_activity, Activity.get_by_id(id)}, %Object{} = note <- Object.normalize(activity, fetch: false), + {_, true} <- {:visibility, activity_visible_to_actor(note, user)}, %Activity{} = like <- Utils.get_existing_like(user.ap_id, note), {_, {:ok, _}} <- {:cancel_jobs, maybe_cancel_jobs(like)}, {:ok, undo, _} <- Builder.undo(user, like), @@ -307,6 +308,7 @@ defmodule Pleroma.Web.CommonAPI do {:ok, activity} else {:find_activity, _} -> {:error, :not_found} + {:visibility, _} -> {:error, :not_found} _ -> {:error, dgettext("errors", "Could not unfavorite")} end end @@ -514,6 +516,7 @@ defmodule Pleroma.Web.CommonAPI do @spec pin(String.t(), User.t()) :: {:ok, Activity.t()} | Pipeline.errors() def pin(id, %User{} = user) do with %Activity{} = activity <- create_activity_by_id(id), + true <- activity_visible_to_actor(activity, user), true <- activity_belongs_to_actor(activity, user.ap_id), true <- object_type_is_allowed_for_pin(activity.object), true <- activity_is_public(activity), @@ -555,7 +558,7 @@ defmodule Pleroma.Web.CommonAPI do defp activity_is_public(activity) do with false <- Visibility.public?(activity) do - {:error, :visibility_error} + {:error, :non_public_error} end end diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex @@ -393,6 +393,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do with {:ok, activity} <- CommonAPI.pin(ap_id_or_id, user) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) else + # Order matters, if status is not owned by user and is not visible to user + # return 404 just like other endpoints {:error, :pinned_statuses_limit_reached} -> {:error, "You have already pinned the maximum number of statuses"} @@ -400,6 +402,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do {:error, :unprocessable_entity, "Someone else's status cannot be pinned"} {:error, :visibility_error} -> + {:error, :not_found, "Record not found"} + + {:error, :non_public_error} -> {:error, :unprocessable_entity, "Non-public status cannot be pinned"} error -> @@ -449,6 +454,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do ), {:ok, _bookmark} <- Bookmark.create(user.id, activity.id, folder_id) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) + else + false -> + {:error, :not_found, "Record not found"} + + error -> + error end end @@ -462,6 +473,12 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do true <- Visibility.visible_for_user?(activity, user), {:ok, _bookmark} <- Bookmark.destroy(user.id, activity.id) do try_render(conn, "show.json", activity: activity, for: user, as: :activity) + else + false -> + {:error, :not_found, "Record not found"} + + error -> + error end end diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs @@ -1072,7 +1072,7 @@ defmodule Pleroma.Web.CommonAPITest do test "only public can be pinned", %{user: user} do {:ok, activity} = CommonAPI.post(user, %{status: "private status", visibility: "private"}) - {:error, :visibility_error} = CommonAPI.pin(activity.id, user) + {:error, :non_public_error} = CommonAPI.pin(activity.id, user) end test "unpin status", %{user: user, activity: activity} 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 @@ -1630,6 +1630,19 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do assert to_string(activity.id) == id end + test "can't unfavourite post that isn't visible to user" do + user = insert(:user) + %{conn: conn, user: stranger} = oauth_access(["write:favourites"]) + {:ok, activity} = CommonAPI.post(user, %{status: "invisible", visibility: "private"}) + + refute Pleroma.Web.ActivityPub.Visibility.visible_for_user?(activity, stranger) + + assert conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses/#{activity.id}/unfavourite") + |> json_response_and_validate_schema(404) == %{"error" => "Record not found"} + end + test "can't unfavourite post that isn't favourited", %{conn: conn} do activity = insert(:note_activity) @@ -1675,6 +1688,19 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do end end + test "can't favourite post that isn't visible to user" do + user = insert(:user) + %{conn: conn, user: stranger} = oauth_access(["write:favourites"]) + {:ok, activity} = CommonAPI.post(user, %{status: "invisible", visibility: "private"}) + + refute Pleroma.Web.ActivityPub.Visibility.visible_for_user?(activity, stranger) + + assert conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses/#{activity.id}/favourite") + |> json_response_and_validate_schema(404) == %{"error" => "Record not found"} + end + describe "pinned statuses" do setup do: oauth_access(["write:accounts"]) @@ -1721,6 +1747,18 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do } end + test "/pin: returns 404 error when activity not visible to user", %{user: user} do + %{conn: conn, user: stranger} = oauth_access(["write:accounts"]) + {:ok, activity} = CommonAPI.post(user, %{status: "invisible", visibility: "private"}) + + refute Pleroma.Web.ActivityPub.Visibility.visible_for_user?(activity, stranger) + + assert conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses/#{activity.id}/pin") + |> json_response_and_validate_schema(404) == %{"error" => "Record not found"} + end + test "pin by another user", %{activity: activity} do %{conn: conn} = oauth_access(["write:accounts"]) @@ -1892,6 +1930,28 @@ defmodule Pleroma.Web.MastodonAPI.StatusControllerTest do json_response_and_validate_schema(bookmarks, 200) end + test "cannot bookmark invisible post" do + user = insert(:user) + %{conn: conn, user: stranger} = oauth_access(["write:bookmarks"]) + {:ok, activity} = CommonAPI.post(user, %{status: "mocha", visibility: "private"}) + + refute Pleroma.Web.ActivityPub.Visibility.visible_for_user?(activity, stranger) + + resp1 = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses/#{activity.id}/bookmark") + + assert json_response_and_validate_schema(resp1, 404) == %{"error" => "Record not found"} + + resp2 = + conn + |> put_req_header("content-type", "application/json") + |> post("/api/v1/statuses/#{activity.id}/unbookmark") + + assert json_response_and_validate_schema(resp2, 404) == %{"error" => "Record not found"} + end + test "bookmark folders" do %{conn: conn, user: user} = oauth_access(["write:bookmarks", "read:bookmarks"])