4533d0114ac72a48ced1840fbac1421984f2268f
[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 {:ok, users, count} <- filter_relay_user(users),
338 do:
339 conn
340 |> json(
341 AccountView.render("index.json",
342 users: users,
343 count: count,
344 page_size: page_size
345 )
346 )
347 end
348
349 defp filter_relay_user(users) do
350 filtered_users = Enum.reject(users, &(&1.ap_id == Relay.relay_ap_id()))
351
352 {:ok, filtered_users, length(filtered_users)}
353 end
354
355 @filters ~w(local external active deactivated is_admin is_moderator)
356
357 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
358 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
359
360 defp maybe_parse_filters(filters) do
361 filters
362 |> String.split(",")
363 |> Enum.filter(&Enum.member?(@filters, &1))
364 |> Enum.map(&String.to_atom(&1))
365 |> Enum.into(%{}, &{&1, true})
366 end
367
368 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
369 "permission_group" => permission_group,
370 "nicknames" => nicknames
371 })
372 when permission_group in ["moderator", "admin"] do
373 update = %{:"is_#{permission_group}" => true}
374
375 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
376
377 for u <- users, do: User.admin_api_update(u, update)
378
379 ModerationLog.insert_log(%{
380 action: "grant",
381 actor: admin,
382 subject: users,
383 permission: permission_group
384 })
385
386 json(conn, update)
387 end
388
389 def right_add_multiple(conn, _) do
390 render_error(conn, :not_found, "No such permission_group")
391 end
392
393 def right_add(%{assigns: %{user: admin}} = conn, %{
394 "permission_group" => permission_group,
395 "nickname" => nickname
396 })
397 when permission_group in ["moderator", "admin"] do
398 fields = %{:"is_#{permission_group}" => true}
399
400 {:ok, user} =
401 nickname
402 |> User.get_cached_by_nickname()
403 |> User.admin_api_update(fields)
404
405 ModerationLog.insert_log(%{
406 action: "grant",
407 actor: admin,
408 subject: [user],
409 permission: permission_group
410 })
411
412 json(conn, fields)
413 end
414
415 def right_add(conn, _) do
416 render_error(conn, :not_found, "No such permission_group")
417 end
418
419 def right_get(conn, %{"nickname" => nickname}) do
420 user = User.get_cached_by_nickname(nickname)
421
422 conn
423 |> json(%{
424 is_moderator: user.is_moderator,
425 is_admin: user.is_admin
426 })
427 end
428
429 def right_delete_multiple(
430 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
431 %{
432 "permission_group" => permission_group,
433 "nicknames" => nicknames
434 }
435 )
436 when permission_group in ["moderator", "admin"] do
437 with false <- Enum.member?(nicknames, admin_nickname) do
438 update = %{:"is_#{permission_group}" => false}
439
440 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
441
442 for u <- users, do: User.admin_api_update(u, update)
443
444 ModerationLog.insert_log(%{
445 action: "revoke",
446 actor: admin,
447 subject: users,
448 permission: permission_group
449 })
450
451 json(conn, update)
452 else
453 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
454 end
455 end
456
457 def right_delete_multiple(conn, _) do
458 render_error(conn, :not_found, "No such permission_group")
459 end
460
461 def right_delete(
462 %{assigns: %{user: admin}} = conn,
463 %{
464 "permission_group" => permission_group,
465 "nickname" => nickname
466 }
467 )
468 when permission_group in ["moderator", "admin"] do
469 fields = %{:"is_#{permission_group}" => false}
470
471 {:ok, user} =
472 nickname
473 |> User.get_cached_by_nickname()
474 |> User.admin_api_update(fields)
475
476 ModerationLog.insert_log(%{
477 action: "revoke",
478 actor: admin,
479 subject: [user],
480 permission: permission_group
481 })
482
483 json(conn, fields)
484 end
485
486 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
487 render_error(conn, :forbidden, "You can't revoke your own admin status.")
488 end
489
490 def relay_list(conn, _params) do
491 with {:ok, list} <- Relay.list() do
492 json(conn, %{relays: list})
493 else
494 _ ->
495 conn
496 |> put_status(500)
497 end
498 end
499
500 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
501 with {:ok, _message} <- Relay.follow(target) do
502 ModerationLog.insert_log(%{
503 action: "relay_follow",
504 actor: admin,
505 target: target
506 })
507
508 json(conn, target)
509 else
510 _ ->
511 conn
512 |> put_status(500)
513 |> json(target)
514 end
515 end
516
517 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
518 with {:ok, _message} <- Relay.unfollow(target) do
519 ModerationLog.insert_log(%{
520 action: "relay_unfollow",
521 actor: admin,
522 target: target
523 })
524
525 json(conn, target)
526 else
527 _ ->
528 conn
529 |> put_status(500)
530 |> json(target)
531 end
532 end
533
534 @doc "Sends registration invite via email"
535 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
536 with true <-
537 Pleroma.Config.get([:instance, :invites_enabled]) &&
538 !Pleroma.Config.get([:instance, :registrations_open]),
539 {:ok, invite_token} <- UserInviteToken.create_invite(),
540 email <-
541 Pleroma.Emails.UserEmail.user_invitation_email(
542 user,
543 invite_token,
544 email,
545 params["name"]
546 ),
547 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
548 json_response(conn, :no_content, "")
549 end
550 end
551
552 @doc "Create an account registration invite token"
553 def create_invite_token(conn, params) do
554 opts = %{}
555
556 opts =
557 if params["max_use"],
558 do: Map.put(opts, :max_use, params["max_use"]),
559 else: opts
560
561 opts =
562 if params["expires_at"],
563 do: Map.put(opts, :expires_at, params["expires_at"]),
564 else: opts
565
566 {:ok, invite} = UserInviteToken.create_invite(opts)
567
568 json(conn, AccountView.render("invite.json", %{invite: invite}))
569 end
570
571 @doc "Get list of created invites"
572 def invites(conn, _params) do
573 invites = UserInviteToken.list_invites()
574
575 conn
576 |> put_view(AccountView)
577 |> render("invites.json", %{invites: invites})
578 end
579
580 @doc "Revokes invite by token"
581 def revoke_invite(conn, %{"token" => token}) do
582 with {:ok, invite} <- UserInviteToken.find_by_token(token),
583 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
584 conn
585 |> put_view(AccountView)
586 |> render("invite.json", %{invite: updated_invite})
587 else
588 nil -> {:error, :not_found}
589 end
590 end
591
592 @doc "Get a password reset token (base64 string) for given nickname"
593 def get_password_reset(conn, %{"nickname" => nickname}) do
594 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
595 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
596
597 conn
598 |> json(%{
599 token: token.token,
600 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
601 })
602 end
603
604 @doc "Force password reset for a given user"
605 def force_password_reset(conn, %{"nickname" => nickname}) do
606 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
607
608 User.force_password_reset_async(user)
609
610 json_response(conn, :no_content, "")
611 end
612
613 def list_reports(conn, params) do
614 {page, page_size} = page_params(params)
615
616 params =
617 params
618 |> Map.put("type", "Flag")
619 |> Map.put("skip_preload", true)
620 |> Map.put("total", true)
621 |> Map.put("limit", page_size)
622 |> Map.put("offset", (page - 1) * page_size)
623
624 reports = ActivityPub.fetch_activities([], params, :offset)
625
626 conn
627 |> put_view(ReportView)
628 |> render("index.json", %{reports: reports})
629 end
630
631 def report_show(conn, %{"id" => id}) do
632 with %Activity{} = report <- Activity.get_by_id(id) do
633 conn
634 |> put_view(ReportView)
635 |> render("show.json", Report.extract_report_info(report))
636 else
637 _ -> {:error, :not_found}
638 end
639 end
640
641 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
642 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
643 ModerationLog.insert_log(%{
644 action: "report_update",
645 actor: admin,
646 subject: report
647 })
648
649 conn
650 |> put_view(ReportView)
651 |> render("show.json", Report.extract_report_info(report))
652 end
653 end
654
655 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
656 with false <- is_nil(params["status"]),
657 %Activity{} <- Activity.get_by_id(id) do
658 params =
659 params
660 |> Map.put("in_reply_to_status_id", id)
661 |> Map.put("visibility", "direct")
662
663 {:ok, activity} = CommonAPI.post(user, params)
664
665 ModerationLog.insert_log(%{
666 action: "report_response",
667 actor: user,
668 subject: activity,
669 text: params["status"]
670 })
671
672 conn
673 |> put_view(StatusView)
674 |> render("show.json", %{activity: activity})
675 else
676 true ->
677 {:param_cast, nil}
678
679 nil ->
680 {:error, :not_found}
681 end
682 end
683
684 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
685 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
686 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
687
688 ModerationLog.insert_log(%{
689 action: "status_update",
690 actor: admin,
691 subject: activity,
692 sensitive: sensitive,
693 visibility: params["visibility"]
694 })
695
696 conn
697 |> put_view(StatusView)
698 |> render("show.json", %{activity: activity})
699 end
700 end
701
702 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
703 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
704 ModerationLog.insert_log(%{
705 action: "status_delete",
706 actor: user,
707 subject_id: id
708 })
709
710 json(conn, %{})
711 end
712 end
713
714 def list_log(conn, params) do
715 {page, page_size} = page_params(params)
716
717 log =
718 ModerationLog.get_all(%{
719 page: page,
720 page_size: page_size,
721 start_date: params["start_date"],
722 end_date: params["end_date"],
723 user_id: params["user_id"],
724 search: params["search"]
725 })
726
727 conn
728 |> put_view(ModerationLogView)
729 |> render("index.json", %{log: log})
730 end
731
732 def migrate_to_db(conn, _params) do
733 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
734 json(conn, %{})
735 end
736
737 def migrate_from_db(conn, _params) do
738 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
739 json(conn, %{})
740 end
741
742 def config_show(conn, _params) do
743 configs = Pleroma.Repo.all(Config)
744
745 conn
746 |> put_view(ConfigView)
747 |> render("index.json", %{configs: configs})
748 end
749
750 def config_update(conn, %{"configs" => configs}) do
751 updated =
752 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
753 updated =
754 Enum.map(configs, fn
755 %{"group" => group, "key" => key, "delete" => "true"} = params ->
756 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
757 config
758
759 %{"group" => group, "key" => key, "value" => value} ->
760 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
761 config
762 end)
763 |> Enum.reject(&is_nil(&1))
764
765 Pleroma.Config.TransferTask.load_and_update_env()
766 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
767 updated
768 else
769 []
770 end
771
772 conn
773 |> put_view(ConfigView)
774 |> render("index.json", %{configs: updated})
775 end
776
777 def reload_emoji(conn, _params) do
778 Pleroma.Emoji.reload()
779
780 conn |> json("ok")
781 end
782
783 def errors(conn, {:error, :not_found}) do
784 conn
785 |> put_status(:not_found)
786 |> json(dgettext("errors", "Not found"))
787 end
788
789 def errors(conn, {:error, reason}) do
790 conn
791 |> put_status(:bad_request)
792 |> json(reason)
793 end
794
795 def errors(conn, {:param_cast, _}) do
796 conn
797 |> put_status(:bad_request)
798 |> json(dgettext("errors", "Invalid parameters"))
799 end
800
801 def errors(conn, _) do
802 conn
803 |> put_status(:internal_server_error)
804 |> json(dgettext("errors", "Something went wrong"))
805 end
806
807 defp page_params(params) do
808 {get_page(params["page"]), get_page_size(params["page_size"])}
809 end
810
811 defp get_page(page_string) when is_nil(page_string), do: 1
812
813 defp get_page(page_string) do
814 case Integer.parse(page_string) do
815 {page, _} -> page
816 :error -> 1
817 end
818 end
819
820 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
821
822 defp get_page_size(page_size_string) do
823 case Integer.parse(page_size_string) do
824 {page_size, _} -> page_size
825 :error -> @users_page_size
826 end
827 end
828 end