1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
6 use Pleroma.Web.ConnCase
7 use Oban.Testing, repo: Pleroma.Repo
9 import ExUnit.CaptureLog
11 import Pleroma.Factory
13 alias Pleroma.Activity
17 alias Pleroma.ModerationLog
19 alias Pleroma.Tests.ObanHelpers
22 alias Pleroma.Web.ActivityPub.Relay
23 alias Pleroma.Web.CommonAPI
24 alias Pleroma.Web.MediaProxy
27 Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
33 admin = insert(:user, is_admin: true)
34 token = insert(:oauth_admin_token, user: admin)
38 |> assign(:user, admin)
39 |> assign(:token, token)
41 {:ok, %{admin: admin, token: token, conn: conn}}
44 describe "with [:auth, :enforce_oauth_admin_scope_usage]," do
45 setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], true)
47 test "GET /api/pleroma/admin/users/:nickname requires admin:read:accounts or broader scope",
50 url = "/api/pleroma/admin/users/#{user.nickname}"
52 good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"])
53 good_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read"])
54 good_token3 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts"])
56 bad_token1 = insert(:oauth_token, user: admin, scopes: ["read:accounts"])
57 bad_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts:partial"])
60 for good_token <- [good_token1, good_token2, good_token3] do
63 |> assign(:user, admin)
64 |> assign(:token, good_token)
67 assert json_response(conn, 200)
70 for good_token <- [good_token1, good_token2, good_token3] do
74 |> assign(:token, good_token)
77 assert json_response(conn, :forbidden)
80 for bad_token <- [bad_token1, bad_token2, bad_token3] do
83 |> assign(:user, admin)
84 |> assign(:token, bad_token)
87 assert json_response(conn, :forbidden)
92 describe "unless [:auth, :enforce_oauth_admin_scope_usage]," do
93 setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], false)
95 test "GET /api/pleroma/admin/users/:nickname requires " <>
96 "read:accounts or admin:read:accounts or broader scope",
99 url = "/api/pleroma/admin/users/#{user.nickname}"
101 good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"])
102 good_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read"])
103 good_token3 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts"])
104 good_token4 = insert(:oauth_token, user: admin, scopes: ["read:accounts"])
105 good_token5 = insert(:oauth_token, user: admin, scopes: ["read"])
107 good_tokens = [good_token1, good_token2, good_token3, good_token4, good_token5]
109 bad_token1 = insert(:oauth_token, user: admin, scopes: ["read:accounts:partial"])
110 bad_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts:partial"])
113 for good_token <- good_tokens do
116 |> assign(:user, admin)
117 |> assign(:token, good_token)
120 assert json_response(conn, 200)
123 for good_token <- good_tokens do
126 |> assign(:user, nil)
127 |> assign(:token, good_token)
130 assert json_response(conn, :forbidden)
133 for bad_token <- [bad_token1, bad_token2, bad_token3] do
136 |> assign(:user, admin)
137 |> assign(:token, bad_token)
140 assert json_response(conn, :forbidden)
145 describe "DELETE /api/pleroma/admin/users" do
146 test "single user", %{admin: admin, conn: conn} do
148 clear_config([:instance, :federating], true)
150 with_mock Pleroma.Web.Federator,
151 publish: fn _ -> nil end do
154 |> put_req_header("accept", "application/json")
155 |> delete("/api/pleroma/admin/users?nickname=#{user.nickname}")
157 ObanHelpers.perform_all()
159 assert User.get_by_nickname(user.nickname).deactivated
161 log_entry = Repo.one(ModerationLog)
163 assert ModerationLog.get_log_entry_message(log_entry) ==
164 "@#{admin.nickname} deleted users: @#{user.nickname}"
166 assert json_response(conn, 200) == [user.nickname]
168 assert called(Pleroma.Web.Federator.publish(:_))
172 test "multiple users", %{admin: admin, conn: conn} do
173 user_one = insert(:user)
174 user_two = insert(:user)
178 |> put_req_header("accept", "application/json")
179 |> delete("/api/pleroma/admin/users", %{
180 nicknames: [user_one.nickname, user_two.nickname]
183 log_entry = Repo.one(ModerationLog)
185 assert ModerationLog.get_log_entry_message(log_entry) ==
186 "@#{admin.nickname} deleted users: @#{user_one.nickname}, @#{user_two.nickname}"
188 response = json_response(conn, 200)
189 assert response -- [user_one.nickname, user_two.nickname] == []
193 describe "/api/pleroma/admin/users" do
194 test "Create", %{conn: conn} do
197 |> put_req_header("accept", "application/json")
198 |> post("/api/pleroma/admin/users", %{
201 "nickname" => "lain",
202 "email" => "lain@example.org",
206 "nickname" => "lain2",
207 "email" => "lain2@example.org",
213 response = json_response(conn, 200) |> Enum.map(&Map.get(&1, "type"))
214 assert response == ["success", "success"]
216 log_entry = Repo.one(ModerationLog)
218 assert ["lain", "lain2"] -- Enum.map(log_entry.data["subjects"], & &1["nickname"]) == []
221 test "Cannot create user with existing email", %{conn: conn} do
226 |> put_req_header("accept", "application/json")
227 |> post("/api/pleroma/admin/users", %{
230 "nickname" => "lain",
231 "email" => user.email,
237 assert json_response(conn, 409) == [
241 "email" => user.email,
244 "error" => "email has already been taken",
250 test "Cannot create user with existing nickname", %{conn: conn} do
255 |> put_req_header("accept", "application/json")
256 |> post("/api/pleroma/admin/users", %{
259 "nickname" => user.nickname,
260 "email" => "someuser@plerama.social",
266 assert json_response(conn, 409) == [
270 "email" => "someuser@plerama.social",
271 "nickname" => user.nickname
273 "error" => "nickname has already been taken",
279 test "Multiple user creation works in transaction", %{conn: conn} do
284 |> put_req_header("accept", "application/json")
285 |> post("/api/pleroma/admin/users", %{
288 "nickname" => "newuser",
289 "email" => "newuser@pleroma.social",
293 "nickname" => "lain",
294 "email" => user.email,
300 assert json_response(conn, 409) == [
304 "email" => user.email,
307 "error" => "email has already been taken",
313 "email" => "newuser@pleroma.social",
314 "nickname" => "newuser"
321 assert User.get_by_nickname("newuser") === nil
325 describe "/api/pleroma/admin/users/:nickname" do
326 test "Show", %{conn: conn} do
329 conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")
332 "deactivated" => false,
333 "id" => to_string(user.id),
335 "nickname" => user.nickname,
336 "roles" => %{"admin" => false, "moderator" => false},
338 "avatar" => User.avatar_url(user) |> MediaProxy.url(),
339 "display_name" => HTML.strip_tags(user.name || user.nickname),
340 "confirmation_pending" => false
343 assert expected == json_response(conn, 200)
346 test "when the user doesn't exist", %{conn: conn} do
349 conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")
351 assert %{"error" => "Not found"} == json_response(conn, 404)
355 describe "/api/pleroma/admin/users/follow" do
356 test "allows to force-follow another user", %{admin: admin, conn: conn} do
358 follower = insert(:user)
361 |> put_req_header("accept", "application/json")
362 |> post("/api/pleroma/admin/users/follow", %{
363 "follower" => follower.nickname,
364 "followed" => user.nickname
367 user = User.get_cached_by_id(user.id)
368 follower = User.get_cached_by_id(follower.id)
370 assert User.following?(follower, user)
372 log_entry = Repo.one(ModerationLog)
374 assert ModerationLog.get_log_entry_message(log_entry) ==
375 "@#{admin.nickname} made @#{follower.nickname} follow @#{user.nickname}"
379 describe "/api/pleroma/admin/users/unfollow" do
380 test "allows to force-unfollow another user", %{admin: admin, conn: conn} do
382 follower = insert(:user)
384 User.follow(follower, user)
387 |> put_req_header("accept", "application/json")
388 |> post("/api/pleroma/admin/users/unfollow", %{
389 "follower" => follower.nickname,
390 "followed" => user.nickname
393 user = User.get_cached_by_id(user.id)
394 follower = User.get_cached_by_id(follower.id)
396 refute User.following?(follower, user)
398 log_entry = Repo.one(ModerationLog)
400 assert ModerationLog.get_log_entry_message(log_entry) ==
401 "@#{admin.nickname} made @#{follower.nickname} unfollow @#{user.nickname}"
405 describe "PUT /api/pleroma/admin/users/tag" do
406 setup %{conn: conn} do
407 user1 = insert(:user, %{tags: ["x"]})
408 user2 = insert(:user, %{tags: ["y"]})
409 user3 = insert(:user, %{tags: ["unchanged"]})
413 |> put_req_header("accept", "application/json")
415 "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <>
416 "#{user2.nickname}&tags[]=foo&tags[]=bar"
419 %{conn: conn, user1: user1, user2: user2, user3: user3}
422 test "it appends specified tags to users with specified nicknames", %{
428 assert json_response(conn, :no_content)
429 assert User.get_cached_by_id(user1.id).tags == ["x", "foo", "bar"]
430 assert User.get_cached_by_id(user2.id).tags == ["y", "foo", "bar"]
432 log_entry = Repo.one(ModerationLog)
435 [user1.nickname, user2.nickname]
436 |> Enum.map(&"@#{&1}")
439 tags = ["foo", "bar"] |> Enum.join(", ")
441 assert ModerationLog.get_log_entry_message(log_entry) ==
442 "@#{admin.nickname} added tags: #{tags} to users: #{users}"
445 test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
446 assert json_response(conn, :no_content)
447 assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
451 describe "DELETE /api/pleroma/admin/users/tag" do
452 setup %{conn: conn} do
453 user1 = insert(:user, %{tags: ["x"]})
454 user2 = insert(:user, %{tags: ["y", "z"]})
455 user3 = insert(:user, %{tags: ["unchanged"]})
459 |> put_req_header("accept", "application/json")
461 "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <>
462 "#{user2.nickname}&tags[]=x&tags[]=z"
465 %{conn: conn, user1: user1, user2: user2, user3: user3}
468 test "it removes specified tags from users with specified nicknames", %{
474 assert json_response(conn, :no_content)
475 assert User.get_cached_by_id(user1.id).tags == []
476 assert User.get_cached_by_id(user2.id).tags == ["y"]
478 log_entry = Repo.one(ModerationLog)
481 [user1.nickname, user2.nickname]
482 |> Enum.map(&"@#{&1}")
485 tags = ["x", "z"] |> Enum.join(", ")
487 assert ModerationLog.get_log_entry_message(log_entry) ==
488 "@#{admin.nickname} removed tags: #{tags} from users: #{users}"
491 test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
492 assert json_response(conn, :no_content)
493 assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
497 describe "/api/pleroma/admin/users/:nickname/permission_group" do
498 test "GET is giving user_info", %{admin: admin, conn: conn} do
501 |> put_req_header("accept", "application/json")
502 |> get("/api/pleroma/admin/users/#{admin.nickname}/permission_group/")
504 assert json_response(conn, 200) == %{
506 "is_moderator" => false
510 test "/:right POST, can add to a permission group", %{admin: admin, conn: conn} do
515 |> put_req_header("accept", "application/json")
516 |> post("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin")
518 assert json_response(conn, 200) == %{
522 log_entry = Repo.one(ModerationLog)
524 assert ModerationLog.get_log_entry_message(log_entry) ==
525 "@#{admin.nickname} made @#{user.nickname} admin"
528 test "/:right POST, can add to a permission group (multiple)", %{admin: admin, conn: conn} do
529 user_one = insert(:user)
530 user_two = insert(:user)
534 |> put_req_header("accept", "application/json")
535 |> post("/api/pleroma/admin/users/permission_group/admin", %{
536 nicknames: [user_one.nickname, user_two.nickname]
539 assert json_response(conn, 200) == %{"is_admin" => true}
541 log_entry = Repo.one(ModerationLog)
543 assert ModerationLog.get_log_entry_message(log_entry) ==
544 "@#{admin.nickname} made @#{user_one.nickname}, @#{user_two.nickname} admin"
547 test "/:right DELETE, can remove from a permission group", %{admin: admin, conn: conn} do
548 user = insert(:user, is_admin: true)
552 |> put_req_header("accept", "application/json")
553 |> delete("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin")
555 assert json_response(conn, 200) == %{"is_admin" => false}
557 log_entry = Repo.one(ModerationLog)
559 assert ModerationLog.get_log_entry_message(log_entry) ==
560 "@#{admin.nickname} revoked admin role from @#{user.nickname}"
563 test "/:right DELETE, can remove from a permission group (multiple)", %{
567 user_one = insert(:user, is_admin: true)
568 user_two = insert(:user, is_admin: true)
572 |> put_req_header("accept", "application/json")
573 |> delete("/api/pleroma/admin/users/permission_group/admin", %{
574 nicknames: [user_one.nickname, user_two.nickname]
577 assert json_response(conn, 200) == %{"is_admin" => false}
579 log_entry = Repo.one(ModerationLog)
581 assert ModerationLog.get_log_entry_message(log_entry) ==
582 "@#{admin.nickname} revoked admin role from @#{user_one.nickname}, @#{
588 test "/api/pleroma/admin/users/:nickname/password_reset", %{conn: conn} do
593 |> put_req_header("accept", "application/json")
594 |> get("/api/pleroma/admin/users/#{user.nickname}/password_reset")
596 resp = json_response(conn, 200)
598 assert Regex.match?(~r/(http:\/\/|https:\/\/)/, resp["link"])
601 describe "GET /api/pleroma/admin/users" do
602 test "renders users array for the first page", %{conn: conn, admin: admin} do
603 user = insert(:user, local: false, tags: ["foo", "bar"])
604 conn = get(conn, "/api/pleroma/admin/users?page=1")
609 "deactivated" => admin.deactivated,
611 "nickname" => admin.nickname,
612 "roles" => %{"admin" => true, "moderator" => false},
615 "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
616 "display_name" => HTML.strip_tags(admin.name || admin.nickname),
617 "confirmation_pending" => false
620 "deactivated" => user.deactivated,
622 "nickname" => user.nickname,
623 "roles" => %{"admin" => false, "moderator" => false},
625 "tags" => ["foo", "bar"],
626 "avatar" => User.avatar_url(user) |> MediaProxy.url(),
627 "display_name" => HTML.strip_tags(user.name || user.nickname),
628 "confirmation_pending" => false
631 |> Enum.sort_by(& &1["nickname"])
633 assert json_response(conn, 200) == %{
640 test "pagination works correctly with service users", %{conn: conn} do
641 service1 = User.get_or_create_service_actor_by_ap_id(Web.base_url() <> "/meido", "meido")
643 insert_list(25, :user)
645 assert %{"count" => 26, "page_size" => 10, "users" => users1} =
647 |> get("/api/pleroma/admin/users?page=1&filters=", %{page_size: "10"})
648 |> json_response(200)
650 assert Enum.count(users1) == 10
651 assert service1 not in users1
653 assert %{"count" => 26, "page_size" => 10, "users" => users2} =
655 |> get("/api/pleroma/admin/users?page=2&filters=", %{page_size: "10"})
656 |> json_response(200)
658 assert Enum.count(users2) == 10
659 assert service1 not in users2
661 assert %{"count" => 26, "page_size" => 10, "users" => users3} =
663 |> get("/api/pleroma/admin/users?page=3&filters=", %{page_size: "10"})
664 |> json_response(200)
666 assert Enum.count(users3) == 6
667 assert service1 not in users3
670 test "renders empty array for the second page", %{conn: conn} do
673 conn = get(conn, "/api/pleroma/admin/users?page=2")
675 assert json_response(conn, 200) == %{
682 test "regular search", %{conn: conn} do
683 user = insert(:user, nickname: "bob")
685 conn = get(conn, "/api/pleroma/admin/users?query=bo")
687 assert json_response(conn, 200) == %{
692 "deactivated" => user.deactivated,
694 "nickname" => user.nickname,
695 "roles" => %{"admin" => false, "moderator" => false},
698 "avatar" => User.avatar_url(user) |> MediaProxy.url(),
699 "display_name" => HTML.strip_tags(user.name || user.nickname),
700 "confirmation_pending" => false
706 test "search by domain", %{conn: conn} do
707 user = insert(:user, nickname: "nickname@domain.com")
710 conn = get(conn, "/api/pleroma/admin/users?query=domain.com")
712 assert json_response(conn, 200) == %{
717 "deactivated" => user.deactivated,
719 "nickname" => user.nickname,
720 "roles" => %{"admin" => false, "moderator" => false},
723 "avatar" => User.avatar_url(user) |> MediaProxy.url(),
724 "display_name" => HTML.strip_tags(user.name || user.nickname),
725 "confirmation_pending" => false
731 test "search by full nickname", %{conn: conn} do
732 user = insert(:user, nickname: "nickname@domain.com")
735 conn = get(conn, "/api/pleroma/admin/users?query=nickname@domain.com")
737 assert json_response(conn, 200) == %{
742 "deactivated" => user.deactivated,
744 "nickname" => user.nickname,
745 "roles" => %{"admin" => false, "moderator" => false},
748 "avatar" => User.avatar_url(user) |> MediaProxy.url(),
749 "display_name" => HTML.strip_tags(user.name || user.nickname),
750 "confirmation_pending" => false
756 test "search by display name", %{conn: conn} do
757 user = insert(:user, name: "Display name")
760 conn = get(conn, "/api/pleroma/admin/users?name=display")
762 assert json_response(conn, 200) == %{
767 "deactivated" => user.deactivated,
769 "nickname" => user.nickname,
770 "roles" => %{"admin" => false, "moderator" => false},
773 "avatar" => User.avatar_url(user) |> MediaProxy.url(),
774 "display_name" => HTML.strip_tags(user.name || user.nickname),
775 "confirmation_pending" => false
781 test "search by email", %{conn: conn} do
782 user = insert(:user, email: "email@example.com")
785 conn = get(conn, "/api/pleroma/admin/users?email=email@example.com")
787 assert json_response(conn, 200) == %{
792 "deactivated" => user.deactivated,
794 "nickname" => user.nickname,
795 "roles" => %{"admin" => false, "moderator" => false},
798 "avatar" => User.avatar_url(user) |> MediaProxy.url(),
799 "display_name" => HTML.strip_tags(user.name || user.nickname),
800 "confirmation_pending" => false
806 test "regular search with page size", %{conn: conn} do
807 user = insert(:user, nickname: "aalice")
808 user2 = insert(:user, nickname: "alice")
810 conn1 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=1")
812 assert json_response(conn1, 200) == %{
817 "deactivated" => user.deactivated,
819 "nickname" => user.nickname,
820 "roles" => %{"admin" => false, "moderator" => false},
823 "avatar" => User.avatar_url(user) |> MediaProxy.url(),
824 "display_name" => HTML.strip_tags(user.name || user.nickname),
825 "confirmation_pending" => false
830 conn2 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=2")
832 assert json_response(conn2, 200) == %{
837 "deactivated" => user2.deactivated,
839 "nickname" => user2.nickname,
840 "roles" => %{"admin" => false, "moderator" => false},
843 "avatar" => User.avatar_url(user2) |> MediaProxy.url(),
844 "display_name" => HTML.strip_tags(user2.name || user2.nickname),
845 "confirmation_pending" => false
851 test "only local users" do
852 admin = insert(:user, is_admin: true, nickname: "john")
853 token = insert(:oauth_admin_token, user: admin)
854 user = insert(:user, nickname: "bob")
856 insert(:user, nickname: "bobb", local: false)
860 |> assign(:user, admin)
861 |> assign(:token, token)
862 |> get("/api/pleroma/admin/users?query=bo&filters=local")
864 assert json_response(conn, 200) == %{
869 "deactivated" => user.deactivated,
871 "nickname" => user.nickname,
872 "roles" => %{"admin" => false, "moderator" => false},
875 "avatar" => User.avatar_url(user) |> MediaProxy.url(),
876 "display_name" => HTML.strip_tags(user.name || user.nickname),
877 "confirmation_pending" => false
883 test "only local users with no query", %{conn: conn, admin: old_admin} do
884 admin = insert(:user, is_admin: true, nickname: "john")
885 user = insert(:user, nickname: "bob")
887 insert(:user, nickname: "bobb", local: false)
889 conn = get(conn, "/api/pleroma/admin/users?filters=local")
894 "deactivated" => user.deactivated,
896 "nickname" => user.nickname,
897 "roles" => %{"admin" => false, "moderator" => false},
900 "avatar" => User.avatar_url(user) |> MediaProxy.url(),
901 "display_name" => HTML.strip_tags(user.name || user.nickname),
902 "confirmation_pending" => false
905 "deactivated" => admin.deactivated,
907 "nickname" => admin.nickname,
908 "roles" => %{"admin" => true, "moderator" => false},
911 "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
912 "display_name" => HTML.strip_tags(admin.name || admin.nickname),
913 "confirmation_pending" => false
916 "deactivated" => false,
917 "id" => old_admin.id,
919 "nickname" => old_admin.nickname,
920 "roles" => %{"admin" => true, "moderator" => false},
922 "avatar" => User.avatar_url(old_admin) |> MediaProxy.url(),
923 "display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname),
924 "confirmation_pending" => false
927 |> Enum.sort_by(& &1["nickname"])
929 assert json_response(conn, 200) == %{
936 test "load only admins", %{conn: conn, admin: admin} do
937 second_admin = insert(:user, is_admin: true)
941 conn = get(conn, "/api/pleroma/admin/users?filters=is_admin")
946 "deactivated" => false,
948 "nickname" => admin.nickname,
949 "roles" => %{"admin" => true, "moderator" => false},
950 "local" => admin.local,
952 "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
953 "display_name" => HTML.strip_tags(admin.name || admin.nickname),
954 "confirmation_pending" => false
957 "deactivated" => false,
958 "id" => second_admin.id,
959 "nickname" => second_admin.nickname,
960 "roles" => %{"admin" => true, "moderator" => false},
961 "local" => second_admin.local,
963 "avatar" => User.avatar_url(second_admin) |> MediaProxy.url(),
964 "display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname),
965 "confirmation_pending" => false
968 |> Enum.sort_by(& &1["nickname"])
970 assert json_response(conn, 200) == %{
977 test "load only moderators", %{conn: conn} do
978 moderator = insert(:user, is_moderator: true)
982 conn = get(conn, "/api/pleroma/admin/users?filters=is_moderator")
984 assert json_response(conn, 200) == %{
989 "deactivated" => false,
990 "id" => moderator.id,
991 "nickname" => moderator.nickname,
992 "roles" => %{"admin" => false, "moderator" => true},
993 "local" => moderator.local,
995 "avatar" => User.avatar_url(moderator) |> MediaProxy.url(),
996 "display_name" => HTML.strip_tags(moderator.name || moderator.nickname),
997 "confirmation_pending" => false
1003 test "load users with tags list", %{conn: conn} do
1004 user1 = insert(:user, tags: ["first"])
1005 user2 = insert(:user, tags: ["second"])
1009 conn = get(conn, "/api/pleroma/admin/users?tags[]=first&tags[]=second")
1014 "deactivated" => false,
1016 "nickname" => user1.nickname,
1017 "roles" => %{"admin" => false, "moderator" => false},
1018 "local" => user1.local,
1019 "tags" => ["first"],
1020 "avatar" => User.avatar_url(user1) |> MediaProxy.url(),
1021 "display_name" => HTML.strip_tags(user1.name || user1.nickname),
1022 "confirmation_pending" => false
1025 "deactivated" => false,
1027 "nickname" => user2.nickname,
1028 "roles" => %{"admin" => false, "moderator" => false},
1029 "local" => user2.local,
1030 "tags" => ["second"],
1031 "avatar" => User.avatar_url(user2) |> MediaProxy.url(),
1032 "display_name" => HTML.strip_tags(user2.name || user2.nickname),
1033 "confirmation_pending" => false
1036 |> Enum.sort_by(& &1["nickname"])
1038 assert json_response(conn, 200) == %{
1045 test "it works with multiple filters" do
1046 admin = insert(:user, nickname: "john", is_admin: true)
1047 token = insert(:oauth_admin_token, user: admin)
1048 user = insert(:user, nickname: "bob", local: false, deactivated: true)
1050 insert(:user, nickname: "ken", local: true, deactivated: true)
1051 insert(:user, nickname: "bobb", local: false, deactivated: false)
1055 |> assign(:user, admin)
1056 |> assign(:token, token)
1057 |> get("/api/pleroma/admin/users?filters=deactivated,external")
1059 assert json_response(conn, 200) == %{
1064 "deactivated" => user.deactivated,
1066 "nickname" => user.nickname,
1067 "roles" => %{"admin" => false, "moderator" => false},
1068 "local" => user.local,
1070 "avatar" => User.avatar_url(user) |> MediaProxy.url(),
1071 "display_name" => HTML.strip_tags(user.name || user.nickname),
1072 "confirmation_pending" => false
1078 test "it omits relay user", %{admin: admin, conn: conn} do
1079 assert %User{} = Relay.get_actor()
1081 conn = get(conn, "/api/pleroma/admin/users")
1083 assert json_response(conn, 200) == %{
1088 "deactivated" => admin.deactivated,
1090 "nickname" => admin.nickname,
1091 "roles" => %{"admin" => true, "moderator" => false},
1094 "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
1095 "display_name" => HTML.strip_tags(admin.name || admin.nickname),
1096 "confirmation_pending" => false
1103 test "PATCH /api/pleroma/admin/users/activate", %{admin: admin, conn: conn} do
1104 user_one = insert(:user, deactivated: true)
1105 user_two = insert(:user, deactivated: true)
1110 "/api/pleroma/admin/users/activate",
1111 %{nicknames: [user_one.nickname, user_two.nickname]}
1114 response = json_response(conn, 200)
1115 assert Enum.map(response["users"], & &1["deactivated"]) == [false, false]
1117 log_entry = Repo.one(ModerationLog)
1119 assert ModerationLog.get_log_entry_message(log_entry) ==
1120 "@#{admin.nickname} activated users: @#{user_one.nickname}, @#{user_two.nickname}"
1123 test "PATCH /api/pleroma/admin/users/deactivate", %{admin: admin, conn: conn} do
1124 user_one = insert(:user, deactivated: false)
1125 user_two = insert(:user, deactivated: false)
1130 "/api/pleroma/admin/users/deactivate",
1131 %{nicknames: [user_one.nickname, user_two.nickname]}
1134 response = json_response(conn, 200)
1135 assert Enum.map(response["users"], & &1["deactivated"]) == [true, true]
1137 log_entry = Repo.one(ModerationLog)
1139 assert ModerationLog.get_log_entry_message(log_entry) ==
1140 "@#{admin.nickname} deactivated users: @#{user_one.nickname}, @#{user_two.nickname}"
1143 test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do
1144 user = insert(:user)
1146 conn = patch(conn, "/api/pleroma/admin/users/#{user.nickname}/toggle_activation")
1148 assert json_response(conn, 200) ==
1150 "deactivated" => !user.deactivated,
1152 "nickname" => user.nickname,
1153 "roles" => %{"admin" => false, "moderator" => false},
1156 "avatar" => User.avatar_url(user) |> MediaProxy.url(),
1157 "display_name" => HTML.strip_tags(user.name || user.nickname),
1158 "confirmation_pending" => false
1161 log_entry = Repo.one(ModerationLog)
1163 assert ModerationLog.get_log_entry_message(log_entry) ==
1164 "@#{admin.nickname} deactivated users: @#{user.nickname}"
1167 describe "PUT disable_mfa" do
1168 test "returns 200 and disable 2fa", %{conn: conn} do
1171 multi_factor_authentication_settings: %MFA.Settings{
1173 totp: %MFA.Settings.TOTP{secret: "otp_secret", confirmed: true}
1179 |> put("/api/pleroma/admin/users/disable_mfa", %{nickname: user.nickname})
1180 |> json_response(200)
1182 assert response == user.nickname
1183 mfa_settings = refresh_record(user).multi_factor_authentication_settings
1185 refute mfa_settings.enabled
1186 refute mfa_settings.totp.confirmed
1189 test "returns 404 if user not found", %{conn: conn} do
1192 |> put("/api/pleroma/admin/users/disable_mfa", %{nickname: "nickname"})
1193 |> json_response(404)
1195 assert response == %{"error" => "Not found"}
1199 describe "GET /api/pleroma/admin/restart" do
1200 setup do: clear_config(:configurable_from_database, true)
1202 test "pleroma restarts", %{conn: conn} do
1204 assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
1205 end) =~ "pleroma restarted"
1207 refute Restarter.Pleroma.need_reboot?()
1211 test "need_reboot flag", %{conn: conn} do
1213 |> get("/api/pleroma/admin/need_reboot")
1214 |> json_response(200) == %{"need_reboot" => false}
1216 Restarter.Pleroma.need_reboot()
1219 |> get("/api/pleroma/admin/need_reboot")
1220 |> json_response(200) == %{"need_reboot" => true}
1222 on_exit(fn -> Restarter.Pleroma.refresh() end)
1225 describe "GET /api/pleroma/admin/users/:nickname/statuses" do
1227 user = insert(:user)
1229 date1 = (DateTime.to_unix(DateTime.utc_now()) + 2000) |> DateTime.from_unix!()
1230 date2 = (DateTime.to_unix(DateTime.utc_now()) + 1000) |> DateTime.from_unix!()
1231 date3 = (DateTime.to_unix(DateTime.utc_now()) + 3000) |> DateTime.from_unix!()
1233 insert(:note_activity, user: user, published: date1)
1234 insert(:note_activity, user: user, published: date2)
1235 insert(:note_activity, user: user, published: date3)
1240 test "renders user's statuses", %{conn: conn, user: user} do
1241 conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses")
1243 assert json_response(conn, 200) |> length() == 3
1246 test "renders user's statuses with a limit", %{conn: conn, user: user} do
1247 conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=2")
1249 assert json_response(conn, 200) |> length() == 2
1252 test "doesn't return private statuses by default", %{conn: conn, user: user} do
1253 {:ok, _private_status} = CommonAPI.post(user, %{status: "private", visibility: "private"})
1255 {:ok, _public_status} = CommonAPI.post(user, %{status: "public", visibility: "public"})
1257 conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses")
1259 assert json_response(conn, 200) |> length() == 4
1262 test "returns private statuses with godmode on", %{conn: conn, user: user} do
1263 {:ok, _private_status} = CommonAPI.post(user, %{status: "private", visibility: "private"})
1265 {:ok, _public_status} = CommonAPI.post(user, %{status: "public", visibility: "public"})
1267 conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?godmode=true")
1269 assert json_response(conn, 200) |> length() == 5
1272 test "excludes reblogs by default", %{conn: conn, user: user} do
1273 other_user = insert(:user)
1274 {:ok, activity} = CommonAPI.post(user, %{status: "."})
1275 {:ok, %Activity{}} = CommonAPI.repeat(activity.id, other_user)
1277 conn_res = get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses")
1278 assert json_response(conn_res, 200) |> length() == 0
1281 get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses?with_reblogs=true")
1283 assert json_response(conn_res, 200) |> length() == 1
1287 describe "GET /api/pleroma/admin/moderation_log" do
1289 moderator = insert(:user, is_moderator: true)
1291 %{moderator: moderator}
1294 test "returns the log", %{conn: conn, admin: admin} do
1295 Repo.insert(%ModerationLog{
1299 "nickname" => admin.nickname,
1302 action: "relay_follow",
1303 target: "https://example.org/relay"
1305 inserted_at: NaiveDateTime.truncate(~N[2017-08-15 15:47:06.597036], :second)
1308 Repo.insert(%ModerationLog{
1312 "nickname" => admin.nickname,
1315 action: "relay_unfollow",
1316 target: "https://example.org/relay"
1318 inserted_at: NaiveDateTime.truncate(~N[2017-08-16 15:47:06.597036], :second)
1321 conn = get(conn, "/api/pleroma/admin/moderation_log")
1323 response = json_response(conn, 200)
1324 [first_entry, second_entry] = response["items"]
1326 assert response["total"] == 2
1327 assert first_entry["data"]["action"] == "relay_unfollow"
1329 assert first_entry["message"] ==
1330 "@#{admin.nickname} unfollowed relay: https://example.org/relay"
1332 assert second_entry["data"]["action"] == "relay_follow"
1334 assert second_entry["message"] ==
1335 "@#{admin.nickname} followed relay: https://example.org/relay"
1338 test "returns the log with pagination", %{conn: conn, admin: admin} do
1339 Repo.insert(%ModerationLog{
1343 "nickname" => admin.nickname,
1346 action: "relay_follow",
1347 target: "https://example.org/relay"
1349 inserted_at: NaiveDateTime.truncate(~N[2017-08-15 15:47:06.597036], :second)
1352 Repo.insert(%ModerationLog{
1356 "nickname" => admin.nickname,
1359 action: "relay_unfollow",
1360 target: "https://example.org/relay"
1362 inserted_at: NaiveDateTime.truncate(~N[2017-08-16 15:47:06.597036], :second)
1365 conn1 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=1")
1367 response1 = json_response(conn1, 200)
1368 [first_entry] = response1["items"]
1370 assert response1["total"] == 2
1371 assert response1["items"] |> length() == 1
1372 assert first_entry["data"]["action"] == "relay_unfollow"
1374 assert first_entry["message"] ==
1375 "@#{admin.nickname} unfollowed relay: https://example.org/relay"
1377 conn2 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=2")
1379 response2 = json_response(conn2, 200)
1380 [second_entry] = response2["items"]
1382 assert response2["total"] == 2
1383 assert response2["items"] |> length() == 1
1384 assert second_entry["data"]["action"] == "relay_follow"
1386 assert second_entry["message"] ==
1387 "@#{admin.nickname} followed relay: https://example.org/relay"
1390 test "filters log by date", %{conn: conn, admin: admin} do
1391 first_date = "2017-08-15T15:47:06Z"
1392 second_date = "2017-08-20T15:47:06Z"
1394 Repo.insert(%ModerationLog{
1398 "nickname" => admin.nickname,
1401 action: "relay_follow",
1402 target: "https://example.org/relay"
1404 inserted_at: NaiveDateTime.from_iso8601!(first_date)
1407 Repo.insert(%ModerationLog{
1411 "nickname" => admin.nickname,
1414 action: "relay_unfollow",
1415 target: "https://example.org/relay"
1417 inserted_at: NaiveDateTime.from_iso8601!(second_date)
1423 "/api/pleroma/admin/moderation_log?start_date=#{second_date}"
1426 response1 = json_response(conn1, 200)
1427 [first_entry] = response1["items"]
1429 assert response1["total"] == 1
1430 assert first_entry["data"]["action"] == "relay_unfollow"
1432 assert first_entry["message"] ==
1433 "@#{admin.nickname} unfollowed relay: https://example.org/relay"
1436 test "returns log filtered by user", %{conn: conn, admin: admin, moderator: moderator} do
1437 Repo.insert(%ModerationLog{
1441 "nickname" => admin.nickname,
1444 action: "relay_follow",
1445 target: "https://example.org/relay"
1449 Repo.insert(%ModerationLog{
1452 "id" => moderator.id,
1453 "nickname" => moderator.nickname,
1456 action: "relay_unfollow",
1457 target: "https://example.org/relay"
1461 conn1 = get(conn, "/api/pleroma/admin/moderation_log?user_id=#{moderator.id}")
1463 response1 = json_response(conn1, 200)
1464 [first_entry] = response1["items"]
1466 assert response1["total"] == 1
1467 assert get_in(first_entry, ["data", "actor", "id"]) == moderator.id
1470 test "returns log filtered by search", %{conn: conn, moderator: moderator} do
1471 ModerationLog.insert_log(%{
1473 action: "relay_follow",
1474 target: "https://example.org/relay"
1477 ModerationLog.insert_log(%{
1479 action: "relay_unfollow",
1480 target: "https://example.org/relay"
1483 conn1 = get(conn, "/api/pleroma/admin/moderation_log?search=unfo")
1485 response1 = json_response(conn1, 200)
1486 [first_entry] = response1["items"]
1488 assert response1["total"] == 1
1490 assert get_in(first_entry, ["data", "message"]) ==
1491 "@#{moderator.nickname} unfollowed relay: https://example.org/relay"
1495 describe "GET /users/:nickname/credentials" do
1496 test "gets the user credentials", %{conn: conn} do
1497 user = insert(:user)
1498 conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials")
1500 response = assert json_response(conn, 200)
1501 assert response["email"] == user.email
1504 test "returns 403 if requested by a non-admin" do
1505 user = insert(:user)
1509 |> assign(:user, user)
1510 |> get("/api/pleroma/admin/users/#{user.nickname}/credentials")
1512 assert json_response(conn, :forbidden)
1516 describe "PATCH /users/:nickname/credentials" do
1518 user = insert(:user)
1522 test "changes password and email", %{conn: conn, admin: admin, user: user} do
1523 assert user.password_reset_pending == false
1526 patch(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials", %{
1527 "password" => "new_password",
1528 "email" => "new_email@example.com",
1529 "name" => "new_name"
1532 assert json_response(conn, 200) == %{"status" => "success"}
1534 ObanHelpers.perform_all()
1536 updated_user = User.get_by_id(user.id)
1538 assert updated_user.email == "new_email@example.com"
1539 assert updated_user.name == "new_name"
1540 assert updated_user.password_hash != user.password_hash
1541 assert updated_user.password_reset_pending == true
1543 [log_entry2, log_entry1] = ModerationLog |> Repo.all() |> Enum.sort()
1545 assert ModerationLog.get_log_entry_message(log_entry1) ==
1546 "@#{admin.nickname} updated users: @#{user.nickname}"
1548 assert ModerationLog.get_log_entry_message(log_entry2) ==
1549 "@#{admin.nickname} forced password reset for users: @#{user.nickname}"
1552 test "returns 403 if requested by a non-admin", %{user: user} do
1555 |> assign(:user, user)
1556 |> patch("/api/pleroma/admin/users/#{user.nickname}/credentials", %{
1557 "password" => "new_password",
1558 "email" => "new_email@example.com",
1559 "name" => "new_name"
1562 assert json_response(conn, :forbidden)
1565 test "changes actor type from permitted list", %{conn: conn, user: user} do
1566 assert user.actor_type == "Person"
1568 assert patch(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials", %{
1569 "actor_type" => "Service"
1571 |> json_response(200) == %{"status" => "success"}
1573 updated_user = User.get_by_id(user.id)
1575 assert updated_user.actor_type == "Service"
1577 assert patch(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials", %{
1578 "actor_type" => "Application"
1580 |> json_response(200) == %{"errors" => %{"actor_type" => "is invalid"}}
1583 test "update non existing user", %{conn: conn} do
1584 assert patch(conn, "/api/pleroma/admin/users/non-existing/credentials", %{
1585 "password" => "new_password"
1587 |> json_response(200) == %{"error" => "Unable to update user."}
1591 describe "PATCH /users/:nickname/force_password_reset" do
1592 test "sets password_reset_pending to true", %{conn: conn} do
1593 user = insert(:user)
1594 assert user.password_reset_pending == false
1597 patch(conn, "/api/pleroma/admin/users/force_password_reset", %{nicknames: [user.nickname]})
1599 assert json_response(conn, 204) == ""
1601 ObanHelpers.perform_all()
1603 assert User.get_by_id(user.id).password_reset_pending == true
1607 describe "relays" do
1608 test "POST /relay", %{conn: conn, admin: admin} do
1610 post(conn, "/api/pleroma/admin/relay", %{
1611 relay_url: "http://mastodon.example.org/users/admin"
1614 assert json_response(conn, 200) == "http://mastodon.example.org/users/admin"
1616 log_entry = Repo.one(ModerationLog)
1618 assert ModerationLog.get_log_entry_message(log_entry) ==
1619 "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin"
1622 test "GET /relay", %{conn: conn} do
1623 relay_user = Pleroma.Web.ActivityPub.Relay.get_actor()
1625 ["http://mastodon.example.org/users/admin", "https://mstdn.io/users/mayuutann"]
1626 |> Enum.each(fn ap_id ->
1627 {:ok, user} = User.get_or_fetch_by_ap_id(ap_id)
1628 User.follow(relay_user, user)
1631 conn = get(conn, "/api/pleroma/admin/relay")
1633 assert json_response(conn, 200)["relays"] -- ["mastodon.example.org", "mstdn.io"] == []
1636 test "DELETE /relay", %{conn: conn, admin: admin} do
1637 post(conn, "/api/pleroma/admin/relay", %{
1638 relay_url: "http://mastodon.example.org/users/admin"
1642 delete(conn, "/api/pleroma/admin/relay", %{
1643 relay_url: "http://mastodon.example.org/users/admin"
1646 assert json_response(conn, 200) == "http://mastodon.example.org/users/admin"
1648 [log_entry_one, log_entry_two] = Repo.all(ModerationLog)
1650 assert ModerationLog.get_log_entry_message(log_entry_one) ==
1651 "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin"
1653 assert ModerationLog.get_log_entry_message(log_entry_two) ==
1654 "@#{admin.nickname} unfollowed relay: http://mastodon.example.org/users/admin"
1658 describe "instances" do
1659 test "GET /instances/:instance/statuses", %{conn: conn} do
1660 user = insert(:user, local: false, nickname: "archaeme@archae.me")
1661 user2 = insert(:user, local: false, nickname: "test@test.com")
1662 insert_pair(:note_activity, user: user)
1663 activity = insert(:note_activity, user: user2)
1665 ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses")
1667 response = json_response(ret_conn, 200)
1669 assert length(response) == 2
1671 ret_conn = get(conn, "/api/pleroma/admin/instances/test.com/statuses")
1673 response = json_response(ret_conn, 200)
1675 assert length(response) == 1
1677 ret_conn = get(conn, "/api/pleroma/admin/instances/nonexistent.com/statuses")
1679 response = json_response(ret_conn, 200)
1681 assert Enum.empty?(response)
1683 CommonAPI.repeat(activity.id, user)
1685 ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses")
1686 response = json_response(ret_conn, 200)
1687 assert length(response) == 2
1689 ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses?with_reblogs=true")
1690 response = json_response(ret_conn, 200)
1691 assert length(response) == 3
1695 describe "PATCH /confirm_email" do
1696 test "it confirms emails of two users", %{conn: conn, admin: admin} do
1697 [first_user, second_user] = insert_pair(:user, confirmation_pending: true)
1699 assert first_user.confirmation_pending == true
1700 assert second_user.confirmation_pending == true
1703 patch(conn, "/api/pleroma/admin/users/confirm_email", %{
1705 first_user.nickname,
1706 second_user.nickname
1710 assert ret_conn.status == 200
1712 assert first_user.confirmation_pending == true
1713 assert second_user.confirmation_pending == true
1715 log_entry = Repo.one(ModerationLog)
1717 assert ModerationLog.get_log_entry_message(log_entry) ==
1718 "@#{admin.nickname} confirmed email for users: @#{first_user.nickname}, @#{
1719 second_user.nickname
1724 describe "PATCH /resend_confirmation_email" do
1725 test "it resend emails for two users", %{conn: conn, admin: admin} do
1726 [first_user, second_user] = insert_pair(:user, confirmation_pending: true)
1729 patch(conn, "/api/pleroma/admin/users/resend_confirmation_email", %{
1731 first_user.nickname,
1732 second_user.nickname
1736 assert ret_conn.status == 200
1738 log_entry = Repo.one(ModerationLog)
1740 assert ModerationLog.get_log_entry_message(log_entry) ==
1741 "@#{admin.nickname} re-sent confirmation email for users: @#{first_user.nickname}, @#{
1742 second_user.nickname
1747 describe "/api/pleroma/admin/stats" do
1748 test "status visibility count", %{conn: conn} do
1749 admin = insert(:user, is_admin: true)
1750 user = insert(:user)
1751 CommonAPI.post(user, %{visibility: "public", status: "hey"})
1752 CommonAPI.post(user, %{visibility: "unlisted", status: "hey"})
1753 CommonAPI.post(user, %{visibility: "unlisted", status: "hey"})
1757 |> assign(:user, admin)
1758 |> get("/api/pleroma/admin/stats")
1759 |> json_response(200)
1761 assert %{"direct" => 0, "private" => 0, "public" => 1, "unlisted" => 2} =
1762 response["status_visibility"]
1767 # Needed for testing
1768 defmodule Pleroma.Web.Endpoint.NotReal do
1771 defmodule Pleroma.Captcha.NotReal do