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