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
30 %{scopes: ["read:accounts"], admin: true}
31 when action in [:list_users, :user_show, :right_get, :show_user_credentials]
36 %{scopes: ["write:accounts"], admin: true}
39 :force_password_reset,
42 :user_toggle_activation,
52 :right_delete_multiple,
53 :update_user_credentials
59 %{scopes: ["write:follows"], admin: true}
60 when action in [:user_follow, :user_unfollow]
65 %{scopes: ["read:statuses"], admin: true}
66 when action in [:list_user_statuses, :list_instance_statuses]
71 %{scopes: ["read:chats"], admin: true}
72 when action in [:list_user_chats]
77 %{scopes: ["read"], admin: true}
87 %{scopes: ["write"], admin: true}
90 :resend_confirmation_email,
96 action_fallback(AdminAPI.FallbackController)
98 def user_delete(conn, %{"nickname" => nickname}) do
99 user_delete(conn, %{"nicknames" => [nickname]})
102 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
105 |> Enum.map(&User.get_cached_by_nickname/1)
108 |> Enum.each(fn user ->
109 {:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
110 Pipeline.common_pipeline(delete_data, local: true)
113 ModerationLog.insert_log(%{
119 json(conn, nicknames)
122 def user_follow(%{assigns: %{user: admin}} = conn, %{
123 "follower" => follower_nick,
124 "followed" => followed_nick
126 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
127 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
128 User.follow(follower, followed)
130 ModerationLog.insert_log(%{
141 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
142 "follower" => follower_nick,
143 "followed" => followed_nick
145 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
146 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
147 User.unfollow(follower, followed)
149 ModerationLog.insert_log(%{
160 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
162 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
168 password_confirmation: password,
172 User.register_changeset(%User{}, user_data, need_confirmation: false)
174 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
175 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
178 case Pleroma.Repo.transaction(changesets) do
183 |> Enum.map(fn user ->
184 {:ok, user} = User.post_register_action(user)
188 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
190 ModerationLog.insert_log(%{
192 subjects: Map.values(users),
198 {:error, id, changeset, _} ->
200 Enum.map(changesets.operations, fn
201 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
202 AccountView.render("create-error.json", %{changeset: changeset})
204 {_, {:changeset, current_changeset, _}} ->
205 AccountView.render("create-error.json", %{changeset: current_changeset})
209 |> put_status(:conflict)
214 def user_show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
215 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
217 |> put_view(AccountView)
218 |> render("show.json", %{user: user})
220 _ -> {:error, :not_found}
224 def list_instance_statuses(conn, %{"instance" => instance} = params) do
225 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
226 {page, page_size} = page_params(params)
229 ActivityPub.fetch_statuses(nil, %{
232 offset: (page - 1) * page_size,
233 exclude_reblogs: not with_reblogs
237 |> put_view(AdminAPI.StatusView)
238 |> render("index.json", %{activities: activities, as: :activity})
241 def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do
242 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
243 godmode = params["godmode"] == "true" || params["godmode"] == true
245 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
246 {_, page_size} = page_params(params)
249 ActivityPub.fetch_user_activities(user, nil, %{
252 exclude_reblogs: not with_reblogs
256 |> put_view(AdminAPI.StatusView)
257 |> render("index.json", %{activities: activities, as: :activity})
259 _ -> {:error, :not_found}
263 def list_user_chats(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = _params) do
264 with %User{id: user_id} <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
266 Pleroma.Chat.for_user_query(user_id)
267 |> Pleroma.Repo.all()
270 |> put_view(AdminAPI.ChatView)
271 |> render("index.json", chats: chats)
273 _ -> {:error, :not_found}
277 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
278 user = User.get_cached_by_nickname(nickname)
280 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
282 action = if user.deactivated, do: "activate", else: "deactivate"
284 ModerationLog.insert_log(%{
291 |> put_view(AccountView)
292 |> render("show.json", %{user: updated_user})
295 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
296 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
297 {:ok, updated_users} = User.deactivate(users, false)
299 ModerationLog.insert_log(%{
306 |> put_view(AccountView)
307 |> render("index.json", %{users: Keyword.values(updated_users)})
310 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
311 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
312 {:ok, updated_users} = User.deactivate(users, true)
314 ModerationLog.insert_log(%{
321 |> put_view(AccountView)
322 |> render("index.json", %{users: Keyword.values(updated_users)})
325 def user_approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
326 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
327 {:ok, updated_users} = User.approve(users)
329 ModerationLog.insert_log(%{
336 |> put_view(AccountView)
337 |> render("index.json", %{users: updated_users})
340 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
341 with {:ok, _} <- User.tag(nicknames, tags) do
342 ModerationLog.insert_log(%{
344 nicknames: nicknames,
349 json_response(conn, :no_content, "")
353 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
354 with {:ok, _} <- User.untag(nicknames, tags) do
355 ModerationLog.insert_log(%{
357 nicknames: nicknames,
362 json_response(conn, :no_content, "")
366 def list_users(conn, params) do
367 {page, page_size} = page_params(params)
368 filters = maybe_parse_filters(params["filters"])
371 query: params["query"],
373 page_size: page_size,
374 tags: params["tags"],
375 name: params["name"],
376 email: params["email"]
379 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do
382 AccountView.render("index.json",
391 @filters ~w(local external active deactivated need_approval 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
685 {get_page(params["page"]), get_page_size(params["page_size"])}
688 defp get_page(page_string) when is_nil(page_string), do: 1
690 defp get_page(page_string) do
691 case Integer.parse(page_string) do
697 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
699 defp get_page_size(page_size_string) do
700 case Integer.parse(page_size_string) do
701 {page_size, _} -> page_size
702 :error -> @users_page_size