From 9de9760aa696657400c762d46dced273c3475be4 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 20 May 2020 18:00:41 +0400 Subject: [PATCH] Move status actions to AdminAPI.StatusController --- .../{ => controllers}/admin_api_controller.ex | 124 +----------- .../controllers/fallback_controller.ex | 31 +++ .../controllers/status_controller.ex | 112 +++++++++++ lib/pleroma/web/router.ex | 8 +- .../admin_api_controller_test.exs | 157 --------------- .../controllers/status_controller_test.exs | 185 ++++++++++++++++++ 6 files changed, 342 insertions(+), 275 deletions(-) rename lib/pleroma/web/admin_api/{ => controllers}/admin_api_controller.ex (89%) create mode 100644 lib/pleroma/web/admin_api/controllers/fallback_controller.ex create mode 100644 lib/pleroma/web/admin_api/controllers/status_controller.ex rename test/web/admin_api/{ => controllers}/admin_api_controller_test.exs (96%) create mode 100644 test/web/admin_api/controllers/status_controller_test.exs diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex similarity index 89% rename from lib/pleroma/web/admin_api/admin_api_controller.ex rename to lib/pleroma/web/admin_api/controllers/admin_api_controller.ex index 647ceb3ba..6b1d64a2e 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -98,13 +98,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do plug( OAuthScopesPlug, %{scopes: ["read:statuses"], admin: true} - when action in [:list_statuses, :list_user_statuses, :list_instance_statuses, :status_show] - ) - - plug( - OAuthScopesPlug, - %{scopes: ["write:statuses"], admin: true} - when action in [:status_update, :status_delete] + when action in [:list_user_statuses, :list_instance_statuses] ) plug( @@ -136,7 +130,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do ] ) - action_fallback(:errors) + action_fallback(AdminAPI.FallbackController) def user_delete(conn, %{"nickname" => nickname}) do user_delete(conn, %{"nicknames" => [nickname]}) @@ -597,16 +591,10 @@ def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) json_response(conn, :no_content, "") else {:registrations_open, _} -> - errors( - conn, - {:error, "To send invites you need to set the `registrations_open` option to false."} - ) + {:error, "To send invites you need to set the `registrations_open` option to false."} {:invites_enabled, _} -> - errors( - conn, - {:error, "To send invites you need to set the `invites_enabled` option to true."} - ) + {:error, "To send invites you need to set the `invites_enabled` option to true."} end end @@ -814,71 +802,6 @@ def report_notes_delete(%{assigns: %{user: user}} = conn, %{ end end - def list_statuses(%{assigns: %{user: _admin}} = conn, params) do - godmode = params["godmode"] == "true" || params["godmode"] == true - local_only = params["local_only"] == "true" || params["local_only"] == true - with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true - {page, page_size} = page_params(params) - - activities = - ActivityPub.fetch_statuses(nil, %{ - "godmode" => godmode, - "local_only" => local_only, - "limit" => page_size, - "offset" => (page - 1) * page_size, - "exclude_reblogs" => !with_reblogs && "true" - }) - - conn - |> put_view(AdminAPI.StatusView) - |> render("index.json", %{activities: activities, as: :activity}) - end - - def status_show(conn, %{"id" => id}) do - with %Activity{} = activity <- Activity.get_by_id(id) do - conn - |> put_view(MastodonAPI.StatusView) - |> render("show.json", %{activity: activity}) - else - _ -> errors(conn, {:error, :not_found}) - end - end - - def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do - params = - params - |> Map.take(["sensitive", "visibility"]) - |> Map.new(fn {key, value} -> {String.to_existing_atom(key), value} end) - - with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do - {:ok, sensitive} = Ecto.Type.cast(:boolean, params[:sensitive]) - - ModerationLog.insert_log(%{ - action: "status_update", - actor: admin, - subject: activity, - sensitive: sensitive, - visibility: params[:visibility] - }) - - conn - |> put_view(MastodonAPI.StatusView) - |> render("show.json", %{activity: activity}) - end - end - - def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do - ModerationLog.insert_log(%{ - action: "status_delete", - actor: user, - subject_id: id - }) - - json(conn, %{}) - end - end - def list_log(conn, params) do {page, page_size} = page_params(params) @@ -904,7 +827,7 @@ def config_descriptions(conn, _params) do end def config_show(conn, %{"only_db" => true}) do - with :ok <- configurable_from_database(conn) do + with :ok <- configurable_from_database() do configs = Pleroma.Repo.all(ConfigDB) conn @@ -914,7 +837,7 @@ def config_show(conn, %{"only_db" => true}) do end def config_show(conn, _params) do - with :ok <- configurable_from_database(conn) do + with :ok <- configurable_from_database() do configs = ConfigDB.get_all_as_keyword() merged = @@ -953,7 +876,7 @@ def config_show(conn, _params) do end def config_update(conn, %{"configs" => configs}) do - with :ok <- configurable_from_database(conn) do + with :ok <- configurable_from_database() do {_errors, results} = configs |> Enum.filter(&whitelisted_config?/1) @@ -997,7 +920,7 @@ def config_update(conn, %{"configs" => configs}) do end def restart(conn, _params) do - with :ok <- configurable_from_database(conn) do + with :ok <- configurable_from_database() do Restarter.Pleroma.restart(Config.get(:env), 50) json(conn, %{}) @@ -1008,14 +931,11 @@ def need_reboot(conn, _params) do json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()}) end - defp configurable_from_database(conn) do + defp configurable_from_database do if Config.get(:configurable_from_database) do :ok else - errors( - conn, - {:error, "To use this endpoint you need to enable configuration from database."} - ) + {:error, "To use this endpoint you need to enable configuration from database."} end end @@ -1159,30 +1079,6 @@ def stats(conn, _) do |> json(%{"status_visibility" => count}) end - defp errors(conn, {:error, :not_found}) do - conn - |> put_status(:not_found) - |> json(dgettext("errors", "Not found")) - end - - defp errors(conn, {:error, reason}) do - conn - |> put_status(:bad_request) - |> json(reason) - end - - defp errors(conn, {:param_cast, _}) do - conn - |> put_status(:bad_request) - |> json(dgettext("errors", "Invalid parameters")) - end - - defp errors(conn, _) do - conn - |> put_status(:internal_server_error) - |> json(dgettext("errors", "Something went wrong")) - end - defp page_params(params) do {get_page(params["page"]), get_page_size(params["page_size"])} end diff --git a/lib/pleroma/web/admin_api/controllers/fallback_controller.ex b/lib/pleroma/web/admin_api/controllers/fallback_controller.ex new file mode 100644 index 000000000..9f7bb92ce --- /dev/null +++ b/lib/pleroma/web/admin_api/controllers/fallback_controller.ex @@ -0,0 +1,31 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.FallbackController do + use Pleroma.Web, :controller + + def call(conn, {:error, :not_found}) do + conn + |> put_status(:not_found) + |> json(dgettext("errors", "Not found")) + end + + def call(conn, {:error, reason}) do + conn + |> put_status(:bad_request) + |> json(reason) + end + + def call(conn, {:param_cast, _}) do + conn + |> put_status(:bad_request) + |> json(dgettext("errors", "Invalid parameters")) + end + + def call(conn, _) do + conn + |> put_status(:internal_server_error) + |> json(dgettext("errors", "Something went wrong")) + end +end diff --git a/lib/pleroma/web/admin_api/controllers/status_controller.ex b/lib/pleroma/web/admin_api/controllers/status_controller.ex new file mode 100644 index 000000000..1e9763979 --- /dev/null +++ b/lib/pleroma/web/admin_api/controllers/status_controller.ex @@ -0,0 +1,112 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.StatusController do + use Pleroma.Web, :controller + + alias Pleroma.Activity + alias Pleroma.ModerationLog + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.MastodonAPI + + require Logger + + @users_page_size 50 + + plug(OAuthScopesPlug, %{scopes: ["read:statuses"], admin: true} when action in [:index, :show]) + + plug( + OAuthScopesPlug, + %{scopes: ["write:statuses"], admin: true} when action in [:update, :delete] + ) + + action_fallback(Pleroma.Web.AdminAPI.FallbackController) + + def index(%{assigns: %{user: _admin}} = conn, params) do + godmode = params["godmode"] == "true" || params["godmode"] == true + local_only = params["local_only"] == "true" || params["local_only"] == true + with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true + {page, page_size} = page_params(params) + + activities = + ActivityPub.fetch_statuses(nil, %{ + "godmode" => godmode, + "local_only" => local_only, + "limit" => page_size, + "offset" => (page - 1) * page_size, + "exclude_reblogs" => !with_reblogs && "true" + }) + + render(conn, "index.json", %{activities: activities, as: :activity}) + end + + def show(conn, %{"id" => id}) do + with %Activity{} = activity <- Activity.get_by_id(id) do + conn + |> put_view(MastodonAPI.StatusView) + |> render("show.json", %{activity: activity}) + else + nil -> {:error, :not_found} + end + end + + def update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do + params = + params + |> Map.take(["sensitive", "visibility"]) + |> Map.new(fn {key, value} -> {String.to_existing_atom(key), value} end) + + with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do + {:ok, sensitive} = Ecto.Type.cast(:boolean, params[:sensitive]) + + ModerationLog.insert_log(%{ + action: "status_update", + actor: admin, + subject: activity, + sensitive: sensitive, + visibility: params[:visibility] + }) + + conn + |> put_view(MastodonAPI.StatusView) + |> render("show.json", %{activity: activity}) + end + end + + def delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do + ModerationLog.insert_log(%{ + action: "status_delete", + actor: user, + subject_id: id + }) + + json(conn, %{}) + end + end + + defp page_params(params) do + {get_page(params["page"]), get_page_size(params["page_size"])} + end + + defp get_page(page_string) when is_nil(page_string), do: 1 + + defp get_page(page_string) do + case Integer.parse(page_string) do + {page, _} -> page + :error -> 1 + end + end + + defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size + + defp get_page_size(page_size_string) do + case Integer.parse(page_size_string) do + {page_size, _} -> page_size + :error -> @users_page_size + end + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 4cacf6255..9e99ab218 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -189,10 +189,10 @@ defmodule Pleroma.Web.Router do post("/reports/:id/notes", AdminAPIController, :report_notes_create) delete("/reports/:report_id/notes/:id", AdminAPIController, :report_notes_delete) - get("/statuses/:id", AdminAPIController, :status_show) - put("/statuses/:id", AdminAPIController, :status_update) - delete("/statuses/:id", AdminAPIController, :status_delete) - get("/statuses", AdminAPIController, :list_statuses) + get("/statuses/:id", StatusController, :show) + put("/statuses/:id", StatusController, :update) + delete("/statuses/:id", StatusController, :delete) + get("/statuses", StatusController, :index) get("/config", AdminAPIController, :config_show) post("/config", AdminAPIController, :config_update) diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/controllers/admin_api_controller_test.exs similarity index 96% rename from test/web/admin_api/admin_api_controller_test.exs rename to test/web/admin_api/controllers/admin_api_controller_test.exs index 370d876d0..2c317e0fe 100644 --- a/test/web/admin_api/admin_api_controller_test.exs +++ b/test/web/admin_api/controllers/admin_api_controller_test.exs @@ -1697,115 +1697,6 @@ test "returns 403 when requested by anonymous" do end end - describe "GET /api/pleroma/admin/statuses/:id" do - test "not found", %{conn: conn} do - assert conn - |> get("/api/pleroma/admin/statuses/not_found") - |> json_response(:not_found) - end - - test "shows activity", %{conn: conn} do - activity = insert(:note_activity) - - response = - conn - |> get("/api/pleroma/admin/statuses/#{activity.id}") - |> json_response(200) - - assert response["id"] == activity.id - end - end - - describe "PUT /api/pleroma/admin/statuses/:id" do - setup do - activity = insert(:note_activity) - - %{id: activity.id} - end - - test "toggle sensitive flag", %{conn: conn, id: id, admin: admin} do - response = - conn - |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "true"}) - |> json_response(:ok) - - assert response["sensitive"] - - log_entry = Repo.one(ModerationLog) - - assert ModerationLog.get_log_entry_message(log_entry) == - "@#{admin.nickname} updated status ##{id}, set sensitive: 'true'" - - response = - conn - |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "false"}) - |> json_response(:ok) - - refute response["sensitive"] - end - - test "change visibility flag", %{conn: conn, id: id, admin: admin} do - response = - conn - |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "public"}) - |> json_response(:ok) - - assert response["visibility"] == "public" - - log_entry = Repo.one(ModerationLog) - - assert ModerationLog.get_log_entry_message(log_entry) == - "@#{admin.nickname} updated status ##{id}, set visibility: 'public'" - - response = - conn - |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "private"}) - |> json_response(:ok) - - assert response["visibility"] == "private" - - response = - conn - |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "unlisted"}) - |> json_response(:ok) - - assert response["visibility"] == "unlisted" - end - - test "returns 400 when visibility is unknown", %{conn: conn, id: id} do - conn = put(conn, "/api/pleroma/admin/statuses/#{id}", %{visibility: "test"}) - - assert json_response(conn, :bad_request) == "Unsupported visibility" - end - end - - describe "DELETE /api/pleroma/admin/statuses/:id" do - setup do - activity = insert(:note_activity) - - %{id: activity.id} - end - - test "deletes status", %{conn: conn, id: id, admin: admin} do - conn - |> delete("/api/pleroma/admin/statuses/#{id}") - |> json_response(:ok) - - refute Activity.get_by_id(id) - - log_entry = Repo.one(ModerationLog) - - assert ModerationLog.get_log_entry_message(log_entry) == - "@#{admin.nickname} deleted status ##{id}" - end - - test "returns 404 when the status does not exist", %{conn: conn} do - conn = delete(conn, "/api/pleroma/admin/statuses/test") - - assert json_response(conn, :not_found) == "Not found" - end - end - describe "GET /api/pleroma/admin/config" do setup do: clear_config(:configurable_from_database, true) @@ -2998,54 +2889,6 @@ test "need_reboot flag", %{conn: conn} do on_exit(fn -> Restarter.Pleroma.refresh() end) end - describe "GET /api/pleroma/admin/statuses" do - test "returns all public and unlisted statuses", %{conn: conn, admin: admin} do - blocked = insert(:user) - user = insert(:user) - User.block(admin, blocked) - - {:ok, _} = CommonAPI.post(user, %{status: "@#{admin.nickname}", visibility: "direct"}) - - {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"}) - {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "private"}) - {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "public"}) - {:ok, _} = CommonAPI.post(blocked, %{status: ".", visibility: "public"}) - - response = - conn - |> get("/api/pleroma/admin/statuses") - |> json_response(200) - - refute "private" in Enum.map(response, & &1["visibility"]) - assert length(response) == 3 - end - - test "returns only local statuses with local_only on", %{conn: conn} do - user = insert(:user) - remote_user = insert(:user, local: false, nickname: "archaeme@archae.me") - insert(:note_activity, user: user, local: true) - insert(:note_activity, user: remote_user, local: false) - - response = - conn - |> get("/api/pleroma/admin/statuses?local_only=true") - |> json_response(200) - - assert length(response) == 1 - end - - test "returns private and direct statuses with godmode on", %{conn: conn, admin: admin} do - user = insert(:user) - - {:ok, _} = CommonAPI.post(user, %{status: "@#{admin.nickname}", visibility: "direct"}) - - {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "private"}) - {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "public"}) - conn = get(conn, "/api/pleroma/admin/statuses?godmode=true") - assert json_response(conn, 200) |> length() == 3 - end - end - describe "GET /api/pleroma/admin/users/:nickname/statuses" do setup do user = insert(:user) diff --git a/test/web/admin_api/controllers/status_controller_test.exs b/test/web/admin_api/controllers/status_controller_test.exs new file mode 100644 index 000000000..8ecc78491 --- /dev/null +++ b/test/web/admin_api/controllers/status_controller_test.exs @@ -0,0 +1,185 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.StatusControllerTest do + use Pleroma.Web.ConnCase + + import Pleroma.Factory + + alias Pleroma.Activity + alias Pleroma.Config + alias Pleroma.ModerationLog + alias Pleroma.Repo + alias Pleroma.User + alias Pleroma.Web.CommonAPI + + setup do + admin = insert(:user, is_admin: true) + token = insert(:oauth_admin_token, user: admin) + + conn = + build_conn() + |> assign(:user, admin) + |> assign(:token, token) + + {:ok, %{admin: admin, token: token, conn: conn}} + end + + describe "GET /api/pleroma/admin/statuses/:id" do + test "not found", %{conn: conn} do + assert conn + |> get("/api/pleroma/admin/statuses/not_found") + |> json_response(:not_found) + end + + test "shows activity", %{conn: conn} do + activity = insert(:note_activity) + + response = + conn + |> get("/api/pleroma/admin/statuses/#{activity.id}") + |> json_response(200) + + assert response["id"] == activity.id + end + end + + describe "PUT /api/pleroma/admin/statuses/:id" do + setup do + activity = insert(:note_activity) + + %{id: activity.id} + end + + test "toggle sensitive flag", %{conn: conn, id: id, admin: admin} do + response = + conn + |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "true"}) + |> json_response(:ok) + + assert response["sensitive"] + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} updated status ##{id}, set sensitive: 'true'" + + response = + conn + |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "false"}) + |> json_response(:ok) + + refute response["sensitive"] + end + + test "change visibility flag", %{conn: conn, id: id, admin: admin} do + response = + conn + |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "public"}) + |> json_response(:ok) + + assert response["visibility"] == "public" + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} updated status ##{id}, set visibility: 'public'" + + response = + conn + |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "private"}) + |> json_response(:ok) + + assert response["visibility"] == "private" + + response = + conn + |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "unlisted"}) + |> json_response(:ok) + + assert response["visibility"] == "unlisted" + end + + test "returns 400 when visibility is unknown", %{conn: conn, id: id} do + conn = put(conn, "/api/pleroma/admin/statuses/#{id}", %{visibility: "test"}) + + assert json_response(conn, :bad_request) == "Unsupported visibility" + end + end + + describe "DELETE /api/pleroma/admin/statuses/:id" do + setup do + activity = insert(:note_activity) + + %{id: activity.id} + end + + test "deletes status", %{conn: conn, id: id, admin: admin} do + conn + |> delete("/api/pleroma/admin/statuses/#{id}") + |> json_response(:ok) + + refute Activity.get_by_id(id) + + log_entry = Repo.one(ModerationLog) + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} deleted status ##{id}" + end + + test "returns 404 when the status does not exist", %{conn: conn} do + conn = delete(conn, "/api/pleroma/admin/statuses/test") + + assert json_response(conn, :not_found) == "Not found" + end + end + + describe "GET /api/pleroma/admin/statuses" do + test "returns all public and unlisted statuses", %{conn: conn, admin: admin} do + blocked = insert(:user) + user = insert(:user) + User.block(admin, blocked) + + {:ok, _} = CommonAPI.post(user, %{status: "@#{admin.nickname}", visibility: "direct"}) + + {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"}) + {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "private"}) + {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "public"}) + {:ok, _} = CommonAPI.post(blocked, %{status: ".", visibility: "public"}) + + response = + conn + |> get("/api/pleroma/admin/statuses") + |> json_response(200) + + refute "private" in Enum.map(response, & &1["visibility"]) + assert length(response) == 3 + end + + test "returns only local statuses with local_only on", %{conn: conn} do + user = insert(:user) + remote_user = insert(:user, local: false, nickname: "archaeme@archae.me") + insert(:note_activity, user: user, local: true) + insert(:note_activity, user: remote_user, local: false) + + response = + conn + |> get("/api/pleroma/admin/statuses?local_only=true") + |> json_response(200) + + assert length(response) == 1 + end + + test "returns private and direct statuses with godmode on", %{conn: conn, admin: admin} do + user = insert(:user) + + {:ok, _} = CommonAPI.post(user, %{status: "@#{admin.nickname}", visibility: "direct"}) + + {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "private"}) + {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "public"}) + conn = get(conn, "/api/pleroma/admin/statuses?godmode=true") + assert json_response(conn, 200) |> length() == 3 + end + end +end