d825a5d281dd45d559cd4d16a3357d026b3216ab
[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_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
235 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
236 {:ok, updated_users} = User.deactivate(users, false)
237
238 ModerationLog.insert_log(%{
239 actor: admin,
240 subject: users,
241 action: "activate"
242 })
243
244 conn
245 |> put_view(AccountView)
246 |> render("index.json", %{users: Keyword.values(updated_users)})
247 end
248
249 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
250 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
251 {:ok, updated_users} = User.deactivate(users, true)
252
253 ModerationLog.insert_log(%{
254 actor: admin,
255 subject: users,
256 action: "deactivate"
257 })
258
259 conn
260 |> put_view(AccountView)
261 |> render("index.json", %{users: Keyword.values(updated_users)})
262 end
263
264 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
265 with {:ok, _} <- User.tag(nicknames, tags) do
266 ModerationLog.insert_log(%{
267 actor: admin,
268 nicknames: nicknames,
269 tags: tags,
270 action: "tag"
271 })
272
273 json_response(conn, :no_content, "")
274 end
275 end
276
277 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
278 with {:ok, _} <- User.untag(nicknames, tags) do
279 ModerationLog.insert_log(%{
280 actor: admin,
281 nicknames: nicknames,
282 tags: tags,
283 action: "untag"
284 })
285
286 json_response(conn, :no_content, "")
287 end
288 end
289
290 def list_users(conn, params) do
291 {page, page_size} = page_params(params)
292 filters = maybe_parse_filters(params["filters"])
293
294 search_params = %{
295 query: params["query"],
296 page: page,
297 page_size: page_size,
298 tags: params["tags"],
299 name: params["name"],
300 email: params["email"]
301 }
302
303 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
304 do:
305 conn
306 |> json(
307 AccountView.render("index.json",
308 users: users,
309 count: count,
310 page_size: page_size
311 )
312 )
313 end
314
315 @filters ~w(local external active deactivated is_admin is_moderator)
316
317 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
318 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
319
320 defp maybe_parse_filters(filters) do
321 filters
322 |> String.split(",")
323 |> Enum.filter(&Enum.member?(@filters, &1))
324 |> Enum.map(&String.to_atom(&1))
325 |> Enum.into(%{}, &{&1, true})
326 end
327
328 def right_add(%{assigns: %{user: admin}} = conn, %{
329 "permission_group" => permission_group,
330 "nicknames" => nicknames
331 })
332 when permission_group in ["moderator", "admin"] do
333 info = Map.put(%{}, "is_" <> permission_group, true)
334
335 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
336
337 User.update_info(users, &User.Info.admin_api_update(&1, info))
338
339 ModerationLog.insert_log(%{
340 action: "grant",
341 actor: admin,
342 subject: users,
343 permission: permission_group
344 })
345
346 json(conn, info)
347 end
348
349 def right_add(conn, _) do
350 render_error(conn, :not_found, "No such permission_group")
351 end
352
353 def right_get(conn, %{"nickname" => nickname}) do
354 user = User.get_cached_by_nickname(nickname)
355
356 conn
357 |> json(%{
358 is_moderator: user.info.is_moderator,
359 is_admin: user.info.is_admin
360 })
361 end
362
363 def right_delete(
364 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
365 %{
366 "permission_group" => permission_group,
367 "nicknames" => nicknames
368 }
369 )
370 when permission_group in ["moderator", "admin"] do
371 with false <- Enum.member?(nicknames, admin_nickname) do
372 info = Map.put(%{}, "is_" <> permission_group, false)
373
374 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
375
376 User.update_info(users, &User.Info.admin_api_update(&1, info))
377
378 ModerationLog.insert_log(%{
379 action: "revoke",
380 actor: admin,
381 subject: users,
382 permission: permission_group
383 })
384
385 json(conn, info)
386 else
387 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
388 end
389 end
390
391 def right_delete(conn, _) do
392 render_error(conn, :not_found, "No such permission_group")
393 end
394
395 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
396 with {:ok, _message} <- Relay.follow(target) do
397 ModerationLog.insert_log(%{
398 action: "relay_follow",
399 actor: admin,
400 target: target
401 })
402
403 json(conn, target)
404 else
405 _ ->
406 conn
407 |> put_status(500)
408 |> json(target)
409 end
410 end
411
412 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
413 with {:ok, _message} <- Relay.unfollow(target) do
414 ModerationLog.insert_log(%{
415 action: "relay_unfollow",
416 actor: admin,
417 target: target
418 })
419
420 json(conn, target)
421 else
422 _ ->
423 conn
424 |> put_status(500)
425 |> json(target)
426 end
427 end
428
429 @doc "Sends registration invite via email"
430 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
431 with true <-
432 Pleroma.Config.get([:instance, :invites_enabled]) &&
433 !Pleroma.Config.get([:instance, :registrations_open]),
434 {:ok, invite_token} <- UserInviteToken.create_invite(),
435 email <-
436 Pleroma.Emails.UserEmail.user_invitation_email(
437 user,
438 invite_token,
439 email,
440 params["name"]
441 ),
442 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
443 json_response(conn, :no_content, "")
444 end
445 end
446
447 @doc "Create an account registration invite token"
448 def create_invite_token(conn, params) do
449 opts = %{}
450
451 opts =
452 if params["max_use"],
453 do: Map.put(opts, :max_use, params["max_use"]),
454 else: opts
455
456 opts =
457 if params["expires_at"],
458 do: Map.put(opts, :expires_at, params["expires_at"]),
459 else: opts
460
461 {:ok, invite} = UserInviteToken.create_invite(opts)
462
463 json(conn, AccountView.render("invite.json", %{invite: invite}))
464 end
465
466 @doc "Get list of created invites"
467 def invites(conn, _params) do
468 invites = UserInviteToken.list_invites()
469
470 conn
471 |> put_view(AccountView)
472 |> render("invites.json", %{invites: invites})
473 end
474
475 @doc "Revokes invite by token"
476 def revoke_invite(conn, %{"token" => token}) do
477 with {:ok, invite} <- UserInviteToken.find_by_token(token),
478 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
479 conn
480 |> put_view(AccountView)
481 |> render("invite.json", %{invite: updated_invite})
482 else
483 nil -> {:error, :not_found}
484 end
485 end
486
487 @doc "Get a password reset token (base64 string) for given nickname"
488 def get_password_reset(conn, %{"nickname" => nickname}) do
489 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
490 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
491
492 conn
493 |> json(%{
494 token: token.token,
495 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
496 })
497 end
498
499 @doc "Force password reset for a given user"
500 def force_password_reset(conn, %{"nickname" => nickname}) do
501 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
502
503 User.force_password_reset_async(user)
504
505 json_response(conn, :no_content, "")
506 end
507
508 def list_reports(conn, params) do
509 {page, page_size} = page_params(params)
510
511 params =
512 params
513 |> Map.put("type", "Flag")
514 |> Map.put("skip_preload", true)
515 |> Map.put("total", true)
516 |> Map.put("limit", page_size)
517 |> Map.put("offset", (page - 1) * page_size)
518
519 reports = ActivityPub.fetch_activities([], params, :offset)
520
521 conn
522 |> put_view(ReportView)
523 |> render("index.json", %{reports: reports})
524 end
525
526 def report_show(conn, %{"id" => id}) do
527 with %Activity{} = report <- Activity.get_by_id(id) do
528 conn
529 |> put_view(ReportView)
530 |> render("show.json", Report.extract_report_info(report))
531 else
532 _ -> {:error, :not_found}
533 end
534 end
535
536 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
537 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
538 ModerationLog.insert_log(%{
539 action: "report_update",
540 actor: admin,
541 subject: report
542 })
543
544 conn
545 |> put_view(ReportView)
546 |> render("show.json", Report.extract_report_info(report))
547 end
548 end
549
550 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
551 with false <- is_nil(params["status"]),
552 %Activity{} <- Activity.get_by_id(id) do
553 params =
554 params
555 |> Map.put("in_reply_to_status_id", id)
556 |> Map.put("visibility", "direct")
557
558 {:ok, activity} = CommonAPI.post(user, params)
559
560 ModerationLog.insert_log(%{
561 action: "report_response",
562 actor: user,
563 subject: activity,
564 text: params["status"]
565 })
566
567 conn
568 |> put_view(StatusView)
569 |> render("show.json", %{activity: activity})
570 else
571 true ->
572 {:param_cast, nil}
573
574 nil ->
575 {:error, :not_found}
576 end
577 end
578
579 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
580 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
581 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
582
583 ModerationLog.insert_log(%{
584 action: "status_update",
585 actor: admin,
586 subject: activity,
587 sensitive: sensitive,
588 visibility: params["visibility"]
589 })
590
591 conn
592 |> put_view(StatusView)
593 |> render("show.json", %{activity: activity})
594 end
595 end
596
597 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
598 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
599 ModerationLog.insert_log(%{
600 action: "status_delete",
601 actor: user,
602 subject_id: id
603 })
604
605 json(conn, %{})
606 end
607 end
608
609 def list_log(conn, params) do
610 {page, page_size} = page_params(params)
611
612 log =
613 ModerationLog.get_all(%{
614 page: page,
615 page_size: page_size,
616 start_date: params["start_date"],
617 end_date: params["end_date"],
618 user_id: params["user_id"],
619 search: params["search"]
620 })
621
622 conn
623 |> put_view(ModerationLogView)
624 |> render("index.json", %{log: log})
625 end
626
627 def migrate_to_db(conn, _params) do
628 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
629 json(conn, %{})
630 end
631
632 def migrate_from_db(conn, _params) do
633 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
634 json(conn, %{})
635 end
636
637 def config_show(conn, _params) do
638 configs = Pleroma.Repo.all(Config)
639
640 conn
641 |> put_view(ConfigView)
642 |> render("index.json", %{configs: configs})
643 end
644
645 def config_update(conn, %{"configs" => configs}) do
646 updated =
647 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
648 updated =
649 Enum.map(configs, fn
650 %{"group" => group, "key" => key, "delete" => "true"} = params ->
651 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
652 config
653
654 %{"group" => group, "key" => key, "value" => value} ->
655 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
656 config
657 end)
658 |> Enum.reject(&is_nil(&1))
659
660 Pleroma.Config.TransferTask.load_and_update_env()
661 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
662 updated
663 else
664 []
665 end
666
667 conn
668 |> put_view(ConfigView)
669 |> render("index.json", %{configs: updated})
670 end
671
672 def reload_emoji(conn, _params) do
673 Pleroma.Emoji.reload()
674
675 conn |> json("ok")
676 end
677
678 def errors(conn, {:error, :not_found}) do
679 conn
680 |> put_status(:not_found)
681 |> json(dgettext("errors", "Not found"))
682 end
683
684 def errors(conn, {:error, reason}) do
685 conn
686 |> put_status(:bad_request)
687 |> json(reason)
688 end
689
690 def errors(conn, {:param_cast, _}) do
691 conn
692 |> put_status(:bad_request)
693 |> json(dgettext("errors", "Invalid parameters"))
694 end
695
696 def errors(conn, _) do
697 conn
698 |> put_status(:internal_server_error)
699 |> json(dgettext("errors", "Something went wrong"))
700 end
701
702 defp page_params(params) do
703 {get_page(params["page"]), get_page_size(params["page_size"])}
704 end
705
706 defp get_page(page_string) when is_nil(page_string), do: 1
707
708 defp get_page(page_string) do
709 case Integer.parse(page_string) do
710 {page, _} -> page
711 :error -> 1
712 end
713 end
714
715 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
716
717 defp get_page_size(page_size_string) do
718 case Integer.parse(page_size_string) do
719 {page_size, _} -> page_size
720 :error -> @users_page_size
721 end
722 end
723 end