From 35e85734838e5318a8ca2c48fb0e67750621941b Mon Sep 17 00:00:00 2001 From: Maksim Pechnikov Date: Wed, 14 Oct 2020 09:26:05 +0300 Subject: [PATCH] added `/api/pleroma/admin/users/tag` --- CHANGELOG.md | 5 + docs/development/API/admin_api.md | 13 ++ lib/pleroma/user.ex | 9 ++ lib/pleroma/web/activity_pub/mrf/tag_policy.ex | 11 ++ .../admin_api/controllers/admin_api_controller.ex | 28 ---- .../web/admin_api/controllers/tag_controller.ex | 62 +++++++++ lib/pleroma/web/router.ex | 5 +- .../admin_api/controllers/tag_controller_test.exs | 150 +++++++++++++++++++++ 8 files changed, 253 insertions(+), 30 deletions(-) create mode 100644 lib/pleroma/web/admin_api/controllers/tag_controller.ex create mode 100644 test/web/admin_api/controllers/tag_controller_test.exs diff --git a/CHANGELOG.md b/CHANGELOG.md index a1fa22398..301c4f2aa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -88,6 +88,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: `/api/v1/accounts/:id` & `/api/v1/mutes` endpoints accept `with_relationships` parameter and return filled `pleroma.relationship` field. - Mastodon API: Endpoint to remove a conversation (`DELETE /api/v1/conversations/:id`). - Mastodon API: `expires_in` in the scheduled post `params` field on `/api/v1/statuses` and `/api/v1/scheduled_statuses/:id` endpoints. +- Add `GET /api/pleroma/admin/users/tag` - returns a list of users tags. ### Fixed @@ -152,6 +153,10 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Security - Fixed the possibility of using file uploads to spoof posts. +======= +- Mix tasks for controlling user account confirmation status in bulk (`mix pleroma.user confirm_all` and `mix pleroma.user unconfirm_all`) +- Mix task for sending confirmation emails to all unconfirmed users (`mix pleroma.email send_confirmation_mails`) +- Mix task option for force-unfollowing relays ### Changed diff --git a/docs/development/API/admin_api.md b/docs/development/API/admin_api.md index 8f855d251..63e287b88 100644 --- a/docs/development/API/admin_api.md +++ b/docs/development/API/admin_api.md @@ -119,6 +119,19 @@ The `/api/v1/pleroma/admin/*` path is backwards compatible with `/api/pleroma/ad } ``` +## `GET /api/v1/pleroma/admin/users/tag` + +### List tags + +- Params: None + +- Response: + +``` json +["verify", "mrf_tag:media-force-nsfw"] + +``` + ## `PUT /api/v1/pleroma/admin/users/tag` ### Tag a list of users diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index c1aa0f716..5cbe5bb75 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -1259,6 +1259,15 @@ defmodule Pleroma.User do |> Repo.all() end + @spec list_tags() :: list(String.t()) + def list_tags do + from( + u in __MODULE__, + select: type(fragment("DISTINCT unnest(?)", u.tags), :string) + ) + |> Repo.all() + end + def increase_note_count(%User{} = user) do User |> where(id: ^user.id) diff --git a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex index 528093ac0..316ea0368 100644 --- a/lib/pleroma/web/activity_pub/mrf/tag_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/tag_policy.ex @@ -21,6 +21,17 @@ defmodule Pleroma.Web.ActivityPub.MRF.TagPolicy do require Pleroma.Constants + def policy_tags do + [ + "mrf_tag:media-force-nsfw", + "mrf_tag:media-strip", + "mrf_tag:force-unlisted", + "mrf_tag:sandbox", + "mrf_tag:disable-remote-subscription", + "mrf_tag:disable-any-subscription" + ] + end + defp get_tags(%User{tags: tags}) when is_list(tags), do: tags defp get_tags(_), do: [] diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex index 839ac1a8d..7f06e11ed 100644 --- a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -35,8 +35,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do when action in [ :get_password_reset, :force_password_reset, - :tag_users, - :untag_users, :right_add, :right_add_multiple, :right_delete, @@ -138,32 +136,6 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end end - def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do - with {:ok, _} <- User.tag(nicknames, tags) do - ModerationLog.insert_log(%{ - actor: admin, - nicknames: nicknames, - tags: tags, - action: "tag" - }) - - json_response(conn, :no_content, "") - end - end - - def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do - with {:ok, _} <- User.untag(nicknames, tags) do - ModerationLog.insert_log(%{ - actor: admin, - nicknames: nicknames, - tags: tags, - action: "untag" - }) - - json_response(conn, :no_content, "") - end - end - def right_add_multiple(%{assigns: %{user: admin}} = conn, %{ "permission_group" => permission_group, "nicknames" => nicknames diff --git a/lib/pleroma/web/admin_api/controllers/tag_controller.ex b/lib/pleroma/web/admin_api/controllers/tag_controller.ex new file mode 100644 index 000000000..9419bf31e --- /dev/null +++ b/lib/pleroma/web/admin_api/controllers/tag_controller.ex @@ -0,0 +1,62 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.TagController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [json_response: 3] + + alias Pleroma.ModerationLog + alias Pleroma.User + alias Pleroma.Web.AdminAPI + alias Pleroma.Web.Plugs.OAuthScopesPlug + + plug( + OAuthScopesPlug, + %{scopes: ["write:accounts"], admin: true} when action in [:tag, :untag] + ) + + plug( + OAuthScopesPlug, + %{scopes: ["read:accounts"], admin: true} when action in [:list] + ) + + action_fallback(AdminAPI.FallbackController) + + def list(%{assigns: %{user: _admin}} = conn, _) do + tags = + Pleroma.User.list_tags() + |> Kernel.++(Pleroma.Web.ActivityPub.MRF.TagPolicy.policy_tags()) + |> Enum.uniq() + |> Enum.sort() + + json(conn, tags) + end + + def tag(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do + with {:ok, _} <- User.tag(nicknames, tags) do + ModerationLog.insert_log(%{ + actor: admin, + nicknames: nicknames, + tags: tags, + action: "tag" + }) + + json_response(conn, :no_content, "") + end + end + + def untag(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do + with {:ok, _} <- User.untag(nicknames, tags) do + ModerationLog.insert_log(%{ + actor: admin, + nicknames: nicknames, + tags: tags, + action: "untag" + }) + + json_response(conn, :no_content, "") + end + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index de0bd27d7..7410b29b6 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -159,8 +159,9 @@ defmodule Pleroma.Web.Router do pipe_through(:admin_api) put("/users/disable_mfa", AdminAPIController, :disable_mfa) - put("/users/tag", AdminAPIController, :tag_users) - delete("/users/tag", AdminAPIController, :untag_users) + get("/users/tag", TagController, :list) + put("/users/tag", TagController, :tag) + delete("/users/tag", TagController, :untag) get("/users/:nickname/permission_group", AdminAPIController, :right_get) get("/users/:nickname/permission_group/:permission_group", AdminAPIController, :right_get) diff --git a/test/web/admin_api/controllers/tag_controller_test.exs b/test/web/admin_api/controllers/tag_controller_test.exs new file mode 100644 index 000000000..671bda53a --- /dev/null +++ b/test/web/admin_api/controllers/tag_controller_test.exs @@ -0,0 +1,150 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.TagControllerTest do + use Pleroma.Web.ConnCase + use Oban.Testing, repo: Pleroma.Repo + + import Pleroma.Factory + + alias Pleroma.ModerationLog + alias Pleroma.Repo + alias Pleroma.User + + setup_all do + Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end) + + :ok + end + + 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/users/tag" do + test "it returns user tags and mrf policy tags", %{conn: conn} do + insert(:user, %{tags: ["x"]}) + insert(:user, %{tags: ["y"]}) + insert(:user, %{tags: ["unchanged"]}) + + response = + conn + |> put_req_header("accept", "application/json") + |> get("/api/pleroma/admin/users/tag") + |> json_response(200) + + assert [ + "mrf_tag:disable-any-subscription", + "mrf_tag:disable-remote-subscription", + "mrf_tag:force-unlisted", + "mrf_tag:media-force-nsfw", + "mrf_tag:media-strip", + "mrf_tag:sandbox", + "unchanged", + "x", + "y" + ] == response + end + end + + describe "PUT /api/pleroma/admin/users/tag" do + setup %{conn: conn} do + user1 = insert(:user, %{tags: ["x"]}) + user2 = insert(:user, %{tags: ["y"]}) + user3 = insert(:user, %{tags: ["unchanged"]}) + + conn = + conn + |> put_req_header("accept", "application/json") + |> put( + "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <> + "#{user2.nickname}&tags[]=foo&tags[]=bar" + ) + + %{conn: conn, user1: user1, user2: user2, user3: user3} + end + + test "it appends specified tags to users with specified nicknames", %{ + conn: conn, + admin: admin, + user1: user1, + user2: user2 + } do + assert empty_json_response(conn) + assert User.get_cached_by_id(user1.id).tags == ["x", "foo", "bar"] + assert User.get_cached_by_id(user2.id).tags == ["y", "foo", "bar"] + + log_entry = Repo.one(ModerationLog) + + users = + [user1.nickname, user2.nickname] + |> Enum.map(&"@#{&1}") + |> Enum.join(", ") + + tags = ["foo", "bar"] |> Enum.join(", ") + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} added tags: #{tags} to users: #{users}" + end + + test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do + assert empty_json_response(conn) + assert User.get_cached_by_id(user3.id).tags == ["unchanged"] + end + end + + describe "DELETE /api/pleroma/admin/users/tag" do + setup %{conn: conn} do + user1 = insert(:user, %{tags: ["x"]}) + user2 = insert(:user, %{tags: ["y", "z"]}) + user3 = insert(:user, %{tags: ["unchanged"]}) + + conn = + conn + |> put_req_header("accept", "application/json") + |> delete( + "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <> + "#{user2.nickname}&tags[]=x&tags[]=z" + ) + + %{conn: conn, user1: user1, user2: user2, user3: user3} + end + + test "it removes specified tags from users with specified nicknames", %{ + conn: conn, + admin: admin, + user1: user1, + user2: user2 + } do + assert empty_json_response(conn) + assert User.get_cached_by_id(user1.id).tags == [] + assert User.get_cached_by_id(user2.id).tags == ["y"] + + log_entry = Repo.one(ModerationLog) + + users = + [user1.nickname, user2.nickname] + |> Enum.map(&"@#{&1}") + |> Enum.join(", ") + + tags = ["x", "z"] |> Enum.join(", ") + + assert ModerationLog.get_log_entry_message(log_entry) == + "@#{admin.nickname} removed tags: #{tags} from users: #{users}" + end + + test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do + assert empty_json_response(conn) + assert User.get_cached_by_id(user3.id).tags == ["unchanged"] + end + end +end