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.ActivityPub.Relay
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.Router
33 %{scopes: ["read:accounts"], admin: true}
34 when action in [:list_users, :user_show, :right_get, :show_user_credentials]
39 %{scopes: ["write:accounts"], admin: true}
42 :force_password_reset,
45 :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, :relay_follow, :relay_unfollow]
67 %{scopes: ["read:statuses"], admin: true}
68 when action in [:list_user_statuses, :list_instance_statuses]
73 %{scopes: ["read"], admin: true}
84 %{scopes: ["write"], admin: true}
87 :resend_confirmation_email,
93 action_fallback(AdminAPI.FallbackController)
95 def user_delete(conn, %{"nickname" => nickname}) do
96 user_delete(conn, %{"nicknames" => [nickname]})
99 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
102 |> Enum.map(&User.get_cached_by_nickname/1)
105 |> Enum.each(fn user ->
106 {:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
107 Pipeline.common_pipeline(delete_data, local: true)
110 ModerationLog.insert_log(%{
120 def user_follow(%{assigns: %{user: admin}} = conn, %{
121 "follower" => follower_nick,
122 "followed" => followed_nick
124 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
125 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
126 User.follow(follower, followed)
128 ModerationLog.insert_log(%{
140 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
141 "follower" => follower_nick,
142 "followed" => followed_nick
144 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
145 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
146 User.unfollow(follower, followed)
148 ModerationLog.insert_log(%{
160 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
162 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
168 password_confirmation: password,
172 User.register_changeset(%User{}, user_data, need_confirmation: false)
174 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
175 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
178 case Pleroma.Repo.transaction(changesets) do
183 |> Enum.map(fn user ->
184 {:ok, user} = User.post_register_action(user)
188 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
190 ModerationLog.insert_log(%{
192 subjects: Map.values(users),
199 {:error, id, changeset, _} ->
201 Enum.map(changesets.operations, fn
202 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
203 AccountView.render("create-error.json", %{changeset: changeset})
205 {_, {:changeset, current_changeset, _}} ->
206 AccountView.render("create-error.json", %{changeset: current_changeset})
210 |> put_status(:conflict)
215 def user_show(conn, %{"nickname" => nickname}) do
216 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
218 |> put_view(AccountView)
219 |> render("show.json", %{user: user})
221 _ -> {:error, :not_found}
225 def list_instance_statuses(conn, %{"instance" => instance} = params) do
226 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
227 {page, page_size} = page_params(params)
230 ActivityPub.fetch_statuses(nil, %{
231 "instance" => instance,
232 "limit" => page_size,
233 "offset" => (page - 1) * page_size,
234 "exclude_reblogs" => !with_reblogs && "true"
238 |> put_view(AdminAPI.StatusView)
239 |> render("index.json", %{activities: activities, as: :activity})
242 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
243 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
244 godmode = params["godmode"] == "true" || params["godmode"] == true
246 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
247 {_, page_size} = page_params(params)
250 ActivityPub.fetch_user_activities(user, nil, %{
251 "limit" => page_size,
252 "godmode" => godmode,
253 "exclude_reblogs" => !with_reblogs && "true"
257 |> put_view(AdminAPI.StatusView)
258 |> render("index.json", %{activities: activities, as: :activity})
260 _ -> {:error, :not_found}
264 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
265 user = User.get_cached_by_nickname(nickname)
267 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
269 action = if user.deactivated, do: "activate", else: "deactivate"
271 ModerationLog.insert_log(%{
278 |> put_view(AccountView)
279 |> render("show.json", %{user: updated_user})
282 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
283 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
284 {:ok, updated_users} = User.deactivate(users, false)
286 ModerationLog.insert_log(%{
293 |> put_view(AccountView)
294 |> render("index.json", %{users: Keyword.values(updated_users)})
297 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
298 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
299 {:ok, updated_users} = User.deactivate(users, true)
301 ModerationLog.insert_log(%{
308 |> put_view(AccountView)
309 |> render("index.json", %{users: Keyword.values(updated_users)})
312 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
313 with {:ok, _} <- User.tag(nicknames, tags) do
314 ModerationLog.insert_log(%{
316 nicknames: nicknames,
321 json_response(conn, :no_content, "")
325 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
326 with {:ok, _} <- User.untag(nicknames, tags) do
327 ModerationLog.insert_log(%{
329 nicknames: nicknames,
334 json_response(conn, :no_content, "")
338 def list_users(conn, params) do
339 {page, page_size} = page_params(params)
340 filters = maybe_parse_filters(params["filters"])
343 query: params["query"],
345 page_size: page_size,
346 tags: params["tags"],
347 name: params["name"],
348 email: params["email"]
351 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do
354 AccountView.render("index.json", users: users, count: count, page_size: page_size)
359 @filters ~w(local external active deactivated is_admin is_moderator)
361 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
362 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
364 defp maybe_parse_filters(filters) do
367 |> Enum.filter(&Enum.member?(@filters, &1))
368 |> Enum.map(&String.to_atom(&1))
369 |> Enum.into(%{}, &{&1, true})
372 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
373 "permission_group" => permission_group,
374 "nicknames" => nicknames
376 when permission_group in ["moderator", "admin"] do
377 update = %{:"is_#{permission_group}" => true}
379 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
381 for u <- users, do: User.admin_api_update(u, update)
383 ModerationLog.insert_log(%{
387 permission: permission_group
393 def right_add_multiple(conn, _) do
394 render_error(conn, :not_found, "No such permission_group")
397 def right_add(%{assigns: %{user: admin}} = conn, %{
398 "permission_group" => permission_group,
399 "nickname" => nickname
401 when permission_group in ["moderator", "admin"] do
402 fields = %{:"is_#{permission_group}" => true}
406 |> User.get_cached_by_nickname()
407 |> User.admin_api_update(fields)
409 ModerationLog.insert_log(%{
413 permission: permission_group
419 def right_add(conn, _) do
420 render_error(conn, :not_found, "No such permission_group")
423 def right_get(conn, %{"nickname" => nickname}) do
424 user = User.get_cached_by_nickname(nickname)
428 is_moderator: user.is_moderator,
429 is_admin: user.is_admin
433 def right_delete_multiple(
434 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
436 "permission_group" => permission_group,
437 "nicknames" => nicknames
440 when permission_group in ["moderator", "admin"] do
441 with false <- Enum.member?(nicknames, admin_nickname) do
442 update = %{:"is_#{permission_group}" => false}
444 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
446 for u <- users, do: User.admin_api_update(u, update)
448 ModerationLog.insert_log(%{
452 permission: permission_group
457 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
461 def right_delete_multiple(conn, _) do
462 render_error(conn, :not_found, "No such permission_group")
466 %{assigns: %{user: admin}} = conn,
468 "permission_group" => permission_group,
469 "nickname" => nickname
472 when permission_group in ["moderator", "admin"] do
473 fields = %{:"is_#{permission_group}" => false}
477 |> User.get_cached_by_nickname()
478 |> User.admin_api_update(fields)
480 ModerationLog.insert_log(%{
484 permission: permission_group
490 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
491 render_error(conn, :forbidden, "You can't revoke your own admin status.")
494 def relay_list(conn, _params) do
495 with {:ok, list} <- Relay.list() do
496 json(conn, %{relays: list})
504 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
505 with {:ok, _message} <- Relay.follow(target) do
506 ModerationLog.insert_log(%{
507 action: "relay_follow",
521 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
522 with {:ok, _message} <- Relay.unfollow(target) do
523 ModerationLog.insert_log(%{
524 action: "relay_unfollow",
538 @doc "Get a password reset token (base64 string) for given nickname"
539 def get_password_reset(conn, %{"nickname" => nickname}) do
540 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
541 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
546 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
550 @doc "Force password reset for a given user"
551 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
552 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
554 Enum.each(users, &User.force_password_reset_async/1)
556 ModerationLog.insert_log(%{
559 action: "force_password_reset"
562 json_response(conn, :no_content, "")
565 @doc "Disable mfa for user's account."
566 def disable_mfa(conn, %{"nickname" => nickname}) do
567 case User.get_by_nickname(nickname) do
577 @doc "Show a given user's credentials"
578 def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
579 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
581 |> put_view(AccountView)
582 |> render("credentials.json", %{user: user, for: admin})
584 _ -> {:error, :not_found}
588 @doc "Updates a given user"
589 def update_user_credentials(
590 %{assigns: %{user: admin}} = conn,
591 %{"nickname" => nickname} = params
593 with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)},
595 User.update_as_admin(user, params) do
596 ModerationLog.insert_log(%{
599 action: "updated_users"
602 if params["password"] do
603 User.force_password_reset_async(user)
606 ModerationLog.insert_log(%{
609 action: "force_password_reset"
612 json(conn, %{status: "success"})
614 {:error, changeset} ->
615 errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
617 json(conn, %{errors: errors})
620 json(conn, %{error: "Unable to update user."})
624 def list_log(conn, params) do
625 {page, page_size} = page_params(params)
628 ModerationLog.get_all(%{
630 page_size: page_size,
631 start_date: params["start_date"],
632 end_date: params["end_date"],
633 user_id: params["user_id"],
634 search: params["search"]
638 |> put_view(ModerationLogView)
639 |> render("index.json", %{log: log})
642 def restart(conn, _params) do
643 with :ok <- configurable_from_database() do
644 Restarter.Pleroma.restart(Config.get(:env), 50)
650 def need_reboot(conn, _params) do
651 json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
654 defp configurable_from_database do
655 if Config.get(:configurable_from_database) do
658 {:error, "To use this endpoint you need to enable configuration from database."}
662 def reload_emoji(conn, _params) do
663 Pleroma.Emoji.reload()
668 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
669 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
671 User.toggle_confirmation(users)
673 ModerationLog.insert_log(%{
676 action: "confirm_email"
682 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
683 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
685 User.try_send_confirmation_email(users)
687 ModerationLog.insert_log(%{
690 action: "resend_confirmation_email"
696 def stats(conn, _) do
697 count = Stats.get_status_visibility_count()
700 |> json(%{"status_visibility" => count})
703 defp page_params(params) do
704 {get_page(params["page"]), get_page_size(params["page_size"])}
707 defp get_page(page_string) when is_nil(page_string), do: 1
709 defp get_page(page_string) do
710 case Integer.parse(page_string) do
716 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
718 defp get_page_size(page_size_string) do
719 case Integer.parse(page_size_string) do
720 {page_size, _} -> page_size
721 :error -> @users_page_size