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,
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"], admin: true}
83 %{scopes: ["write"], admin: true}
86 :resend_confirmation_email,
92 action_fallback(AdminAPI.FallbackController)
94 def user_delete(conn, %{"nickname" => nickname}) do
95 user_delete(conn, %{"nicknames" => [nickname]})
98 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
101 |> Enum.map(&User.get_cached_by_nickname/1)
104 |> Enum.each(fn user ->
105 {:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
106 Pipeline.common_pipeline(delete_data, local: true)
109 ModerationLog.insert_log(%{
115 json(conn, nicknames)
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(%{
137 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
138 "follower" => follower_nick,
139 "followed" => followed_nick
141 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
142 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
143 User.unfollow(follower, followed)
145 ModerationLog.insert_log(%{
156 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
158 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
164 password_confirmation: password,
168 User.register_changeset(%User{}, user_data, need_confirmation: false)
170 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
171 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
174 case Pleroma.Repo.transaction(changesets) do
179 |> Enum.map(fn user ->
180 {:ok, user} = User.post_register_action(user)
184 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
186 ModerationLog.insert_log(%{
188 subjects: Map.values(users),
194 {:error, id, changeset, _} ->
196 Enum.map(changesets.operations, fn
197 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
198 AccountView.render("create-error.json", %{changeset: changeset})
200 {_, {:changeset, current_changeset, _}} ->
201 AccountView.render("create-error.json", %{changeset: current_changeset})
205 |> put_status(:conflict)
210 def user_show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
211 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
213 |> put_view(AccountView)
214 |> render("show.json", %{user: user})
216 _ -> {:error, :not_found}
220 def list_instance_statuses(conn, %{"instance" => instance} = params) do
221 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
222 {page, page_size} = page_params(params)
225 ActivityPub.fetch_statuses(nil, %{
228 offset: (page - 1) * page_size,
229 exclude_reblogs: not with_reblogs
233 |> put_view(AdminAPI.StatusView)
234 |> render("index.json", %{activities: activities, as: :activity})
237 def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do
238 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
239 godmode = params["godmode"] == "true" || params["godmode"] == true
241 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
242 {_, page_size} = page_params(params)
245 ActivityPub.fetch_user_activities(user, nil, %{
248 exclude_reblogs: not with_reblogs
252 |> put_view(AdminAPI.StatusView)
253 |> render("index.json", %{activities: activities, as: :activity})
255 _ -> {:error, :not_found}
259 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
260 user = User.get_cached_by_nickname(nickname)
262 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
264 action = if user.deactivated, do: "activate", else: "deactivate"
266 ModerationLog.insert_log(%{
273 |> put_view(AccountView)
274 |> render("show.json", %{user: updated_user})
277 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
278 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
279 {:ok, updated_users} = User.deactivate(users, false)
281 ModerationLog.insert_log(%{
288 |> put_view(AccountView)
289 |> render("index.json", %{users: Keyword.values(updated_users)})
292 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
293 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
294 {:ok, updated_users} = User.deactivate(users, true)
296 ModerationLog.insert_log(%{
303 |> put_view(AccountView)
304 |> render("index.json", %{users: Keyword.values(updated_users)})
307 def user_approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
308 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
309 {:ok, updated_users} = User.approve(users)
311 ModerationLog.insert_log(%{
318 |> put_view(AccountView)
319 |> render("index.json", %{users: updated_users})
322 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
323 with {:ok, _} <- User.tag(nicknames, tags) do
324 ModerationLog.insert_log(%{
326 nicknames: nicknames,
331 json_response(conn, :no_content, "")
335 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
336 with {:ok, _} <- User.untag(nicknames, tags) do
337 ModerationLog.insert_log(%{
339 nicknames: nicknames,
344 json_response(conn, :no_content, "")
348 def list_users(conn, params) do
349 {page, page_size} = page_params(params)
350 filters = maybe_parse_filters(params["filters"])
353 query: params["query"],
355 page_size: page_size,
356 tags: params["tags"],
357 name: params["name"],
358 email: params["email"]
361 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do
364 AccountView.render("index.json", users: users, count: count, page_size: page_size)
369 @filters ~w(local external active deactivated need_approval is_admin is_moderator)
371 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
372 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
374 defp maybe_parse_filters(filters) do
377 |> Enum.filter(&Enum.member?(@filters, &1))
378 |> Enum.map(&String.to_atom/1)
379 |> Map.new(&{&1, true})
382 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
383 "permission_group" => permission_group,
384 "nicknames" => nicknames
386 when permission_group in ["moderator", "admin"] do
387 update = %{:"is_#{permission_group}" => true}
389 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
391 for u <- users, do: User.admin_api_update(u, update)
393 ModerationLog.insert_log(%{
397 permission: permission_group
403 def right_add_multiple(conn, _) do
404 render_error(conn, :not_found, "No such permission_group")
407 def right_add(%{assigns: %{user: admin}} = conn, %{
408 "permission_group" => permission_group,
409 "nickname" => nickname
411 when permission_group in ["moderator", "admin"] do
412 fields = %{:"is_#{permission_group}" => true}
416 |> User.get_cached_by_nickname()
417 |> User.admin_api_update(fields)
419 ModerationLog.insert_log(%{
423 permission: permission_group
429 def right_add(conn, _) do
430 render_error(conn, :not_found, "No such permission_group")
433 def right_get(conn, %{"nickname" => nickname}) do
434 user = User.get_cached_by_nickname(nickname)
438 is_moderator: user.is_moderator,
439 is_admin: user.is_admin
443 def right_delete_multiple(
444 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
446 "permission_group" => permission_group,
447 "nicknames" => nicknames
450 when permission_group in ["moderator", "admin"] do
451 with false <- Enum.member?(nicknames, admin_nickname) do
452 update = %{:"is_#{permission_group}" => false}
454 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
456 for u <- users, do: User.admin_api_update(u, update)
458 ModerationLog.insert_log(%{
462 permission: permission_group
467 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
471 def right_delete_multiple(conn, _) do
472 render_error(conn, :not_found, "No such permission_group")
476 %{assigns: %{user: admin}} = conn,
478 "permission_group" => permission_group,
479 "nickname" => nickname
482 when permission_group in ["moderator", "admin"] do
483 fields = %{:"is_#{permission_group}" => false}
487 |> User.get_cached_by_nickname()
488 |> User.admin_api_update(fields)
490 ModerationLog.insert_log(%{
494 permission: permission_group
500 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
501 render_error(conn, :forbidden, "You can't revoke your own admin status.")
504 @doc "Get a password reset token (base64 string) for given nickname"
505 def get_password_reset(conn, %{"nickname" => nickname}) do
506 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
507 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
512 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
516 @doc "Force password reset for a given user"
517 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
518 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
520 Enum.each(users, &User.force_password_reset_async/1)
522 ModerationLog.insert_log(%{
525 action: "force_password_reset"
528 json_response(conn, :no_content, "")
531 @doc "Disable mfa for user's account."
532 def disable_mfa(conn, %{"nickname" => nickname}) do
533 case User.get_by_nickname(nickname) do
543 @doc "Show a given user's credentials"
544 def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
545 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
547 |> put_view(AccountView)
548 |> render("credentials.json", %{user: user, for: admin})
550 _ -> {:error, :not_found}
554 @doc "Updates a given user"
555 def update_user_credentials(
556 %{assigns: %{user: admin}} = conn,
557 %{"nickname" => nickname} = params
559 with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)},
561 User.update_as_admin(user, params) do
562 ModerationLog.insert_log(%{
565 action: "updated_users"
568 if params["password"] do
569 User.force_password_reset_async(user)
572 ModerationLog.insert_log(%{
575 action: "force_password_reset"
578 json(conn, %{status: "success"})
580 {:error, changeset} ->
581 errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
590 def list_log(conn, params) do
591 {page, page_size} = page_params(params)
594 ModerationLog.get_all(%{
596 page_size: page_size,
597 start_date: params["start_date"],
598 end_date: params["end_date"],
599 user_id: params["user_id"],
600 search: params["search"]
604 |> put_view(ModerationLogView)
605 |> render("index.json", %{log: log})
608 def restart(conn, _params) do
609 with :ok <- configurable_from_database() do
610 Restarter.Pleroma.restart(Config.get(:env), 50)
616 def need_reboot(conn, _params) do
617 json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
620 defp configurable_from_database do
621 if Config.get(:configurable_from_database) do
624 {:error, "To use this endpoint you need to enable configuration from database."}
628 def reload_emoji(conn, _params) do
629 Pleroma.Emoji.reload()
634 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
635 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
637 User.toggle_confirmation(users)
639 ModerationLog.insert_log(%{
642 action: "confirm_email"
648 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
649 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
651 User.try_send_confirmation_email(users)
653 ModerationLog.insert_log(%{
656 action: "resend_confirmation_email"
662 def stats(conn, params) do
663 counters = Stats.get_status_visibility_count(params["instance"])
665 json(conn, %{"status_visibility" => counters})
668 defp page_params(params) do
669 {get_page(params["page"]), get_page_size(params["page_size"])}
672 defp get_page(page_string) when is_nil(page_string), do: 1
674 defp get_page(page_string) do
675 case Integer.parse(page_string) do
681 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
683 defp get_page_size(page_size_string) do
684 case Integer.parse(page_size_string) do
685 {page_size, _} -> page_size
686 :error -> @users_page_size