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(%{
114 json(conn, nicknames)
117 def user_follow(%{assigns: %{user: admin}} = conn, %{
118 "follower" => follower_nick,
119 "followed" => followed_nick
121 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
122 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
123 User.follow(follower, followed)
125 ModerationLog.insert_log(%{
136 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
137 "follower" => follower_nick,
138 "followed" => followed_nick
140 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
141 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
142 User.unfollow(follower, followed)
144 ModerationLog.insert_log(%{
155 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
157 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
163 password_confirmation: password,
167 User.register_changeset(%User{}, user_data, need_confirmation: false)
169 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
170 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
173 case Pleroma.Repo.transaction(changesets) do
178 |> Enum.map(fn user ->
179 {:ok, user} = User.post_register_action(user)
183 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
185 ModerationLog.insert_log(%{
187 subjects: Map.values(users),
193 {:error, id, changeset, _} ->
195 Enum.map(changesets.operations, fn
196 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
197 AccountView.render("create-error.json", %{changeset: changeset})
199 {_, {:changeset, current_changeset, _}} ->
200 AccountView.render("create-error.json", %{changeset: current_changeset})
204 |> put_status(:conflict)
209 def user_show(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
210 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
212 |> put_view(AccountView)
213 |> render("show.json", %{user: user})
215 _ -> {:error, :not_found}
219 def list_instance_statuses(conn, %{"instance" => instance} = params) do
220 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
221 {page, page_size} = page_params(params)
224 ActivityPub.fetch_statuses(nil, %{
227 offset: (page - 1) * page_size,
228 exclude_reblogs: not with_reblogs
232 |> put_view(AdminAPI.StatusView)
233 |> render("index.json", %{activities: activities, as: :activity})
236 def list_user_statuses(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname} = params) do
237 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
238 godmode = params["godmode"] == "true" || params["godmode"] == true
240 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
241 {_, page_size} = page_params(params)
244 ActivityPub.fetch_user_activities(user, nil, %{
247 exclude_reblogs: not with_reblogs
251 |> put_view(AdminAPI.StatusView)
252 |> render("index.json", %{activities: activities, as: :activity})
254 _ -> {:error, :not_found}
258 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
259 user = User.get_cached_by_nickname(nickname)
261 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
263 action = if user.deactivated, do: "activate", else: "deactivate"
265 ModerationLog.insert_log(%{
272 |> put_view(AccountView)
273 |> render("show.json", %{user: updated_user})
276 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
277 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
278 {:ok, updated_users} = User.deactivate(users, false)
280 ModerationLog.insert_log(%{
287 |> put_view(AccountView)
288 |> render("index.json", %{users: Keyword.values(updated_users)})
291 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
292 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
293 {:ok, updated_users} = User.deactivate(users, true)
295 ModerationLog.insert_log(%{
302 |> put_view(AccountView)
303 |> render("index.json", %{users: Keyword.values(updated_users)})
306 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
307 with {:ok, _} <- User.tag(nicknames, tags) do
308 ModerationLog.insert_log(%{
310 nicknames: nicknames,
315 json_response(conn, :no_content, "")
319 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
320 with {:ok, _} <- User.untag(nicknames, tags) do
321 ModerationLog.insert_log(%{
323 nicknames: nicknames,
328 json_response(conn, :no_content, "")
332 def list_users(conn, params) do
333 {page, page_size} = page_params(params)
334 filters = maybe_parse_filters(params["filters"])
337 query: params["query"],
339 page_size: page_size,
340 tags: params["tags"],
341 name: params["name"],
342 email: params["email"]
345 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do
348 AccountView.render("index.json", users: users, count: count, page_size: page_size)
353 @filters ~w(local external active deactivated is_admin is_moderator)
355 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
356 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
358 defp maybe_parse_filters(filters) do
361 |> Enum.filter(&Enum.member?(@filters, &1))
362 |> Enum.map(&String.to_atom/1)
363 |> Map.new(&{&1, true})
366 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
367 "permission_group" => permission_group,
368 "nicknames" => nicknames
370 when permission_group in ["moderator", "admin"] do
371 update = %{:"is_#{permission_group}" => true}
373 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
375 for u <- users, do: User.admin_api_update(u, update)
377 ModerationLog.insert_log(%{
381 permission: permission_group
387 def right_add_multiple(conn, _) do
388 render_error(conn, :not_found, "No such permission_group")
391 def right_add(%{assigns: %{user: admin}} = conn, %{
392 "permission_group" => permission_group,
393 "nickname" => nickname
395 when permission_group in ["moderator", "admin"] do
396 fields = %{:"is_#{permission_group}" => true}
400 |> User.get_cached_by_nickname()
401 |> User.admin_api_update(fields)
403 ModerationLog.insert_log(%{
407 permission: permission_group
413 def right_add(conn, _) do
414 render_error(conn, :not_found, "No such permission_group")
417 def right_get(conn, %{"nickname" => nickname}) do
418 user = User.get_cached_by_nickname(nickname)
422 is_moderator: user.is_moderator,
423 is_admin: user.is_admin
427 def right_delete_multiple(
428 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
430 "permission_group" => permission_group,
431 "nicknames" => nicknames
434 when permission_group in ["moderator", "admin"] do
435 with false <- Enum.member?(nicknames, admin_nickname) do
436 update = %{:"is_#{permission_group}" => false}
438 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
440 for u <- users, do: User.admin_api_update(u, update)
442 ModerationLog.insert_log(%{
446 permission: permission_group
451 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
455 def right_delete_multiple(conn, _) do
456 render_error(conn, :not_found, "No such permission_group")
460 %{assigns: %{user: admin}} = conn,
462 "permission_group" => permission_group,
463 "nickname" => nickname
466 when permission_group in ["moderator", "admin"] do
467 fields = %{:"is_#{permission_group}" => false}
471 |> User.get_cached_by_nickname()
472 |> User.admin_api_update(fields)
474 ModerationLog.insert_log(%{
478 permission: permission_group
484 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
485 render_error(conn, :forbidden, "You can't revoke your own admin status.")
488 @doc "Get a password reset token (base64 string) for given nickname"
489 def get_password_reset(conn, %{"nickname" => nickname}) do
490 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
491 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
496 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
500 @doc "Force password reset for a given user"
501 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
502 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
504 Enum.each(users, &User.force_password_reset_async/1)
506 ModerationLog.insert_log(%{
509 action: "force_password_reset"
512 json_response(conn, :no_content, "")
515 @doc "Disable mfa for user's account."
516 def disable_mfa(conn, %{"nickname" => nickname}) do
517 case User.get_by_nickname(nickname) do
527 @doc "Show a given user's credentials"
528 def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
529 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
531 |> put_view(AccountView)
532 |> render("credentials.json", %{user: user, for: admin})
534 _ -> {:error, :not_found}
538 @doc "Updates a given user"
539 def update_user_credentials(
540 %{assigns: %{user: admin}} = conn,
541 %{"nickname" => nickname} = params
543 with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)},
545 User.update_as_admin(user, params) do
546 ModerationLog.insert_log(%{
549 action: "updated_users"
552 if params["password"] do
553 User.force_password_reset_async(user)
556 ModerationLog.insert_log(%{
559 action: "force_password_reset"
562 json(conn, %{status: "success"})
564 {:error, changeset} ->
565 errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
574 def list_log(conn, params) do
575 {page, page_size} = page_params(params)
578 ModerationLog.get_all(%{
580 page_size: page_size,
581 start_date: params["start_date"],
582 end_date: params["end_date"],
583 user_id: params["user_id"],
584 search: params["search"]
588 |> put_view(ModerationLogView)
589 |> render("index.json", %{log: log})
592 def restart(conn, _params) do
593 with :ok <- configurable_from_database() do
594 Restarter.Pleroma.restart(Config.get(:env), 50)
600 def need_reboot(conn, _params) do
601 json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
604 defp configurable_from_database do
605 if Config.get(:configurable_from_database) do
608 {:error, "To use this endpoint you need to enable configuration from database."}
612 def reload_emoji(conn, _params) do
613 Pleroma.Emoji.reload()
618 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
619 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
621 User.toggle_confirmation(users)
623 ModerationLog.insert_log(%{
626 action: "confirm_email"
632 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
633 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
635 User.try_send_confirmation_email(users)
637 ModerationLog.insert_log(%{
640 action: "resend_confirmation_email"
646 def stats(conn, params) do
647 counters = Stats.get_status_visibility_count(params["instance"])
649 json(conn, %{"status_visibility" => counters})
652 defp page_params(params) do
653 {get_page(params["page"]), get_page_size(params["page_size"])}
656 defp get_page(page_string) when is_nil(page_string), do: 1
658 defp get_page(page_string) do
659 case Integer.parse(page_string) do
665 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
667 defp get_page_size(page_size_string) do
668 case Integer.parse(page_size_string) do
669 {page_size, _} -> page_size
670 :error -> @users_page_size