Merge branch 'exclude-visibilities-for-timelines' into 'develop'
[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_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
405 with {:ok, _message} <- Relay.follow(target) do
406 ModerationLog.insert_log(%{
407 action: "relay_follow",
408 actor: admin,
409 target: target
410 })
411
412 json(conn, target)
413 else
414 _ ->
415 conn
416 |> put_status(500)
417 |> json(target)
418 end
419 end
420
421 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
422 with {:ok, _message} <- Relay.unfollow(target) do
423 ModerationLog.insert_log(%{
424 action: "relay_unfollow",
425 actor: admin,
426 target: target
427 })
428
429 json(conn, target)
430 else
431 _ ->
432 conn
433 |> put_status(500)
434 |> json(target)
435 end
436 end
437
438 @doc "Sends registration invite via email"
439 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
440 with true <-
441 Pleroma.Config.get([:instance, :invites_enabled]) &&
442 !Pleroma.Config.get([:instance, :registrations_open]),
443 {:ok, invite_token} <- UserInviteToken.create_invite(),
444 email <-
445 Pleroma.Emails.UserEmail.user_invitation_email(
446 user,
447 invite_token,
448 email,
449 params["name"]
450 ),
451 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
452 json_response(conn, :no_content, "")
453 end
454 end
455
456 @doc "Create an account registration invite token"
457 def create_invite_token(conn, params) do
458 opts = %{}
459
460 opts =
461 if params["max_use"],
462 do: Map.put(opts, :max_use, params["max_use"]),
463 else: opts
464
465 opts =
466 if params["expires_at"],
467 do: Map.put(opts, :expires_at, params["expires_at"]),
468 else: opts
469
470 {:ok, invite} = UserInviteToken.create_invite(opts)
471
472 json(conn, AccountView.render("invite.json", %{invite: invite}))
473 end
474
475 @doc "Get list of created invites"
476 def invites(conn, _params) do
477 invites = UserInviteToken.list_invites()
478
479 conn
480 |> put_view(AccountView)
481 |> render("invites.json", %{invites: invites})
482 end
483
484 @doc "Revokes invite by token"
485 def revoke_invite(conn, %{"token" => token}) do
486 with {:ok, invite} <- UserInviteToken.find_by_token(token),
487 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
488 conn
489 |> put_view(AccountView)
490 |> render("invite.json", %{invite: updated_invite})
491 else
492 nil -> {:error, :not_found}
493 end
494 end
495
496 @doc "Get a password reset token (base64 string) for given nickname"
497 def get_password_reset(conn, %{"nickname" => nickname}) do
498 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
499 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
500
501 conn
502 |> json(%{
503 token: token.token,
504 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
505 })
506 end
507
508 @doc "Force password reset for a given user"
509 def force_password_reset(conn, %{"nickname" => nickname}) do
510 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
511
512 User.force_password_reset_async(user)
513
514 json_response(conn, :no_content, "")
515 end
516
517 def list_reports(conn, params) do
518 {page, page_size} = page_params(params)
519
520 params =
521 params
522 |> Map.put("type", "Flag")
523 |> Map.put("skip_preload", true)
524 |> Map.put("total", true)
525 |> Map.put("limit", page_size)
526 |> Map.put("offset", (page - 1) * page_size)
527
528 reports = ActivityPub.fetch_activities([], params, :offset)
529
530 conn
531 |> put_view(ReportView)
532 |> render("index.json", %{reports: reports})
533 end
534
535 def report_show(conn, %{"id" => id}) do
536 with %Activity{} = report <- Activity.get_by_id(id) do
537 conn
538 |> put_view(ReportView)
539 |> render("show.json", Report.extract_report_info(report))
540 else
541 _ -> {:error, :not_found}
542 end
543 end
544
545 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
546 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
547 ModerationLog.insert_log(%{
548 action: "report_update",
549 actor: admin,
550 subject: report
551 })
552
553 conn
554 |> put_view(ReportView)
555 |> render("show.json", Report.extract_report_info(report))
556 end
557 end
558
559 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
560 with false <- is_nil(params["status"]),
561 %Activity{} <- Activity.get_by_id(id) do
562 params =
563 params
564 |> Map.put("in_reply_to_status_id", id)
565 |> Map.put("visibility", "direct")
566
567 {:ok, activity} = CommonAPI.post(user, params)
568
569 ModerationLog.insert_log(%{
570 action: "report_response",
571 actor: user,
572 subject: activity,
573 text: params["status"]
574 })
575
576 conn
577 |> put_view(StatusView)
578 |> render("show.json", %{activity: activity})
579 else
580 true ->
581 {:param_cast, nil}
582
583 nil ->
584 {:error, :not_found}
585 end
586 end
587
588 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
589 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
590 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
591
592 ModerationLog.insert_log(%{
593 action: "status_update",
594 actor: admin,
595 subject: activity,
596 sensitive: sensitive,
597 visibility: params["visibility"]
598 })
599
600 conn
601 |> put_view(StatusView)
602 |> render("show.json", %{activity: activity})
603 end
604 end
605
606 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
607 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
608 ModerationLog.insert_log(%{
609 action: "status_delete",
610 actor: user,
611 subject_id: id
612 })
613
614 json(conn, %{})
615 end
616 end
617
618 def list_log(conn, params) do
619 {page, page_size} = page_params(params)
620
621 log =
622 ModerationLog.get_all(%{
623 page: page,
624 page_size: page_size,
625 start_date: params["start_date"],
626 end_date: params["end_date"],
627 user_id: params["user_id"],
628 search: params["search"]
629 })
630
631 conn
632 |> put_view(ModerationLogView)
633 |> render("index.json", %{log: log})
634 end
635
636 def migrate_to_db(conn, _params) do
637 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
638 json(conn, %{})
639 end
640
641 def migrate_from_db(conn, _params) do
642 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
643 json(conn, %{})
644 end
645
646 def config_show(conn, _params) do
647 configs = Pleroma.Repo.all(Config)
648
649 conn
650 |> put_view(ConfigView)
651 |> render("index.json", %{configs: configs})
652 end
653
654 def config_update(conn, %{"configs" => configs}) do
655 updated =
656 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
657 updated =
658 Enum.map(configs, fn
659 %{"group" => group, "key" => key, "delete" => "true"} = params ->
660 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
661 config
662
663 %{"group" => group, "key" => key, "value" => value} ->
664 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
665 config
666 end)
667 |> Enum.reject(&is_nil(&1))
668
669 Pleroma.Config.TransferTask.load_and_update_env()
670 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
671 updated
672 else
673 []
674 end
675
676 conn
677 |> put_view(ConfigView)
678 |> render("index.json", %{configs: updated})
679 end
680
681 def reload_emoji(conn, _params) do
682 Pleroma.Emoji.reload()
683
684 conn |> json("ok")
685 end
686
687 def errors(conn, {:error, :not_found}) do
688 conn
689 |> put_status(:not_found)
690 |> json(dgettext("errors", "Not found"))
691 end
692
693 def errors(conn, {:error, reason}) do
694 conn
695 |> put_status(:bad_request)
696 |> json(reason)
697 end
698
699 def errors(conn, {:param_cast, _}) do
700 conn
701 |> put_status(:bad_request)
702 |> json(dgettext("errors", "Invalid parameters"))
703 end
704
705 def errors(conn, _) do
706 conn
707 |> put_status(:internal_server_error)
708 |> json(dgettext("errors", "Something went wrong"))
709 end
710
711 defp page_params(params) do
712 {get_page(params["page"]), get_page_size(params["page_size"])}
713 end
714
715 defp get_page(page_string) when is_nil(page_string), do: 1
716
717 defp get_page(page_string) do
718 case Integer.parse(page_string) do
719 {page, _} -> page
720 :error -> 1
721 end
722 end
723
724 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
725
726 defp get_page_size(page_size_string) do
727 case Integer.parse(page_size_string) do
728 {page_size, _} -> page_size
729 :error -> @users_page_size
730 end
731 end
732 end