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
13 alias Pleroma.Plugs.OAuthScopesPlug
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
33 %{scopes: ["read:accounts"], admin: true}
34 when action in [:list_users, :user_show, :right_get, :show_user_credentials]
39 %{scopes: ["write:accounts"], admin: true}
42 :force_password_reset,
45 :user_toggle_activation,
55 :right_delete_multiple,
56 :update_user_credentials
62 %{scopes: ["write:follows"], admin: true}
63 when action in [:user_follow, :user_unfollow]
68 %{scopes: ["read:statuses"], admin: true}
69 when action in [:list_user_statuses, :list_instance_statuses]
74 %{scopes: ["read:chats"], admin: true}
75 when action in [:list_user_chats]
80 %{scopes: ["read"], admin: true}
90 %{scopes: ["write"], admin: true}
93 :resend_confirmation_email,
99 action_fallback(AdminAPI.FallbackController)
101 def user_delete(conn, %{"nickname" => nickname}) do
102 user_delete(conn, %{"nicknames" => [nickname]})
105 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
108 |> Enum.map(&User.get_cached_by_nickname/1)
111 |> Enum.each(fn user ->
112 {:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
113 Pipeline.common_pipeline(delete_data, local: true)
116 ModerationLog.insert_log(%{
122 json(conn, nicknames)
125 def user_follow(%{assigns: %{user: admin}} = conn, %{
126 "follower" => follower_nick,
127 "followed" => followed_nick
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)
133 ModerationLog.insert_log(%{
144 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
145 "follower" => follower_nick,
146 "followed" => followed_nick
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)
152 ModerationLog.insert_log(%{
163 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
165 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
171 password_confirmation: password,
175 User.register_changeset(%User{}, user_data, need_confirmation: false)
177 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
178 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
181 case Pleroma.Repo.transaction(changesets) do
186 |> Enum.map(fn user ->
187 {:ok, user} = User.post_register_action(user)
191 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
193 ModerationLog.insert_log(%{
195 subjects: Map.values(users),
201 {:error, id, changeset, _} ->
203 Enum.map(changesets.operations, fn
204 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
205 AccountView.render("create-error.json", %{changeset: changeset})
207 {_, {:changeset, current_changeset, _}} ->
208 AccountView.render("create-error.json", %{changeset: current_changeset})
212 |> put_status(:conflict)
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
220 |> put_view(AccountView)
221 |> render("show.json", %{user: user})
223 _ -> {:error, :not_found}
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)
232 ActivityPub.fetch_statuses(nil, %{
235 offset: (page - 1) * page_size,
236 exclude_reblogs: not with_reblogs
240 |> put_view(AdminAPI.StatusView)
241 |> render("index.json", %{activities: activities, as: :activity})
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
248 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
249 {_, page_size} = page_params(params)
252 ActivityPub.fetch_user_activities(user, nil, %{
255 exclude_reblogs: not with_reblogs
259 |> put_view(AdminAPI.StatusView)
260 |> render("index.json", %{activities: activities, as: :activity})
262 _ -> {:error, :not_found}
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
269 Pleroma.Chat.for_user_query(user_id)
270 |> Pleroma.Repo.all()
273 |> put_view(PleromaAPI.ChatView)
274 |> render("index.json", chats: chats)
276 _ -> {:error, :not_found}
280 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
281 user = User.get_cached_by_nickname(nickname)
283 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
285 action = if user.deactivated, do: "activate", else: "deactivate"
287 ModerationLog.insert_log(%{
294 |> put_view(AccountView)
295 |> render("show.json", %{user: updated_user})
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)
302 ModerationLog.insert_log(%{
309 |> put_view(AccountView)
310 |> render("index.json", %{users: Keyword.values(updated_users)})
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)
317 ModerationLog.insert_log(%{
324 |> put_view(AccountView)
325 |> render("index.json", %{users: Keyword.values(updated_users)})
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)
332 ModerationLog.insert_log(%{
339 |> put_view(AccountView)
340 |> render("index.json", %{users: updated_users})
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(%{
347 nicknames: nicknames,
352 json_response(conn, :no_content, "")
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(%{
360 nicknames: nicknames,
365 json_response(conn, :no_content, "")
369 def list_users(conn, params) do
370 {page, page_size} = page_params(params)
371 filters = maybe_parse_filters(params["filters"])
374 query: params["query"],
376 page_size: page_size,
377 tags: params["tags"],
378 name: params["name"],
379 email: params["email"]
382 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do
385 AccountView.render("index.json",
394 @filters ~w(local external active deactivated need_approval is_admin is_moderator)
396 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
397 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
399 defp maybe_parse_filters(filters) do
402 |> Enum.filter(&Enum.member?(@filters, &1))
403 |> Map.new(&{String.to_existing_atom(&1), true})
406 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
407 "permission_group" => permission_group,
408 "nicknames" => nicknames
410 when permission_group in ["moderator", "admin"] do
411 update = %{:"is_#{permission_group}" => true}
413 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
415 for u <- users, do: User.admin_api_update(u, update)
417 ModerationLog.insert_log(%{
421 permission: permission_group
427 def right_add_multiple(conn, _) do
428 render_error(conn, :not_found, "No such permission_group")
431 def right_add(%{assigns: %{user: admin}} = conn, %{
432 "permission_group" => permission_group,
433 "nickname" => nickname
435 when permission_group in ["moderator", "admin"] do
436 fields = %{:"is_#{permission_group}" => true}
440 |> User.get_cached_by_nickname()
441 |> User.admin_api_update(fields)
443 ModerationLog.insert_log(%{
447 permission: permission_group
453 def right_add(conn, _) do
454 render_error(conn, :not_found, "No such permission_group")
457 def right_get(conn, %{"nickname" => nickname}) do
458 user = User.get_cached_by_nickname(nickname)
462 is_moderator: user.is_moderator,
463 is_admin: user.is_admin
467 def right_delete_multiple(
468 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
470 "permission_group" => permission_group,
471 "nicknames" => nicknames
474 when permission_group in ["moderator", "admin"] do
475 with false <- Enum.member?(nicknames, admin_nickname) do
476 update = %{:"is_#{permission_group}" => false}
478 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
480 for u <- users, do: User.admin_api_update(u, update)
482 ModerationLog.insert_log(%{
486 permission: permission_group
491 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
495 def right_delete_multiple(conn, _) do
496 render_error(conn, :not_found, "No such permission_group")
500 %{assigns: %{user: admin}} = conn,
502 "permission_group" => permission_group,
503 "nickname" => nickname
506 when permission_group in ["moderator", "admin"] do
507 fields = %{:"is_#{permission_group}" => false}
511 |> User.get_cached_by_nickname()
512 |> User.admin_api_update(fields)
514 ModerationLog.insert_log(%{
518 permission: permission_group
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.")
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)
536 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
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)
544 Enum.each(users, &User.force_password_reset_async/1)
546 ModerationLog.insert_log(%{
549 action: "force_password_reset"
552 json_response(conn, :no_content, "")
555 @doc "Disable mfa for user's account."
556 def disable_mfa(conn, %{"nickname" => nickname}) do
557 case User.get_by_nickname(nickname) do
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
571 |> put_view(AccountView)
572 |> render("credentials.json", %{user: user, for: admin})
574 _ -> {:error, :not_found}
578 @doc "Updates a given user"
579 def update_user_credentials(
580 %{assigns: %{user: admin}} = conn,
581 %{"nickname" => nickname} = params
583 with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)},
585 User.update_as_admin(user, params) do
586 ModerationLog.insert_log(%{
589 action: "updated_users"
592 if params["password"] do
593 User.force_password_reset_async(user)
596 ModerationLog.insert_log(%{
599 action: "force_password_reset"
602 json(conn, %{status: "success"})
604 {:error, changeset} ->
605 errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
614 def list_log(conn, params) do
615 {page, page_size} = page_params(params)
618 ModerationLog.get_all(%{
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"]
628 |> put_view(ModerationLogView)
629 |> render("index.json", %{log: log})
632 def restart(conn, _params) do
633 with :ok <- configurable_from_database() do
634 Restarter.Pleroma.restart(Config.get(:env), 50)
640 def need_reboot(conn, _params) do
641 json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
644 defp configurable_from_database do
645 if Config.get(:configurable_from_database) do
648 {:error, "To use this endpoint you need to enable configuration from database."}
652 def reload_emoji(conn, _params) do
653 Pleroma.Emoji.reload()
658 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
659 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
661 User.toggle_confirmation(users)
663 ModerationLog.insert_log(%{actor: admin, subject: users, action: "confirm_email"})
668 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
670 Enum.map(nicknames, fn nickname ->
672 |> User.get_cached_by_nickname()
673 |> User.send_confirmation_email()
676 ModerationLog.insert_log(%{actor: admin, subject: users, action: "resend_confirmation_email"})
681 def stats(conn, params) do
682 counters = Stats.get_status_visibility_count(params["instance"])
684 json(conn, %{"status_visibility" => counters})
687 defp page_params(params) do
688 {get_page(params["page"]), get_page_size(params["page_size"])}
691 defp get_page(page_string) when is_nil(page_string), do: 1
693 defp get_page(page_string) do
694 case Integer.parse(page_string) do
700 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
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