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