commit: 2b76243ec82f2c246264f2a1f655b975f12e7290
parent a4e480a6368e20e26293fa88205b63d8e3bb837e
Author: Phantasm <phantasm@centrum.cz>
Date: Wed, 3 Dec 2025 23:34:39 +0100
CommonAPI: Fail when user sends report with posts not visible to them
Diffstat:
6 files changed, 124 insertions(+), 3 deletions(-)
diff --git a/lib/pleroma/web/api_spec/operations/report_operation.ex b/lib/pleroma/web/api_spec/operations/report_operation.ex
@@ -24,7 +24,17 @@ defmodule Pleroma.Web.ApiSpec.ReportOperation do
requestBody: Helpers.request_body("Parameters", create_request(), required: true),
responses: %{
200 => Operation.response("Report", "application/json", create_response()),
- 400 => Operation.response("Report", "application/json", ApiError)
+ 400 => Operation.response("Report", "application/json", ApiError),
+ 404 =>
+ Operation.response(
+ "Report",
+ "application/json",
+ %Schema{
+ allOf: [ApiError],
+ title: "Report",
+ example: %{"error" => "Record not found"}
+ }
+ )
}
}
end
diff --git a/lib/pleroma/web/common_api.ex b/lib/pleroma/web/common_api.ex
@@ -620,6 +620,7 @@ defmodule Pleroma.Web.CommonAPI do
with {:ok, account} <- get_reported_account(data.account_id),
{:ok, {content_html, _, _}} <- make_report_content_html(data[:comment]),
{:ok, statuses} <- get_report_statuses(account, data),
+ true <- check_statuses_visibility(user, statuses),
rules <- get_report_rules(Map.get(data, :rule_ids, nil)) do
ActivityPub.flag(%{
context: Utils.generate_context_id(),
@@ -630,9 +631,27 @@ defmodule Pleroma.Web.CommonAPI do
forward: Map.get(data, :forward, false),
rules: rules
})
+ else
+ false ->
+ {:error, :visibility}
+
+ error ->
+ error
end
end
+ defp check_statuses_visibility(user, statuses) when is_list(statuses) do
+ visibility = for status <- statuses, do: Visibility.visible_for_user?(status, user)
+
+ case Enum.all?(visibility) do
+ true -> true
+ _ -> false
+ end
+ end
+
+ # There are no statuses associated with the report, pass!
+ defp check_statuses_visibility(_, status) when status == nil, do: true
+
defp get_reported_account(account_id) do
case User.get_cached_by_id(account_id) do
%User{} = account -> {:ok, account}
diff --git a/lib/pleroma/web/common_api/activity_draft.ex b/lib/pleroma/web/common_api/activity_draft.ex
@@ -147,7 +147,7 @@ defmodule Pleroma.Web.CommonAPI.ActivityDraft do
# TODO: Fix this quirk in FE and remove here and other affected places
with %Activity{} = activity <- Activity.get_by_id(id),
true <- Visibility.visible_for_user?(activity, draft.user),
- {:type, type} when type in ["Create", "Announce"] <- {:type, activity.data["type"]} do
+ {_, type} when type in ["Create", "Announce"] <- {:type, activity.data["type"]} do
%__MODULE__{draft | in_reply_to: activity}
else
nil ->
diff --git a/lib/pleroma/web/mastodon_api/controllers/report_controller.ex b/lib/pleroma/web/mastodon_api/controllers/report_controller.ex
@@ -16,6 +16,12 @@ defmodule Pleroma.Web.MastodonAPI.ReportController do
def create(%{assigns: %{user: user}, body_params: params} = conn, _) do
with {:ok, activity} <- Pleroma.Web.CommonAPI.report(user, params) do
render(conn, "show.json", activity: activity)
+ else
+ {:error, :visibility} ->
+ {:error, :not_found, "Record not found"}
+
+ error ->
+ error
end
end
end
diff --git a/test/pleroma/web/common_api_test.exs b/test/pleroma/web/common_api_test.exs
@@ -1286,6 +1286,47 @@ defmodule Pleroma.Web.CommonAPITest do
} = flag_activity
end
+ test "doesn't create a report when post is not visible to user" do
+ reporter = insert(:user)
+ target_user = insert(:user)
+ {:ok, post} = CommonAPI.post(target_user, %{status: "Eric", visibility: "private"})
+
+ assert Pleroma.Web.ActivityPub.Visibility.private?(post)
+ refute Pleroma.Web.ActivityPub.Visibility.visible_for_user?(post, reporter)
+
+ # Fails when all status are invisible
+ report_data = %{
+ account_id: target_user.id,
+ comment: "foobar",
+ status_ids: [post.id]
+ }
+
+ assert {:error, :visibility} = CommonAPI.report(reporter, report_data)
+ end
+
+ test "doesn't create a report when some posts are not visible to user" do
+ reporter = insert(:user)
+ target_user = insert(:user)
+
+ {:ok, visible_activity} = CommonAPI.post(target_user, %{status: "cofe"})
+
+ {:ok, invisibile_activity} =
+ CommonAPI.post(target_user, %{status: "cawfee", visibility: "private"})
+
+ assert Pleroma.Web.ActivityPub.Visibility.private?(invisibile_activity)
+ assert Pleroma.Web.ActivityPub.Visibility.public?(visible_activity)
+ refute Pleroma.Web.ActivityPub.Visibility.visible_for_user?(invisibile_activity, reporter)
+
+ # Fails when some statuses are invisible
+ report_data_partial = %{
+ account_id: target_user.id,
+ comment: "foobar",
+ status_ids: [visible_activity.id, invisibile_activity.id]
+ }
+
+ assert {:error, :visibility} = CommonAPI.report(reporter, report_data_partial)
+ end
+
test "updates report state" do
[reporter, target_user] = insert_pair(:user)
activity = insert(:note_activity, user: target_user)
diff --git a/test/pleroma/web/mastodon_api/controllers/report_controller_test.exs b/test/pleroma/web/mastodon_api/controllers/report_controller_test.exs
@@ -147,7 +147,7 @@ defmodule Pleroma.Web.MastodonAPI.ReportControllerTest do
|> json_response_and_validate_schema(400)
end
- test "returns error when account is not exist", %{
+ test "returns error when account does not exist", %{
conn: conn,
activity: activity
} do
@@ -159,6 +159,51 @@ defmodule Pleroma.Web.MastodonAPI.ReportControllerTest do
assert json_response_and_validate_schema(conn, 400) == %{"error" => "Account not found"}
end
+ test "returns not found when post isn't visible to reporter", %{user: target_user} do
+ %{conn: conn, user: reporter} = oauth_access(["write:reports"])
+
+ {:ok, invisible_activity} =
+ CommonAPI.post(target_user, %{status: "Invisible!", visibility: "private"})
+
+ assert Pleroma.Web.ActivityPub.Visibility.private?(invisible_activity)
+ refute Pleroma.Web.ActivityPub.Visibility.visible_for_user?(invisible_activity, reporter)
+
+ assert %{"error" => "Record not found"} =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post(
+ "/api/v1/reports",
+ %{"account_id" => target_user.id, "status_ids" => [invisible_activity.id]}
+ )
+ |> json_response_and_validate_schema(404)
+ end
+
+ test "returns not found when some post aren't visible to reporter", %{
+ activity: activity,
+ user: target_user
+ } do
+ %{conn: conn, user: reporter} = oauth_access(["write:reports"])
+
+ {:ok, invisible_activity} =
+ CommonAPI.post(target_user, %{status: "Invisible!", visibility: "private"})
+
+ assert Pleroma.Web.ActivityPub.Visibility.private?(invisible_activity)
+ assert Pleroma.Web.ActivityPub.Visibility.visible_for_user?(activity, reporter)
+ refute Pleroma.Web.ActivityPub.Visibility.visible_for_user?(invisible_activity, reporter)
+
+ assert %{"error" => "Record not found"} =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> post(
+ "/api/v1/reports",
+ %{
+ "account_id" => target_user.id,
+ "status_ids" => [activity.id, invisible_activity.id]
+ }
+ )
+ |> json_response_and_validate_schema(404)
+ end
+
test "doesn't fail if an admin has no email", %{conn: conn, target_user: target_user} do
insert(:user, %{is_admin: true, email: nil})