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"], admin: true}
35 when action in [:list_users, :user_show, :right_get]
40 %{scopes: ["write:accounts"], admin: true}
45 :user_toggle_activation,
55 plug(OAuthScopesPlug, %{scopes: ["read:invites"], admin: true} when action == :invites)
59 %{scopes: ["write:invites"], admin: true}
60 when action in [:create_invite_token, :revoke_invite, :email_invite]
65 %{scopes: ["write:follows"], admin: true}
66 when action in [:user_follow, :user_unfollow, :relay_follow, :relay_unfollow]
71 %{scopes: ["read:reports"], admin: true}
72 when action in [:list_reports, :report_show]
77 %{scopes: ["write:reports"], admin: true}
78 when action in [:reports_update]
83 %{scopes: ["read:statuses"], admin: true}
84 when action == :list_user_statuses
89 %{scopes: ["write:statuses"], admin: true}
90 when action in [:status_update, :status_delete]
95 %{scopes: ["read"], admin: true}
96 when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log]
101 %{scopes: ["write"], admin: true}
102 when action == :config_update
107 action_fallback(:errors)
109 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
110 user = User.get_cached_by_nickname(nickname)
113 ModerationLog.insert_log(%{
123 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
124 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
127 ModerationLog.insert_log(%{
137 def user_follow(%{assigns: %{user: admin}} = conn, %{
138 "follower" => follower_nick,
139 "followed" => followed_nick
141 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
142 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
143 User.follow(follower, followed)
145 ModerationLog.insert_log(%{
157 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
158 "follower" => follower_nick,
159 "followed" => followed_nick
161 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
162 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
163 User.unfollow(follower, followed)
165 ModerationLog.insert_log(%{
177 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
179 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
185 password_confirmation: password,
189 User.register_changeset(%User{}, user_data, need_confirmation: false)
191 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
192 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
195 case Pleroma.Repo.transaction(changesets) do
200 |> Enum.map(fn user ->
201 {:ok, user} = User.post_register_action(user)
205 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
207 ModerationLog.insert_log(%{
209 subjects: Map.values(users),
216 {:error, id, changeset, _} ->
218 Enum.map(changesets.operations, fn
219 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
220 AccountView.render("create-error.json", %{changeset: changeset})
222 {_, {:changeset, current_changeset, _}} ->
223 AccountView.render("create-error.json", %{changeset: current_changeset})
227 |> put_status(:conflict)
232 def user_show(conn, %{"nickname" => nickname}) do
233 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
235 |> put_view(AccountView)
236 |> render("show.json", %{user: user})
238 _ -> {:error, :not_found}
242 def list_instance_statuses(conn, %{"instance" => instance} = params) do
243 {page, page_size} = page_params(params)
246 ActivityPub.fetch_instance_activities(%{
247 "instance" => instance,
248 "limit" => page_size,
249 "offset" => (page - 1) * page_size
253 |> put_view(Pleroma.Web.AdminAPI.StatusView)
254 |> render("index.json", %{activities: activities, as: :activity})
257 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
258 godmode = params["godmode"] == "true" || params["godmode"] == true
260 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
261 {_, page_size} = page_params(params)
264 ActivityPub.fetch_user_activities(user, nil, %{
265 "limit" => page_size,
270 |> put_view(StatusView)
271 |> render("index.json", %{activities: activities, as: :activity})
273 _ -> {:error, :not_found}
277 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
278 user = User.get_cached_by_nickname(nickname)
280 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
282 action = if user.deactivated, do: "activate", else: "deactivate"
284 ModerationLog.insert_log(%{
291 |> put_view(AccountView)
292 |> render("show.json", %{user: updated_user})
295 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
296 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
297 {:ok, updated_users} = User.deactivate(users, false)
299 ModerationLog.insert_log(%{
306 |> put_view(AccountView)
307 |> render("index.json", %{users: Keyword.values(updated_users)})
310 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
311 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
312 {:ok, updated_users} = User.deactivate(users, true)
314 ModerationLog.insert_log(%{
321 |> put_view(AccountView)
322 |> render("index.json", %{users: Keyword.values(updated_users)})
325 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
326 with {:ok, _} <- User.tag(nicknames, tags) do
327 ModerationLog.insert_log(%{
329 nicknames: nicknames,
334 json_response(conn, :no_content, "")
338 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
339 with {:ok, _} <- User.untag(nicknames, tags) do
340 ModerationLog.insert_log(%{
342 nicknames: nicknames,
347 json_response(conn, :no_content, "")
351 def list_users(conn, params) do
352 {page, page_size} = page_params(params)
353 filters = maybe_parse_filters(params["filters"])
356 query: params["query"],
358 page_size: page_size,
359 tags: params["tags"],
360 name: params["name"],
361 email: params["email"]
364 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
365 {:ok, users, count} <- filter_service_users(users, count),
369 AccountView.render("index.json",
377 defp filter_service_users(users, count) do
378 filtered_users = Enum.reject(users, &service_user?/1)
379 count = if Enum.any?(users, &service_user?/1), do: length(filtered_users), else: count
381 {:ok, filtered_users, count}
384 defp service_user?(user) do
385 String.match?(user.ap_id, ~r/.*\/relay$/) or
386 String.match?(user.ap_id, ~r/.*\/internal\/fetch$/)
389 @filters ~w(local external active deactivated is_admin is_moderator)
391 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
392 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
394 defp maybe_parse_filters(filters) do
397 |> Enum.filter(&Enum.member?(@filters, &1))
398 |> Enum.map(&String.to_atom(&1))
399 |> Enum.into(%{}, &{&1, true})
402 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
403 "permission_group" => permission_group,
404 "nicknames" => nicknames
406 when permission_group in ["moderator", "admin"] do
407 update = %{:"is_#{permission_group}" => true}
409 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
411 for u <- users, do: User.admin_api_update(u, update)
413 ModerationLog.insert_log(%{
417 permission: permission_group
423 def right_add_multiple(conn, _) do
424 render_error(conn, :not_found, "No such permission_group")
427 def right_add(%{assigns: %{user: admin}} = conn, %{
428 "permission_group" => permission_group,
429 "nickname" => nickname
431 when permission_group in ["moderator", "admin"] do
432 fields = %{:"is_#{permission_group}" => true}
436 |> User.get_cached_by_nickname()
437 |> User.admin_api_update(fields)
439 ModerationLog.insert_log(%{
443 permission: permission_group
449 def right_add(conn, _) do
450 render_error(conn, :not_found, "No such permission_group")
453 def right_get(conn, %{"nickname" => nickname}) do
454 user = User.get_cached_by_nickname(nickname)
458 is_moderator: user.is_moderator,
459 is_admin: user.is_admin
463 def right_delete_multiple(
464 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
466 "permission_group" => permission_group,
467 "nicknames" => nicknames
470 when permission_group in ["moderator", "admin"] do
471 with false <- Enum.member?(nicknames, admin_nickname) do
472 update = %{:"is_#{permission_group}" => false}
474 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
476 for u <- users, do: User.admin_api_update(u, update)
478 ModerationLog.insert_log(%{
482 permission: permission_group
487 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
491 def right_delete_multiple(conn, _) do
492 render_error(conn, :not_found, "No such permission_group")
496 %{assigns: %{user: admin}} = conn,
498 "permission_group" => permission_group,
499 "nickname" => nickname
502 when permission_group in ["moderator", "admin"] do
503 fields = %{:"is_#{permission_group}" => false}
507 |> User.get_cached_by_nickname()
508 |> User.admin_api_update(fields)
510 ModerationLog.insert_log(%{
514 permission: permission_group
520 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
521 render_error(conn, :forbidden, "You can't revoke your own admin status.")
524 def relay_list(conn, _params) do
525 with {:ok, list} <- Relay.list() do
526 json(conn, %{relays: list})
534 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
535 with {:ok, _message} <- Relay.follow(target) do
536 ModerationLog.insert_log(%{
537 action: "relay_follow",
551 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
552 with {:ok, _message} <- Relay.unfollow(target) do
553 ModerationLog.insert_log(%{
554 action: "relay_unfollow",
568 @doc "Sends registration invite via email"
569 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
571 Pleroma.Config.get([:instance, :invites_enabled]) &&
572 !Pleroma.Config.get([:instance, :registrations_open]),
573 {:ok, invite_token} <- UserInviteToken.create_invite(),
575 Pleroma.Emails.UserEmail.user_invitation_email(
581 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
582 json_response(conn, :no_content, "")
586 @doc "Create an account registration invite token"
587 def create_invite_token(conn, params) do
591 if params["max_use"],
592 do: Map.put(opts, :max_use, params["max_use"]),
596 if params["expires_at"],
597 do: Map.put(opts, :expires_at, params["expires_at"]),
600 {:ok, invite} = UserInviteToken.create_invite(opts)
602 json(conn, AccountView.render("invite.json", %{invite: invite}))
605 @doc "Get list of created invites"
606 def invites(conn, _params) do
607 invites = UserInviteToken.list_invites()
610 |> put_view(AccountView)
611 |> render("invites.json", %{invites: invites})
614 @doc "Revokes invite by token"
615 def revoke_invite(conn, %{"token" => token}) do
616 with {:ok, invite} <- UserInviteToken.find_by_token(token),
617 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
619 |> put_view(AccountView)
620 |> render("invite.json", %{invite: updated_invite})
622 nil -> {:error, :not_found}
626 @doc "Get a password reset token (base64 string) for given nickname"
627 def get_password_reset(conn, %{"nickname" => nickname}) do
628 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
629 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
634 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
638 @doc "Force password reset for a given user"
639 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
640 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
642 Enum.map(users, &User.force_password_reset_async/1)
644 ModerationLog.insert_log(%{
647 action: "force_password_reset"
650 json_response(conn, :no_content, "")
653 def list_reports(conn, params) do
654 {page, page_size} = page_params(params)
656 reports = Utils.get_reports(params, page, page_size)
659 |> put_view(ReportView)
660 |> render("index.json", %{reports: reports})
663 def list_grouped_reports(conn, _params) do
664 statuses = Utils.get_reported_activities()
667 |> put_view(ReportView)
668 |> render("index_grouped.json", Utils.get_reports_grouped_by_status(statuses))
671 def report_show(conn, %{"id" => id}) do
672 with %Activity{} = report <- Activity.get_by_id(id) do
674 |> put_view(ReportView)
675 |> render("show.json", Report.extract_report_info(report))
677 _ -> {:error, :not_found}
681 def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
684 |> Enum.map(fn report ->
685 with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do
686 ModerationLog.insert_log(%{
687 action: "report_update",
694 {:error, message} -> %{id: report["id"], error: message}
698 case Enum.any?(result, &Map.has_key?(&1, :error)) do
699 true -> json_response(conn, :bad_request, result)
700 false -> json_response(conn, :no_content, "")
704 def report_notes_create(%{assigns: %{user: user}} = conn, %{
708 with {:ok, _} <- ReportNote.create(user.id, report_id, content) do
709 ModerationLog.insert_log(%{
710 action: "report_note",
712 subject: Activity.get_by_id(report_id),
716 json_response(conn, :no_content, "")
718 _ -> json_response(conn, :bad_request, "")
722 def report_notes_delete(%{assigns: %{user: user}} = conn, %{
724 "report_id" => report_id
726 with {:ok, note} <- ReportNote.destroy(note_id) do
727 ModerationLog.insert_log(%{
728 action: "report_note_delete",
730 subject: Activity.get_by_id(report_id),
734 json_response(conn, :no_content, "")
736 _ -> json_response(conn, :bad_request, "")
740 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
741 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
742 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
744 ModerationLog.insert_log(%{
745 action: "status_update",
748 sensitive: sensitive,
749 visibility: params["visibility"]
753 |> put_view(StatusView)
754 |> render("show.json", %{activity: activity})
758 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
759 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
760 ModerationLog.insert_log(%{
761 action: "status_delete",
770 def list_log(conn, params) do
771 {page, page_size} = page_params(params)
774 ModerationLog.get_all(%{
776 page_size: page_size,
777 start_date: params["start_date"],
778 end_date: params["end_date"],
779 user_id: params["user_id"],
780 search: params["search"]
784 |> put_view(ModerationLogView)
785 |> render("index.json", %{log: log})
788 def migrate_to_db(conn, _params) do
789 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
793 def migrate_from_db(conn, _params) do
794 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
798 def config_show(conn, _params) do
799 configs = Pleroma.Repo.all(Config)
802 |> put_view(ConfigView)
803 |> render("index.json", %{configs: configs})
806 def config_update(conn, %{"configs" => configs}) do
808 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
811 %{"group" => group, "key" => key, "delete" => "true"} = params ->
812 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
815 %{"group" => group, "key" => key, "value" => value} ->
816 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
819 |> Enum.reject(&is_nil(&1))
821 Pleroma.Config.TransferTask.load_and_update_env()
822 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
829 |> put_view(ConfigView)
830 |> render("index.json", %{configs: updated})
833 def reload_emoji(conn, _params) do
834 Pleroma.Emoji.reload()
839 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
840 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
842 User.toggle_confirmation(users)
844 ModerationLog.insert_log(%{
847 action: "confirm_email"
853 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
854 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
856 User.try_send_confirmation_email(users)
858 ModerationLog.insert_log(%{
861 action: "resend_confirmation_email"
867 def errors(conn, {:error, :not_found}) do
869 |> put_status(:not_found)
870 |> json(dgettext("errors", "Not found"))
873 def errors(conn, {:error, reason}) do
875 |> put_status(:bad_request)
879 def errors(conn, {:param_cast, _}) do
881 |> put_status(:bad_request)
882 |> json(dgettext("errors", "Invalid parameters"))
885 def errors(conn, _) do
887 |> put_status(:internal_server_error)
888 |> json(dgettext("errors", "Something went wrong"))
891 defp page_params(params) do
892 {get_page(params["page"]), get_page_size(params["page_size"])}
895 defp get_page(page_string) when is_nil(page_string), do: 1
897 defp get_page(page_string) do
898 case Integer.parse(page_string) do
904 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
906 defp get_page_size(page_size_string) do
907 case Integer.parse(page_size_string) do
908 {page_size, _} -> page_size
909 :error -> @users_page_size