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
9 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
13 alias Pleroma.ModerationLog
14 alias Pleroma.Plugs.OAuthScopesPlug
17 alias Pleroma.Web.ActivityPub.ActivityPub
18 alias Pleroma.Web.ActivityPub.Builder
19 alias Pleroma.Web.ActivityPub.Pipeline
20 alias Pleroma.Web.AdminAPI
21 alias Pleroma.Web.AdminAPI.AccountView
22 alias Pleroma.Web.AdminAPI.ModerationLogView
23 alias Pleroma.Web.AdminAPI.Search
24 alias Pleroma.Web.Endpoint
25 alias Pleroma.Web.PleromaAPI
26 alias Pleroma.Web.Router
34 %{scopes: ["read:accounts"], admin: true}
35 when action in [:list_users, :user_show, :right_get, :show_user_credentials]
40 %{scopes: ["write:accounts"], admin: true}
43 :force_password_reset,
46 :user_toggle_activation,
56 :right_delete_multiple,
57 :update_user_credentials
63 %{scopes: ["write:follows"], admin: true}
64 when action in [:user_follow, :user_unfollow]
69 %{scopes: ["read:statuses"], admin: true}
70 when action in [:list_user_statuses, :list_instance_statuses]
75 %{scopes: ["read:chats"], admin: true}
76 when action in [:list_user_chats]
81 %{scopes: ["read"], admin: true}
91 %{scopes: ["write"], admin: true}
94 :resend_confirmation_email,
100 action_fallback(AdminAPI.FallbackController)
102 def user_delete(conn, %{"nickname" => nickname}) do
103 user_delete(conn, %{"nicknames" => [nickname]})
106 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
109 |> Enum.map(&User.get_cached_by_nickname/1)
112 |> Enum.each(fn user ->
113 {:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
114 Pipeline.common_pipeline(delete_data, local: true)
117 ModerationLog.insert_log(%{
123 json(conn, nicknames)
126 def user_follow(%{assigns: %{user: admin}} = conn, %{
127 "follower" => follower_nick,
128 "followed" => followed_nick
130 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
131 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
132 User.follow(follower, followed)
134 ModerationLog.insert_log(%{
145 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
146 "follower" => follower_nick,
147 "followed" => followed_nick
149 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
150 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
151 User.unfollow(follower, followed)
153 ModerationLog.insert_log(%{
164 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
166 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
172 password_confirmation: password,
176 User.register_changeset(%User{}, user_data, need_confirmation: false)
178 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
179 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
182 case Pleroma.Repo.transaction(changesets) do
187 |> Enum.map(fn user ->
188 {:ok, user} = User.post_register_action(user)
192 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
194 ModerationLog.insert_log(%{
196 subjects: Map.values(users),
202 {:error, id, changeset, _} ->
204 Enum.map(changesets.operations, fn
205 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
206 AccountView.render("create-error.json", %{changeset: changeset})
208 {_, {:changeset, current_changeset, _}} ->
209 AccountView.render("create-error.json", %{changeset: current_changeset})
213 |> put_status(:conflict)
218 def user_show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
219 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
221 |> put_view(AccountView)
222 |> render("show.json", %{user: user})
224 _ -> {:error, :not_found}
228 def list_instance_statuses(conn, %{"instance" => instance} = params) do
229 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
230 {page, page_size} = page_params(params)
233 ActivityPub.fetch_statuses(nil, %{
236 offset: (page - 1) * page_size,
237 exclude_reblogs: not with_reblogs
241 |> put_view(AdminAPI.StatusView)
242 |> render("index.json", %{activities: activities, as: :activity})
245 def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do
246 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
247 godmode = params["godmode"] == "true" || params["godmode"] == true
249 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
250 {_, page_size} = page_params(params)
253 ActivityPub.fetch_user_activities(user, nil, %{
256 exclude_reblogs: not with_reblogs
260 |> put_view(AdminAPI.StatusView)
261 |> render("index.json", %{activities: activities, as: :activity})
263 _ -> {:error, :not_found}
267 def list_user_chats(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = _params) do
268 with %User{id: user_id} <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
270 from(c in Pleroma.Chat,
271 where: c.user_id == ^user_id,
272 order_by: [desc: c.updated_at],
273 inner_join: u in User,
274 on: u.ap_id == c.recipient
276 |> Pleroma.Repo.all()
279 |> put_view(PleromaAPI.ChatView)
280 |> render("index.json", chats: chats)
282 _ -> {:error, :not_found}
286 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
287 user = User.get_cached_by_nickname(nickname)
289 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
291 action = if user.deactivated, do: "activate", else: "deactivate"
293 ModerationLog.insert_log(%{
300 |> put_view(AccountView)
301 |> render("show.json", %{user: updated_user})
304 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
305 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
306 {:ok, updated_users} = User.deactivate(users, false)
308 ModerationLog.insert_log(%{
315 |> put_view(AccountView)
316 |> render("index.json", %{users: Keyword.values(updated_users)})
319 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
320 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
321 {:ok, updated_users} = User.deactivate(users, true)
323 ModerationLog.insert_log(%{
330 |> put_view(AccountView)
331 |> render("index.json", %{users: Keyword.values(updated_users)})
334 def user_approve(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
335 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
336 {:ok, updated_users} = User.approve(users)
338 ModerationLog.insert_log(%{
345 |> put_view(AccountView)
346 |> render("index.json", %{users: updated_users})
349 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
350 with {:ok, _} <- User.tag(nicknames, tags) do
351 ModerationLog.insert_log(%{
353 nicknames: nicknames,
358 json_response(conn, :no_content, "")
362 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
363 with {:ok, _} <- User.untag(nicknames, tags) do
364 ModerationLog.insert_log(%{
366 nicknames: nicknames,
371 json_response(conn, :no_content, "")
375 def list_users(conn, params) do
376 {page, page_size} = page_params(params)
377 filters = maybe_parse_filters(params["filters"])
380 query: params["query"],
382 page_size: page_size,
383 tags: params["tags"],
384 name: params["name"],
385 email: params["email"]
388 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do
391 AccountView.render("index.json",
400 @filters ~w(local external active deactivated need_approval is_admin is_moderator)
402 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
403 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
405 defp maybe_parse_filters(filters) do
408 |> Enum.filter(&Enum.member?(@filters, &1))
409 |> Map.new(&{String.to_existing_atom(&1), true})
412 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
413 "permission_group" => permission_group,
414 "nicknames" => nicknames
416 when permission_group in ["moderator", "admin"] do
417 update = %{:"is_#{permission_group}" => true}
419 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
421 for u <- users, do: User.admin_api_update(u, update)
423 ModerationLog.insert_log(%{
427 permission: permission_group
433 def right_add_multiple(conn, _) do
434 render_error(conn, :not_found, "No such permission_group")
437 def right_add(%{assigns: %{user: admin}} = conn, %{
438 "permission_group" => permission_group,
439 "nickname" => nickname
441 when permission_group in ["moderator", "admin"] do
442 fields = %{:"is_#{permission_group}" => true}
446 |> User.get_cached_by_nickname()
447 |> User.admin_api_update(fields)
449 ModerationLog.insert_log(%{
453 permission: permission_group
459 def right_add(conn, _) do
460 render_error(conn, :not_found, "No such permission_group")
463 def right_get(conn, %{"nickname" => nickname}) do
464 user = User.get_cached_by_nickname(nickname)
468 is_moderator: user.is_moderator,
469 is_admin: user.is_admin
473 def right_delete_multiple(
474 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
476 "permission_group" => permission_group,
477 "nicknames" => nicknames
480 when permission_group in ["moderator", "admin"] do
481 with false <- Enum.member?(nicknames, admin_nickname) do
482 update = %{:"is_#{permission_group}" => false}
484 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
486 for u <- users, do: User.admin_api_update(u, update)
488 ModerationLog.insert_log(%{
492 permission: permission_group
497 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
501 def right_delete_multiple(conn, _) do
502 render_error(conn, :not_found, "No such permission_group")
506 %{assigns: %{user: admin}} = conn,
508 "permission_group" => permission_group,
509 "nickname" => nickname
512 when permission_group in ["moderator", "admin"] do
513 fields = %{:"is_#{permission_group}" => false}
517 |> User.get_cached_by_nickname()
518 |> User.admin_api_update(fields)
520 ModerationLog.insert_log(%{
524 permission: permission_group
530 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
531 render_error(conn, :forbidden, "You can't revoke your own admin status.")
534 @doc "Get a password reset token (base64 string) for given nickname"
535 def get_password_reset(conn, %{"nickname" => nickname}) do
536 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
537 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
542 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
546 @doc "Force password reset for a given user"
547 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
548 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
550 Enum.each(users, &User.force_password_reset_async/1)
552 ModerationLog.insert_log(%{
555 action: "force_password_reset"
558 json_response(conn, :no_content, "")
561 @doc "Disable mfa for user's account."
562 def disable_mfa(conn, %{"nickname" => nickname}) do
563 case User.get_by_nickname(nickname) do
573 @doc "Show a given user's credentials"
574 def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
575 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
577 |> put_view(AccountView)
578 |> render("credentials.json", %{user: user, for: admin})
580 _ -> {:error, :not_found}
584 @doc "Updates a given user"
585 def update_user_credentials(
586 %{assigns: %{user: admin}} = conn,
587 %{"nickname" => nickname} = params
589 with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)},
591 User.update_as_admin(user, params) do
592 ModerationLog.insert_log(%{
595 action: "updated_users"
598 if params["password"] do
599 User.force_password_reset_async(user)
602 ModerationLog.insert_log(%{
605 action: "force_password_reset"
608 json(conn, %{status: "success"})
610 {:error, changeset} ->
611 errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
620 def list_log(conn, params) do
621 {page, page_size} = page_params(params)
624 ModerationLog.get_all(%{
626 page_size: page_size,
627 start_date: params["start_date"],
628 end_date: params["end_date"],
629 user_id: params["user_id"],
630 search: params["search"]
634 |> put_view(ModerationLogView)
635 |> render("index.json", %{log: log})
638 def restart(conn, _params) do
639 with :ok <- configurable_from_database() do
640 Restarter.Pleroma.restart(Config.get(:env), 50)
646 def need_reboot(conn, _params) do
647 json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
650 defp configurable_from_database do
651 if Config.get(:configurable_from_database) do
654 {:error, "To use this endpoint you need to enable configuration from database."}
658 def reload_emoji(conn, _params) do
659 Pleroma.Emoji.reload()
664 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
665 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
667 User.toggle_confirmation(users)
669 ModerationLog.insert_log(%{actor: admin, subject: users, action: "confirm_email"})
674 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
676 Enum.map(nicknames, fn nickname ->
678 |> User.get_cached_by_nickname()
679 |> User.send_confirmation_email()
682 ModerationLog.insert_log(%{actor: admin, subject: users, action: "resend_confirmation_email"})
687 def stats(conn, params) do
688 counters = Stats.get_status_visibility_count(params["instance"])
690 json(conn, %{"status_visibility" => counters})
693 defp page_params(params) do
694 {get_page(params["page"]), get_page_size(params["page_size"])}
697 defp get_page(page_string) when is_nil(page_string), do: 1
699 defp get_page(page_string) do
700 case Integer.parse(page_string) do
706 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
708 defp get_page_size(page_size_string) do
709 case Integer.parse(page_size_string) do
710 {page_size, _} -> page_size
711 :error -> @users_page_size