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",
373 @filters ~w(local external active deactivated need_approval is_admin is_moderator)
375 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
376 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
378 defp maybe_parse_filters(filters) do
381 |> Enum.filter(&Enum.member?(@filters, &1))
382 |> Map.new(&{String.to_existing_atom(&1), true})
385 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
386 "permission_group" => permission_group,
387 "nicknames" => nicknames
389 when permission_group in ["moderator", "admin"] do
390 update = %{:"is_#{permission_group}" => true}
392 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
394 for u <- users, do: User.admin_api_update(u, update)
396 ModerationLog.insert_log(%{
400 permission: permission_group
406 def right_add_multiple(conn, _) do
407 render_error(conn, :not_found, "No such permission_group")
410 def right_add(%{assigns: %{user: admin}} = conn, %{
411 "permission_group" => permission_group,
412 "nickname" => nickname
414 when permission_group in ["moderator", "admin"] do
415 fields = %{:"is_#{permission_group}" => true}
419 |> User.get_cached_by_nickname()
420 |> User.admin_api_update(fields)
422 ModerationLog.insert_log(%{
426 permission: permission_group
432 def right_add(conn, _) do
433 render_error(conn, :not_found, "No such permission_group")
436 def right_get(conn, %{"nickname" => nickname}) do
437 user = User.get_cached_by_nickname(nickname)
441 is_moderator: user.is_moderator,
442 is_admin: user.is_admin
446 def right_delete_multiple(
447 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
449 "permission_group" => permission_group,
450 "nicknames" => nicknames
453 when permission_group in ["moderator", "admin"] do
454 with false <- Enum.member?(nicknames, admin_nickname) do
455 update = %{:"is_#{permission_group}" => false}
457 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
459 for u <- users, do: User.admin_api_update(u, update)
461 ModerationLog.insert_log(%{
465 permission: permission_group
470 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
474 def right_delete_multiple(conn, _) do
475 render_error(conn, :not_found, "No such permission_group")
479 %{assigns: %{user: admin}} = conn,
481 "permission_group" => permission_group,
482 "nickname" => nickname
485 when permission_group in ["moderator", "admin"] do
486 fields = %{:"is_#{permission_group}" => false}
490 |> User.get_cached_by_nickname()
491 |> User.admin_api_update(fields)
493 ModerationLog.insert_log(%{
497 permission: permission_group
503 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
504 render_error(conn, :forbidden, "You can't revoke your own admin status.")
507 @doc "Get a password reset token (base64 string) for given nickname"
508 def get_password_reset(conn, %{"nickname" => nickname}) do
509 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
510 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
515 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
519 @doc "Force password reset for a given user"
520 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
521 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
523 Enum.each(users, &User.force_password_reset_async/1)
525 ModerationLog.insert_log(%{
528 action: "force_password_reset"
531 json_response(conn, :no_content, "")
534 @doc "Disable mfa for user's account."
535 def disable_mfa(conn, %{"nickname" => nickname}) do
536 case User.get_by_nickname(nickname) do
546 @doc "Show a given user's credentials"
547 def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
548 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
550 |> put_view(AccountView)
551 |> render("credentials.json", %{user: user, for: admin})
553 _ -> {:error, :not_found}
557 @doc "Updates a given user"
558 def update_user_credentials(
559 %{assigns: %{user: admin}} = conn,
560 %{"nickname" => nickname} = params
562 with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)},
564 User.update_as_admin(user, params) do
565 ModerationLog.insert_log(%{
568 action: "updated_users"
571 if params["password"] do
572 User.force_password_reset_async(user)
575 ModerationLog.insert_log(%{
578 action: "force_password_reset"
581 json(conn, %{status: "success"})
583 {:error, changeset} ->
584 errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
593 def list_log(conn, params) do
594 {page, page_size} = page_params(params)
597 ModerationLog.get_all(%{
599 page_size: page_size,
600 start_date: params["start_date"],
601 end_date: params["end_date"],
602 user_id: params["user_id"],
603 search: params["search"]
607 |> put_view(ModerationLogView)
608 |> render("index.json", %{log: log})
611 def restart(conn, _params) do
612 with :ok <- configurable_from_database() do
613 Restarter.Pleroma.restart(Config.get(:env), 50)
619 def need_reboot(conn, _params) do
620 json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
623 defp configurable_from_database do
624 if Config.get(:configurable_from_database) do
627 {:error, "To use this endpoint you need to enable configuration from database."}
631 def reload_emoji(conn, _params) do
632 Pleroma.Emoji.reload()
637 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
638 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
640 User.toggle_confirmation(users)
642 ModerationLog.insert_log(%{actor: admin, subject: users, action: "confirm_email"})
647 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
649 Enum.map(nicknames, fn nickname ->
651 |> User.get_cached_by_nickname()
652 |> User.send_confirmation_email()
655 ModerationLog.insert_log(%{actor: admin, subject: users, action: "resend_confirmation_email"})
660 def stats(conn, params) do
661 counters = Stats.get_status_visibility_count(params["instance"])
663 json(conn, %{"status_visibility" => counters})
666 defp page_params(params) do
667 {get_page(params["page"]), get_page_size(params["page_size"])}
670 defp get_page(page_string) when is_nil(page_string), do: 1
672 defp get_page(page_string) do
673 case Integer.parse(page_string) do
679 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
681 defp get_page_size(page_size_string) do
682 case Integer.parse(page_size_string) do
683 {page_size, _} -> page_size
684 :error -> @users_page_size