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.Router
32 %{scopes: ["read:accounts"], admin: true}
33 when action in [:list_users, :user_show, :right_get, :show_user_credentials]
38 %{scopes: ["write:accounts"], admin: true}
41 :force_password_reset,
44 :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"], admin: true}
82 %{scopes: ["write"], admin: true}
85 :resend_confirmation_email,
91 action_fallback(AdminAPI.FallbackController)
93 def user_delete(conn, %{"nickname" => nickname}) do
94 user_delete(conn, %{"nicknames" => [nickname]})
97 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
100 |> Enum.map(&User.get_cached_by_nickname/1)
103 |> Enum.each(fn user ->
104 {:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
105 Pipeline.common_pipeline(delete_data, local: true)
108 ModerationLog.insert_log(%{
118 def user_follow(%{assigns: %{user: admin}} = conn, %{
119 "follower" => follower_nick,
120 "followed" => followed_nick
122 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
123 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
124 User.follow(follower, followed)
126 ModerationLog.insert_log(%{
138 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
139 "follower" => follower_nick,
140 "followed" => followed_nick
142 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
143 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
144 User.unfollow(follower, followed)
146 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),
197 {:error, id, changeset, _} ->
199 Enum.map(changesets.operations, fn
200 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
201 AccountView.render("create-error.json", %{changeset: changeset})
203 {_, {:changeset, current_changeset, _}} ->
204 AccountView.render("create-error.json", %{changeset: current_changeset})
208 |> put_status(:conflict)
213 def user_show(conn, %{"nickname" => nickname}) do
214 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
216 |> put_view(AccountView)
217 |> render("show.json", %{user: user})
219 _ -> {:error, :not_found}
223 def list_instance_statuses(conn, %{"instance" => instance} = params) do
224 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
225 {page, page_size} = page_params(params)
228 ActivityPub.fetch_statuses(nil, %{
231 offset: (page - 1) * page_size,
232 exclude_reblogs: not with_reblogs
236 |> put_view(AdminAPI.StatusView)
237 |> render("index.json", %{activities: activities, as: :activity})
240 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
241 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
242 godmode = params["godmode"] == "true" || params["godmode"] == true
244 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
245 {_, page_size} = page_params(params)
248 ActivityPub.fetch_user_activities(user, nil, %{
251 exclude_reblogs: not with_reblogs
255 |> put_view(AdminAPI.StatusView)
256 |> render("index.json", %{activities: activities, as: :activity})
258 _ -> {:error, :not_found}
262 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
263 user = User.get_cached_by_nickname(nickname)
265 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
267 action = if user.deactivated, do: "activate", else: "deactivate"
269 ModerationLog.insert_log(%{
276 |> put_view(AccountView)
277 |> render("show.json", %{user: updated_user})
280 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
281 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
282 {:ok, updated_users} = User.deactivate(users, false)
284 ModerationLog.insert_log(%{
291 |> put_view(AccountView)
292 |> render("index.json", %{users: Keyword.values(updated_users)})
295 def user_deactivate(%{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, true)
299 ModerationLog.insert_log(%{
306 |> put_view(AccountView)
307 |> render("index.json", %{users: Keyword.values(updated_users)})
310 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
311 with {:ok, _} <- User.tag(nicknames, tags) do
312 ModerationLog.insert_log(%{
314 nicknames: nicknames,
319 json_response(conn, :no_content, "")
323 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
324 with {:ok, _} <- User.untag(nicknames, tags) do
325 ModerationLog.insert_log(%{
327 nicknames: nicknames,
332 json_response(conn, :no_content, "")
336 def list_users(conn, params) do
337 {page, page_size} = page_params(params)
338 filters = maybe_parse_filters(params["filters"])
341 query: params["query"],
343 page_size: page_size,
344 tags: params["tags"],
345 name: params["name"],
346 email: params["email"]
349 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do
352 AccountView.render("index.json", users: users, count: count, page_size: page_size)
357 @filters ~w(local external active deactivated is_admin is_moderator)
359 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
360 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
362 defp maybe_parse_filters(filters) do
365 |> Enum.filter(&Enum.member?(@filters, &1))
366 |> Enum.map(&String.to_atom(&1))
367 |> Enum.into(%{}, &{&1, true})
370 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
371 "permission_group" => permission_group,
372 "nicknames" => nicknames
374 when permission_group in ["moderator", "admin"] do
375 update = %{:"is_#{permission_group}" => true}
377 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
379 for u <- users, do: User.admin_api_update(u, update)
381 ModerationLog.insert_log(%{
385 permission: permission_group
391 def right_add_multiple(conn, _) do
392 render_error(conn, :not_found, "No such permission_group")
395 def right_add(%{assigns: %{user: admin}} = conn, %{
396 "permission_group" => permission_group,
397 "nickname" => nickname
399 when permission_group in ["moderator", "admin"] do
400 fields = %{:"is_#{permission_group}" => true}
404 |> User.get_cached_by_nickname()
405 |> User.admin_api_update(fields)
407 ModerationLog.insert_log(%{
411 permission: permission_group
417 def right_add(conn, _) do
418 render_error(conn, :not_found, "No such permission_group")
421 def right_get(conn, %{"nickname" => nickname}) do
422 user = User.get_cached_by_nickname(nickname)
426 is_moderator: user.is_moderator,
427 is_admin: user.is_admin
431 def right_delete_multiple(
432 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
434 "permission_group" => permission_group,
435 "nicknames" => nicknames
438 when permission_group in ["moderator", "admin"] do
439 with false <- Enum.member?(nicknames, admin_nickname) do
440 update = %{:"is_#{permission_group}" => false}
442 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
444 for u <- users, do: User.admin_api_update(u, update)
446 ModerationLog.insert_log(%{
450 permission: permission_group
455 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
459 def right_delete_multiple(conn, _) do
460 render_error(conn, :not_found, "No such permission_group")
464 %{assigns: %{user: admin}} = conn,
466 "permission_group" => permission_group,
467 "nickname" => nickname
470 when permission_group in ["moderator", "admin"] do
471 fields = %{:"is_#{permission_group}" => false}
475 |> User.get_cached_by_nickname()
476 |> User.admin_api_update(fields)
478 ModerationLog.insert_log(%{
482 permission: permission_group
488 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
489 render_error(conn, :forbidden, "You can't revoke your own admin status.")
492 @doc "Get a password reset token (base64 string) for given nickname"
493 def get_password_reset(conn, %{"nickname" => nickname}) do
494 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
495 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
500 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
504 @doc "Force password reset for a given user"
505 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
506 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
508 Enum.each(users, &User.force_password_reset_async/1)
510 ModerationLog.insert_log(%{
513 action: "force_password_reset"
516 json_response(conn, :no_content, "")
519 @doc "Disable mfa for user's account."
520 def disable_mfa(conn, %{"nickname" => nickname}) do
521 case User.get_by_nickname(nickname) do
531 @doc "Show a given user's credentials"
532 def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
533 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
535 |> put_view(AccountView)
536 |> render("credentials.json", %{user: user, for: admin})
538 _ -> {:error, :not_found}
542 @doc "Updates a given user"
543 def update_user_credentials(
544 %{assigns: %{user: admin}} = conn,
545 %{"nickname" => nickname} = params
547 with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)},
549 User.update_as_admin(user, params) do
550 ModerationLog.insert_log(%{
553 action: "updated_users"
556 if params["password"] do
557 User.force_password_reset_async(user)
560 ModerationLog.insert_log(%{
563 action: "force_password_reset"
566 json(conn, %{status: "success"})
568 {:error, changeset} ->
569 errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
571 json(conn, %{errors: errors})
574 json(conn, %{error: "Unable to update user."})
578 def list_log(conn, params) do
579 {page, page_size} = page_params(params)
582 ModerationLog.get_all(%{
584 page_size: page_size,
585 start_date: params["start_date"],
586 end_date: params["end_date"],
587 user_id: params["user_id"],
588 search: params["search"]
592 |> put_view(ModerationLogView)
593 |> render("index.json", %{log: log})
596 def restart(conn, _params) do
597 with :ok <- configurable_from_database() do
598 Restarter.Pleroma.restart(Config.get(:env), 50)
604 def need_reboot(conn, _params) do
605 json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
608 defp configurable_from_database do
609 if Config.get(:configurable_from_database) do
612 {:error, "To use this endpoint you need to enable configuration from database."}
616 def reload_emoji(conn, _params) do
617 Pleroma.Emoji.reload()
622 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
623 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
625 User.toggle_confirmation(users)
627 ModerationLog.insert_log(%{
630 action: "confirm_email"
636 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
637 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
639 User.try_send_confirmation_email(users)
641 ModerationLog.insert_log(%{
644 action: "resend_confirmation_email"
650 def stats(conn, _) do
651 count = Stats.get_status_visibility_count()
654 |> json(%{"status_visibility" => count})
657 defp page_params(params) do
658 {get_page(params["page"]), get_page_size(params["page_size"])}
661 defp get_page(page_string) when is_nil(page_string), do: 1
663 defp get_page(page_string) do
664 case Integer.parse(page_string) do
670 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
672 defp get_page_size(page_size_string) do
673 case Integer.parse(page_size_string) do
674 {page_size, _} -> page_size
675 :error -> @users_page_size