1 # Pleroma: A lightweight social networking server
2 # Copyright © 2017-2019 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 alias Pleroma.ModerationLog
9 alias Pleroma.Plugs.OAuthScopesPlug
11 alias Pleroma.UserInviteToken
12 alias Pleroma.Web.ActivityPub.ActivityPub
13 alias Pleroma.Web.ActivityPub.Relay
14 alias Pleroma.Web.ActivityPub.Utils
15 alias Pleroma.Web.AdminAPI.AccountView
16 alias Pleroma.Web.AdminAPI.Config
17 alias Pleroma.Web.AdminAPI.ConfigView
18 alias Pleroma.Web.AdminAPI.ModerationLogView
19 alias Pleroma.Web.AdminAPI.Report
20 alias Pleroma.Web.AdminAPI.ReportView
21 alias Pleroma.Web.AdminAPI.Search
22 alias Pleroma.Web.CommonAPI
23 alias Pleroma.Web.Endpoint
24 alias Pleroma.Web.MastodonAPI.StatusView
25 alias Pleroma.Web.Router
27 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
33 %{scopes: Pleroma.Config.oauth_admin_scopes("read:accounts")}
34 when action in [:list_users, :user_show, :right_get, :invites]
39 %{scopes: Pleroma.Config.oauth_admin_scopes("write:accounts")}
49 :user_toggle_activation,
61 %{scopes: Pleroma.Config.oauth_admin_scopes("read:reports")}
62 when action in [:list_reports, :report_show]
67 %{scopes: Pleroma.Config.oauth_admin_scopes("write:reports")}
68 when action in [:report_update_state, :report_respond]
73 %{scopes: Pleroma.Config.oauth_admin_scopes("read:statuses")}
74 when action == :list_user_statuses
79 %{scopes: Pleroma.Config.oauth_admin_scopes("write:statuses")}
80 when action in [:status_update, :status_delete]
85 %{scopes: Pleroma.Config.oauth_admin_scopes("read")}
86 when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log]
91 %{scopes: Pleroma.Config.oauth_admin_scopes("write")}
92 when action in [:relay_follow, :relay_unfollow, :config_update]
97 action_fallback(:errors)
99 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
100 user = User.get_cached_by_nickname(nickname)
103 ModerationLog.insert_log(%{
113 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
114 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
117 ModerationLog.insert_log(%{
127 def user_follow(%{assigns: %{user: admin}} = conn, %{
128 "follower" => follower_nick,
129 "followed" => followed_nick
131 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
132 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
133 User.follow(follower, followed)
135 ModerationLog.insert_log(%{
147 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
148 "follower" => follower_nick,
149 "followed" => followed_nick
151 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
152 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
153 User.unfollow(follower, followed)
155 ModerationLog.insert_log(%{
167 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
169 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
175 password_confirmation: password,
179 User.register_changeset(%User{}, user_data, need_confirmation: false)
181 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
182 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
185 case Pleroma.Repo.transaction(changesets) do
190 |> Enum.map(fn user ->
191 {:ok, user} = User.post_register_action(user)
195 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
197 ModerationLog.insert_log(%{
199 subjects: Map.values(users),
206 {:error, id, changeset, _} ->
208 Enum.map(changesets.operations, fn
209 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
210 AccountView.render("create-error.json", %{changeset: changeset})
212 {_, {:changeset, current_changeset, _}} ->
213 AccountView.render("create-error.json", %{changeset: current_changeset})
217 |> put_status(:conflict)
222 def user_show(conn, %{"nickname" => nickname}) do
223 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
225 |> put_view(AccountView)
226 |> render("show.json", %{user: user})
228 _ -> {:error, :not_found}
232 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
233 godmode = params["godmode"] == "true" || params["godmode"] == true
235 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
236 {_, page_size} = page_params(params)
239 ActivityPub.fetch_user_activities(user, nil, %{
240 "limit" => page_size,
245 |> put_view(StatusView)
246 |> render("index.json", %{activities: activities, as: :activity})
248 _ -> {:error, :not_found}
252 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
253 user = User.get_cached_by_nickname(nickname)
255 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
257 action = if user.deactivated, do: "activate", else: "deactivate"
259 ModerationLog.insert_log(%{
266 |> put_view(AccountView)
267 |> render("show.json", %{user: updated_user})
270 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
271 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
272 {:ok, updated_users} = User.deactivate(users, false)
274 ModerationLog.insert_log(%{
281 |> put_view(AccountView)
282 |> render("index.json", %{users: Keyword.values(updated_users)})
285 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
286 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
287 {:ok, updated_users} = User.deactivate(users, true)
289 ModerationLog.insert_log(%{
296 |> put_view(AccountView)
297 |> render("index.json", %{users: Keyword.values(updated_users)})
300 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
301 with {:ok, _} <- User.tag(nicknames, tags) do
302 ModerationLog.insert_log(%{
304 nicknames: nicknames,
309 json_response(conn, :no_content, "")
313 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
314 with {:ok, _} <- User.untag(nicknames, tags) do
315 ModerationLog.insert_log(%{
317 nicknames: nicknames,
322 json_response(conn, :no_content, "")
326 def list_users(conn, params) do
327 {page, page_size} = page_params(params)
328 filters = maybe_parse_filters(params["filters"])
331 query: params["query"],
333 page_size: page_size,
334 tags: params["tags"],
335 name: params["name"],
336 email: params["email"]
339 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
340 {:ok, users, count} <- filter_relay_user(users, count),
344 AccountView.render("index.json",
352 defp filter_relay_user(users, count) do
353 filtered_users = Enum.reject(users, &relay_user?/1)
354 count = if Enum.any?(users, &relay_user?/1), do: length(filtered_users), else: count
356 {:ok, filtered_users, count}
359 defp relay_user?(user) do
360 user.ap_id == Relay.relay_ap_id()
363 @filters ~w(local external active deactivated is_admin is_moderator)
365 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
366 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
368 defp maybe_parse_filters(filters) do
371 |> Enum.filter(&Enum.member?(@filters, &1))
372 |> Enum.map(&String.to_atom(&1))
373 |> Enum.into(%{}, &{&1, true})
376 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
377 "permission_group" => permission_group,
378 "nicknames" => nicknames
380 when permission_group in ["moderator", "admin"] do
381 update = %{:"is_#{permission_group}" => true}
383 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
385 for u <- users, do: User.admin_api_update(u, update)
387 ModerationLog.insert_log(%{
391 permission: permission_group
397 def right_add_multiple(conn, _) do
398 render_error(conn, :not_found, "No such permission_group")
401 def right_add(%{assigns: %{user: admin}} = conn, %{
402 "permission_group" => permission_group,
403 "nickname" => nickname
405 when permission_group in ["moderator", "admin"] do
406 fields = %{:"is_#{permission_group}" => true}
410 |> User.get_cached_by_nickname()
411 |> User.admin_api_update(fields)
413 ModerationLog.insert_log(%{
417 permission: permission_group
423 def right_add(conn, _) do
424 render_error(conn, :not_found, "No such permission_group")
427 def right_get(conn, %{"nickname" => nickname}) do
428 user = User.get_cached_by_nickname(nickname)
432 is_moderator: user.is_moderator,
433 is_admin: user.is_admin
437 def right_delete_multiple(
438 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
440 "permission_group" => permission_group,
441 "nicknames" => nicknames
444 when permission_group in ["moderator", "admin"] do
445 with false <- Enum.member?(nicknames, admin_nickname) do
446 update = %{:"is_#{permission_group}" => false}
448 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
450 for u <- users, do: User.admin_api_update(u, update)
452 ModerationLog.insert_log(%{
456 permission: permission_group
461 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
465 def right_delete_multiple(conn, _) do
466 render_error(conn, :not_found, "No such permission_group")
470 %{assigns: %{user: admin}} = conn,
472 "permission_group" => permission_group,
473 "nickname" => nickname
476 when permission_group in ["moderator", "admin"] do
477 fields = %{:"is_#{permission_group}" => false}
481 |> User.get_cached_by_nickname()
482 |> User.admin_api_update(fields)
484 ModerationLog.insert_log(%{
488 permission: permission_group
494 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
495 render_error(conn, :forbidden, "You can't revoke your own admin status.")
498 def relay_list(conn, _params) do
499 with {:ok, list} <- Relay.list() do
500 json(conn, %{relays: list})
508 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
509 with {:ok, _message} <- Relay.follow(target) do
510 ModerationLog.insert_log(%{
511 action: "relay_follow",
525 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
526 with {:ok, _message} <- Relay.unfollow(target) do
527 ModerationLog.insert_log(%{
528 action: "relay_unfollow",
542 @doc "Sends registration invite via email"
543 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
545 Pleroma.Config.get([:instance, :invites_enabled]) &&
546 !Pleroma.Config.get([:instance, :registrations_open]),
547 {:ok, invite_token} <- UserInviteToken.create_invite(),
549 Pleroma.Emails.UserEmail.user_invitation_email(
555 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
556 json_response(conn, :no_content, "")
560 @doc "Create an account registration invite token"
561 def create_invite_token(conn, params) do
565 if params["max_use"],
566 do: Map.put(opts, :max_use, params["max_use"]),
570 if params["expires_at"],
571 do: Map.put(opts, :expires_at, params["expires_at"]),
574 {:ok, invite} = UserInviteToken.create_invite(opts)
576 json(conn, AccountView.render("invite.json", %{invite: invite}))
579 @doc "Get list of created invites"
580 def invites(conn, _params) do
581 invites = UserInviteToken.list_invites()
584 |> put_view(AccountView)
585 |> render("invites.json", %{invites: invites})
588 @doc "Revokes invite by token"
589 def revoke_invite(conn, %{"token" => token}) do
590 with {:ok, invite} <- UserInviteToken.find_by_token(token),
591 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
593 |> put_view(AccountView)
594 |> render("invite.json", %{invite: updated_invite})
596 nil -> {:error, :not_found}
600 @doc "Get a password reset token (base64 string) for given nickname"
601 def get_password_reset(conn, %{"nickname" => nickname}) do
602 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
603 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
608 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
612 @doc "Force password reset for a given user"
613 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
614 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
616 Enum.map(users, &User.force_password_reset_async/1)
618 ModerationLog.insert_log(%{
621 action: "force_password_reset"
624 json_response(conn, :no_content, "")
627 def list_reports(conn, params) do
628 {page, page_size} = page_params(params)
631 |> put_view(ReportView)
632 |> render("index.json", %{reports: Utils.get_reports(params, page, page_size)})
635 def list_grouped_reports(conn, _params) do
636 reports = Utils.get_reported_activities()
639 |> put_view(ReportView)
640 |> render("index_grouped.json", Utils.get_reports_grouped_by_status(reports))
643 def report_show(conn, %{"id" => id}) do
644 with %Activity{} = report <- Activity.get_by_id(id) do
646 |> put_view(ReportView)
647 |> render("show.json", Report.extract_report_info(report))
649 _ -> {:error, :not_found}
653 def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
656 |> Enum.map(fn report ->
657 with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do
658 ModerationLog.insert_log(%{
659 action: "report_update",
666 {:error, message} -> %{id: report["id"], error: message}
670 case Enum.any?(result, &Map.has_key?(&1, :error)) do
671 true -> json_response(conn, :bad_request, result)
672 false -> json_response(conn, :no_content, "")
676 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
677 with false <- is_nil(params["status"]),
678 %Activity{} <- Activity.get_by_id(id) do
681 |> Map.put("in_reply_to_status_id", id)
682 |> Map.put("visibility", "direct")
684 {:ok, activity} = CommonAPI.post(user, params)
686 ModerationLog.insert_log(%{
687 action: "report_response",
690 text: params["status"]
694 |> put_view(StatusView)
695 |> render("show.json", %{activity: activity})
705 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
706 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
707 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
709 ModerationLog.insert_log(%{
710 action: "status_update",
713 sensitive: sensitive,
714 visibility: params["visibility"]
718 |> put_view(StatusView)
719 |> render("show.json", %{activity: activity})
723 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
724 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
725 ModerationLog.insert_log(%{
726 action: "status_delete",
735 def list_log(conn, params) do
736 {page, page_size} = page_params(params)
739 ModerationLog.get_all(%{
741 page_size: page_size,
742 start_date: params["start_date"],
743 end_date: params["end_date"],
744 user_id: params["user_id"],
745 search: params["search"]
749 |> put_view(ModerationLogView)
750 |> render("index.json", %{log: log})
753 def migrate_to_db(conn, _params) do
754 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
758 def migrate_from_db(conn, _params) do
759 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
763 def config_show(conn, _params) do
764 configs = Pleroma.Repo.all(Config)
767 |> put_view(ConfigView)
768 |> render("index.json", %{configs: configs})
771 def config_update(conn, %{"configs" => configs}) do
773 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
776 %{"group" => group, "key" => key, "delete" => "true"} = params ->
777 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
780 %{"group" => group, "key" => key, "value" => value} ->
781 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
784 |> Enum.reject(&is_nil(&1))
786 Pleroma.Config.TransferTask.load_and_update_env()
787 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
794 |> put_view(ConfigView)
795 |> render("index.json", %{configs: updated})
798 def reload_emoji(conn, _params) do
799 Pleroma.Emoji.reload()
804 def errors(conn, {:error, :not_found}) do
806 |> put_status(:not_found)
807 |> json(dgettext("errors", "Not found"))
810 def errors(conn, {:error, reason}) do
812 |> put_status(:bad_request)
816 def errors(conn, {:param_cast, _}) do
818 |> put_status(:bad_request)
819 |> json(dgettext("errors", "Invalid parameters"))
822 def errors(conn, _) do
824 |> put_status(:internal_server_error)
825 |> json(dgettext("errors", "Something went wrong"))
828 defp page_params(params) do
829 {get_page(params["page"]), get_page_size(params["page_size"])}
832 defp get_page(page_string) when is_nil(page_string), do: 1
834 defp get_page(page_string) do
835 case Integer.parse(page_string) do
841 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
843 defp get_page_size(page_size_string) do
844 case Integer.parse(page_size_string) do
845 {page_size, _} -> page_size
846 :error -> @users_page_size