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
10 alias Pleroma.ReportNote
12 alias Pleroma.UserInviteToken
13 alias Pleroma.Web.ActivityPub.ActivityPub
14 alias Pleroma.Web.ActivityPub.Relay
15 alias Pleroma.Web.ActivityPub.Utils
16 alias Pleroma.Web.AdminAPI.AccountView
17 alias Pleroma.Web.AdminAPI.Config
18 alias Pleroma.Web.AdminAPI.ConfigView
19 alias Pleroma.Web.AdminAPI.ModerationLogView
20 alias Pleroma.Web.AdminAPI.Report
21 alias Pleroma.Web.AdminAPI.ReportView
22 alias Pleroma.Web.AdminAPI.Search
23 alias Pleroma.Web.CommonAPI
24 alias Pleroma.Web.Endpoint
25 alias Pleroma.Web.MastodonAPI.StatusView
26 alias Pleroma.Web.Router
28 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
34 %{scopes: ["read:accounts"]}
35 when action in [:list_users, :user_show, :right_get, :invites]
40 %{scopes: ["write:accounts"]}
50 :user_toggle_activation,
62 %{scopes: ["read:reports"]} when action in [:list_reports, :report_show]
67 %{scopes: ["write:reports"]}
68 when action in [:report_update_state, :report_respond]
73 %{scopes: ["read:statuses"]} when action == :list_user_statuses
78 %{scopes: ["write:statuses"]}
79 when action in [:status_update, :status_delete]
85 when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log]
91 when action in [:relay_follow, :relay_unfollow, :config_update]
96 action_fallback(:errors)
98 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
99 user = User.get_cached_by_nickname(nickname)
102 ModerationLog.insert_log(%{
112 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
113 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
116 ModerationLog.insert_log(%{
126 def user_follow(%{assigns: %{user: admin}} = conn, %{
127 "follower" => follower_nick,
128 "followed" => followed_nick
130 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
131 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
132 User.follow(follower, followed)
134 ModerationLog.insert_log(%{
146 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
147 "follower" => follower_nick,
148 "followed" => followed_nick
150 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
151 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
152 User.unfollow(follower, followed)
154 ModerationLog.insert_log(%{
166 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
168 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
174 password_confirmation: password,
178 User.register_changeset(%User{}, user_data, need_confirmation: false)
180 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
181 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
184 case Pleroma.Repo.transaction(changesets) do
189 |> Enum.map(fn user ->
190 {:ok, user} = User.post_register_action(user)
194 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
196 ModerationLog.insert_log(%{
198 subjects: Map.values(users),
205 {:error, id, changeset, _} ->
207 Enum.map(changesets.operations, fn
208 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
209 AccountView.render("create-error.json", %{changeset: changeset})
211 {_, {:changeset, current_changeset, _}} ->
212 AccountView.render("create-error.json", %{changeset: current_changeset})
216 |> put_status(:conflict)
221 def user_show(conn, %{"nickname" => nickname}) do
222 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
224 |> put_view(AccountView)
225 |> render("show.json", %{user: user})
227 _ -> {:error, :not_found}
231 def list_instance_statuses(conn, %{"instance" => instance} = params) do
232 {page, page_size} = page_params(params)
235 ActivityPub.fetch_instance_activities(%{
236 "instance" => instance,
237 "limit" => page_size,
238 "offset" => (page - 1) * page_size
242 |> put_view(StatusView)
243 |> render("index.json", %{activities: activities, as: :activity})
246 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
247 godmode = params["godmode"] == "true" || params["godmode"] == true
249 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
250 {_, page_size} = page_params(params)
253 ActivityPub.fetch_user_activities(user, nil, %{
254 "limit" => page_size,
259 |> put_view(StatusView)
260 |> render("index.json", %{activities: activities, as: :activity})
262 _ -> {:error, :not_found}
266 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
267 user = User.get_cached_by_nickname(nickname)
269 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
271 action = if user.deactivated, do: "activate", else: "deactivate"
273 ModerationLog.insert_log(%{
280 |> put_view(AccountView)
281 |> render("show.json", %{user: updated_user})
284 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
285 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
286 {:ok, updated_users} = User.deactivate(users, false)
288 ModerationLog.insert_log(%{
295 |> put_view(AccountView)
296 |> render("index.json", %{users: Keyword.values(updated_users)})
299 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
300 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
301 {:ok, updated_users} = User.deactivate(users, true)
303 ModerationLog.insert_log(%{
310 |> put_view(AccountView)
311 |> render("index.json", %{users: Keyword.values(updated_users)})
314 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
315 with {:ok, _} <- User.tag(nicknames, tags) do
316 ModerationLog.insert_log(%{
318 nicknames: nicknames,
323 json_response(conn, :no_content, "")
327 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
328 with {:ok, _} <- User.untag(nicknames, tags) do
329 ModerationLog.insert_log(%{
331 nicknames: nicknames,
336 json_response(conn, :no_content, "")
340 def list_users(conn, params) do
341 {page, page_size} = page_params(params)
342 filters = maybe_parse_filters(params["filters"])
345 query: params["query"],
347 page_size: page_size,
348 tags: params["tags"],
349 name: params["name"],
350 email: params["email"]
353 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
354 {:ok, users, count} <- filter_service_users(users, count),
358 AccountView.render("index.json",
366 defp filter_service_users(users, count) do
367 filtered_users = Enum.reject(users, &service_user?/1)
368 count = if Enum.any?(users, &service_user?/1), do: length(filtered_users), else: count
370 {:ok, filtered_users, count}
373 defp service_user?(user) do
374 String.match?(user.ap_id, ~r/.*\/relay$/) or
375 String.match?(user.ap_id, ~r/.*\/internal\/fetch$/)
378 @filters ~w(local external active deactivated is_admin is_moderator)
380 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
381 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
383 defp maybe_parse_filters(filters) do
386 |> Enum.filter(&Enum.member?(@filters, &1))
387 |> Enum.map(&String.to_atom(&1))
388 |> Enum.into(%{}, &{&1, true})
391 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
392 "permission_group" => permission_group,
393 "nicknames" => nicknames
395 when permission_group in ["moderator", "admin"] do
396 update = %{:"is_#{permission_group}" => true}
398 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
400 for u <- users, do: User.admin_api_update(u, update)
402 ModerationLog.insert_log(%{
406 permission: permission_group
412 def right_add_multiple(conn, _) do
413 render_error(conn, :not_found, "No such permission_group")
416 def right_add(%{assigns: %{user: admin}} = conn, %{
417 "permission_group" => permission_group,
418 "nickname" => nickname
420 when permission_group in ["moderator", "admin"] do
421 fields = %{:"is_#{permission_group}" => true}
425 |> User.get_cached_by_nickname()
426 |> User.admin_api_update(fields)
428 ModerationLog.insert_log(%{
432 permission: permission_group
438 def right_add(conn, _) do
439 render_error(conn, :not_found, "No such permission_group")
442 def right_get(conn, %{"nickname" => nickname}) do
443 user = User.get_cached_by_nickname(nickname)
447 is_moderator: user.is_moderator,
448 is_admin: user.is_admin
452 def right_delete_multiple(
453 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
455 "permission_group" => permission_group,
456 "nicknames" => nicknames
459 when permission_group in ["moderator", "admin"] do
460 with false <- Enum.member?(nicknames, admin_nickname) do
461 update = %{:"is_#{permission_group}" => false}
463 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
465 for u <- users, do: User.admin_api_update(u, update)
467 ModerationLog.insert_log(%{
471 permission: permission_group
476 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
480 def right_delete_multiple(conn, _) do
481 render_error(conn, :not_found, "No such permission_group")
485 %{assigns: %{user: admin}} = conn,
487 "permission_group" => permission_group,
488 "nickname" => nickname
491 when permission_group in ["moderator", "admin"] do
492 fields = %{:"is_#{permission_group}" => false}
496 |> User.get_cached_by_nickname()
497 |> User.admin_api_update(fields)
499 ModerationLog.insert_log(%{
503 permission: permission_group
509 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
510 render_error(conn, :forbidden, "You can't revoke your own admin status.")
513 def relay_list(conn, _params) do
514 with {:ok, list} <- Relay.list() do
515 json(conn, %{relays: list})
523 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
524 with {:ok, _message} <- Relay.follow(target) do
525 ModerationLog.insert_log(%{
526 action: "relay_follow",
540 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
541 with {:ok, _message} <- Relay.unfollow(target) do
542 ModerationLog.insert_log(%{
543 action: "relay_unfollow",
557 @doc "Sends registration invite via email"
558 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
560 Pleroma.Config.get([:instance, :invites_enabled]) &&
561 !Pleroma.Config.get([:instance, :registrations_open]),
562 {:ok, invite_token} <- UserInviteToken.create_invite(),
564 Pleroma.Emails.UserEmail.user_invitation_email(
570 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
571 json_response(conn, :no_content, "")
575 @doc "Create an account registration invite token"
576 def create_invite_token(conn, params) do
580 if params["max_use"],
581 do: Map.put(opts, :max_use, params["max_use"]),
585 if params["expires_at"],
586 do: Map.put(opts, :expires_at, params["expires_at"]),
589 {:ok, invite} = UserInviteToken.create_invite(opts)
591 json(conn, AccountView.render("invite.json", %{invite: invite}))
594 @doc "Get list of created invites"
595 def invites(conn, _params) do
596 invites = UserInviteToken.list_invites()
599 |> put_view(AccountView)
600 |> render("invites.json", %{invites: invites})
603 @doc "Revokes invite by token"
604 def revoke_invite(conn, %{"token" => token}) do
605 with {:ok, invite} <- UserInviteToken.find_by_token(token),
606 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
608 |> put_view(AccountView)
609 |> render("invite.json", %{invite: updated_invite})
611 nil -> {:error, :not_found}
615 @doc "Get a password reset token (base64 string) for given nickname"
616 def get_password_reset(conn, %{"nickname" => nickname}) do
617 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
618 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
623 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
627 @doc "Force password reset for a given user"
628 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
629 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
631 Enum.map(users, &User.force_password_reset_async/1)
633 ModerationLog.insert_log(%{
636 action: "force_password_reset"
639 json_response(conn, :no_content, "")
642 def list_reports(conn, params) do
643 {page, page_size} = page_params(params)
645 reports = Utils.get_reports(params, page, page_size)
648 |> put_view(ReportView)
649 |> render("index.json", %{reports: reports})
652 def list_grouped_reports(conn, _params) do
653 reports = Utils.get_reported_activities()
656 |> put_view(ReportView)
657 |> render("index_grouped.json", Utils.get_reports_grouped_by_status(reports))
660 def report_show(conn, %{"id" => id}) do
661 with %Activity{} = report <- Activity.get_by_id(id) do
663 |> put_view(ReportView)
664 |> render("show.json", Report.extract_report_info(report))
666 _ -> {:error, :not_found}
670 def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
673 |> Enum.map(fn report ->
674 with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do
675 ModerationLog.insert_log(%{
676 action: "report_update",
683 {:error, message} -> %{id: report["id"], error: message}
687 case Enum.any?(result, &Map.has_key?(&1, :error)) do
688 true -> json_response(conn, :bad_request, result)
689 false -> json_response(conn, :no_content, "")
693 def report_notes_create(%{assigns: %{user: user}} = conn, %{
697 with {:ok, _} <- ReportNote.create(user.id, report_id, content) do
698 ModerationLog.insert_log(%{
699 action: "report_note",
701 subject: Activity.get_by_id(report_id),
705 json_response(conn, :no_content, "")
707 _ -> json_response(conn, :bad_request, "")
711 def report_notes_delete(%{assigns: %{user: user}} = conn, %{
713 "report_id" => report_id
715 with {:ok, note} <- ReportNote.destroy(note_id) do
716 ModerationLog.insert_log(%{
717 action: "report_note_delete",
719 subject: Activity.get_by_id(report_id),
723 json_response(conn, :no_content, "")
725 _ -> json_response(conn, :bad_request, "")
729 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
730 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
731 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
733 ModerationLog.insert_log(%{
734 action: "status_update",
737 sensitive: sensitive,
738 visibility: params["visibility"]
742 |> put_view(StatusView)
743 |> render("show.json", %{activity: activity})
747 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
748 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
749 ModerationLog.insert_log(%{
750 action: "status_delete",
759 def list_log(conn, params) do
760 {page, page_size} = page_params(params)
763 ModerationLog.get_all(%{
765 page_size: page_size,
766 start_date: params["start_date"],
767 end_date: params["end_date"],
768 user_id: params["user_id"],
769 search: params["search"]
773 |> put_view(ModerationLogView)
774 |> render("index.json", %{log: log})
777 def migrate_to_db(conn, _params) do
778 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
782 def migrate_from_db(conn, _params) do
783 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
787 def config_show(conn, _params) do
788 configs = Pleroma.Repo.all(Config)
791 |> put_view(ConfigView)
792 |> render("index.json", %{configs: configs})
795 def config_update(conn, %{"configs" => configs}) do
797 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
800 %{"group" => group, "key" => key, "delete" => "true"} = params ->
801 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
804 %{"group" => group, "key" => key, "value" => value} ->
805 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
808 |> Enum.reject(&is_nil(&1))
810 Pleroma.Config.TransferTask.load_and_update_env()
811 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
818 |> put_view(ConfigView)
819 |> render("index.json", %{configs: updated})
822 def reload_emoji(conn, _params) do
823 Pleroma.Emoji.reload()
828 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
829 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
831 User.toggle_confirmation(users)
833 ModerationLog.insert_log(%{
836 action: "confirm_email"
842 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
843 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
845 User.try_send_confirmation_email(users)
847 ModerationLog.insert_log(%{
850 action: "resend_confirmation_email"
856 def errors(conn, {:error, :not_found}) do
858 |> put_status(:not_found)
859 |> json(dgettext("errors", "Not found"))
862 def errors(conn, {:error, reason}) do
864 |> put_status(:bad_request)
868 def errors(conn, {:param_cast, _}) do
870 |> put_status(:bad_request)
871 |> json(dgettext("errors", "Invalid parameters"))
874 def errors(conn, _) do
876 |> put_status(:internal_server_error)
877 |> json(dgettext("errors", "Something went wrong"))
880 defp page_params(params) do
881 {get_page(params["page"]), get_page_size(params["page_size"])}
884 defp get_page(page_string) when is_nil(page_string), do: 1
886 defp get_page(page_string) do
887 case Integer.parse(page_string) do
893 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
895 defp get_page_size(page_size_string) do
896 case Integer.parse(page_size_string) do
897 {page_size, _} -> page_size
898 :error -> @users_page_size