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]
10 alias Pleroma.Activity
13 alias Pleroma.ModerationLog
14 alias Pleroma.Plugs.OAuthScopesPlug
15 alias Pleroma.ReportNote
18 alias Pleroma.UserInviteToken
19 alias Pleroma.Web.ActivityPub.ActivityPub
20 alias Pleroma.Web.ActivityPub.Builder
21 alias Pleroma.Web.ActivityPub.Pipeline
22 alias Pleroma.Web.ActivityPub.Relay
23 alias Pleroma.Web.ActivityPub.Utils
24 alias Pleroma.Web.AdminAPI
25 alias Pleroma.Web.AdminAPI.AccountView
26 alias Pleroma.Web.AdminAPI.ModerationLogView
27 alias Pleroma.Web.AdminAPI.Report
28 alias Pleroma.Web.AdminAPI.ReportView
29 alias Pleroma.Web.AdminAPI.Search
30 alias Pleroma.Web.CommonAPI
31 alias Pleroma.Web.Endpoint
32 alias Pleroma.Web.MastodonAPI
33 alias Pleroma.Web.MastodonAPI.AppView
34 alias Pleroma.Web.OAuth.App
35 alias Pleroma.Web.Router
43 %{scopes: ["read:accounts"], admin: true}
44 when action in [:list_users, :user_show, :right_get, :show_user_credentials]
49 %{scopes: ["write:accounts"], admin: true}
52 :force_password_reset,
55 :user_toggle_activation,
64 :right_delete_multiple,
65 :update_user_credentials
69 plug(OAuthScopesPlug, %{scopes: ["read:invites"], admin: true} when action == :invites)
73 %{scopes: ["write:invites"], admin: true}
74 when action in [:create_invite_token, :revoke_invite, :email_invite]
79 %{scopes: ["write:follows"], admin: true}
80 when action in [:user_follow, :user_unfollow, :relay_follow, :relay_unfollow]
85 %{scopes: ["read:reports"], admin: true}
86 when action in [:list_reports, :report_show]
91 %{scopes: ["write:reports"], admin: true}
92 when action in [:reports_update, :report_notes_create, :report_notes_delete]
97 %{scopes: ["read:statuses"], admin: true}
98 when action in [:list_user_statuses, :list_instance_statuses]
103 %{scopes: ["read"], admin: true}
114 %{scopes: ["write"], admin: true}
117 :resend_confirmation_email,
127 action_fallback(AdminAPI.FallbackController)
129 def user_delete(conn, %{"nickname" => nickname}) do
130 user_delete(conn, %{"nicknames" => [nickname]})
133 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
136 |> Enum.map(&User.get_cached_by_nickname/1)
139 |> Enum.each(fn user ->
140 {:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
141 Pipeline.common_pipeline(delete_data, local: true)
144 ModerationLog.insert_log(%{
154 def user_follow(%{assigns: %{user: admin}} = conn, %{
155 "follower" => follower_nick,
156 "followed" => followed_nick
158 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
159 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
160 User.follow(follower, followed)
162 ModerationLog.insert_log(%{
174 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
175 "follower" => follower_nick,
176 "followed" => followed_nick
178 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
179 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
180 User.unfollow(follower, followed)
182 ModerationLog.insert_log(%{
194 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
196 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
202 password_confirmation: password,
206 User.register_changeset(%User{}, user_data, need_confirmation: false)
208 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
209 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
212 case Pleroma.Repo.transaction(changesets) do
217 |> Enum.map(fn user ->
218 {:ok, user} = User.post_register_action(user)
222 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
224 ModerationLog.insert_log(%{
226 subjects: Map.values(users),
233 {:error, id, changeset, _} ->
235 Enum.map(changesets.operations, fn
236 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
237 AccountView.render("create-error.json", %{changeset: changeset})
239 {_, {:changeset, current_changeset, _}} ->
240 AccountView.render("create-error.json", %{changeset: current_changeset})
244 |> put_status(:conflict)
249 def user_show(conn, %{"nickname" => nickname}) do
250 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
252 |> put_view(AccountView)
253 |> render("show.json", %{user: user})
255 _ -> {:error, :not_found}
259 def list_instance_statuses(conn, %{"instance" => instance} = params) do
260 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
261 {page, page_size} = page_params(params)
264 ActivityPub.fetch_statuses(nil, %{
265 "instance" => instance,
266 "limit" => page_size,
267 "offset" => (page - 1) * page_size,
268 "exclude_reblogs" => !with_reblogs && "true"
272 |> put_view(AdminAPI.StatusView)
273 |> render("index.json", %{activities: activities, as: :activity})
276 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
277 with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true
278 godmode = params["godmode"] == "true" || params["godmode"] == true
280 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
281 {_, page_size} = page_params(params)
284 ActivityPub.fetch_user_activities(user, nil, %{
285 "limit" => page_size,
286 "godmode" => godmode,
287 "exclude_reblogs" => !with_reblogs && "true"
291 |> put_view(MastodonAPI.StatusView)
292 |> render("index.json", %{activities: activities, as: :activity})
294 _ -> {:error, :not_found}
298 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
299 user = User.get_cached_by_nickname(nickname)
301 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
303 action = if user.deactivated, do: "activate", else: "deactivate"
305 ModerationLog.insert_log(%{
312 |> put_view(AccountView)
313 |> render("show.json", %{user: updated_user})
316 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
317 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
318 {:ok, updated_users} = User.deactivate(users, false)
320 ModerationLog.insert_log(%{
327 |> put_view(AccountView)
328 |> render("index.json", %{users: Keyword.values(updated_users)})
331 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
332 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
333 {:ok, updated_users} = User.deactivate(users, true)
335 ModerationLog.insert_log(%{
342 |> put_view(AccountView)
343 |> render("index.json", %{users: Keyword.values(updated_users)})
346 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
347 with {:ok, _} <- User.tag(nicknames, tags) do
348 ModerationLog.insert_log(%{
350 nicknames: nicknames,
355 json_response(conn, :no_content, "")
359 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
360 with {:ok, _} <- User.untag(nicknames, tags) do
361 ModerationLog.insert_log(%{
363 nicknames: nicknames,
368 json_response(conn, :no_content, "")
372 def list_users(conn, params) do
373 {page, page_size} = page_params(params)
374 filters = maybe_parse_filters(params["filters"])
377 query: params["query"],
379 page_size: page_size,
380 tags: params["tags"],
381 name: params["name"],
382 email: params["email"]
385 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do
388 AccountView.render("index.json", users: users, count: count, page_size: page_size)
393 @filters ~w(local external active deactivated is_admin is_moderator)
395 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
396 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
398 defp maybe_parse_filters(filters) do
401 |> Enum.filter(&Enum.member?(@filters, &1))
402 |> Enum.map(&String.to_atom(&1))
403 |> Enum.into(%{}, &{&1, true})
406 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
407 "permission_group" => permission_group,
408 "nicknames" => nicknames
410 when permission_group in ["moderator", "admin"] do
411 update = %{:"is_#{permission_group}" => true}
413 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
415 for u <- users, do: User.admin_api_update(u, update)
417 ModerationLog.insert_log(%{
421 permission: permission_group
427 def right_add_multiple(conn, _) do
428 render_error(conn, :not_found, "No such permission_group")
431 def right_add(%{assigns: %{user: admin}} = conn, %{
432 "permission_group" => permission_group,
433 "nickname" => nickname
435 when permission_group in ["moderator", "admin"] do
436 fields = %{:"is_#{permission_group}" => true}
440 |> User.get_cached_by_nickname()
441 |> User.admin_api_update(fields)
443 ModerationLog.insert_log(%{
447 permission: permission_group
453 def right_add(conn, _) do
454 render_error(conn, :not_found, "No such permission_group")
457 def right_get(conn, %{"nickname" => nickname}) do
458 user = User.get_cached_by_nickname(nickname)
462 is_moderator: user.is_moderator,
463 is_admin: user.is_admin
467 def right_delete_multiple(
468 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
470 "permission_group" => permission_group,
471 "nicknames" => nicknames
474 when permission_group in ["moderator", "admin"] do
475 with false <- Enum.member?(nicknames, admin_nickname) do
476 update = %{:"is_#{permission_group}" => false}
478 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
480 for u <- users, do: User.admin_api_update(u, update)
482 ModerationLog.insert_log(%{
486 permission: permission_group
491 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
495 def right_delete_multiple(conn, _) do
496 render_error(conn, :not_found, "No such permission_group")
500 %{assigns: %{user: admin}} = conn,
502 "permission_group" => permission_group,
503 "nickname" => nickname
506 when permission_group in ["moderator", "admin"] do
507 fields = %{:"is_#{permission_group}" => false}
511 |> User.get_cached_by_nickname()
512 |> User.admin_api_update(fields)
514 ModerationLog.insert_log(%{
518 permission: permission_group
524 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
525 render_error(conn, :forbidden, "You can't revoke your own admin status.")
528 def relay_list(conn, _params) do
529 with {:ok, list} <- Relay.list() do
530 json(conn, %{relays: list})
538 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
539 with {:ok, _message} <- Relay.follow(target) do
540 ModerationLog.insert_log(%{
541 action: "relay_follow",
555 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
556 with {:ok, _message} <- Relay.unfollow(target) do
557 ModerationLog.insert_log(%{
558 action: "relay_unfollow",
572 @doc "Sends registration invite via email"
573 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
574 with {_, false} <- {:registrations_open, Config.get([:instance, :registrations_open])},
575 {_, true} <- {:invites_enabled, Config.get([:instance, :invites_enabled])},
576 {:ok, invite_token} <- UserInviteToken.create_invite(),
578 Pleroma.Emails.UserEmail.user_invitation_email(
584 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
585 json_response(conn, :no_content, "")
587 {:registrations_open, _} ->
588 {:error, "To send invites you need to set the `registrations_open` option to false."}
590 {:invites_enabled, _} ->
591 {:error, "To send invites you need to set the `invites_enabled` option to true."}
595 @doc "Create an account registration invite token"
596 def create_invite_token(conn, params) do
600 if params["max_use"],
601 do: Map.put(opts, :max_use, params["max_use"]),
605 if params["expires_at"],
606 do: Map.put(opts, :expires_at, params["expires_at"]),
609 {:ok, invite} = UserInviteToken.create_invite(opts)
611 json(conn, AccountView.render("invite.json", %{invite: invite}))
614 @doc "Get list of created invites"
615 def invites(conn, _params) do
616 invites = UserInviteToken.list_invites()
619 |> put_view(AccountView)
620 |> render("invites.json", %{invites: invites})
623 @doc "Revokes invite by token"
624 def revoke_invite(conn, %{"token" => token}) do
625 with {:ok, invite} <- UserInviteToken.find_by_token(token),
626 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
628 |> put_view(AccountView)
629 |> render("invite.json", %{invite: updated_invite})
631 nil -> {:error, :not_found}
635 @doc "Get a password reset token (base64 string) for given nickname"
636 def get_password_reset(conn, %{"nickname" => nickname}) do
637 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
638 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
643 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
647 @doc "Force password reset for a given user"
648 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
649 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
651 Enum.each(users, &User.force_password_reset_async/1)
653 ModerationLog.insert_log(%{
656 action: "force_password_reset"
659 json_response(conn, :no_content, "")
662 @doc "Disable mfa for user's account."
663 def disable_mfa(conn, %{"nickname" => nickname}) do
664 case User.get_by_nickname(nickname) do
674 @doc "Show a given user's credentials"
675 def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
676 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
678 |> put_view(AccountView)
679 |> render("credentials.json", %{user: user, for: admin})
681 _ -> {:error, :not_found}
685 @doc "Updates a given user"
686 def update_user_credentials(
687 %{assigns: %{user: admin}} = conn,
688 %{"nickname" => nickname} = params
690 with {_, %User{} = user} <- {:user, User.get_cached_by_nickname(nickname)},
692 User.update_as_admin(user, params) do
693 ModerationLog.insert_log(%{
696 action: "updated_users"
699 if params["password"] do
700 User.force_password_reset_async(user)
703 ModerationLog.insert_log(%{
706 action: "force_password_reset"
709 json(conn, %{status: "success"})
711 {:error, changeset} ->
712 errors = Map.new(changeset.errors, fn {key, {error, _}} -> {key, error} end)
714 json(conn, %{errors: errors})
717 json(conn, %{error: "Unable to update user."})
721 def list_reports(conn, params) do
722 {page, page_size} = page_params(params)
724 reports = Utils.get_reports(params, page, page_size)
727 |> put_view(ReportView)
728 |> render("index.json", %{reports: reports})
731 def report_show(conn, %{"id" => id}) do
732 with %Activity{} = report <- Activity.get_by_id(id) do
734 |> put_view(ReportView)
735 |> render("show.json", Report.extract_report_info(report))
737 _ -> {:error, :not_found}
741 def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
744 |> Enum.map(fn report ->
745 with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do
746 ModerationLog.insert_log(%{
747 action: "report_update",
754 {:error, message} -> %{id: report["id"], error: message}
758 case Enum.any?(result, &Map.has_key?(&1, :error)) do
759 true -> json_response(conn, :bad_request, result)
760 false -> json_response(conn, :no_content, "")
764 def report_notes_create(%{assigns: %{user: user}} = conn, %{
768 with {:ok, _} <- ReportNote.create(user.id, report_id, content) do
769 ModerationLog.insert_log(%{
770 action: "report_note",
772 subject: Activity.get_by_id(report_id),
776 json_response(conn, :no_content, "")
778 _ -> json_response(conn, :bad_request, "")
782 def report_notes_delete(%{assigns: %{user: user}} = conn, %{
784 "report_id" => report_id
786 with {:ok, note} <- ReportNote.destroy(note_id) do
787 ModerationLog.insert_log(%{
788 action: "report_note_delete",
790 subject: Activity.get_by_id(report_id),
794 json_response(conn, :no_content, "")
796 _ -> json_response(conn, :bad_request, "")
800 def list_log(conn, params) do
801 {page, page_size} = page_params(params)
804 ModerationLog.get_all(%{
806 page_size: page_size,
807 start_date: params["start_date"],
808 end_date: params["end_date"],
809 user_id: params["user_id"],
810 search: params["search"]
814 |> put_view(ModerationLogView)
815 |> render("index.json", %{log: log})
818 def restart(conn, _params) do
819 with :ok <- configurable_from_database() do
820 Restarter.Pleroma.restart(Config.get(:env), 50)
826 def need_reboot(conn, _params) do
827 json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()})
830 defp configurable_from_database do
831 if Config.get(:configurable_from_database) do
834 {:error, "To use this endpoint you need to enable configuration from database."}
838 def reload_emoji(conn, _params) do
839 Pleroma.Emoji.reload()
844 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
845 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
847 User.toggle_confirmation(users)
849 ModerationLog.insert_log(%{
852 action: "confirm_email"
858 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
859 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
861 User.try_send_confirmation_email(users)
863 ModerationLog.insert_log(%{
866 action: "resend_confirmation_email"
872 def oauth_app_create(conn, params) do
875 Map.put(params, "client_name", params["name"])
881 case App.create(params) do
883 AppView.render("show.json", %{app: app, admin: true})
885 {:error, changeset} ->
886 App.errors(changeset)
892 def oauth_app_update(conn, params) do
895 Map.put(params, "client_name", params["name"])
900 with {:ok, app} <- App.update(params) do
901 json(conn, AppView.render("show.json", %{app: app, admin: true}))
903 {:error, changeset} ->
904 json(conn, App.errors(changeset))
907 json_response(conn, :bad_request, "")
911 def oauth_app_list(conn, params) do
912 {page, page_size} = page_params(params)
915 client_name: params["name"],
916 client_id: params["client_id"],
922 if Map.has_key?(params, "trusted") do
923 Map.put(search_params, :trusted, params["trusted"])
928 with {:ok, apps, count} <- App.search(search_params) do
931 AppView.render("index.json",
934 page_size: page_size,
941 def oauth_app_delete(conn, params) do
942 with {:ok, _app} <- App.destroy(params["id"]) do
943 json_response(conn, :no_content, "")
945 _ -> json_response(conn, :bad_request, "")
949 def stats(conn, _) do
950 count = Stats.get_status_visibility_count()
953 |> json(%{"status_visibility" => count})
956 defp page_params(params) do
957 {get_page(params["page"]), get_page_size(params["page_size"])}
960 defp get_page(page_string) when is_nil(page_string), do: 1
962 defp get_page(page_string) do
963 case Integer.parse(page_string) do
969 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
971 defp get_page_size(page_size_string) do
972 case Integer.parse(page_size_string) do
973 {page_size, _} -> page_size
974 :error -> @users_page_size