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,
9 only: [json_response: 3, fetch_integer_param: 3]
13 alias Pleroma.ModerationLog
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.Plugs.OAuthScopesPlug
25 alias Pleroma.Web.Router
31 %{scopes: ["read:accounts"], admin: true}
32 when action in [:list_users, :user_show, :right_get, :show_user_credentials]
37 %{scopes: ["write:accounts"], admin: true}
40 :force_password_reset,
43 :user_toggle_activation,
53 :right_delete_multiple,
54 :update_user_credentials
60 %{scopes: ["write:follows"], admin: true}
61 when action in [:user_follow, :user_unfollow]
66 %{scopes: ["read:statuses"], admin: true}
67 when action in [:list_user_statuses, :list_instance_statuses]
72 %{scopes: ["read:chats"], admin: true}
73 when action in [:list_user_chats]
78 %{scopes: ["read"], admin: true}
88 %{scopes: ["write"], admin: true}
91 :resend_confirmation_email,
97 action_fallback(AdminAPI.FallbackController)
99 def user_delete(conn, %{"nickname" => nickname}) do
100 user_delete(conn, %{"nicknames" => [nickname]})
103 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
104 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
106 Enum.each(users, fn user ->
107 {:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
108 Pipeline.common_pipeline(delete_data, local: true)
111 ModerationLog.insert_log(%{
117 json(conn, nicknames)
120 def user_follow(%{assigns: %{user: admin}} = conn, %{
121 "follower" => follower_nick,
122 "followed" => followed_nick
124 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
125 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
126 User.follow(follower, followed)
128 ModerationLog.insert_log(%{
139 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
140 "follower" => follower_nick,
141 "followed" => followed_nick
143 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
144 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
145 User.unfollow(follower, followed)
147 ModerationLog.insert_log(%{
158 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
160 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
166 password_confirmation: password,
170 User.register_changeset(%User{}, user_data, need_confirmation: false)
172 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
173 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
176 case Pleroma.Repo.transaction(changesets) do
181 |> Enum.map(fn user ->
182 {:ok, user} = User.post_register_action(user)
186 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
188 ModerationLog.insert_log(%{
190 subjects: Map.values(users),
196 {:error, id, changeset, _} ->
198 Enum.map(changesets.operations, fn
199 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
200 AccountView.render("create-error.json", %{changeset: changeset})
202 {_, {:changeset, current_changeset, _}} ->
203 AccountView.render("create-error.json", %{changeset: current_changeset})
207 |> put_status(:conflict)
212 def user_show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
213 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
215 |> put_view(AccountView)
216 |> render("show.json", %{user: user})
218 _ -> {:error, :not_found}
222 def list_instance_statuses(conn, %{"instance" => instance} = params) do
223 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
224 {page, page_size} = page_params(params)
227 ActivityPub.fetch_statuses(nil, %{
230 offset: (page - 1) * page_size,
231 exclude_reblogs: not with_reblogs
235 |> put_view(AdminAPI.StatusView)
236 |> render("index.json", %{activities: activities, as: :activity})
239 def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do
240 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
241 godmode = params["godmode"] == "true" || params["godmode"] == true
243 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
244 {_, page_size} = page_params(params)
247 ActivityPub.fetch_user_activities(user, nil, %{
250 exclude_reblogs: not with_reblogs
254 |> put_view(AdminAPI.StatusView)
255 |> render("index.json", %{activities: activities, as: :activity})
257 _ -> {:error, :not_found}
261 def list_user_chats(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = _params) do
262 with %User{id: user_id} <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
264 Pleroma.Chat.for_user_query(user_id)
265 |> Pleroma.Repo.all()
268 |> put_view(AdminAPI.ChatView)
269 |> render("index.json", chats: chats)
271 _ -> {:error, :not_found}
275 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
276 user = User.get_cached_by_nickname(nickname)
278 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
280 action = if user.deactivated, do: "activate", else: "deactivate"
282 ModerationLog.insert_log(%{
289 |> put_view(AccountView)
290 |> render("show.json", %{user: updated_user})
293 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
294 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
295 {:ok, updated_users} = User.deactivate(users, false)
297 ModerationLog.insert_log(%{
304 |> put_view(AccountView)
305 |> render("index.json", %{users: Keyword.values(updated_users)})
308 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
309 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
310 {:ok, updated_users} = User.deactivate(users, true)
312 ModerationLog.insert_log(%{
319 |> put_view(AccountView)
320 |> render("index.json", %{users: Keyword.values(updated_users)})
323 def user_approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
324 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
325 {:ok, updated_users} = User.approve(users)
327 ModerationLog.insert_log(%{
334 |> put_view(AccountView)
335 |> render("index.json", %{users: updated_users})
338 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
339 with {:ok, _} <- User.tag(nicknames, tags) do
340 ModerationLog.insert_log(%{
342 nicknames: nicknames,
347 json_response(conn, :no_content, "")
351 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
352 with {:ok, _} <- User.untag(nicknames, tags) do
353 ModerationLog.insert_log(%{
355 nicknames: nicknames,
360 json_response(conn, :no_content, "")
364 def list_users(conn, params) do
365 {page, page_size} = page_params(params)
366 filters = maybe_parse_filters(params["filters"])
370 query: params["query"],
372 page_size: page_size,
373 tags: params["tags"],
374 name: params["name"],
375 email: params["email"]
377 |> Map.merge(filters)
379 with {:ok, users, count} <- Search.user(search_params) do
382 AccountView.render("index.json",
391 @filters ~w(local external active deactivated need_approval need_confirmed is_admin is_moderator)
393 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
394 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
396 defp maybe_parse_filters(filters) do
399 |> Enum.filter(&Enum.member?(@filters, &1))
400 |> Map.new(&{String.to_existing_atom(&1), true})
403 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
404 "permission_group" => permission_group,
405 "nicknames" => nicknames
407 when permission_group in ["moderator", "admin"] do
408 update = %{:"is_#{permission_group}" => true}
410 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
412 for u <- users, do: User.admin_api_update(u, update)
414 ModerationLog.insert_log(%{
418 permission: permission_group
424 def right_add_multiple(conn, _) do
425 render_error(conn, :not_found, "No such permission_group")
428 def right_add(%{assigns: %{user: admin}} = conn, %{
429 "permission_group" => permission_group,
430 "nickname" => nickname
432 when permission_group in ["moderator", "admin"] do
433 fields = %{:"is_#{permission_group}" => true}
437 |> User.get_cached_by_nickname()
438 |> User.admin_api_update(fields)
440 ModerationLog.insert_log(%{
444 permission: permission_group
450 def right_add(conn, _) do
451 render_error(conn, :not_found, "No such permission_group")
454 def right_get(conn, %{"nickname" => nickname}) do
455 user = User.get_cached_by_nickname(nickname)
459 is_moderator: user.is_moderator,
460 is_admin: user.is_admin
464 def right_delete_multiple(
465 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
467 "permission_group" => permission_group,
468 "nicknames" => nicknames
471 when permission_group in ["moderator", "admin"] do
472 with false <- Enum.member?(nicknames, admin_nickname) do
473 update = %{:"is_#{permission_group}" => false}
475 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
477 for u <- users, do: User.admin_api_update(u, update)
479 ModerationLog.insert_log(%{
483 permission: permission_group
488 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
492 def right_delete_multiple(conn, _) do
493 render_error(conn, :not_found, "No such permission_group")
497 %{assigns: %{user: admin}} = conn,
499 "permission_group" => permission_group,
500 "nickname" => nickname
503 when permission_group in ["moderator", "admin"] do
504 fields = %{:"is_#{permission_group}" => false}
508 |> User.get_cached_by_nickname()
509 |> User.admin_api_update(fields)
511 ModerationLog.insert_log(%{
515 permission: permission_group
521 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
522 render_error(conn, :forbidden, "You can't revoke your own admin status.")
525 @doc "Get a password reset token (base64 string) for given nickname"
526 def get_password_reset(conn, %{"nickname" => nickname}) do
527 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
528 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
533 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
537 @doc "Force password reset for a given user"
538 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
539 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
541 Enum.each(users, &User.force_password_reset_async/1)
543 ModerationLog.insert_log(%{
546 action: "force_password_reset"
549 json_response(conn, :no_content, "")
552 @doc "Disable mfa for user's account."
553 def disable_mfa(conn, %{"nickname" => nickname}) do
554 case User.get_by_nickname(nickname) do
564 @doc "Show a given user's credentials"
565 def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
566 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
568 |> put_view(AccountView)
569 |> render("credentials.json", %{user: user, for: admin})
571 _ -> {:error, :not_found}
575 @doc "Updates a given user"
576 def update_user_credentials(
577 %{assigns: %{user: admin}} = conn,
578 %{"nickname" => nickname} = params
580 with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)},
582 User.update_as_admin(user, params) do
583 ModerationLog.insert_log(%{
586 action: "updated_users"
589 if params["password"] do
590 User.force_password_reset_async(user)
593 ModerationLog.insert_log(%{
596 action: "force_password_reset"
599 json(conn, %{status: "success"})
601 {:error, changeset} ->
602 errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
611 def list_log(conn, params) do
612 {page, page_size} = page_params(params)
615 ModerationLog.get_all(%{
617 page_size: page_size,
618 start_date: params["start_date"],
619 end_date: params["end_date"],
620 user_id: params["user_id"],
621 search: params["search"]
625 |> put_view(ModerationLogView)
626 |> render("index.json", %{log: log})
629 def restart(conn, _params) do
630 with :ok <- configurable_from_database() do
631 Restarter.Pleroma.restart(Config.get(:env), 50)
637 def need_reboot(conn, _params) do
638 json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
641 defp configurable_from_database do
642 if Config.get(:configurable_from_database) do
645 {:error, "To use this endpoint you need to enable configuration from database."}
649 def reload_emoji(conn, _params) do
650 Pleroma.Emoji.reload()
655 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
656 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
658 User.toggle_confirmation(users)
660 ModerationLog.insert_log(%{actor: admin, subject: users, action: "confirm_email"})
665 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
667 Enum.map(nicknames, fn nickname ->
669 |> User.get_cached_by_nickname()
670 |> User.send_confirmation_email()
673 ModerationLog.insert_log(%{actor: admin, subject: users, action: "resend_confirmation_email"})
678 def stats(conn, params) do
679 counters = Stats.get_status_visibility_count(params["instance"])
681 json(conn, %{"status_visibility" => counters})
684 defp page_params(params) do
686 fetch_integer_param(params, "page", 1),
687 fetch_integer_param(params, "page_size", @users_page_size)