total rebase
[anni] / test / pleroma / web / admin_api / controllers / admin_api_controller_test.exs
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
6   use Pleroma.Web.ConnCase, async: false
7   use Oban.Testing, repo: Pleroma.Repo
8
9   import ExUnit.CaptureLog
10   import Pleroma.Factory
11   import Swoosh.TestAssertions
12
13   alias Pleroma.Activity
14   alias Pleroma.MFA
15   alias Pleroma.ModerationLog
16   alias Pleroma.Repo
17   alias Pleroma.Tests.ObanHelpers
18   alias Pleroma.UnstubbedConfigMock, as: ConfigMock
19   alias Pleroma.User
20   alias Pleroma.Web.CommonAPI
21
22   setup_all do
23     Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
24
25     :ok
26   end
27
28   setup do
29     admin = insert(:user, is_admin: true)
30     token = insert(:oauth_admin_token, user: admin)
31
32     conn =
33       build_conn()
34       |> assign(:user, admin)
35       |> assign(:token, token)
36
37     {:ok, %{admin: admin, token: token, conn: conn}}
38   end
39
40   test "with valid `admin_token` query parameter, skips OAuth scopes check" do
41     clear_config([:admin_token], "password123")
42
43     user = insert(:user)
44
45     conn = get(build_conn(), "/api/pleroma/admin/users/#{user.nickname}?admin_token=password123")
46
47     assert json_response(conn, 200)
48   end
49
50   test "GET /api/pleroma/admin/users/:nickname requires admin:read:accounts or broader scope",
51        %{admin: admin} do
52     user = insert(:user)
53     url = "/api/pleroma/admin/users/#{user.nickname}"
54
55     good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"])
56     good_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read"])
57     good_token3 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts"])
58
59     bad_token1 = insert(:oauth_token, user: admin, scopes: ["read:accounts"])
60     bad_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts:partial"])
61     bad_token3 = nil
62
63     for good_token <- [good_token1, good_token2, good_token3] do
64       conn =
65         build_conn()
66         |> assign(:user, admin)
67         |> assign(:token, good_token)
68         |> get(url)
69
70       assert json_response(conn, 200)
71     end
72
73     for good_token <- [good_token1, good_token2, good_token3] do
74       conn =
75         build_conn()
76         |> assign(:user, nil)
77         |> assign(:token, good_token)
78         |> get(url)
79
80       assert json_response(conn, :forbidden)
81     end
82
83     for bad_token <- [bad_token1, bad_token2, bad_token3] do
84       conn =
85         build_conn()
86         |> assign(:user, admin)
87         |> assign(:token, bad_token)
88         |> get(url)
89
90       assert json_response(conn, :forbidden)
91     end
92   end
93
94   describe "PUT /api/pleroma/admin/users/tag" do
95     setup %{conn: conn} do
96       clear_config([:instance, :admin_privileges], [:users_manage_tags])
97
98       user1 = insert(:user, %{tags: ["x"]})
99       user2 = insert(:user, %{tags: ["y"]})
100       user3 = insert(:user, %{tags: ["unchanged"]})
101
102       %{conn: conn, user1: user1, user2: user2, user3: user3}
103     end
104
105     test "it appends specified tags to users with specified nicknames", %{
106       conn: conn,
107       admin: admin,
108       user1: user1,
109       user2: user2
110     } do
111       conn =
112         conn
113         |> put_req_header("accept", "application/json")
114         |> put(
115           "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <>
116             "#{user2.nickname}&tags[]=foo&tags[]=bar"
117         )
118
119       assert empty_json_response(conn)
120       assert User.get_cached_by_id(user1.id).tags == ["x", "foo", "bar"]
121       assert User.get_cached_by_id(user2.id).tags == ["y", "foo", "bar"]
122
123       log_entry = Repo.one(ModerationLog)
124
125       users =
126         [user1.nickname, user2.nickname]
127         |> Enum.map(&"@#{&1}")
128         |> Enum.join(", ")
129
130       tags = ["foo", "bar"] |> Enum.join(", ")
131
132       assert ModerationLog.get_log_entry_message(log_entry) ==
133                "@#{admin.nickname} added tags: #{tags} to users: #{users}"
134     end
135
136     test "it does not modify tags of not specified users", %{
137       conn: conn,
138       user1: user1,
139       user2: user2,
140       user3: user3
141     } do
142       conn =
143         conn
144         |> put_req_header("accept", "application/json")
145         |> put(
146           "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <>
147             "#{user2.nickname}&tags[]=foo&tags[]=bar"
148         )
149
150       assert empty_json_response(conn)
151       assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
152     end
153
154     test "it requires privileged role :users_manage_tags", %{conn: conn} do
155       clear_config([:instance, :admin_privileges], [])
156
157       response =
158         conn
159         |> put_req_header("accept", "application/json")
160         |> put("/api/pleroma/admin/users/tag?nicknames[]=nickname&tags[]=foo&tags[]=bar")
161
162       assert json_response(response, :forbidden)
163     end
164   end
165
166   describe "DELETE /api/pleroma/admin/users/tag" do
167     setup %{conn: conn} do
168       clear_config([:instance, :admin_privileges], [:users_manage_tags])
169       user1 = insert(:user, %{tags: ["x"]})
170       user2 = insert(:user, %{tags: ["y", "z"]})
171       user3 = insert(:user, %{tags: ["unchanged"]})
172
173       %{conn: conn, user1: user1, user2: user2, user3: user3}
174     end
175
176     test "it removes specified tags from users with specified nicknames", %{
177       conn: conn,
178       admin: admin,
179       user1: user1,
180       user2: user2
181     } do
182       conn =
183         conn
184         |> put_req_header("accept", "application/json")
185         |> delete(
186           "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <>
187             "#{user2.nickname}&tags[]=x&tags[]=z"
188         )
189
190       assert empty_json_response(conn)
191       assert User.get_cached_by_id(user1.id).tags == []
192       assert User.get_cached_by_id(user2.id).tags == ["y"]
193
194       log_entry = Repo.one(ModerationLog)
195
196       users =
197         [user1.nickname, user2.nickname]
198         |> Enum.map(&"@#{&1}")
199         |> Enum.join(", ")
200
201       tags = ["x", "z"] |> Enum.join(", ")
202
203       assert ModerationLog.get_log_entry_message(log_entry) ==
204                "@#{admin.nickname} removed tags: #{tags} from users: #{users}"
205     end
206
207     test "it does not modify tags of not specified users", %{
208       conn: conn,
209       user1: user1,
210       user2: user2,
211       user3: user3
212     } do
213       conn =
214         conn
215         |> put_req_header("accept", "application/json")
216         |> delete(
217           "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <>
218             "#{user2.nickname}&tags[]=x&tags[]=z"
219         )
220
221       assert empty_json_response(conn)
222       assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
223     end
224
225     test "it requires privileged role :users_manage_tags", %{conn: conn} do
226       clear_config([:instance, :admin_privileges], [])
227
228       response =
229         conn
230         |> put_req_header("accept", "application/json")
231         |> delete("/api/pleroma/admin/users/tag?nicknames[]=nickname&tags[]=foo&tags[]=bar")
232
233       assert json_response(response, :forbidden)
234     end
235   end
236
237   describe "/api/pleroma/admin/users/:nickname/permission_group" do
238     test "GET is giving user_info", %{admin: admin, conn: conn} do
239       conn =
240         conn
241         |> put_req_header("accept", "application/json")
242         |> get("/api/pleroma/admin/users/#{admin.nickname}/permission_group/")
243
244       assert json_response(conn, 200) == %{
245                "is_admin" => true,
246                "is_moderator" => false
247              }
248     end
249
250     test "/:right POST, can add to a permission group", %{admin: admin, conn: conn} do
251       user = insert(:user)
252
253       conn =
254         conn
255         |> put_req_header("accept", "application/json")
256         |> post("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin")
257
258       assert json_response(conn, 200) == %{
259                "is_admin" => true
260              }
261
262       log_entry = Repo.one(ModerationLog)
263
264       assert ModerationLog.get_log_entry_message(log_entry) ==
265                "@#{admin.nickname} made @#{user.nickname} admin"
266     end
267
268     test "/:right POST, can add to a permission group (multiple)", %{admin: admin, conn: conn} do
269       user_one = insert(:user)
270       user_two = insert(:user)
271
272       conn =
273         conn
274         |> put_req_header("accept", "application/json")
275         |> post("/api/pleroma/admin/users/permission_group/admin", %{
276           nicknames: [user_one.nickname, user_two.nickname]
277         })
278
279       assert json_response(conn, 200) == %{"is_admin" => true}
280
281       log_entry = Repo.one(ModerationLog)
282
283       assert ModerationLog.get_log_entry_message(log_entry) ==
284                "@#{admin.nickname} made @#{user_one.nickname}, @#{user_two.nickname} admin"
285     end
286
287     test "/:right DELETE, can remove from a permission group", %{admin: admin, conn: conn} do
288       user = insert(:user, is_admin: true)
289
290       conn =
291         conn
292         |> put_req_header("accept", "application/json")
293         |> delete("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin")
294
295       assert json_response(conn, 200) == %{"is_admin" => false}
296
297       log_entry = Repo.one(ModerationLog)
298
299       assert ModerationLog.get_log_entry_message(log_entry) ==
300                "@#{admin.nickname} revoked admin role from @#{user.nickname}"
301     end
302
303     test "/:right DELETE, can remove from a permission group (multiple)", %{
304       admin: admin,
305       conn: conn
306     } do
307       user_one = insert(:user, is_admin: true)
308       user_two = insert(:user, is_admin: true)
309
310       conn =
311         conn
312         |> put_req_header("accept", "application/json")
313         |> delete("/api/pleroma/admin/users/permission_group/admin", %{
314           nicknames: [user_one.nickname, user_two.nickname]
315         })
316
317       assert json_response(conn, 200) == %{"is_admin" => false}
318
319       log_entry = Repo.one(ModerationLog)
320
321       assert ModerationLog.get_log_entry_message(log_entry) ==
322                "@#{admin.nickname} revoked admin role from @#{user_one.nickname}, @#{user_two.nickname}"
323     end
324   end
325
326   describe "/api/pleroma/admin/users/:nickname/password_reset" do
327     test "it returns a password reset link", %{conn: conn} do
328       clear_config([:instance, :admin_privileges], [:users_manage_credentials])
329
330       user = insert(:user)
331
332       conn =
333         conn
334         |> put_req_header("accept", "application/json")
335         |> get("/api/pleroma/admin/users/#{user.nickname}/password_reset")
336
337       resp = json_response(conn, 200)
338
339       assert Regex.match?(~r/(http:\/\/|https:\/\/)/, resp["link"])
340     end
341
342     test "it requires privileged role :users_manage_credentials", %{conn: conn} do
343       clear_config([:instance, :admin_privileges], [])
344
345       response =
346         conn
347         |> put_req_header("accept", "application/json")
348         |> get("/api/pleroma/admin/users/nickname/password_reset")
349
350       assert json_response(response, :forbidden)
351     end
352   end
353
354   describe "PUT disable_mfa" do
355     test "returns 200 and disable 2fa", %{conn: conn} do
356       clear_config([:instance, :admin_privileges], [:users_manage_credentials])
357
358       user =
359         insert(:user,
360           multi_factor_authentication_settings: %MFA.Settings{
361             enabled: true,
362             totp: %MFA.Settings.TOTP{secret: "otp_secret", confirmed: true}
363           }
364         )
365
366       response =
367         conn
368         |> put("/api/pleroma/admin/users/disable_mfa", %{nickname: user.nickname})
369         |> json_response(200)
370
371       assert response == user.nickname
372       mfa_settings = refresh_record(user).multi_factor_authentication_settings
373
374       refute mfa_settings.enabled
375       refute mfa_settings.totp.confirmed
376     end
377
378     test "returns 404 if user not found", %{conn: conn} do
379       clear_config([:instance, :admin_privileges], [:users_manage_credentials])
380
381       response =
382         conn
383         |> put("/api/pleroma/admin/users/disable_mfa", %{nickname: "nickname"})
384         |> json_response(404)
385
386       assert response == %{"error" => "Not found"}
387     end
388
389     test "it requires privileged role :users_manage_credentials", %{conn: conn} do
390       clear_config([:instance, :admin_privileges], [])
391
392       response =
393         conn
394         |> put("/api/pleroma/admin/users/disable_mfa", %{nickname: "nickname"})
395
396       assert json_response(response, :forbidden)
397     end
398   end
399
400   describe "GET /api/pleroma/admin/restart" do
401     setup do: clear_config(:configurable_from_database, true)
402
403     test "pleroma restarts", %{conn: conn} do
404       capture_log(fn ->
405         assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
406       end) =~ "pleroma restarted"
407
408       refute Restarter.Pleroma.need_reboot?()
409     end
410   end
411
412   test "need_reboot flag", %{conn: conn} do
413     assert conn
414            |> get("/api/pleroma/admin/need_reboot")
415            |> json_response(200) == %{"need_reboot" => false}
416
417     Restarter.Pleroma.need_reboot()
418
419     assert conn
420            |> get("/api/pleroma/admin/need_reboot")
421            |> json_response(200) == %{"need_reboot" => true}
422
423     on_exit(fn -> Restarter.Pleroma.refresh() end)
424   end
425
426   describe "GET /api/pleroma/admin/users/:nickname/statuses" do
427     setup do
428       clear_config([:instance, :admin_privileges], [:messages_read])
429
430       user = insert(:user)
431
432       insert(:note_activity, user: user)
433       insert(:note_activity, user: user)
434       insert(:note_activity, user: user)
435
436       %{user: user}
437     end
438
439     test "renders user's statuses", %{conn: conn, user: user} do
440       conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses")
441
442       assert %{"total" => 3, "activities" => activities} = json_response(conn, 200)
443       assert length(activities) == 3
444     end
445
446     test "it requires privileged role :messages_read", %{conn: conn, user: user} do
447       clear_config([:instance, :admin_privileges], [])
448
449       conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses")
450
451       assert json_response(conn, :forbidden)
452     end
453
454     test "renders user's statuses with pagination", %{conn: conn, user: user} do
455       %{"total" => 3, "activities" => [activity1]} =
456         conn
457         |> get("/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=1&page=1")
458         |> json_response(200)
459
460       %{"total" => 3, "activities" => [activity2]} =
461         conn
462         |> get("/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=1&page=2")
463         |> json_response(200)
464
465       refute activity1 == activity2
466     end
467
468     test "doesn't return private statuses by default", %{conn: conn, user: user} do
469       {:ok, _private_status} = CommonAPI.post(user, %{status: "private", visibility: "private"})
470
471       {:ok, _public_status} = CommonAPI.post(user, %{status: "public", visibility: "public"})
472
473       %{"total" => 4, "activities" => activities} =
474         conn
475         |> get("/api/pleroma/admin/users/#{user.nickname}/statuses")
476         |> json_response(200)
477
478       assert length(activities) == 4
479     end
480
481     test "returns private statuses with godmode on", %{conn: conn, user: user} do
482       {:ok, _private_status} = CommonAPI.post(user, %{status: "private", visibility: "private"})
483
484       {:ok, _public_status} = CommonAPI.post(user, %{status: "public", visibility: "public"})
485
486       %{"total" => 5, "activities" => activities} =
487         conn
488         |> get("/api/pleroma/admin/users/#{user.nickname}/statuses?godmode=true")
489         |> json_response(200)
490
491       assert length(activities) == 5
492     end
493
494     test "excludes reblogs by default", %{conn: conn, user: user} do
495       other_user = insert(:user)
496       {:ok, activity} = CommonAPI.post(user, %{status: "."})
497       {:ok, %Activity{}} = CommonAPI.repeat(activity.id, other_user)
498
499       assert %{"total" => 0, "activities" => []} ==
500                conn
501                |> get("/api/pleroma/admin/users/#{other_user.nickname}/statuses")
502                |> json_response(200)
503
504       assert %{"total" => 1, "activities" => [_]} =
505                conn
506                |> get(
507                  "/api/pleroma/admin/users/#{other_user.nickname}/statuses?with_reblogs=true"
508                )
509                |> json_response(200)
510     end
511   end
512
513   describe "GET /api/pleroma/admin/users/:nickname/chats" do
514     setup do
515       clear_config([:instance, :admin_privileges], [:messages_read])
516
517       user = insert(:user)
518
519       %{user: user}
520     end
521
522     test "renders user's chats", %{conn: conn, user: user} do
523       recipients = insert_list(3, :user)
524
525       Enum.each(recipients, fn recipient ->
526         CommonAPI.post_chat_message(user, recipient, "yo")
527       end)
528
529       conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/chats")
530
531       assert json_response(conn, 200) |> length() == 3
532     end
533
534     test "it requires privileged role :messages_read", %{conn: conn, user: user} do
535       clear_config([:instance, :admin_privileges], [])
536
537       conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/chats")
538
539       assert json_response(conn, :forbidden)
540     end
541   end
542
543   describe "GET /api/pleroma/admin/users/:nickname/chats unauthorized" do
544     setup do
545       user = insert(:user)
546       recipient = insert(:user)
547       CommonAPI.post_chat_message(user, recipient, "yo")
548       %{conn: conn} = oauth_access(["read:chats"])
549       %{conn: conn, user: user}
550     end
551
552     test "returns 403", %{conn: conn, user: user} do
553       conn
554       |> get("/api/pleroma/admin/users/#{user.nickname}/chats")
555       |> json_response(403)
556     end
557   end
558
559   describe "GET /api/pleroma/admin/users/:nickname/chats unauthenticated" do
560     setup do
561       user = insert(:user)
562       recipient = insert(:user)
563       CommonAPI.post_chat_message(user, recipient, "yo")
564       %{conn: build_conn(), user: user}
565     end
566
567     test "returns 403", %{conn: conn, user: user} do
568       conn
569       |> get("/api/pleroma/admin/users/#{user.nickname}/chats")
570       |> json_response(403)
571     end
572   end
573
574   describe "GET /api/pleroma/admin/moderation_log" do
575     setup do
576       clear_config([:instance, :admin_privileges], [:moderation_log_read])
577       moderator = insert(:user, is_moderator: true)
578
579       %{moderator: moderator}
580     end
581
582     test "returns the log", %{conn: conn, admin: admin} do
583       Repo.insert(%ModerationLog{
584         data: %{
585           actor: %{
586             "id" => admin.id,
587             "nickname" => admin.nickname,
588             "type" => "user"
589           },
590           action: "relay_follow",
591           target: "https://example.org/relay"
592         },
593         inserted_at: NaiveDateTime.truncate(~N[2017-08-15 15:47:06.597036], :second)
594       })
595
596       Repo.insert(%ModerationLog{
597         data: %{
598           actor: %{
599             "id" => admin.id,
600             "nickname" => admin.nickname,
601             "type" => "user"
602           },
603           action: "relay_unfollow",
604           target: "https://example.org/relay"
605         },
606         inserted_at: NaiveDateTime.truncate(~N[2017-08-16 15:47:06.597036], :second)
607       })
608
609       conn = get(conn, "/api/pleroma/admin/moderation_log")
610
611       response = json_response(conn, 200)
612       [first_entry, second_entry] = response["items"]
613
614       assert response["total"] == 2
615       assert first_entry["data"]["action"] == "relay_unfollow"
616
617       assert first_entry["message"] ==
618                "@#{admin.nickname} unfollowed relay: https://example.org/relay"
619
620       assert second_entry["data"]["action"] == "relay_follow"
621
622       assert second_entry["message"] ==
623                "@#{admin.nickname} followed relay: https://example.org/relay"
624     end
625
626     test "returns the log with pagination", %{conn: conn, admin: admin} do
627       Repo.insert(%ModerationLog{
628         data: %{
629           actor: %{
630             "id" => admin.id,
631             "nickname" => admin.nickname,
632             "type" => "user"
633           },
634           action: "relay_follow",
635           target: "https://example.org/relay"
636         },
637         inserted_at: NaiveDateTime.truncate(~N[2017-08-15 15:47:06.597036], :second)
638       })
639
640       Repo.insert(%ModerationLog{
641         data: %{
642           actor: %{
643             "id" => admin.id,
644             "nickname" => admin.nickname,
645             "type" => "user"
646           },
647           action: "relay_unfollow",
648           target: "https://example.org/relay"
649         },
650         inserted_at: NaiveDateTime.truncate(~N[2017-08-16 15:47:06.597036], :second)
651       })
652
653       conn1 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=1")
654
655       response1 = json_response(conn1, 200)
656       [first_entry] = response1["items"]
657
658       assert response1["total"] == 2
659       assert response1["items"] |> length() == 1
660       assert first_entry["data"]["action"] == "relay_unfollow"
661
662       assert first_entry["message"] ==
663                "@#{admin.nickname} unfollowed relay: https://example.org/relay"
664
665       conn2 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=2")
666
667       response2 = json_response(conn2, 200)
668       [second_entry] = response2["items"]
669
670       assert response2["total"] == 2
671       assert response2["items"] |> length() == 1
672       assert second_entry["data"]["action"] == "relay_follow"
673
674       assert second_entry["message"] ==
675                "@#{admin.nickname} followed relay: https://example.org/relay"
676     end
677
678     test "filters log by date", %{conn: conn, admin: admin} do
679       first_date = "2017-08-15T15:47:06Z"
680       second_date = "2017-08-20T15:47:06Z"
681
682       Repo.insert(%ModerationLog{
683         data: %{
684           actor: %{
685             "id" => admin.id,
686             "nickname" => admin.nickname,
687             "type" => "user"
688           },
689           action: "relay_follow",
690           target: "https://example.org/relay"
691         },
692         inserted_at: NaiveDateTime.from_iso8601!(first_date)
693       })
694
695       Repo.insert(%ModerationLog{
696         data: %{
697           actor: %{
698             "id" => admin.id,
699             "nickname" => admin.nickname,
700             "type" => "user"
701           },
702           action: "relay_unfollow",
703           target: "https://example.org/relay"
704         },
705         inserted_at: NaiveDateTime.from_iso8601!(second_date)
706       })
707
708       conn1 =
709         get(
710           conn,
711           "/api/pleroma/admin/moderation_log?start_date=#{second_date}"
712         )
713
714       response1 = json_response(conn1, 200)
715       [first_entry] = response1["items"]
716
717       assert response1["total"] == 1
718       assert first_entry["data"]["action"] == "relay_unfollow"
719
720       assert first_entry["message"] ==
721                "@#{admin.nickname} unfollowed relay: https://example.org/relay"
722     end
723
724     test "returns log filtered by user", %{conn: conn, admin: admin, moderator: moderator} do
725       Repo.insert(%ModerationLog{
726         data: %{
727           actor: %{
728             "id" => admin.id,
729             "nickname" => admin.nickname,
730             "type" => "user"
731           },
732           action: "relay_follow",
733           target: "https://example.org/relay"
734         }
735       })
736
737       Repo.insert(%ModerationLog{
738         data: %{
739           actor: %{
740             "id" => moderator.id,
741             "nickname" => moderator.nickname,
742             "type" => "user"
743           },
744           action: "relay_unfollow",
745           target: "https://example.org/relay"
746         }
747       })
748
749       conn1 = get(conn, "/api/pleroma/admin/moderation_log?user_id=#{moderator.id}")
750
751       response1 = json_response(conn1, 200)
752       [first_entry] = response1["items"]
753
754       assert response1["total"] == 1
755       assert get_in(first_entry, ["data", "actor", "id"]) == moderator.id
756     end
757
758     test "returns log filtered by search", %{conn: conn, moderator: moderator} do
759       ModerationLog.insert_log(%{
760         actor: moderator,
761         action: "relay_follow",
762         target: "https://example.org/relay"
763       })
764
765       ModerationLog.insert_log(%{
766         actor: moderator,
767         action: "relay_unfollow",
768         target: "https://example.org/relay"
769       })
770
771       conn1 = get(conn, "/api/pleroma/admin/moderation_log?search=unfo")
772
773       response1 = json_response(conn1, 200)
774       [first_entry] = response1["items"]
775
776       assert response1["total"] == 1
777
778       assert get_in(first_entry, ["data", "message"]) ==
779                "@#{moderator.nickname} unfollowed relay: https://example.org/relay"
780     end
781
782     test "it requires privileged role :moderation_log_read", %{conn: conn} do
783       clear_config([:instance, :admin_privileges], [])
784
785       assert conn
786              |> put_req_header("content-type", "multipart/form-data")
787              |> get("/api/pleroma/admin/moderation_log")
788              |> json_response(:forbidden)
789     end
790   end
791
792   test "gets a remote users when [:instance, :limit_to_local_content] is set to :unauthenticated",
793        %{conn: conn} do
794     clear_config(Pleroma.Config.get([:instance, :limit_to_local_content]), :unauthenticated)
795     user = insert(:user, %{local: false, nickname: "u@peer1.com"})
796     conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials")
797
798     assert json_response(conn, 200)
799   end
800
801   describe "GET /users/:nickname/credentials" do
802     test "gets the user credentials", %{conn: conn} do
803       clear_config([:instance, :admin_privileges], [:users_manage_credentials])
804       user = insert(:user)
805       conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials")
806
807       response = assert json_response(conn, 200)
808       assert response["email"] == user.email
809     end
810
811     test "returns 403 if requested by a non-admin" do
812       clear_config([:instance, :admin_privileges], [:users_manage_credentials])
813       user = insert(:user)
814
815       conn =
816         build_conn()
817         |> assign(:user, user)
818         |> get("/api/pleroma/admin/users/#{user.nickname}/credentials")
819
820       assert json_response(conn, :forbidden)
821     end
822
823     test "it requires privileged role :users_manage_credentials", %{conn: conn} do
824       clear_config([:instance, :admin_privileges], [])
825
826       response =
827         conn
828         |> get("/api/pleroma/admin/users/nickname/credentials")
829
830       assert json_response(response, :forbidden)
831     end
832   end
833
834   describe "PATCH /users/:nickname/credentials" do
835     setup do
836       user = insert(:user)
837       [user: user]
838     end
839
840     test "changes password and email", %{conn: conn, admin: admin, user: user} do
841       clear_config([:instance, :admin_privileges], [:users_manage_credentials])
842
843       assert user.password_reset_pending == false
844
845       conn =
846         patch(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials", %{
847           "password" => "new_password",
848           "email" => "new_email@example.com",
849           "name" => "new_name"
850         })
851
852       assert json_response(conn, 200) == %{"status" => "success"}
853
854       ObanHelpers.perform_all()
855
856       updated_user = User.get_by_id(user.id)
857
858       assert updated_user.email == "new_email@example.com"
859       assert updated_user.name == "new_name"
860       assert updated_user.password_hash != user.password_hash
861       assert updated_user.password_reset_pending == true
862
863       [log_entry2, log_entry1] = ModerationLog |> Repo.all() |> Enum.sort()
864
865       assert ModerationLog.get_log_entry_message(log_entry1) ==
866                "@#{admin.nickname} updated users: @#{user.nickname}"
867
868       assert ModerationLog.get_log_entry_message(log_entry2) ==
869                "@#{admin.nickname} forced password reset for users: @#{user.nickname}"
870     end
871
872     test "returns 403 if requested by a non-admin", %{user: user} do
873       conn =
874         build_conn()
875         |> assign(:user, user)
876         |> patch("/api/pleroma/admin/users/#{user.nickname}/credentials", %{
877           "password" => "new_password",
878           "email" => "new_email@example.com",
879           "name" => "new_name"
880         })
881
882       assert json_response(conn, :forbidden)
883     end
884
885     test "returns 403 if not privileged with :users_manage_credentials", %{conn: conn, user: user} do
886       clear_config([:instance, :admin_privileges], [])
887
888       conn =
889         patch(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials", %{
890           "password" => "new_password",
891           "email" => "new_email@example.com",
892           "name" => "new_name"
893         })
894
895       assert json_response(conn, :forbidden)
896     end
897
898     test "changes actor type from permitted list", %{conn: conn, user: user} do
899       assert user.actor_type == "Person"
900
901       assert patch(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials", %{
902                "actor_type" => "Service"
903              })
904              |> json_response(200) == %{"status" => "success"}
905
906       updated_user = User.get_by_id(user.id)
907
908       assert updated_user.actor_type == "Service"
909
910       assert patch(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials", %{
911                "actor_type" => "Application"
912              })
913              |> json_response(400) == %{"errors" => %{"actor_type" => "is invalid"}}
914     end
915
916     test "update non existing user", %{conn: conn} do
917       assert patch(conn, "/api/pleroma/admin/users/non-existing/credentials", %{
918                "password" => "new_password"
919              })
920              |> json_response(404) == %{"error" => "Not found"}
921     end
922   end
923
924   describe "PATCH /users/:nickname/force_password_reset" do
925     test "sets password_reset_pending to true", %{conn: conn} do
926       clear_config([:instance, :admin_privileges], [:users_manage_credentials])
927       user = insert(:user)
928       assert user.password_reset_pending == false
929
930       conn =
931         patch(conn, "/api/pleroma/admin/users/force_password_reset", %{nicknames: [user.nickname]})
932
933       assert empty_json_response(conn) == ""
934
935       ObanHelpers.perform_all()
936
937       assert User.get_by_id(user.id).password_reset_pending == true
938     end
939
940     test "it requires privileged role :users_manage_credentials", %{conn: conn} do
941       clear_config([:instance, :admin_privileges], [])
942
943       response =
944         conn
945         |> patch("/api/pleroma/admin/users/force_password_reset", %{nickname: "nickname"})
946
947       assert json_response(response, :forbidden)
948     end
949   end
950
951   describe "PATCH /confirm_email" do
952     test "it confirms emails of two users", %{conn: conn, admin: admin} do
953       clear_config([:instance, :admin_privileges], [:users_manage_credentials])
954       [first_user, second_user] = insert_pair(:user, is_confirmed: false)
955
956       refute first_user.is_confirmed
957       refute second_user.is_confirmed
958
959       ret_conn =
960         patch(conn, "/api/pleroma/admin/users/confirm_email", %{
961           nicknames: [
962             first_user.nickname,
963             second_user.nickname
964           ]
965         })
966
967       assert ret_conn.status == 200
968
969       first_user = User.get_by_id(first_user.id)
970       second_user = User.get_by_id(second_user.id)
971
972       assert first_user.is_confirmed
973       assert second_user.is_confirmed
974
975       log_entry = Repo.one(ModerationLog)
976
977       assert ModerationLog.get_log_entry_message(log_entry) ==
978                "@#{admin.nickname} confirmed email for users: @#{first_user.nickname}, @#{second_user.nickname}"
979     end
980
981     test "it requires privileged role :users_manage_credentials", %{conn: conn} do
982       clear_config([:instance, :admin_privileges], [])
983
984       response =
985         conn
986         |> patch("/api/pleroma/admin/users/confirm_email", %{nicknames: ["nickname"]})
987
988       assert json_response(response, :forbidden)
989     end
990   end
991
992   describe "PATCH /resend_confirmation_email" do
993     test "it resend emails for two users", %{conn: conn, admin: admin} do
994       clear_config([:instance, :admin_privileges], [:users_manage_credentials])
995       [first_user, second_user] = insert_pair(:user, is_confirmed: false)
996
997       ret_conn =
998         patch(conn, "/api/pleroma/admin/users/resend_confirmation_email", %{
999           nicknames: [
1000             first_user.nickname,
1001             second_user.nickname
1002           ]
1003         })
1004
1005       assert ret_conn.status == 200
1006
1007       log_entry = Repo.one(ModerationLog)
1008
1009       assert ModerationLog.get_log_entry_message(log_entry) ==
1010                "@#{admin.nickname} re-sent confirmation email for users: @#{first_user.nickname}, @#{second_user.nickname}"
1011
1012       ObanHelpers.perform_all()
1013
1014       Pleroma.Emails.UserEmail.account_confirmation_email(first_user)
1015       # temporary hackney fix until hackney max_connections bug is fixed
1016       # https://git.pleroma.social/pleroma/pleroma/-/issues/2101
1017       |> Swoosh.Email.put_private(:hackney_options, ssl_options: [versions: [:"tlsv1.2"]])
1018       |> assert_email_sent()
1019     end
1020
1021     test "it requires privileged role :users_manage_credentials", %{conn: conn} do
1022       clear_config([:instance, :admin_privileges], [])
1023
1024       response =
1025         conn
1026         |> patch("/api/pleroma/admin/users/resend_confirmation_email", %{nicknames: ["nickname"]})
1027
1028       assert json_response(response, :forbidden)
1029     end
1030   end
1031
1032   describe "/api/pleroma/admin/stats" do
1033     setup do
1034       clear_config([:instance, :admin_privileges], [:statistics_read])
1035     end
1036
1037     test "status visibility count", %{conn: conn} do
1038       user = insert(:user)
1039       CommonAPI.post(user, %{visibility: "public", status: "hey"})
1040       CommonAPI.post(user, %{visibility: "unlisted", status: "hey"})
1041       CommonAPI.post(user, %{visibility: "unlisted", status: "hey"})
1042
1043       response =
1044         conn
1045         |> get("/api/pleroma/admin/stats")
1046         |> json_response(200)
1047
1048       assert %{"direct" => 0, "private" => 0, "public" => 1, "unlisted" => 2} =
1049                response["status_visibility"]
1050     end
1051
1052     test "by instance", %{conn: conn} do
1053       user1 = insert(:user)
1054       instance2 = "instance2.tld"
1055       user2 = insert(:user, %{ap_id: "https://#{instance2}/@actor"})
1056
1057       CommonAPI.post(user1, %{visibility: "public", status: "hey"})
1058       CommonAPI.post(user2, %{visibility: "unlisted", status: "hey"})
1059       CommonAPI.post(user2, %{visibility: "private", status: "hey"})
1060
1061       response =
1062         conn
1063         |> get("/api/pleroma/admin/stats", instance: instance2)
1064         |> json_response(200)
1065
1066       assert %{"direct" => 0, "private" => 1, "public" => 0, "unlisted" => 1} =
1067                response["status_visibility"]
1068     end
1069
1070     test "it requires privileged role :statistics_read", %{conn: conn} do
1071       clear_config([:instance, :admin_privileges], [])
1072
1073       assert conn
1074              |> get("/api/pleroma/admin/stats", instance: "lain.wired")
1075              |> json_response(:forbidden)
1076     end
1077   end
1078
1079   describe "/api/pleroma/backups" do
1080     test "it creates a backup", %{conn: conn} do
1081       ConfigMock
1082       |> Mox.stub_with(Pleroma.Config)
1083
1084       admin = %{id: admin_id, nickname: admin_nickname} = insert(:user, is_admin: true)
1085       token = insert(:oauth_admin_token, user: admin)
1086       user = %{id: user_id, nickname: user_nickname} = insert(:user)
1087
1088       assert "" ==
1089                conn
1090                |> assign(:user, admin)
1091                |> assign(:token, token)
1092                |> post("/api/pleroma/admin/backups", %{nickname: user.nickname})
1093                |> json_response(200)
1094
1095       assert [backup] = Repo.all(Pleroma.User.Backup)
1096
1097       ObanHelpers.perform_all()
1098
1099       email = Pleroma.Emails.UserEmail.backup_is_ready_email(backup, admin.id)
1100
1101       assert String.contains?(email.html_body, "Admin @#{admin.nickname} requested a full backup")
1102       assert_email_sent(to: {user.name, user.email}, html_body: email.html_body)
1103
1104       log_message = "@#{admin_nickname} requested account backup for @#{user_nickname}"
1105
1106       assert [
1107                %{
1108                  data: %{
1109                    "action" => "create_backup",
1110                    "actor" => %{
1111                      "id" => ^admin_id,
1112                      "nickname" => ^admin_nickname
1113                    },
1114                    "message" => ^log_message,
1115                    "subject" => %{
1116                      "id" => ^user_id,
1117                      "nickname" => ^user_nickname
1118                    }
1119                  }
1120                }
1121              ] = Pleroma.ModerationLog |> Repo.all()
1122     end
1123
1124     test "it doesn't limit admins", %{conn: conn} do
1125       admin = insert(:user, is_admin: true)
1126       token = insert(:oauth_admin_token, user: admin)
1127       user = insert(:user)
1128
1129       assert "" ==
1130                conn
1131                |> assign(:user, admin)
1132                |> assign(:token, token)
1133                |> post("/api/pleroma/admin/backups", %{nickname: user.nickname})
1134                |> json_response(200)
1135
1136       assert [_backup] = Repo.all(Pleroma.User.Backup)
1137
1138       assert "" ==
1139                conn
1140                |> assign(:user, admin)
1141                |> assign(:token, token)
1142                |> post("/api/pleroma/admin/backups", %{nickname: user.nickname})
1143                |> json_response(200)
1144
1145       assert Repo.aggregate(Pleroma.User.Backup, :count) == 2
1146     end
1147   end
1148
1149   describe "POST /api/v1/pleroma/admin/reload_emoji" do
1150     setup do
1151       clear_config([:instance, :admin_privileges], [:emoji_manage_emoji])
1152
1153       admin = insert(:user, is_admin: true)
1154       token = insert(:oauth_admin_token, user: admin)
1155
1156       conn =
1157         build_conn()
1158         |> assign(:user, admin)
1159         |> assign(:token, token)
1160
1161       {:ok, %{conn: conn, admin: admin}}
1162     end
1163
1164     test "it requires privileged role :emoji_manage_emoji", %{conn: conn} do
1165       assert conn
1166              |> post("/api/v1/pleroma/admin/reload_emoji")
1167              |> json_response(200)
1168
1169       clear_config([:instance, :admin_privileges], [])
1170
1171       assert conn
1172              |> post("/api/v1/pleroma/admin/reload_emoji")
1173              |> json_response(:forbidden)
1174     end
1175   end
1176 end
1177
1178 # Needed for testing
1179 defmodule Pleroma.Web.Endpoint.NotReal do
1180 end
1181
1182 defmodule Pleroma.Captcha.NotReal do
1183 end