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",
357 @filters ~w(local external active deactivated is_admin is_moderator)
359 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
360 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
362 defp maybe_parse_filters(filters) do
365 |> Enum.filter(&Enum.member?(@filters, &1))
366 |> Enum.map(&String.to_atom/1)
367 |> Map.new(&{&1, true})
370 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
371 "permission_group" => permission_group,
372 "nicknames" => nicknames
374 when permission_group in ["moderator", "admin"] do
375 update = %{:"is_#{permission_group}" => true}
377 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
379 for u <- users, do: User.admin_api_update(u, update)
381 ModerationLog.insert_log(%{
385 permission: permission_group
391 def right_add_multiple(conn, _) do
392 render_error(conn, :not_found, "No such permission_group")
395 def right_add(%{assigns: %{user: admin}} = conn, %{
396 "permission_group" => permission_group,
397 "nickname" => nickname
399 when permission_group in ["moderator", "admin"] do
400 fields = %{:"is_#{permission_group}" => true}
404 |> User.get_cached_by_nickname()
405 |> User.admin_api_update(fields)
407 ModerationLog.insert_log(%{
411 permission: permission_group
417 def right_add(conn, _) do
418 render_error(conn, :not_found, "No such permission_group")
421 def right_get(conn, %{"nickname" => nickname}) do
422 user = User.get_cached_by_nickname(nickname)
426 is_moderator: user.is_moderator,
427 is_admin: user.is_admin
431 def right_delete_multiple(
432 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
434 "permission_group" => permission_group,
435 "nicknames" => nicknames
438 when permission_group in ["moderator", "admin"] do
439 with false <- Enum.member?(nicknames, admin_nickname) do
440 update = %{:"is_#{permission_group}" => false}
442 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
444 for u <- users, do: User.admin_api_update(u, update)
446 ModerationLog.insert_log(%{
450 permission: permission_group
455 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
459 def right_delete_multiple(conn, _) do
460 render_error(conn, :not_found, "No such permission_group")
464 %{assigns: %{user: admin}} = conn,
466 "permission_group" => permission_group,
467 "nickname" => nickname
470 when permission_group in ["moderator", "admin"] do
471 fields = %{:"is_#{permission_group}" => false}
475 |> User.get_cached_by_nickname()
476 |> User.admin_api_update(fields)
478 ModerationLog.insert_log(%{
482 permission: permission_group
488 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
489 render_error(conn, :forbidden, "You can't revoke your own admin status.")
492 @doc "Get a password reset token (base64 string) for given nickname"
493 def get_password_reset(conn, %{"nickname" => nickname}) do
494 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
495 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
500 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
504 @doc "Force password reset for a given user"
505 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
506 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
508 Enum.each(users, &User.force_password_reset_async/1)
510 ModerationLog.insert_log(%{
513 action: "force_password_reset"
516 json_response(conn, :no_content, "")
519 @doc "Disable mfa for user's account."
520 def disable_mfa(conn, %{"nickname" => nickname}) do
521 case User.get_by_nickname(nickname) do
531 @doc "Show a given user's credentials"
532 def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
533 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
535 |> put_view(AccountView)
536 |> render("credentials.json", %{user: user, for: admin})
538 _ -> {:error, :not_found}
542 @doc "Updates a given user"
543 def update_user_credentials(
544 %{assigns: %{user: admin}} = conn,
545 %{"nickname" => nickname} = params
547 with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)},
549 User.update_as_admin(user, params) do
550 ModerationLog.insert_log(%{
553 action: "updated_users"
556 if params["password"] do
557 User.force_password_reset_async(user)
560 ModerationLog.insert_log(%{
563 action: "force_password_reset"
566 json(conn, %{status: "success"})
568 {:error, changeset} ->
569 errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
578 def list_log(conn, params) do
579 {page, page_size} = page_params(params)
582 ModerationLog.get_all(%{
584 page_size: page_size,
585 start_date: params["start_date"],
586 end_date: params["end_date"],
587 user_id: params["user_id"],
588 search: params["search"]
592 |> put_view(ModerationLogView)
593 |> render("index.json", %{log: log})
596 def restart(conn, _params) do
597 with :ok <- configurable_from_database() do
598 Restarter.Pleroma.restart(Config.get(:env), 50)
604 def need_reboot(conn, _params) do
605 json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
608 defp configurable_from_database do
609 if Config.get(:configurable_from_database) do
612 {:error, "To use this endpoint you need to enable configuration from database."}
616 def reload_emoji(conn, _params) do
617 Pleroma.Emoji.reload()
622 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
623 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
625 User.toggle_confirmation(users)
627 ModerationLog.insert_log(%{actor: admin, subject: users, action: "confirm_email"})
632 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
634 Enum.map(nicknames, fn nickname ->
636 |> User.get_cached_by_nickname()
637 |> User.send_confirmation_email()
640 ModerationLog.insert_log(%{actor: admin, subject: users, action: "resend_confirmation_email"})
645 def stats(conn, params) do
646 counters = Stats.get_status_visibility_count(params["instance"])
648 json(conn, %{"status_visibility" => counters})
651 defp page_params(params) do
652 {get_page(params["page"]), get_page_size(params["page_size"])}
655 defp get_page(page_string) when is_nil(page_string), do: 1
657 defp get_page(page_string) do
658 case Integer.parse(page_string) do
664 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
666 defp get_page_size(page_size_string) do
667 case Integer.parse(page_size_string) do
668 {page_size, _} -> page_size
669 :error -> @users_page_size