diff --git a/lib/pleroma/web/api_spec/operations/instance_operation.ex b/lib/pleroma/web/api_spec/operations/instance_operation.ex index bccea7486f..e2f3bf1085 100644 --- a/lib/pleroma/web/api_spec/operations/instance_operation.ex +++ b/lib/pleroma/web/api_spec/operations/instance_operation.ex @@ -40,7 +40,7 @@ def rules_operation do summary: "Retrieve list of instance rules", operationId: "InstanceController.rules", responses: %{ - 200 => Operation.response("Array of domains", "application/json", array_of_rules()) + 200 => Operation.response("Array of rules", "application/json", array_of_rules()) } } end diff --git a/lib/pleroma/web/api_spec/operations/mastodon_admin/account_operation.ex b/lib/pleroma/web/api_spec/operations/mastodon_admin/account_operation.ex index b6d28e7d31..3ca61b5daa 100644 --- a/lib/pleroma/web/api_spec/operations/mastodon_admin/account_operation.ex +++ b/lib/pleroma/web/api_spec/operations/mastodon_admin/account_operation.ex @@ -20,7 +20,7 @@ def index_operation do %Operation{ tags: ["User administration"], summary: "View accounts by criteria", - operationId: "Admin.AccountController.index", + operationId: "MastodonAdmin.AccountController.index", description: "View accounts matching certain criteria for filtering, up to 100 at a time.", security: [%{"oAuth" => ["admin:read:accounts"]}], parameters: @@ -97,7 +97,7 @@ def show_operation do %Operation{ tags: ["User administration"], summary: "View a specific account", - operationId: "Admin.AccountController.show", + operationId: "MastodonAdmin.AccountController.show", description: "View admin-level information about the given account.", security: [%{"oAuth" => ["admin:read:accounts"]}], parameters: [ @@ -115,7 +115,7 @@ def account_action_operation do %Operation{ tags: ["User administration"], summary: "Perform an action against an account", - operationId: "Admin.AccountController.account_action", + operationId: "MastodonAdmin.AccountController.account_action", description: "Perform an action against an account and log this action in the moderation history.", security: [%{"oAuth" => ["admin:write:accounts"]}], @@ -147,7 +147,7 @@ def delete_operation do %Operation{ tags: ["User administration"], summary: "Delete a specific account", - operationId: "Admin.AccountController.delete", + operationId: "MastodonAdmin.AccountController.delete", description: "Delete the given account.", security: [%{"oAuth" => ["admin:write:accounts"]}], parameters: [ @@ -165,7 +165,7 @@ def enable_operation do %Operation{ tags: ["User administration"], summary: "Re-enable account", - operationId: "Admin.AccountController.enable", + operationId: "MastodonAdmin.AccountController.enable", description: "Re-enable a local account whose login is currently disabled.", security: [%{"oAuth" => ["admin:write:accounts"]}], parameters: [ @@ -183,7 +183,7 @@ def unsensitive_operation do %Operation{ tags: ["User administration"], summary: "Unsensitive account", - operationId: "Admin.AccountController.unsensitive", + operationId: "MastodonAdmin.AccountController.unsensitive", description: "Unsensitive a currently sensitized account.", security: [%{"oAuth" => ["admin:write:accounts"]}], parameters: [ @@ -201,7 +201,7 @@ def unsilence_operation do %Operation{ tags: ["User administration"], summary: "Unsilence account", - operationId: "Admin.AccountController.unsilence", + operationId: "MastodonAdmin.AccountController.unsilence", description: "Unsilence a currently silenced account.", parameters: [ Operation.parameter(:id, :path, :string, "ID of the account") @@ -218,7 +218,7 @@ def unsuspend_operation do %Operation{ tags: ["User administration"], summary: "Unsuspend account", - operationId: "Admin.AccountController.unsuspend", + operationId: "MastodonAdmin.AccountController.unsuspend", description: "Unsuspend a currently suspended account.", parameters: [ Operation.parameter(:id, :path, :string, "ID of the account") @@ -235,7 +235,7 @@ def approve_operation do %Operation{ tags: ["User administration"], summary: "Approve pending account", - operationId: "Admin.AccountController.approve", + operationId: "MastodonAdmin.AccountController.approve", description: "Approve the given local account if it is currently pending approval.", parameters: [ Operation.parameter(:id, :path, :string, "ID of the account") @@ -252,7 +252,7 @@ def reject_operation do %Operation{ tags: ["User administration"], summary: "Reject pending account", - operationId: "Admin.AccountController.reject", + operationId: "MastodonAdmin.AccountController.reject", description: "Reject the given local account if it is currently pending approval.", parameters: [ Operation.parameter(:id, :path, :string, "ID of the account") @@ -266,7 +266,7 @@ def reject_operation do } end - defp account do + def account do %Schema{ title: "AdminAccount", description: "Admin-level information about a given account.", diff --git a/lib/pleroma/web/api_spec/operations/mastodon_admin/report_operation.ex b/lib/pleroma/web/api_spec/operations/mastodon_admin/report_operation.ex new file mode 100644 index 0000000000..a89010a4dd --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/mastodon_admin/report_operation.ex @@ -0,0 +1,136 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.MastodonAdmin.ReportOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.MastodonAdmin.AccountOperation + alias Pleroma.Web.ApiSpec.Schemas.ApiError + alias Pleroma.Web.ApiSpec.Schemas.FlakeID + alias Pleroma.Web.ApiSpec.Schemas.Status + + import Pleroma.Web.ApiSpec.Helpers + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def index_operation do + %Operation{ + tags: ["Report methods"], + summary: "View all reports", + operationId: "MastodonAdmin.ReportController.index", + description: + "View all reports. Pagination may be done with HTTP Link header in the response.", + security: [%{"oAuth" => ["admin:read:reports"]}], + parameters: + [ + Operation.parameter(:resolved, :query, :boolean, "Filter for resolved reports"), + Operation.parameter(:account_id, :query, :string, "Filter by author account id"), + Operation.parameter( + :target_account_id, + :query, + :string, + "Filter by report target account id (not implemented)" + ) + ] ++ + pagination_params(), + responses: %{ + 200 => + Operation.response("Account", "application/json", %Schema{ + title: "ArrayOfReports", + type: :array, + items: report() + }), + 401 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def show_operation do + %Operation{ + tags: ["Report methods"], + summary: "View a single report", + operationId: "MastodonAdmin.ReportController.show", + description: "View information about the report with the given ID.", + security: [%{"oAuth" => ["admin:read:reports"]}], + parameters: [ + Operation.parameter(:id, :path, :string, "ID of the report") + ], + responses: %{ + 200 => Operation.response("Account", "application/json", report()), + 401 => Operation.response("Error", "application/json", ApiError), + 404 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def resolve_operation do + %Operation{ + tags: ["Report methods"], + summary: "Mark as resolved", + operationId: "MastodonAdmin.ReportController.resolve", + description: "Mark a report as resolved with no further action taken.", + security: [%{"oAuth" => ["admin:write:reports"]}], + parameters: [ + Operation.parameter(:id, :path, :string, "ID of the report") + ], + responses: %{ + 200 => Operation.response("Account", "application/json", report()), + 400 => Operation.response("Error", "application/json", ApiError), + 401 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def reopen_operation do + %Operation{ + tags: ["Report methods"], + summary: "Re-open report", + operationId: "MastodonAdmin.ReportController.reopen", + description: "Reopen a currently closed report.", + security: [%{"oAuth" => ["admin:write:reports"]}], + parameters: [ + Operation.parameter(:id, :path, :string, "ID of the report") + ], + responses: %{ + 200 => Operation.response("Account", "application/json", report()), + 400 => Operation.response("Error", "application/json", ApiError), + 401 => Operation.response("Error", "application/json", ApiError) + } + } + end + + defp report do + %Schema{ + title: "Report", + type: :object, + properties: %{ + id: FlakeID, + action_taken: %Schema{type: :boolean}, + category: %Schema{type: :string}, + comment: %Schema{type: :string}, + created_at: %Schema{type: :string, format: "date-time"}, + updated_at: %Schema{type: :string, format: "date-time"}, + account: AccountOperation.account(), + target_account: AccountOperation.account(), + statuses: %Schema{ + type: :array, + items: Status + }, + rules: %Schema{ + type: :array, + items: %Schema{ + type: :object, + properties: %{ + id: %Schema{type: :integer}, + text: %Schema{type: :string} + } + } + } + } + } + end +end diff --git a/lib/pleroma/web/mastodon_api/admin/controllers/report_controller.ex b/lib/pleroma/web/mastodon_api/admin/controllers/report_controller.ex new file mode 100644 index 0000000000..8099e1c4cf --- /dev/null +++ b/lib/pleroma/web/mastodon_api/admin/controllers/report_controller.ex @@ -0,0 +1,102 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.Admin.ReportController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, + only: [ + add_link_headers: 2, + json_response: 3 + ] + + alias Pleroma.Activity + alias Pleroma.Pagination + alias Pleroma.User + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.AdminAPI.Report + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.Plugs.OAuthScopesPlug + + plug(Pleroma.Web.ApiSpec.CastAndValidate) + + plug(OAuthScopesPlug, %{scopes: ["admin:read:reports"]} when action in [:index, :show]) + + plug(OAuthScopesPlug, %{scopes: ["admin:write:reports"]} when action in [:resolve, :reopen]) + + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.MastodonAdmin.ReportOperation + + def index(conn, params) do + opts = + %{} + |> Map.put(:type, "Flag") + |> Map.put(:skip_preload, true) + |> Map.put(:preload_report_notes, true) + |> Map.put(:total, true) + |> restrict_state(params) + |> restrict_actor(params) + + # |> restrict_target(params) + + reports = + ActivityPub.fetch_activities_query([], opts) + |> Pagination.fetch_paginated(params) + + conn + |> add_link_headers(reports) + |> render("index.json", reports: reports) + end + + def show(conn, %{id: id}) do + with %Activity{} = report <- Activity.get_report(id) do + render(conn, "show.json", Report.extract_report_info(report)) + else + _ -> {:error, :not_found} + end + end + + def resolve(conn, %{id: id}) do + with {:ok, report} <- CommonAPI.update_report_state(id, "resolved") do + render(conn, "show.json", Report.extract_report_info(report)) + else + {:error, error} -> + json_response(conn, :bad_request, %{error: error}) + end + end + + def reopen(conn, %{id: id}) do + with {:ok, report} <- CommonAPI.update_report_state(id, "open") do + render(conn, "show.json", Report.extract_report_info(report)) + else + {:error, error} -> + json_response(conn, :bad_request, %{error: error}) + end + end + + defp restrict_state(opts, %{resolved: true}), do: Map.put(opts, :state, "resolved") + + defp restrict_state(opts, %{resolved: false}), do: Map.put(opts, :state, "open") + + defp restrict_state(opts, _params), do: opts + + defp restrict_actor(opts, %{account_id: actor}) do + with %User{ap_id: ap_id} <- User.get_by_id(actor) do + Map.put(opts, :actor_id, ap_id) + else + _ -> Map.put(opts, :actor_id, actor) + end + end + + defp restrict_actor(opts, _params), do: opts + + # defp restrict_target(opts, %{target_account_id: target}) do + # with %User{id: id} <- User.get_by_ap_id(target) do + # Map.put(opts, :user_actor_id, id) + # else + # _ -> Map.put(opts, :user_actor_id, target) + # end + # end + + # defp restrict_target(opts, _params), do: opts +end diff --git a/lib/pleroma/web/mastodon_api/admin/views/report_view.ex b/lib/pleroma/web/mastodon_api/admin/views/report_view.ex new file mode 100644 index 0000000000..dbcb3392f3 --- /dev/null +++ b/lib/pleroma/web/mastodon_api/admin/views/report_view.ex @@ -0,0 +1,59 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.MastodonAPI.Admin.ReportView do + use Pleroma.Web, :view + + alias Pleroma.HTML + alias Pleroma.User + alias Pleroma.Web.AdminAPI.Report + alias Pleroma.Web.CommonAPI.Utils + alias Pleroma.Web.MastodonAPI + alias Pleroma.Web.MastodonAPI.Admin.AccountView + alias Pleroma.Web.MastodonAPI.InstanceView + alias Pleroma.Web.MastodonAPI.StatusView + + def render("index.json", %{reports: reports}) do + reports + |> Enum.map(&Report.extract_report_info/1) + |> Enum.map(&render(__MODULE__, "show.json", &1)) + + # |> render_many(__MODULE__, "show.json", as: :report) + end + + def render("show.json", %{ + report: report, + user: account, + account: target_account, + statuses: statuses + }) do + created_at = Utils.to_masto_date(report.data["published"]) + + content = + unless is_nil(report.data["content"]) do + HTML.filter_tags(report.data["content"]) + else + nil + end + + %{ + id: report.id, + action_taken: report.data["state"] != "open", + category: "other", + comment: content, + created_at: created_at, + updated_at: created_at, + account: AccountView.render("show.json", %{user: account}), + target_account: AccountView.render("show.json", %{user: target_account}), + assigned_account: nil, + action_taken_by_account: nil, + statuses: + StatusView.render("index.json", %{ + activities: statuses, + as: :activity + }), + rules: [] + } + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 792c4a8c0f..6587cf4aa5 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -313,6 +313,11 @@ defmodule Pleroma.Web.Router do # post("/accounts/:id/unsuspend", AccountController, :unsuspend) post("/accounts/:id/approve", AccountController, :approve) post("/accounts/:id/reject", AccountController, :reject) + + get("/reports", ReportController, :index) + get("/reports/:id", ReportController, :show) + post("/reports/:id/resolve", ReportController, :resolve) + post("/reports/:id/reopen", ReportController, :reopen) end scope "/api/v1/pleroma/emoji", Pleroma.Web.PleromaAPI do diff --git a/test/pleroma/web/mastodon_api/admin/controllers/account_controller_test.exs b/test/pleroma/web/mastodon_api/admin/controllers/account_controller_test.exs index 91a13ef164..c1414059a0 100644 --- a/test/pleroma/web/mastodon_api/admin/controllers/account_controller_test.exs +++ b/test/pleroma/web/mastodon_api/admin/controllers/account_controller_test.exs @@ -138,9 +138,10 @@ test "reject account", %{conn: conn} do test "do not allow rejecting already accepted accounts", %{conn: conn} do %{id: id} = user = insert(:user, is_approved: true) - assert %{"error" => "User is approved"} == conn - |> post("/api/v1/admin/accounts/#{id}/reject") - |> json_response_and_validate_schema(400) + assert %{"error" => "User is approved"} == + conn + |> post("/api/v1/admin/accounts/#{id}/reject") + |> json_response_and_validate_schema(400) user = Repo.reload!(user)