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