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"]}
34 when action in [:list_users, :user_show, :right_get, :invites]
39 %{scopes: ["write:accounts"]}
49 :user_toggle_activation,
61 %{scopes: ["read:reports"]} when action in [:list_reports, :report_show]
66 %{scopes: ["write:reports"]}
67 when action in [:report_update_state, :report_respond]
72 %{scopes: ["read:statuses"]} when action == :list_user_statuses
77 %{scopes: ["write:statuses"]}
78 when action in [:status_update, :status_delete]
84 when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log]
90 when action in [:relay_follow, :relay_unfollow, :config_update]
95 action_fallback(:errors)
97 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
98 user = User.get_cached_by_nickname(nickname)
101 ModerationLog.insert_log(%{
111 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
112 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
115 ModerationLog.insert_log(%{
125 def user_follow(%{assigns: %{user: admin}} = conn, %{
126 "follower" => follower_nick,
127 "followed" => followed_nick
129 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
130 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
131 User.follow(follower, followed)
133 ModerationLog.insert_log(%{
145 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
146 "follower" => follower_nick,
147 "followed" => followed_nick
149 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
150 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
151 User.unfollow(follower, followed)
153 ModerationLog.insert_log(%{
165 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
167 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
173 password_confirmation: password,
177 User.register_changeset(%User{}, user_data, need_confirmation: false)
179 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
180 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
183 case Pleroma.Repo.transaction(changesets) do
188 |> Enum.map(fn user ->
189 {:ok, user} = User.post_register_action(user)
193 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
195 ModerationLog.insert_log(%{
197 subjects: Map.values(users),
204 {:error, id, changeset, _} ->
206 Enum.map(changesets.operations, fn
207 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
208 AccountView.render("create-error.json", %{changeset: changeset})
210 {_, {:changeset, current_changeset, _}} ->
211 AccountView.render("create-error.json", %{changeset: current_changeset})
215 |> put_status(:conflict)
220 def user_show(conn, %{"nickname" => nickname}) do
221 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
223 |> put_view(AccountView)
224 |> render("show.json", %{user: user})
226 _ -> {:error, :not_found}
230 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
231 godmode = params["godmode"] == "true" || params["godmode"] == true
233 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
234 {_, page_size} = page_params(params)
237 ActivityPub.fetch_user_activities(user, nil, %{
238 "limit" => page_size,
243 |> put_view(StatusView)
244 |> render("index.json", %{activities: activities, as: :activity})
246 _ -> {:error, :not_found}
250 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
251 user = User.get_cached_by_nickname(nickname)
253 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
255 action = if user.deactivated, do: "activate", else: "deactivate"
257 ModerationLog.insert_log(%{
264 |> put_view(AccountView)
265 |> render("show.json", %{user: updated_user})
268 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
269 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
270 {:ok, updated_users} = User.deactivate(users, false)
272 ModerationLog.insert_log(%{
279 |> put_view(AccountView)
280 |> render("index.json", %{users: Keyword.values(updated_users)})
283 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
284 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
285 {:ok, updated_users} = User.deactivate(users, true)
287 ModerationLog.insert_log(%{
294 |> put_view(AccountView)
295 |> render("index.json", %{users: Keyword.values(updated_users)})
298 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
299 with {:ok, _} <- User.tag(nicknames, tags) do
300 ModerationLog.insert_log(%{
302 nicknames: nicknames,
307 json_response(conn, :no_content, "")
311 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
312 with {:ok, _} <- User.untag(nicknames, tags) do
313 ModerationLog.insert_log(%{
315 nicknames: nicknames,
320 json_response(conn, :no_content, "")
324 def list_users(conn, params) do
325 {page, page_size} = page_params(params)
326 filters = maybe_parse_filters(params["filters"])
329 query: params["query"],
331 page_size: page_size,
332 tags: params["tags"],
333 name: params["name"],
334 email: params["email"]
337 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
338 {:ok, users, count} <- filter_service_users(users, count),
342 AccountView.render("index.json",
350 defp filter_service_users(users, count) do
351 filtered_users = Enum.reject(users, &service_user?/1)
352 count = if Enum.any?(users, &service_user?/1), do: length(filtered_users), else: count
354 {:ok, filtered_users, count}
357 defp service_user?(user) do
358 String.match?(user.ap_id, ~r/.*\/relay$/) or
359 String.match?(user.ap_id, ~r/.*\/internal\/fetch$/)
362 @filters ~w(local external active deactivated is_admin is_moderator)
364 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
365 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
367 defp maybe_parse_filters(filters) do
370 |> Enum.filter(&Enum.member?(@filters, &1))
371 |> Enum.map(&String.to_atom(&1))
372 |> Enum.into(%{}, &{&1, true})
375 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
376 "permission_group" => permission_group,
377 "nicknames" => nicknames
379 when permission_group in ["moderator", "admin"] do
380 update = %{:"is_#{permission_group}" => true}
382 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
384 for u <- users, do: User.admin_api_update(u, update)
386 ModerationLog.insert_log(%{
390 permission: permission_group
396 def right_add_multiple(conn, _) do
397 render_error(conn, :not_found, "No such permission_group")
400 def right_add(%{assigns: %{user: admin}} = conn, %{
401 "permission_group" => permission_group,
402 "nickname" => nickname
404 when permission_group in ["moderator", "admin"] do
405 fields = %{:"is_#{permission_group}" => true}
409 |> User.get_cached_by_nickname()
410 |> User.admin_api_update(fields)
412 ModerationLog.insert_log(%{
416 permission: permission_group
422 def right_add(conn, _) do
423 render_error(conn, :not_found, "No such permission_group")
426 def right_get(conn, %{"nickname" => nickname}) do
427 user = User.get_cached_by_nickname(nickname)
431 is_moderator: user.is_moderator,
432 is_admin: user.is_admin
436 def right_delete_multiple(
437 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
439 "permission_group" => permission_group,
440 "nicknames" => nicknames
443 when permission_group in ["moderator", "admin"] do
444 with false <- Enum.member?(nicknames, admin_nickname) do
445 update = %{:"is_#{permission_group}" => false}
447 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
449 for u <- users, do: User.admin_api_update(u, update)
451 ModerationLog.insert_log(%{
455 permission: permission_group
460 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
464 def right_delete_multiple(conn, _) do
465 render_error(conn, :not_found, "No such permission_group")
469 %{assigns: %{user: admin}} = conn,
471 "permission_group" => permission_group,
472 "nickname" => nickname
475 when permission_group in ["moderator", "admin"] do
476 fields = %{:"is_#{permission_group}" => false}
480 |> User.get_cached_by_nickname()
481 |> User.admin_api_update(fields)
483 ModerationLog.insert_log(%{
487 permission: permission_group
493 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
494 render_error(conn, :forbidden, "You can't revoke your own admin status.")
497 def relay_list(conn, _params) do
498 with {:ok, list} <- Relay.list() do
499 json(conn, %{relays: list})
507 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
508 with {:ok, _message} <- Relay.follow(target) do
509 ModerationLog.insert_log(%{
510 action: "relay_follow",
524 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
525 with {:ok, _message} <- Relay.unfollow(target) do
526 ModerationLog.insert_log(%{
527 action: "relay_unfollow",
541 @doc "Sends registration invite via email"
542 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
544 Pleroma.Config.get([:instance, :invites_enabled]) &&
545 !Pleroma.Config.get([:instance, :registrations_open]),
546 {:ok, invite_token} <- UserInviteToken.create_invite(),
548 Pleroma.Emails.UserEmail.user_invitation_email(
554 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
555 json_response(conn, :no_content, "")
559 @doc "Create an account registration invite token"
560 def create_invite_token(conn, params) do
564 if params["max_use"],
565 do: Map.put(opts, :max_use, params["max_use"]),
569 if params["expires_at"],
570 do: Map.put(opts, :expires_at, params["expires_at"]),
573 {:ok, invite} = UserInviteToken.create_invite(opts)
575 json(conn, AccountView.render("invite.json", %{invite: invite}))
578 @doc "Get list of created invites"
579 def invites(conn, _params) do
580 invites = UserInviteToken.list_invites()
583 |> put_view(AccountView)
584 |> render("invites.json", %{invites: invites})
587 @doc "Revokes invite by token"
588 def revoke_invite(conn, %{"token" => token}) do
589 with {:ok, invite} <- UserInviteToken.find_by_token(token),
590 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
592 |> put_view(AccountView)
593 |> render("invite.json", %{invite: updated_invite})
595 nil -> {:error, :not_found}
599 @doc "Get a password reset token (base64 string) for given nickname"
600 def get_password_reset(conn, %{"nickname" => nickname}) do
601 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
602 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
607 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
611 @doc "Force password reset for a given user"
612 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
613 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
615 Enum.map(users, &User.force_password_reset_async/1)
617 ModerationLog.insert_log(%{
620 action: "force_password_reset"
623 json_response(conn, :no_content, "")
626 def list_reports(conn, params) do
627 {page, page_size} = page_params(params)
630 |> put_view(ReportView)
631 |> render("index.json", %{reports: Utils.get_reports(params, page, page_size)})
634 def list_grouped_reports(conn, _params) do
635 reports = Utils.get_reported_activities()
638 |> put_view(ReportView)
639 |> render("index_grouped.json", Utils.get_reports_grouped_by_status(reports))
642 def report_show(conn, %{"id" => id}) do
643 with %Activity{} = report <- Activity.get_by_id(id) do
645 |> put_view(ReportView)
646 |> render("show.json", Report.extract_report_info(report))
648 _ -> {:error, :not_found}
652 def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
655 |> Enum.map(fn report ->
656 with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do
657 ModerationLog.insert_log(%{
658 action: "report_update",
665 {:error, message} -> %{id: report["id"], error: message}
669 case Enum.any?(result, &Map.has_key?(&1, :error)) do
670 true -> json_response(conn, :bad_request, result)
671 false -> json_response(conn, :no_content, "")
675 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
676 with false <- is_nil(params["status"]),
677 %Activity{} <- Activity.get_by_id(id) do
680 |> Map.put("in_reply_to_status_id", id)
681 |> Map.put("visibility", "direct")
683 {:ok, activity} = CommonAPI.post(user, params)
685 ModerationLog.insert_log(%{
686 action: "report_response",
689 text: params["status"]
693 |> put_view(StatusView)
694 |> render("show.json", %{activity: activity})
704 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
705 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
706 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
708 ModerationLog.insert_log(%{
709 action: "status_update",
712 sensitive: sensitive,
713 visibility: params["visibility"]
717 |> put_view(StatusView)
718 |> render("show.json", %{activity: activity})
722 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
723 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
724 ModerationLog.insert_log(%{
725 action: "status_delete",
734 def list_log(conn, params) do
735 {page, page_size} = page_params(params)
738 ModerationLog.get_all(%{
740 page_size: page_size,
741 start_date: params["start_date"],
742 end_date: params["end_date"],
743 user_id: params["user_id"],
744 search: params["search"]
748 |> put_view(ModerationLogView)
749 |> render("index.json", %{log: log})
752 def migrate_to_db(conn, _params) do
753 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
757 def migrate_from_db(conn, _params) do
758 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
762 def config_show(conn, _params) do
763 configs = Pleroma.Repo.all(Config)
766 |> put_view(ConfigView)
767 |> render("index.json", %{configs: configs})
770 def config_update(conn, %{"configs" => configs}) do
772 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
775 %{"group" => group, "key" => key, "delete" => "true"} = params ->
776 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
779 %{"group" => group, "key" => key, "value" => value} ->
780 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
783 |> Enum.reject(&is_nil(&1))
785 Pleroma.Config.TransferTask.load_and_update_env()
786 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
793 |> put_view(ConfigView)
794 |> render("index.json", %{configs: updated})
797 def reload_emoji(conn, _params) do
798 Pleroma.Emoji.reload()
803 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
804 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
806 User.toggle_confirmation(users)
808 ModerationLog.insert_log(%{
811 action: "confirm_email"
817 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
818 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
820 User.try_send_confirmation_email(users)
822 ModerationLog.insert_log(%{
825 action: "resend_confirmation_email"
831 def errors(conn, {:error, :not_found}) do
833 |> put_status(:not_found)
834 |> json(dgettext("errors", "Not found"))
837 def errors(conn, {:error, reason}) do
839 |> put_status(:bad_request)
843 def errors(conn, {:param_cast, _}) do
845 |> put_status(:bad_request)
846 |> json(dgettext("errors", "Invalid parameters"))
849 def errors(conn, _) do
851 |> put_status(:internal_server_error)
852 |> json(dgettext("errors", "Something went wrong"))
855 defp page_params(params) do
856 {get_page(params["page"]), get_page_size(params["page_size"])}
859 defp get_page(page_string) when is_nil(page_string), do: 1
861 defp get_page(page_string) do
862 case Integer.parse(page_string) do
868 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
870 defp get_page_size(page_size_string) do
871 case Integer.parse(page_size_string) do
872 {page_size, _} -> page_size
873 :error -> @users_page_size