1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
3 # SPDX-License-Identifier: AGPL-3.0-only
5 defmodule Pleroma.Web.AdminAPI.AdminAPIController do
6 use Pleroma.Web, :controller
8 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
12 alias Pleroma.ModerationLog
15 alias Pleroma.Web.ActivityPub.ActivityPub
16 alias Pleroma.Web.ActivityPub.Builder
17 alias Pleroma.Web.ActivityPub.Pipeline
18 alias Pleroma.Web.AdminAPI
19 alias Pleroma.Web.AdminAPI.AccountView
20 alias Pleroma.Web.AdminAPI.ModerationLogView
21 alias Pleroma.Web.AdminAPI.Search
22 alias Pleroma.Web.Endpoint
23 alias Pleroma.Web.Plugs.OAuthScopesPlug
24 alias Pleroma.Web.Router
32 %{scopes: ["read:accounts"], admin: true}
33 when action in [:list_users, :user_show, :right_get, :show_user_credentials, :create_backup]
38 %{scopes: ["write:accounts"], admin: true}
41 :force_password_reset,
44 :user_toggle_activation,
54 :right_delete_multiple,
55 :update_user_credentials
61 %{scopes: ["write:follows"], admin: true}
62 when action in [:user_follow, :user_unfollow]
67 %{scopes: ["read:statuses"], admin: true}
68 when action in [:list_user_statuses, :list_instance_statuses]
73 %{scopes: ["read:chats"], admin: true}
74 when action in [:list_user_chats]
79 %{scopes: ["read"], admin: true}
89 %{scopes: ["write"], admin: true}
92 :resend_confirmation_email,
98 action_fallback(AdminAPI.FallbackController)
100 def user_delete(conn, %{"nickname" => nickname}) do
101 user_delete(conn, %{"nicknames" => [nickname]})
104 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
107 |> Enum.map(&User.get_cached_by_nickname/1)
110 |> Enum.each(fn user ->
111 {:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
112 Pipeline.common_pipeline(delete_data, local: true)
115 ModerationLog.insert_log(%{
121 json(conn, nicknames)
124 def user_follow(%{assigns: %{user: admin}} = conn, %{
125 "follower" => follower_nick,
126 "followed" => followed_nick
128 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
129 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
130 User.follow(follower, followed)
132 ModerationLog.insert_log(%{
143 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
144 "follower" => follower_nick,
145 "followed" => followed_nick
147 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
148 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
149 User.unfollow(follower, followed)
151 ModerationLog.insert_log(%{
162 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
164 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
170 password_confirmation: password,
174 User.register_changeset(%User{}, user_data, need_confirmation: false)
176 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
177 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
180 case Pleroma.Repo.transaction(changesets) do
185 |> Enum.map(fn user ->
186 {:ok, user} = User.post_register_action(user)
190 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
192 ModerationLog.insert_log(%{
194 subjects: Map.values(users),
200 {:error, id, changeset, _} ->
202 Enum.map(changesets.operations, fn
203 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
204 AccountView.render("create-error.json", %{changeset: changeset})
206 {_, {:changeset, current_changeset, _}} ->
207 AccountView.render("create-error.json", %{changeset: current_changeset})
211 |> put_status(:conflict)
216 def user_show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
217 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
219 |> put_view(AccountView)
220 |> render("show.json", %{user: user})
222 _ -> {:error, :not_found}
226 def list_instance_statuses(conn, %{"instance" => instance} = params) do
227 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
228 {page, page_size} = page_params(params)
231 ActivityPub.fetch_statuses(nil, %{
234 offset: (page - 1) * page_size,
235 exclude_reblogs: not with_reblogs
239 |> put_view(AdminAPI.StatusView)
240 |> render("index.json", %{activities: activities, as: :activity})
243 def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do
244 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
245 godmode = params["godmode"] == "true" || params["godmode"] == true
247 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
248 {_, page_size} = page_params(params)
251 ActivityPub.fetch_user_activities(user, nil, %{
254 exclude_reblogs: not with_reblogs
258 |> put_view(AdminAPI.StatusView)
259 |> render("index.json", %{activities: activities, as: :activity})
261 _ -> {:error, :not_found}
265 def list_user_chats(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = _params) do
266 with %User{id: user_id} <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
268 Pleroma.Chat.for_user_query(user_id)
269 |> Pleroma.Repo.all()
272 |> put_view(AdminAPI.ChatView)
273 |> render("index.json", chats: chats)
275 _ -> {:error, :not_found}
279 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
280 user = User.get_cached_by_nickname(nickname)
282 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
284 action = if user.deactivated, do: "activate", else: "deactivate"
286 ModerationLog.insert_log(%{
293 |> put_view(AccountView)
294 |> render("show.json", %{user: updated_user})
297 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
298 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
299 {:ok, updated_users} = User.deactivate(users, false)
301 ModerationLog.insert_log(%{
308 |> put_view(AccountView)
309 |> render("index.json", %{users: Keyword.values(updated_users)})
312 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
313 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
314 {:ok, updated_users} = User.deactivate(users, true)
316 ModerationLog.insert_log(%{
323 |> put_view(AccountView)
324 |> render("index.json", %{users: Keyword.values(updated_users)})
327 def user_approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
328 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
329 {:ok, updated_users} = User.approve(users)
331 ModerationLog.insert_log(%{
338 |> put_view(AccountView)
339 |> render("index.json", %{users: updated_users})
342 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
343 with {:ok, _} <- User.tag(nicknames, tags) do
344 ModerationLog.insert_log(%{
346 nicknames: nicknames,
351 json_response(conn, :no_content, "")
355 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
356 with {:ok, _} <- User.untag(nicknames, tags) do
357 ModerationLog.insert_log(%{
359 nicknames: nicknames,
364 json_response(conn, :no_content, "")
368 def list_users(conn, params) do
369 {page, page_size} = page_params(params)
370 filters = maybe_parse_filters(params["filters"])
373 query: params["query"],
375 page_size: page_size,
376 tags: params["tags"],
377 name: params["name"],
378 email: params["email"]
381 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do
384 AccountView.render("index.json",
393 @filters ~w(local external active deactivated need_approval is_admin is_moderator)
395 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
396 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
398 defp maybe_parse_filters(filters) do
401 |> Enum.filter(&Enum.member?(@filters, &1))
402 |> Map.new(&{String.to_existing_atom(&1), true})
405 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
406 "permission_group" => permission_group,
407 "nicknames" => nicknames
409 when permission_group in ["moderator", "admin"] do
410 update = %{:"is_#{permission_group}" => true}
412 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
414 for u <- users, do: User.admin_api_update(u, update)
416 ModerationLog.insert_log(%{
420 permission: permission_group
426 def right_add_multiple(conn, _) do
427 render_error(conn, :not_found, "No such permission_group")
430 def right_add(%{assigns: %{user: admin}} = conn, %{
431 "permission_group" => permission_group,
432 "nickname" => nickname
434 when permission_group in ["moderator", "admin"] do
435 fields = %{:"is_#{permission_group}" => true}
439 |> User.get_cached_by_nickname()
440 |> User.admin_api_update(fields)
442 ModerationLog.insert_log(%{
446 permission: permission_group
452 def right_add(conn, _) do
453 render_error(conn, :not_found, "No such permission_group")
456 def right_get(conn, %{"nickname" => nickname}) do
457 user = User.get_cached_by_nickname(nickname)
461 is_moderator: user.is_moderator,
462 is_admin: user.is_admin
466 def right_delete_multiple(
467 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
469 "permission_group" => permission_group,
470 "nicknames" => nicknames
473 when permission_group in ["moderator", "admin"] do
474 with false <- Enum.member?(nicknames, admin_nickname) do
475 update = %{:"is_#{permission_group}" => false}
477 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
479 for u <- users, do: User.admin_api_update(u, update)
481 ModerationLog.insert_log(%{
485 permission: permission_group
490 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
494 def right_delete_multiple(conn, _) do
495 render_error(conn, :not_found, "No such permission_group")
499 %{assigns: %{user: admin}} = conn,
501 "permission_group" => permission_group,
502 "nickname" => nickname
505 when permission_group in ["moderator", "admin"] do
506 fields = %{:"is_#{permission_group}" => false}
510 |> User.get_cached_by_nickname()
511 |> User.admin_api_update(fields)
513 ModerationLog.insert_log(%{
517 permission: permission_group
523 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
524 render_error(conn, :forbidden, "You can't revoke your own admin status.")
527 @doc "Get a password reset token (base64 string) for given nickname"
528 def get_password_reset(conn, %{"nickname" => nickname}) do
529 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
530 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
535 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
539 @doc "Force password reset for a given user"
540 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
541 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
543 Enum.each(users, &User.force_password_reset_async/1)
545 ModerationLog.insert_log(%{
548 action: "force_password_reset"
551 json_response(conn, :no_content, "")
554 @doc "Disable mfa for user's account."
555 def disable_mfa(conn, %{"nickname" => nickname}) do
556 case User.get_by_nickname(nickname) do
566 @doc "Show a given user's credentials"
567 def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
568 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
570 |> put_view(AccountView)
571 |> render("credentials.json", %{user: user, for: admin})
573 _ -> {:error, :not_found}
577 @doc "Updates a given user"
578 def update_user_credentials(
579 %{assigns: %{user: admin}} = conn,
580 %{"nickname" => nickname} = params
582 with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)},
584 User.update_as_admin(user, params) do
585 ModerationLog.insert_log(%{
588 action: "updated_users"
591 if params["password"] do
592 User.force_password_reset_async(user)
595 ModerationLog.insert_log(%{
598 action: "force_password_reset"
601 json(conn, %{status: "success"})
603 {:error, changeset} ->
604 errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
613 def list_log(conn, params) do
614 {page, page_size} = page_params(params)
617 ModerationLog.get_all(%{
619 page_size: page_size,
620 start_date: params["start_date"],
621 end_date: params["end_date"],
622 user_id: params["user_id"],
623 search: params["search"]
627 |> put_view(ModerationLogView)
628 |> render("index.json", %{log: log})
631 def restart(conn, _params) do
632 with :ok <- configurable_from_database() do
633 Restarter.Pleroma.restart(Config.get(:env), 50)
639 def need_reboot(conn, _params) do
640 json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
643 defp configurable_from_database do
644 if Config.get(:configurable_from_database) do
647 {:error, "To use this endpoint you need to enable configuration from database."}
651 def reload_emoji(conn, _params) do
652 Pleroma.Emoji.reload()
657 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
658 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
660 User.toggle_confirmation(users)
662 ModerationLog.insert_log(%{actor: admin, subject: users, action: "confirm_email"})
667 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
669 Enum.map(nicknames, fn nickname ->
671 |> User.get_cached_by_nickname()
672 |> User.send_confirmation_email()
675 ModerationLog.insert_log(%{actor: admin, subject: users, action: "resend_confirmation_email"})
680 def stats(conn, params) do
681 counters = Stats.get_status_visibility_count(params["instance"])
683 json(conn, %{"status_visibility" => counters})
686 def create_backup(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
687 with %User{} = user <- User.get_by_nickname(nickname),
688 {:ok, _} <- Pleroma.User.Backup.create(user, admin.id) do
689 ModerationLog.insert_log(%{actor: admin, subject: user, action: "create_backup"})
695 defp page_params(params) do
696 {get_page(params["page"]), get_page_size(params["page_size"])}
699 defp get_page(page_string) when is_nil(page_string), do: 1
701 defp get_page(page_string) do
702 case Integer.parse(page_string) do
708 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
710 defp get_page_size(page_size_string) do
711 case Integer.parse(page_size_string) do
712 {page_size, _} -> page_size
713 :error -> @users_page_size