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: ["read:accounts"], admin: true}
34 when action in [:list_users, :user_show, :right_get, :invites]
39 %{scopes: ["write:accounts"], admin: true}
49 :user_toggle_activation,
61 %{scopes: ["read:reports"], admin: true}
62 when action in [:list_reports, :report_show]
67 %{scopes: ["write:reports"], admin: true}
68 when action in [:report_update_state, :report_respond]
73 %{scopes: ["read:statuses"], admin: true}
74 when action == :list_user_statuses
79 %{scopes: ["write:statuses"], admin: true}
80 when action in [:status_update, :status_delete]
85 %{scopes: ["read"], admin: true}
86 when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log]
91 %{scopes: ["write"], admin: true}
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_instance_statuses(conn, %{"instance" => instance} = params) do
233 {page, page_size} = page_params(params)
236 ActivityPub.fetch_instance_activities(%{
237 "instance" => instance,
238 "limit" => page_size,
239 "offset" => (page - 1) * page_size
243 |> put_view(StatusView)
244 |> render("index.json", %{activities: activities, as: :activity})
247 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
248 godmode = params["godmode"] == "true" || params["godmode"] == true
250 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
251 {_, page_size} = page_params(params)
254 ActivityPub.fetch_user_activities(user, nil, %{
255 "limit" => page_size,
260 |> put_view(StatusView)
261 |> render("index.json", %{activities: activities, as: :activity})
263 _ -> {:error, :not_found}
267 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
268 user = User.get_cached_by_nickname(nickname)
270 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
272 action = if user.deactivated, do: "activate", else: "deactivate"
274 ModerationLog.insert_log(%{
281 |> put_view(AccountView)
282 |> render("show.json", %{user: updated_user})
285 def user_activate(%{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, false)
289 ModerationLog.insert_log(%{
296 |> put_view(AccountView)
297 |> render("index.json", %{users: Keyword.values(updated_users)})
300 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
301 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
302 {:ok, updated_users} = User.deactivate(users, true)
304 ModerationLog.insert_log(%{
311 |> put_view(AccountView)
312 |> render("index.json", %{users: Keyword.values(updated_users)})
315 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
316 with {:ok, _} <- User.tag(nicknames, tags) do
317 ModerationLog.insert_log(%{
319 nicknames: nicknames,
324 json_response(conn, :no_content, "")
328 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
329 with {:ok, _} <- User.untag(nicknames, tags) do
330 ModerationLog.insert_log(%{
332 nicknames: nicknames,
337 json_response(conn, :no_content, "")
341 def list_users(conn, params) do
342 {page, page_size} = page_params(params)
343 filters = maybe_parse_filters(params["filters"])
346 query: params["query"],
348 page_size: page_size,
349 tags: params["tags"],
350 name: params["name"],
351 email: params["email"]
354 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
355 {:ok, users, count} <- filter_service_users(users, count),
359 AccountView.render("index.json",
367 defp filter_service_users(users, count) do
368 filtered_users = Enum.reject(users, &service_user?/1)
369 count = if Enum.any?(users, &service_user?/1), do: length(filtered_users), else: count
371 {:ok, filtered_users, count}
374 defp service_user?(user) do
375 String.match?(user.ap_id, ~r/.*\/relay$/) or
376 String.match?(user.ap_id, ~r/.*\/internal\/fetch$/)
379 @filters ~w(local external active deactivated is_admin is_moderator)
381 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
382 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
384 defp maybe_parse_filters(filters) do
387 |> Enum.filter(&Enum.member?(@filters, &1))
388 |> Enum.map(&String.to_atom(&1))
389 |> Enum.into(%{}, &{&1, true})
392 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
393 "permission_group" => permission_group,
394 "nicknames" => nicknames
396 when permission_group in ["moderator", "admin"] do
397 update = %{:"is_#{permission_group}" => true}
399 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
401 for u <- users, do: User.admin_api_update(u, update)
403 ModerationLog.insert_log(%{
407 permission: permission_group
413 def right_add_multiple(conn, _) do
414 render_error(conn, :not_found, "No such permission_group")
417 def right_add(%{assigns: %{user: admin}} = conn, %{
418 "permission_group" => permission_group,
419 "nickname" => nickname
421 when permission_group in ["moderator", "admin"] do
422 fields = %{:"is_#{permission_group}" => true}
426 |> User.get_cached_by_nickname()
427 |> User.admin_api_update(fields)
429 ModerationLog.insert_log(%{
433 permission: permission_group
439 def right_add(conn, _) do
440 render_error(conn, :not_found, "No such permission_group")
443 def right_get(conn, %{"nickname" => nickname}) do
444 user = User.get_cached_by_nickname(nickname)
448 is_moderator: user.is_moderator,
449 is_admin: user.is_admin
453 def right_delete_multiple(
454 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
456 "permission_group" => permission_group,
457 "nicknames" => nicknames
460 when permission_group in ["moderator", "admin"] do
461 with false <- Enum.member?(nicknames, admin_nickname) do
462 update = %{:"is_#{permission_group}" => false}
464 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
466 for u <- users, do: User.admin_api_update(u, update)
468 ModerationLog.insert_log(%{
472 permission: permission_group
477 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
481 def right_delete_multiple(conn, _) do
482 render_error(conn, :not_found, "No such permission_group")
486 %{assigns: %{user: admin}} = conn,
488 "permission_group" => permission_group,
489 "nickname" => nickname
492 when permission_group in ["moderator", "admin"] do
493 fields = %{:"is_#{permission_group}" => false}
497 |> User.get_cached_by_nickname()
498 |> User.admin_api_update(fields)
500 ModerationLog.insert_log(%{
504 permission: permission_group
510 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
511 render_error(conn, :forbidden, "You can't revoke your own admin status.")
514 def relay_list(conn, _params) do
515 with {:ok, list} <- Relay.list() do
516 json(conn, %{relays: list})
524 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
525 with {:ok, _message} <- Relay.follow(target) do
526 ModerationLog.insert_log(%{
527 action: "relay_follow",
541 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
542 with {:ok, _message} <- Relay.unfollow(target) do
543 ModerationLog.insert_log(%{
544 action: "relay_unfollow",
558 @doc "Sends registration invite via email"
559 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
561 Pleroma.Config.get([:instance, :invites_enabled]) &&
562 !Pleroma.Config.get([:instance, :registrations_open]),
563 {:ok, invite_token} <- UserInviteToken.create_invite(),
565 Pleroma.Emails.UserEmail.user_invitation_email(
571 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
572 json_response(conn, :no_content, "")
576 @doc "Create an account registration invite token"
577 def create_invite_token(conn, params) do
581 if params["max_use"],
582 do: Map.put(opts, :max_use, params["max_use"]),
586 if params["expires_at"],
587 do: Map.put(opts, :expires_at, params["expires_at"]),
590 {:ok, invite} = UserInviteToken.create_invite(opts)
592 json(conn, AccountView.render("invite.json", %{invite: invite}))
595 @doc "Get list of created invites"
596 def invites(conn, _params) do
597 invites = UserInviteToken.list_invites()
600 |> put_view(AccountView)
601 |> render("invites.json", %{invites: invites})
604 @doc "Revokes invite by token"
605 def revoke_invite(conn, %{"token" => token}) do
606 with {:ok, invite} <- UserInviteToken.find_by_token(token),
607 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
609 |> put_view(AccountView)
610 |> render("invite.json", %{invite: updated_invite})
612 nil -> {:error, :not_found}
616 @doc "Get a password reset token (base64 string) for given nickname"
617 def get_password_reset(conn, %{"nickname" => nickname}) do
618 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
619 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
624 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
628 @doc "Force password reset for a given user"
629 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
630 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
632 Enum.map(users, &User.force_password_reset_async/1)
634 ModerationLog.insert_log(%{
637 action: "force_password_reset"
640 json_response(conn, :no_content, "")
643 def list_reports(conn, params) do
644 {page, page_size} = page_params(params)
647 |> put_view(ReportView)
648 |> render("index.json", %{reports: Utils.get_reports(params, page, page_size)})
651 def list_grouped_reports(conn, _params) do
652 statuses = Utils.get_reported_activities()
655 |> put_view(ReportView)
656 |> render("index_grouped.json", Utils.get_reports_grouped_by_status(statuses))
659 def report_show(conn, %{"id" => id}) do
660 with %Activity{} = report <- Activity.get_by_id(id) do
662 |> put_view(ReportView)
663 |> render("show.json", Report.extract_report_info(report))
665 _ -> {:error, :not_found}
669 def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
672 |> Enum.map(fn report ->
673 with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do
674 ModerationLog.insert_log(%{
675 action: "report_update",
682 {:error, message} -> %{id: report["id"], error: message}
686 case Enum.any?(result, &Map.has_key?(&1, :error)) do
687 true -> json_response(conn, :bad_request, result)
688 false -> json_response(conn, :no_content, "")
692 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
693 with false <- is_nil(params["status"]),
694 %Activity{} <- Activity.get_by_id(id) do
697 |> Map.put("in_reply_to_status_id", id)
698 |> Map.put("visibility", "direct")
700 {:ok, activity} = CommonAPI.post(user, params)
702 ModerationLog.insert_log(%{
703 action: "report_response",
706 text: params["status"]
710 |> put_view(StatusView)
711 |> render("show.json", %{activity: activity})
721 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
722 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
723 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
725 ModerationLog.insert_log(%{
726 action: "status_update",
729 sensitive: sensitive,
730 visibility: params["visibility"]
734 |> put_view(StatusView)
735 |> render("show.json", %{activity: activity})
739 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
740 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
741 ModerationLog.insert_log(%{
742 action: "status_delete",
751 def list_log(conn, params) do
752 {page, page_size} = page_params(params)
755 ModerationLog.get_all(%{
757 page_size: page_size,
758 start_date: params["start_date"],
759 end_date: params["end_date"],
760 user_id: params["user_id"],
761 search: params["search"]
765 |> put_view(ModerationLogView)
766 |> render("index.json", %{log: log})
769 def migrate_to_db(conn, _params) do
770 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
774 def migrate_from_db(conn, _params) do
775 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
779 def config_show(conn, _params) do
780 configs = Pleroma.Repo.all(Config)
783 |> put_view(ConfigView)
784 |> render("index.json", %{configs: configs})
787 def config_update(conn, %{"configs" => configs}) do
789 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
792 %{"group" => group, "key" => key, "delete" => "true"} = params ->
793 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
796 %{"group" => group, "key" => key, "value" => value} ->
797 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
800 |> Enum.reject(&is_nil(&1))
802 Pleroma.Config.TransferTask.load_and_update_env()
803 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
810 |> put_view(ConfigView)
811 |> render("index.json", %{configs: updated})
814 def reload_emoji(conn, _params) do
815 Pleroma.Emoji.reload()
820 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
821 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
823 User.toggle_confirmation(users)
825 ModerationLog.insert_log(%{
828 action: "confirm_email"
834 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
835 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
837 User.try_send_confirmation_email(users)
839 ModerationLog.insert_log(%{
842 action: "resend_confirmation_email"
848 def errors(conn, {:error, :not_found}) do
850 |> put_status(:not_found)
851 |> json(dgettext("errors", "Not found"))
854 def errors(conn, {:error, reason}) do
856 |> put_status(:bad_request)
860 def errors(conn, {:param_cast, _}) do
862 |> put_status(:bad_request)
863 |> json(dgettext("errors", "Invalid parameters"))
866 def errors(conn, _) do
868 |> put_status(:internal_server_error)
869 |> json(dgettext("errors", "Something went wrong"))
872 defp page_params(params) do
873 {get_page(params["page"]), get_page_size(params["page_size"])}
876 defp get_page(page_string) when is_nil(page_string), do: 1
878 defp get_page(page_string) do
879 case Integer.parse(page_string) do
885 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
887 defp get_page_size(page_size_string) do
888 case Integer.parse(page_size_string) do
889 {page_size, _} -> page_size
890 :error -> @users_page_size