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