Merge branch 'develop' into feature/store-statuses-data-inside-flag
[akkoma] / lib / pleroma / web / admin_api / admin_api_controller.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
4
5 defmodule Pleroma.Web.AdminAPI.AdminAPIController do
6 use Pleroma.Web, :controller
7 alias Pleroma.Activity
8 alias Pleroma.ModerationLog
9 alias Pleroma.Plugs.OAuthScopesPlug
10 alias Pleroma.User
11 alias Pleroma.UserInviteToken
12 alias Pleroma.Web.ActivityPub.ActivityPub
13 alias Pleroma.Web.ActivityPub.Relay
14 alias Pleroma.Web.AdminAPI.AccountView
15 alias Pleroma.Web.AdminAPI.Config
16 alias Pleroma.Web.AdminAPI.ConfigView
17 alias Pleroma.Web.AdminAPI.ModerationLogView
18 alias Pleroma.Web.AdminAPI.Report
19 alias Pleroma.Web.AdminAPI.ReportView
20 alias Pleroma.Web.AdminAPI.Search
21 alias Pleroma.Web.CommonAPI
22 alias Pleroma.Web.Endpoint
23 alias Pleroma.Web.MastodonAPI.StatusView
24 alias Pleroma.Web.Router
25
26 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
27
28 require Logger
29
30 plug(
31 OAuthScopesPlug,
32 %{scopes: ["read:accounts"]}
33 when action in [:list_users, :user_show, :right_get, :invites]
34 )
35
36 plug(
37 OAuthScopesPlug,
38 %{scopes: ["write:accounts"]}
39 when action in [
40 :get_invite_token,
41 :revoke_invite,
42 :email_invite,
43 :get_password_reset,
44 :user_follow,
45 :user_unfollow,
46 :user_delete,
47 :users_create,
48 :user_toggle_activation,
49 :user_activate,
50 :user_deactivate,
51 :tag_users,
52 :untag_users,
53 :right_add,
54 :right_delete
55 ]
56 )
57
58 plug(
59 OAuthScopesPlug,
60 %{scopes: ["read:reports"]} when action in [:list_reports, :report_show]
61 )
62
63 plug(
64 OAuthScopesPlug,
65 %{scopes: ["write:reports"]}
66 when action in [:report_update_state, :report_respond]
67 )
68
69 plug(
70 OAuthScopesPlug,
71 %{scopes: ["read:statuses"]} when action == :list_user_statuses
72 )
73
74 plug(
75 OAuthScopesPlug,
76 %{scopes: ["write:statuses"]}
77 when action in [:status_update, :status_delete]
78 )
79
80 plug(
81 OAuthScopesPlug,
82 %{scopes: ["read"]}
83 when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log]
84 )
85
86 plug(
87 OAuthScopesPlug,
88 %{scopes: ["write"]}
89 when action in [:relay_follow, :relay_unfollow, :config_update]
90 )
91
92 @users_page_size 50
93
94 action_fallback(:errors)
95
96 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
97 user = User.get_cached_by_nickname(nickname)
98 User.delete(user)
99
100 ModerationLog.insert_log(%{
101 actor: admin,
102 subject: [user],
103 action: "delete"
104 })
105
106 conn
107 |> json(nickname)
108 end
109
110 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
111 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
112 User.delete(users)
113
114 ModerationLog.insert_log(%{
115 actor: admin,
116 subject: users,
117 action: "delete"
118 })
119
120 conn
121 |> json(nicknames)
122 end
123
124 def user_follow(%{assigns: %{user: admin}} = conn, %{
125 "follower" => follower_nick,
126 "followed" => followed_nick
127 }) do
128 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
129 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
130 User.follow(follower, followed)
131
132 ModerationLog.insert_log(%{
133 actor: admin,
134 followed: followed,
135 follower: follower,
136 action: "follow"
137 })
138 end
139
140 conn
141 |> json("ok")
142 end
143
144 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
145 "follower" => follower_nick,
146 "followed" => followed_nick
147 }) do
148 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
149 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
150 User.unfollow(follower, followed)
151
152 ModerationLog.insert_log(%{
153 actor: admin,
154 followed: followed,
155 follower: follower,
156 action: "unfollow"
157 })
158 end
159
160 conn
161 |> json("ok")
162 end
163
164 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
165 changesets =
166 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
167 user_data = %{
168 nickname: nickname,
169 name: nickname,
170 email: email,
171 password: password,
172 password_confirmation: password,
173 bio: "."
174 }
175
176 User.register_changeset(%User{}, user_data, need_confirmation: false)
177 end)
178 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
179 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
180 end)
181
182 case Pleroma.Repo.transaction(changesets) do
183 {:ok, users} ->
184 res =
185 users
186 |> Map.values()
187 |> Enum.map(fn user ->
188 {:ok, user} = User.post_register_action(user)
189
190 user
191 end)
192 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
193
194 ModerationLog.insert_log(%{
195 actor: admin,
196 subjects: Map.values(users),
197 action: "create"
198 })
199
200 conn
201 |> json(res)
202
203 {:error, id, changeset, _} ->
204 res =
205 Enum.map(changesets.operations, fn
206 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
207 AccountView.render("create-error.json", %{changeset: changeset})
208
209 {_, {:changeset, current_changeset, _}} ->
210 AccountView.render("create-error.json", %{changeset: current_changeset})
211 end)
212
213 conn
214 |> put_status(:conflict)
215 |> json(res)
216 end
217 end
218
219 def user_show(conn, %{"nickname" => nickname}) do
220 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
221 conn
222 |> put_view(AccountView)
223 |> render("show.json", %{user: user})
224 else
225 _ -> {:error, :not_found}
226 end
227 end
228
229 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
230 godmode = params["godmode"] == "true" || params["godmode"] == true
231
232 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
233 {_, page_size} = page_params(params)
234
235 activities =
236 ActivityPub.fetch_user_activities(user, nil, %{
237 "limit" => page_size,
238 "godmode" => godmode
239 })
240
241 conn
242 |> put_view(StatusView)
243 |> render("index.json", %{activities: activities, as: :activity})
244 else
245 _ -> {:error, :not_found}
246 end
247 end
248
249 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
250 user = User.get_cached_by_nickname(nickname)
251
252 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
253
254 action = if user.deactivated, do: "activate", else: "deactivate"
255
256 ModerationLog.insert_log(%{
257 actor: admin,
258 subject: [user],
259 action: action
260 })
261
262 conn
263 |> put_view(AccountView)
264 |> render("show.json", %{user: updated_user})
265 end
266
267 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
268 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
269 {:ok, updated_users} = User.deactivate(users, false)
270
271 ModerationLog.insert_log(%{
272 actor: admin,
273 subject: users,
274 action: "activate"
275 })
276
277 conn
278 |> put_view(AccountView)
279 |> render("index.json", %{users: Keyword.values(updated_users)})
280 end
281
282 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
283 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
284 {:ok, updated_users} = User.deactivate(users, true)
285
286 ModerationLog.insert_log(%{
287 actor: admin,
288 subject: users,
289 action: "deactivate"
290 })
291
292 conn
293 |> put_view(AccountView)
294 |> render("index.json", %{users: Keyword.values(updated_users)})
295 end
296
297 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
298 with {:ok, _} <- User.tag(nicknames, tags) do
299 ModerationLog.insert_log(%{
300 actor: admin,
301 nicknames: nicknames,
302 tags: tags,
303 action: "tag"
304 })
305
306 json_response(conn, :no_content, "")
307 end
308 end
309
310 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
311 with {:ok, _} <- User.untag(nicknames, tags) do
312 ModerationLog.insert_log(%{
313 actor: admin,
314 nicknames: nicknames,
315 tags: tags,
316 action: "untag"
317 })
318
319 json_response(conn, :no_content, "")
320 end
321 end
322
323 def list_users(conn, params) do
324 {page, page_size} = page_params(params)
325 filters = maybe_parse_filters(params["filters"])
326
327 search_params = %{
328 query: params["query"],
329 page: page,
330 page_size: page_size,
331 tags: params["tags"],
332 name: params["name"],
333 email: params["email"]
334 }
335
336 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
337 do:
338 conn
339 |> json(
340 AccountView.render("index.json",
341 users: users,
342 count: count,
343 page_size: page_size
344 )
345 )
346 end
347
348 @filters ~w(local external active deactivated is_admin is_moderator)
349
350 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
351 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
352
353 defp maybe_parse_filters(filters) do
354 filters
355 |> String.split(",")
356 |> Enum.filter(&Enum.member?(@filters, &1))
357 |> Enum.map(&String.to_atom(&1))
358 |> Enum.into(%{}, &{&1, true})
359 end
360
361 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
362 "permission_group" => permission_group,
363 "nicknames" => nicknames
364 })
365 when permission_group in ["moderator", "admin"] do
366 update = %{:"is_#{permission_group}" => true}
367
368 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
369
370 for u <- users, do: User.admin_api_update(u, update)
371
372 ModerationLog.insert_log(%{
373 action: "grant",
374 actor: admin,
375 subject: users,
376 permission: permission_group
377 })
378
379 json(conn, update)
380 end
381
382 def right_add_multiple(conn, _) do
383 render_error(conn, :not_found, "No such permission_group")
384 end
385
386 def right_add(%{assigns: %{user: admin}} = conn, %{
387 "permission_group" => permission_group,
388 "nickname" => nickname
389 })
390 when permission_group in ["moderator", "admin"] do
391 fields = %{:"is_#{permission_group}" => true}
392
393 {:ok, user} =
394 nickname
395 |> User.get_cached_by_nickname()
396 |> User.admin_api_update(fields)
397
398 ModerationLog.insert_log(%{
399 action: "grant",
400 actor: admin,
401 subject: [user],
402 permission: permission_group
403 })
404
405 json(conn, fields)
406 end
407
408 def right_add(conn, _) do
409 render_error(conn, :not_found, "No such permission_group")
410 end
411
412 def right_get(conn, %{"nickname" => nickname}) do
413 user = User.get_cached_by_nickname(nickname)
414
415 conn
416 |> json(%{
417 is_moderator: user.is_moderator,
418 is_admin: user.is_admin
419 })
420 end
421
422 def right_delete_multiple(
423 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
424 %{
425 "permission_group" => permission_group,
426 "nicknames" => nicknames
427 }
428 )
429 when permission_group in ["moderator", "admin"] do
430 with false <- Enum.member?(nicknames, admin_nickname) do
431 update = %{:"is_#{permission_group}" => false}
432
433 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
434
435 for u <- users, do: User.admin_api_update(u, update)
436
437 ModerationLog.insert_log(%{
438 action: "revoke",
439 actor: admin,
440 subject: users,
441 permission: permission_group
442 })
443
444 json(conn, update)
445 else
446 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
447 end
448 end
449
450 def right_delete_multiple(conn, _) do
451 render_error(conn, :not_found, "No such permission_group")
452 end
453
454 def right_delete(
455 %{assigns: %{user: admin}} = conn,
456 %{
457 "permission_group" => permission_group,
458 "nickname" => nickname
459 }
460 )
461 when permission_group in ["moderator", "admin"] do
462 fields = %{:"is_#{permission_group}" => false}
463
464 {:ok, user} =
465 nickname
466 |> User.get_cached_by_nickname()
467 |> User.admin_api_update(fields)
468
469 ModerationLog.insert_log(%{
470 action: "revoke",
471 actor: admin,
472 subject: [user],
473 permission: permission_group
474 })
475
476 json(conn, fields)
477 end
478
479 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
480 render_error(conn, :forbidden, "You can't revoke your own admin status.")
481 end
482
483 def relay_list(conn, _params) do
484 with {:ok, list} <- Relay.list() do
485 json(conn, %{relays: list})
486 else
487 _ ->
488 conn
489 |> put_status(500)
490 end
491 end
492
493 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
494 with {:ok, _message} <- Relay.follow(target) do
495 ModerationLog.insert_log(%{
496 action: "relay_follow",
497 actor: admin,
498 target: target
499 })
500
501 json(conn, target)
502 else
503 _ ->
504 conn
505 |> put_status(500)
506 |> json(target)
507 end
508 end
509
510 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
511 with {:ok, _message} <- Relay.unfollow(target) do
512 ModerationLog.insert_log(%{
513 action: "relay_unfollow",
514 actor: admin,
515 target: target
516 })
517
518 json(conn, target)
519 else
520 _ ->
521 conn
522 |> put_status(500)
523 |> json(target)
524 end
525 end
526
527 @doc "Sends registration invite via email"
528 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
529 with true <-
530 Pleroma.Config.get([:instance, :invites_enabled]) &&
531 !Pleroma.Config.get([:instance, :registrations_open]),
532 {:ok, invite_token} <- UserInviteToken.create_invite(),
533 email <-
534 Pleroma.Emails.UserEmail.user_invitation_email(
535 user,
536 invite_token,
537 email,
538 params["name"]
539 ),
540 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
541 json_response(conn, :no_content, "")
542 end
543 end
544
545 @doc "Create an account registration invite token"
546 def create_invite_token(conn, params) do
547 opts = %{}
548
549 opts =
550 if params["max_use"],
551 do: Map.put(opts, :max_use, params["max_use"]),
552 else: opts
553
554 opts =
555 if params["expires_at"],
556 do: Map.put(opts, :expires_at, params["expires_at"]),
557 else: opts
558
559 {:ok, invite} = UserInviteToken.create_invite(opts)
560
561 json(conn, AccountView.render("invite.json", %{invite: invite}))
562 end
563
564 @doc "Get list of created invites"
565 def invites(conn, _params) do
566 invites = UserInviteToken.list_invites()
567
568 conn
569 |> put_view(AccountView)
570 |> render("invites.json", %{invites: invites})
571 end
572
573 @doc "Revokes invite by token"
574 def revoke_invite(conn, %{"token" => token}) do
575 with {:ok, invite} <- UserInviteToken.find_by_token(token),
576 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
577 conn
578 |> put_view(AccountView)
579 |> render("invite.json", %{invite: updated_invite})
580 else
581 nil -> {:error, :not_found}
582 end
583 end
584
585 @doc "Get a password reset token (base64 string) for given nickname"
586 def get_password_reset(conn, %{"nickname" => nickname}) do
587 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
588 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
589
590 conn
591 |> json(%{
592 token: token.token,
593 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
594 })
595 end
596
597 @doc "Force password reset for a given user"
598 def force_password_reset(conn, %{"nickname" => nickname}) do
599 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
600
601 User.force_password_reset_async(user)
602
603 json_response(conn, :no_content, "")
604 end
605
606 def list_reports(conn, params) do
607 {page, page_size} = page_params(params)
608
609 params =
610 params
611 |> Map.put("type", "Flag")
612 |> Map.put("skip_preload", true)
613 |> Map.put("total", true)
614 |> Map.put("limit", page_size)
615 |> Map.put("offset", (page - 1) * page_size)
616
617 reports = ActivityPub.fetch_activities([], params, :offset)
618
619 conn
620 |> put_view(ReportView)
621 |> render("index.json", %{reports: reports})
622 end
623
624 def report_show(conn, %{"id" => id}) do
625 with %Activity{} = report <- Activity.get_by_id(id) do
626 conn
627 |> put_view(ReportView)
628 |> render("show.json", Report.extract_report_info(report))
629 else
630 _ -> {:error, :not_found}
631 end
632 end
633
634 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
635 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
636 ModerationLog.insert_log(%{
637 action: "report_update",
638 actor: admin,
639 subject: report
640 })
641
642 conn
643 |> put_view(ReportView)
644 |> render("show.json", Report.extract_report_info(report))
645 end
646 end
647
648 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
649 with false <- is_nil(params["status"]),
650 %Activity{} <- Activity.get_by_id(id) do
651 params =
652 params
653 |> Map.put("in_reply_to_status_id", id)
654 |> Map.put("visibility", "direct")
655
656 {:ok, activity} = CommonAPI.post(user, params)
657
658 ModerationLog.insert_log(%{
659 action: "report_response",
660 actor: user,
661 subject: activity,
662 text: params["status"]
663 })
664
665 conn
666 |> put_view(StatusView)
667 |> render("show.json", %{activity: activity})
668 else
669 true ->
670 {:param_cast, nil}
671
672 nil ->
673 {:error, :not_found}
674 end
675 end
676
677 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
678 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
679 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
680
681 ModerationLog.insert_log(%{
682 action: "status_update",
683 actor: admin,
684 subject: activity,
685 sensitive: sensitive,
686 visibility: params["visibility"]
687 })
688
689 conn
690 |> put_view(StatusView)
691 |> render("show.json", %{activity: activity})
692 end
693 end
694
695 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
696 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
697 ModerationLog.insert_log(%{
698 action: "status_delete",
699 actor: user,
700 subject_id: id
701 })
702
703 json(conn, %{})
704 end
705 end
706
707 def list_log(conn, params) do
708 {page, page_size} = page_params(params)
709
710 log =
711 ModerationLog.get_all(%{
712 page: page,
713 page_size: page_size,
714 start_date: params["start_date"],
715 end_date: params["end_date"],
716 user_id: params["user_id"],
717 search: params["search"]
718 })
719
720 conn
721 |> put_view(ModerationLogView)
722 |> render("index.json", %{log: log})
723 end
724
725 def migrate_to_db(conn, _params) do
726 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
727 json(conn, %{})
728 end
729
730 def migrate_from_db(conn, _params) do
731 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
732 json(conn, %{})
733 end
734
735 def config_show(conn, _params) do
736 configs = Pleroma.Repo.all(Config)
737
738 conn
739 |> put_view(ConfigView)
740 |> render("index.json", %{configs: configs})
741 end
742
743 def config_update(conn, %{"configs" => configs}) do
744 updated =
745 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
746 updated =
747 Enum.map(configs, fn
748 %{"group" => group, "key" => key, "delete" => "true"} = params ->
749 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
750 config
751
752 %{"group" => group, "key" => key, "value" => value} ->
753 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
754 config
755 end)
756 |> Enum.reject(&is_nil(&1))
757
758 Pleroma.Config.TransferTask.load_and_update_env()
759 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
760 updated
761 else
762 []
763 end
764
765 conn
766 |> put_view(ConfigView)
767 |> render("index.json", %{configs: updated})
768 end
769
770 def reload_emoji(conn, _params) do
771 Pleroma.Emoji.reload()
772
773 conn |> json("ok")
774 end
775
776 def errors(conn, {:error, :not_found}) do
777 conn
778 |> put_status(:not_found)
779 |> json(dgettext("errors", "Not found"))
780 end
781
782 def errors(conn, {:error, reason}) do
783 conn
784 |> put_status(:bad_request)
785 |> json(reason)
786 end
787
788 def errors(conn, {:param_cast, _}) do
789 conn
790 |> put_status(:bad_request)
791 |> json(dgettext("errors", "Invalid parameters"))
792 end
793
794 def errors(conn, _) do
795 conn
796 |> put_status(:internal_server_error)
797 |> json(dgettext("errors", "Something went wrong"))
798 end
799
800 defp page_params(params) do
801 {get_page(params["page"]), get_page_size(params["page_size"])}
802 end
803
804 defp get_page(page_string) when is_nil(page_string), do: 1
805
806 defp get_page(page_string) do
807 case Integer.parse(page_string) do
808 {page, _} -> page
809 :error -> 1
810 end
811 end
812
813 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
814
815 defp get_page_size(page_size_string) do
816 case Integer.parse(page_size_string) do
817 {page_size, _} -> page_size
818 :error -> @users_page_size
819 end
820 end
821 end