Refactor with Chat.for_user_query/1
[akkoma] / lib / pleroma / web / admin_api / controllers / admin_api_controller.ex
1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 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
8 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
9
10 alias Pleroma.Config
11 alias Pleroma.MFA
12 alias Pleroma.ModerationLog
13 alias Pleroma.Plugs.OAuthScopesPlug
14 alias Pleroma.Stats
15 alias Pleroma.User
16 alias Pleroma.Web.ActivityPub.ActivityPub
17 alias Pleroma.Web.ActivityPub.Builder
18 alias Pleroma.Web.ActivityPub.Pipeline
19 alias Pleroma.Web.AdminAPI
20 alias Pleroma.Web.AdminAPI.AccountView
21 alias Pleroma.Web.AdminAPI.ModerationLogView
22 alias Pleroma.Web.AdminAPI.Search
23 alias Pleroma.Web.Endpoint
24 alias Pleroma.Web.PleromaAPI
25 alias Pleroma.Web.Router
26
27 require Logger
28
29 @users_page_size 50
30
31 plug(
32 OAuthScopesPlug,
33 %{scopes: ["read:accounts"], admin: true}
34 when action in [:list_users, :user_show, :right_get, :show_user_credentials]
35 )
36
37 plug(
38 OAuthScopesPlug,
39 %{scopes: ["write:accounts"], admin: true}
40 when action in [
41 :get_password_reset,
42 :force_password_reset,
43 :user_delete,
44 :users_create,
45 :user_toggle_activation,
46 :user_activate,
47 :user_deactivate,
48 :user_approve,
49 :tag_users,
50 :untag_users,
51 :right_add,
52 :right_add_multiple,
53 :right_delete,
54 :disable_mfa,
55 :right_delete_multiple,
56 :update_user_credentials
57 ]
58 )
59
60 plug(
61 OAuthScopesPlug,
62 %{scopes: ["write:follows"], admin: true}
63 when action in [:user_follow, :user_unfollow]
64 )
65
66 plug(
67 OAuthScopesPlug,
68 %{scopes: ["read:statuses"], admin: true}
69 when action in [:list_user_statuses, :list_instance_statuses]
70 )
71
72 plug(
73 OAuthScopesPlug,
74 %{scopes: ["read:chats"], admin: true}
75 when action in [:list_user_chats]
76 )
77
78 plug(
79 OAuthScopesPlug,
80 %{scopes: ["read"], admin: true}
81 when action in [
82 :list_log,
83 :stats,
84 :need_reboot
85 ]
86 )
87
88 plug(
89 OAuthScopesPlug,
90 %{scopes: ["write"], admin: true}
91 when action in [
92 :restart,
93 :resend_confirmation_email,
94 :confirm_email,
95 :reload_emoji
96 ]
97 )
98
99 action_fallback(AdminAPI.FallbackController)
100
101 def user_delete(conn, %{"nickname" => nickname}) do
102 user_delete(conn, %{"nicknames" => [nickname]})
103 end
104
105 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
106 users =
107 nicknames
108 |> Enum.map(&User.get_cached_by_nickname/1)
109
110 users
111 |> Enum.each(fn user ->
112 {:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
113 Pipeline.common_pipeline(delete_data, local: true)
114 end)
115
116 ModerationLog.insert_log(%{
117 actor: admin,
118 subject: users,
119 action: "delete"
120 })
121
122 json(conn, nicknames)
123 end
124
125 def user_follow(%{assigns: %{user: admin}} = conn, %{
126 "follower" => follower_nick,
127 "followed" => followed_nick
128 }) do
129 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
130 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
131 User.follow(follower, followed)
132
133 ModerationLog.insert_log(%{
134 actor: admin,
135 followed: followed,
136 follower: follower,
137 action: "follow"
138 })
139 end
140
141 json(conn, "ok")
142 end
143
144 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
145 "follower" => follower_nick,
146 "followed" => followed_nick
147 }) do
148 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
149 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
150 User.unfollow(follower, followed)
151
152 ModerationLog.insert_log(%{
153 actor: admin,
154 followed: followed,
155 follower: follower,
156 action: "unfollow"
157 })
158 end
159
160 json(conn, "ok")
161 end
162
163 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
164 changesets =
165 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
166 user_data = %{
167 nickname: nickname,
168 name: nickname,
169 email: email,
170 password: password,
171 password_confirmation: password,
172 bio: "."
173 }
174
175 User.register_changeset(%User{}, user_data, need_confirmation: false)
176 end)
177 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
178 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
179 end)
180
181 case Pleroma.Repo.transaction(changesets) do
182 {:ok, users} ->
183 res =
184 users
185 |> Map.values()
186 |> Enum.map(fn user ->
187 {:ok, user} = User.post_register_action(user)
188
189 user
190 end)
191 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
192
193 ModerationLog.insert_log(%{
194 actor: admin,
195 subjects: Map.values(users),
196 action: "create"
197 })
198
199 json(conn, res)
200
201 {:error, id, changeset, _} ->
202 res =
203 Enum.map(changesets.operations, fn
204 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
205 AccountView.render("create-error.json", %{changeset: changeset})
206
207 {_, {:changeset, current_changeset, _}} ->
208 AccountView.render("create-error.json", %{changeset: current_changeset})
209 end)
210
211 conn
212 |> put_status(:conflict)
213 |> json(res)
214 end
215 end
216
217 def user_show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
218 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
219 conn
220 |> put_view(AccountView)
221 |> render("show.json", %{user: user})
222 else
223 _ -> {:error, :not_found}
224 end
225 end
226
227 def list_instance_statuses(conn, %{"instance" => instance} = params) do
228 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
229 {page, page_size} = page_params(params)
230
231 activities =
232 ActivityPub.fetch_statuses(nil, %{
233 instance: instance,
234 limit: page_size,
235 offset: (page - 1) * page_size,
236 exclude_reblogs: not with_reblogs
237 })
238
239 conn
240 |> put_view(AdminAPI.StatusView)
241 |> render("index.json", %{activities: activities, as: :activity})
242 end
243
244 def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do
245 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
246 godmode = params["godmode"] == "true" || params["godmode"] == true
247
248 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
249 {_, page_size} = page_params(params)
250
251 activities =
252 ActivityPub.fetch_user_activities(user, nil, %{
253 limit: page_size,
254 godmode: godmode,
255 exclude_reblogs: not with_reblogs
256 })
257
258 conn
259 |> put_view(AdminAPI.StatusView)
260 |> render("index.json", %{activities: activities, as: :activity})
261 else
262 _ -> {:error, :not_found}
263 end
264 end
265
266 def list_user_chats(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = _params) do
267 with %User{id: user_id} <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
268 chats =
269 Pleroma.Chat.for_user_query(user_id)
270 |> Pleroma.Repo.all()
271
272 conn
273 |> put_view(PleromaAPI.ChatView)
274 |> render("index.json", chats: chats)
275 else
276 _ -> {:error, :not_found}
277 end
278 end
279
280 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
281 user = User.get_cached_by_nickname(nickname)
282
283 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
284
285 action = if user.deactivated, do: "activate", else: "deactivate"
286
287 ModerationLog.insert_log(%{
288 actor: admin,
289 subject: [user],
290 action: action
291 })
292
293 conn
294 |> put_view(AccountView)
295 |> render("show.json", %{user: updated_user})
296 end
297
298 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
299 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
300 {:ok, updated_users} = User.deactivate(users, false)
301
302 ModerationLog.insert_log(%{
303 actor: admin,
304 subject: users,
305 action: "activate"
306 })
307
308 conn
309 |> put_view(AccountView)
310 |> render("index.json", %{users: Keyword.values(updated_users)})
311 end
312
313 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
314 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
315 {:ok, updated_users} = User.deactivate(users, true)
316
317 ModerationLog.insert_log(%{
318 actor: admin,
319 subject: users,
320 action: "deactivate"
321 })
322
323 conn
324 |> put_view(AccountView)
325 |> render("index.json", %{users: Keyword.values(updated_users)})
326 end
327
328 def user_approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
329 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
330 {:ok, updated_users} = User.approve(users)
331
332 ModerationLog.insert_log(%{
333 actor: admin,
334 subject: users,
335 action: "approve"
336 })
337
338 conn
339 |> put_view(AccountView)
340 |> render("index.json", %{users: updated_users})
341 end
342
343 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
344 with {:ok, _} <- User.tag(nicknames, tags) do
345 ModerationLog.insert_log(%{
346 actor: admin,
347 nicknames: nicknames,
348 tags: tags,
349 action: "tag"
350 })
351
352 json_response(conn, :no_content, "")
353 end
354 end
355
356 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
357 with {:ok, _} <- User.untag(nicknames, tags) do
358 ModerationLog.insert_log(%{
359 actor: admin,
360 nicknames: nicknames,
361 tags: tags,
362 action: "untag"
363 })
364
365 json_response(conn, :no_content, "")
366 end
367 end
368
369 def list_users(conn, params) do
370 {page, page_size} = page_params(params)
371 filters = maybe_parse_filters(params["filters"])
372
373 search_params = %{
374 query: params["query"],
375 page: page,
376 page_size: page_size,
377 tags: params["tags"],
378 name: params["name"],
379 email: params["email"]
380 }
381
382 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do
383 json(
384 conn,
385 AccountView.render("index.json",
386 users: users,
387 count: count,
388 page_size: page_size
389 )
390 )
391 end
392 end
393
394 @filters ~w(local external active deactivated need_approval is_admin is_moderator)
395
396 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
397 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
398
399 defp maybe_parse_filters(filters) do
400 filters
401 |> String.split(",")
402 |> Enum.filter(&Enum.member?(@filters, &1))
403 |> Map.new(&{String.to_existing_atom(&1), true})
404 end
405
406 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
407 "permission_group" => permission_group,
408 "nicknames" => nicknames
409 })
410 when permission_group in ["moderator", "admin"] do
411 update = %{:"is_#{permission_group}" => true}
412
413 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
414
415 for u <- users, do: User.admin_api_update(u, update)
416
417 ModerationLog.insert_log(%{
418 action: "grant",
419 actor: admin,
420 subject: users,
421 permission: permission_group
422 })
423
424 json(conn, update)
425 end
426
427 def right_add_multiple(conn, _) do
428 render_error(conn, :not_found, "No such permission_group")
429 end
430
431 def right_add(%{assigns: %{user: admin}} = conn, %{
432 "permission_group" => permission_group,
433 "nickname" => nickname
434 })
435 when permission_group in ["moderator", "admin"] do
436 fields = %{:"is_#{permission_group}" => true}
437
438 {:ok, user} =
439 nickname
440 |> User.get_cached_by_nickname()
441 |> User.admin_api_update(fields)
442
443 ModerationLog.insert_log(%{
444 action: "grant",
445 actor: admin,
446 subject: [user],
447 permission: permission_group
448 })
449
450 json(conn, fields)
451 end
452
453 def right_add(conn, _) do
454 render_error(conn, :not_found, "No such permission_group")
455 end
456
457 def right_get(conn, %{"nickname" => nickname}) do
458 user = User.get_cached_by_nickname(nickname)
459
460 conn
461 |> json(%{
462 is_moderator: user.is_moderator,
463 is_admin: user.is_admin
464 })
465 end
466
467 def right_delete_multiple(
468 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
469 %{
470 "permission_group" => permission_group,
471 "nicknames" => nicknames
472 }
473 )
474 when permission_group in ["moderator", "admin"] do
475 with false <- Enum.member?(nicknames, admin_nickname) do
476 update = %{:"is_#{permission_group}" => false}
477
478 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
479
480 for u <- users, do: User.admin_api_update(u, update)
481
482 ModerationLog.insert_log(%{
483 action: "revoke",
484 actor: admin,
485 subject: users,
486 permission: permission_group
487 })
488
489 json(conn, update)
490 else
491 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
492 end
493 end
494
495 def right_delete_multiple(conn, _) do
496 render_error(conn, :not_found, "No such permission_group")
497 end
498
499 def right_delete(
500 %{assigns: %{user: admin}} = conn,
501 %{
502 "permission_group" => permission_group,
503 "nickname" => nickname
504 }
505 )
506 when permission_group in ["moderator", "admin"] do
507 fields = %{:"is_#{permission_group}" => false}
508
509 {:ok, user} =
510 nickname
511 |> User.get_cached_by_nickname()
512 |> User.admin_api_update(fields)
513
514 ModerationLog.insert_log(%{
515 action: "revoke",
516 actor: admin,
517 subject: [user],
518 permission: permission_group
519 })
520
521 json(conn, fields)
522 end
523
524 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
525 render_error(conn, :forbidden, "You can't revoke your own admin status.")
526 end
527
528 @doc "Get a password reset token (base64 string) for given nickname"
529 def get_password_reset(conn, %{"nickname" => nickname}) do
530 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
531 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
532
533 conn
534 |> json(%{
535 token: token.token,
536 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
537 })
538 end
539
540 @doc "Force password reset for a given user"
541 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
542 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
543
544 Enum.each(users, &User.force_password_reset_async/1)
545
546 ModerationLog.insert_log(%{
547 actor: admin,
548 subject: users,
549 action: "force_password_reset"
550 })
551
552 json_response(conn, :no_content, "")
553 end
554
555 @doc "Disable mfa for user's account."
556 def disable_mfa(conn, %{"nickname" => nickname}) do
557 case User.get_by_nickname(nickname) do
558 %User{} = user ->
559 MFA.disable(user)
560 json(conn, nickname)
561
562 _ ->
563 {:error, :not_found}
564 end
565 end
566
567 @doc "Show a given user's credentials"
568 def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
569 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
570 conn
571 |> put_view(AccountView)
572 |> render("credentials.json", %{user: user, for: admin})
573 else
574 _ -> {:error, :not_found}
575 end
576 end
577
578 @doc "Updates a given user"
579 def update_user_credentials(
580 %{assigns: %{user: admin}} = conn,
581 %{"nickname" => nickname} = params
582 ) do
583 with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)},
584 {:ok, _user} <-
585 User.update_as_admin(user, params) do
586 ModerationLog.insert_log(%{
587 actor: admin,
588 subject: [user],
589 action: "updated_users"
590 })
591
592 if params["password"] do
593 User.force_password_reset_async(user)
594 end
595
596 ModerationLog.insert_log(%{
597 actor: admin,
598 subject: [user],
599 action: "force_password_reset"
600 })
601
602 json(conn, %{status: "success"})
603 else
604 {:error, changeset} ->
605 errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
606
607 {:errors, errors}
608
609 _ ->
610 {:error, :not_found}
611 end
612 end
613
614 def list_log(conn, params) do
615 {page, page_size} = page_params(params)
616
617 log =
618 ModerationLog.get_all(%{
619 page: page,
620 page_size: page_size,
621 start_date: params["start_date"],
622 end_date: params["end_date"],
623 user_id: params["user_id"],
624 search: params["search"]
625 })
626
627 conn
628 |> put_view(ModerationLogView)
629 |> render("index.json", %{log: log})
630 end
631
632 def restart(conn, _params) do
633 with :ok <- configurable_from_database() do
634 Restarter.Pleroma.restart(Config.get(:env), 50)
635
636 json(conn, %{})
637 end
638 end
639
640 def need_reboot(conn, _params) do
641 json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
642 end
643
644 defp configurable_from_database do
645 if Config.get(:configurable_from_database) do
646 :ok
647 else
648 {:error, "To use this endpoint you need to enable configuration from database."}
649 end
650 end
651
652 def reload_emoji(conn, _params) do
653 Pleroma.Emoji.reload()
654
655 json(conn, "ok")
656 end
657
658 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
659 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
660
661 User.toggle_confirmation(users)
662
663 ModerationLog.insert_log(%{actor: admin, subject: users, action: "confirm_email"})
664
665 json(conn, "")
666 end
667
668 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
669 users =
670 Enum.map(nicknames, fn nickname ->
671 nickname
672 |> User.get_cached_by_nickname()
673 |> User.send_confirmation_email()
674 end)
675
676 ModerationLog.insert_log(%{actor: admin, subject: users, action: "resend_confirmation_email"})
677
678 json(conn, "")
679 end
680
681 def stats(conn, params) do
682 counters = Stats.get_status_visibility_count(params["instance"])
683
684 json(conn, %{"status_visibility" => counters})
685 end
686
687 defp page_params(params) do
688 {get_page(params["page"]), get_page_size(params["page_size"])}
689 end
690
691 defp get_page(page_string) when is_nil(page_string), do: 1
692
693 defp get_page(page_string) do
694 case Integer.parse(page_string) do
695 {page, _} -> page
696 :error -> 1
697 end
698 end
699
700 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
701
702 defp get_page_size(page_size_string) do
703 case Integer.parse(page_size_string) do
704 {page_size, _} -> page_size
705 :error -> @users_page_size
706 end
707 end
708 end