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 |> Enum.map(&String.to_atom/1)
383 |> Map.new(&{&1, true})
386 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
387 "permission_group" => permission_group,
388 "nicknames" => nicknames
390 when permission_group in ["moderator", "admin"] do
391 update = %{:"is_#{permission_group}" => true}
393 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
395 for u <- users, do: User.admin_api_update(u, update)
397 ModerationLog.insert_log(%{
401 permission: permission_group
407 def right_add_multiple(conn, _) do
408 render_error(conn, :not_found, "No such permission_group")
411 def right_add(%{assigns: %{user: admin}} = conn, %{
412 "permission_group" => permission_group,
413 "nickname" => nickname
415 when permission_group in ["moderator", "admin"] do
416 fields = %{:"is_#{permission_group}" => true}
420 |> User.get_cached_by_nickname()
421 |> User.admin_api_update(fields)
423 ModerationLog.insert_log(%{
427 permission: permission_group
433 def right_add(conn, _) do
434 render_error(conn, :not_found, "No such permission_group")
437 def right_get(conn, %{"nickname" => nickname}) do
438 user = User.get_cached_by_nickname(nickname)
442 is_moderator: user.is_moderator,
443 is_admin: user.is_admin
447 def right_delete_multiple(
448 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
450 "permission_group" => permission_group,
451 "nicknames" => nicknames
454 when permission_group in ["moderator", "admin"] do
455 with false <- Enum.member?(nicknames, admin_nickname) do
456 update = %{:"is_#{permission_group}" => false}
458 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
460 for u <- users, do: User.admin_api_update(u, update)
462 ModerationLog.insert_log(%{
466 permission: permission_group
471 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
475 def right_delete_multiple(conn, _) do
476 render_error(conn, :not_found, "No such permission_group")
480 %{assigns: %{user: admin}} = conn,
482 "permission_group" => permission_group,
483 "nickname" => nickname
486 when permission_group in ["moderator", "admin"] do
487 fields = %{:"is_#{permission_group}" => false}
491 |> User.get_cached_by_nickname()
492 |> User.admin_api_update(fields)
494 ModerationLog.insert_log(%{
498 permission: permission_group
504 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
505 render_error(conn, :forbidden, "You can't revoke your own admin status.")
508 @doc "Get a password reset token (base64 string) for given nickname"
509 def get_password_reset(conn, %{"nickname" => nickname}) do
510 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
511 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
516 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
520 @doc "Force password reset for a given user"
521 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
522 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
524 Enum.each(users, &User.force_password_reset_async/1)
526 ModerationLog.insert_log(%{
529 action: "force_password_reset"
532 json_response(conn, :no_content, "")
535 @doc "Disable mfa for user's account."
536 def disable_mfa(conn, %{"nickname" => nickname}) do
537 case User.get_by_nickname(nickname) do
547 @doc "Show a given user's credentials"
548 def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
549 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
551 |> put_view(AccountView)
552 |> render("credentials.json", %{user: user, for: admin})
554 _ -> {:error, :not_found}
558 @doc "Updates a given user"
559 def update_user_credentials(
560 %{assigns: %{user: admin}} = conn,
561 %{"nickname" => nickname} = params
563 with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)},
565 User.update_as_admin(user, params) do
566 ModerationLog.insert_log(%{
569 action: "updated_users"
572 if params["password"] do
573 User.force_password_reset_async(user)
576 ModerationLog.insert_log(%{
579 action: "force_password_reset"
582 json(conn, %{status: "success"})
584 {:error, changeset} ->
585 errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
594 def list_log(conn, params) do
595 {page, page_size} = page_params(params)
598 ModerationLog.get_all(%{
600 page_size: page_size,
601 start_date: params["start_date"],
602 end_date: params["end_date"],
603 user_id: params["user_id"],
604 search: params["search"]
608 |> put_view(ModerationLogView)
609 |> render("index.json", %{log: log})
612 def restart(conn, _params) do
613 with :ok <- configurable_from_database() do
614 Restarter.Pleroma.restart(Config.get(:env), 50)
620 def need_reboot(conn, _params) do
621 json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
624 defp configurable_from_database do
625 if Config.get(:configurable_from_database) do
628 {:error, "To use this endpoint you need to enable configuration from database."}
632 def reload_emoji(conn, _params) do
633 Pleroma.Emoji.reload()
638 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
639 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
641 User.toggle_confirmation(users)
643 ModerationLog.insert_log(%{actor: admin, subject: users, action: "confirm_email"})
648 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
650 Enum.map(nicknames, fn nickname ->
652 |> User.get_cached_by_nickname()
653 |> User.send_confirmation_email()
656 ModerationLog.insert_log(%{actor: admin, subject: users, action: "resend_confirmation_email"})
661 def stats(conn, params) do
662 counters = Stats.get_status_visibility_count(params["instance"])
664 json(conn, %{"status_visibility" => counters})
667 defp page_params(params) do
668 {get_page(params["page"]), get_page_size(params["page_size"])}
671 defp get_page(page_string) when is_nil(page_string), do: 1
673 defp get_page(page_string) do
674 case Integer.parse(page_string) do
680 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
682 defp get_page_size(page_size_string) do
683 case Integer.parse(page_size_string) do
684 {page_size, _} -> page_size
685 :error -> @users_page_size