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