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_instance_statuses(conn, %{"instance" => instance} = params) do
231 {page, page_size} = page_params(params)
234 ActivityPub.fetch_instance_activities(%{
235 "instance" => instance,
236 "limit" => page_size,
237 "offset" => (page - 1) * page_size
241 |> put_view(StatusView)
242 |> render("index.json", %{activities: activities, as: :activity})
245 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
246 godmode = params["godmode"] == "true" || params["godmode"] == true
248 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
249 {_, page_size} = page_params(params)
252 ActivityPub.fetch_user_activities(user, nil, %{
253 "limit" => page_size,
258 |> put_view(StatusView)
259 |> render("index.json", %{activities: activities, as: :activity})
261 _ -> {:error, :not_found}
265 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
266 user = User.get_cached_by_nickname(nickname)
268 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
270 action = if user.deactivated, do: "activate", else: "deactivate"
272 ModerationLog.insert_log(%{
279 |> put_view(AccountView)
280 |> render("show.json", %{user: updated_user})
283 def user_activate(%{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, false)
287 ModerationLog.insert_log(%{
294 |> put_view(AccountView)
295 |> render("index.json", %{users: Keyword.values(updated_users)})
298 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
299 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
300 {:ok, updated_users} = User.deactivate(users, true)
302 ModerationLog.insert_log(%{
309 |> put_view(AccountView)
310 |> render("index.json", %{users: Keyword.values(updated_users)})
313 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
314 with {:ok, _} <- User.tag(nicknames, tags) do
315 ModerationLog.insert_log(%{
317 nicknames: nicknames,
322 json_response(conn, :no_content, "")
326 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
327 with {:ok, _} <- User.untag(nicknames, tags) do
328 ModerationLog.insert_log(%{
330 nicknames: nicknames,
335 json_response(conn, :no_content, "")
339 def list_users(conn, params) do
340 {page, page_size} = page_params(params)
341 filters = maybe_parse_filters(params["filters"])
344 query: params["query"],
346 page_size: page_size,
347 tags: params["tags"],
348 name: params["name"],
349 email: params["email"]
352 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
353 {:ok, users, count} <- filter_relay_user(users, count),
357 AccountView.render("index.json",
365 defp filter_relay_user(users, count) do
366 filtered_users = Enum.reject(users, &relay_user?/1)
367 count = if Enum.any?(users, &relay_user?/1), do: length(filtered_users), else: count
369 {:ok, filtered_users, count}
372 defp relay_user?(user) do
373 user.ap_id == Relay.relay_ap_id()
376 @filters ~w(local external active deactivated is_admin is_moderator)
378 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
379 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
381 defp maybe_parse_filters(filters) do
384 |> Enum.filter(&Enum.member?(@filters, &1))
385 |> Enum.map(&String.to_atom(&1))
386 |> Enum.into(%{}, &{&1, true})
389 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
390 "permission_group" => permission_group,
391 "nicknames" => nicknames
393 when permission_group in ["moderator", "admin"] do
394 update = %{:"is_#{permission_group}" => true}
396 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
398 for u <- users, do: User.admin_api_update(u, update)
400 ModerationLog.insert_log(%{
404 permission: permission_group
410 def right_add_multiple(conn, _) do
411 render_error(conn, :not_found, "No such permission_group")
414 def right_add(%{assigns: %{user: admin}} = conn, %{
415 "permission_group" => permission_group,
416 "nickname" => nickname
418 when permission_group in ["moderator", "admin"] do
419 fields = %{:"is_#{permission_group}" => true}
423 |> User.get_cached_by_nickname()
424 |> User.admin_api_update(fields)
426 ModerationLog.insert_log(%{
430 permission: permission_group
436 def right_add(conn, _) do
437 render_error(conn, :not_found, "No such permission_group")
440 def right_get(conn, %{"nickname" => nickname}) do
441 user = User.get_cached_by_nickname(nickname)
445 is_moderator: user.is_moderator,
446 is_admin: user.is_admin
450 def right_delete_multiple(
451 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
453 "permission_group" => permission_group,
454 "nicknames" => nicknames
457 when permission_group in ["moderator", "admin"] do
458 with false <- Enum.member?(nicknames, admin_nickname) do
459 update = %{:"is_#{permission_group}" => false}
461 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
463 for u <- users, do: User.admin_api_update(u, update)
465 ModerationLog.insert_log(%{
469 permission: permission_group
474 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
478 def right_delete_multiple(conn, _) do
479 render_error(conn, :not_found, "No such permission_group")
483 %{assigns: %{user: admin}} = conn,
485 "permission_group" => permission_group,
486 "nickname" => nickname
489 when permission_group in ["moderator", "admin"] do
490 fields = %{:"is_#{permission_group}" => false}
494 |> User.get_cached_by_nickname()
495 |> User.admin_api_update(fields)
497 ModerationLog.insert_log(%{
501 permission: permission_group
507 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
508 render_error(conn, :forbidden, "You can't revoke your own admin status.")
511 def relay_list(conn, _params) do
512 with {:ok, list} <- Relay.list() do
513 json(conn, %{relays: list})
521 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
522 with {:ok, _message} <- Relay.follow(target) do
523 ModerationLog.insert_log(%{
524 action: "relay_follow",
538 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
539 with {:ok, _message} <- Relay.unfollow(target) do
540 ModerationLog.insert_log(%{
541 action: "relay_unfollow",
555 @doc "Sends registration invite via email"
556 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
558 Pleroma.Config.get([:instance, :invites_enabled]) &&
559 !Pleroma.Config.get([:instance, :registrations_open]),
560 {:ok, invite_token} <- UserInviteToken.create_invite(),
562 Pleroma.Emails.UserEmail.user_invitation_email(
568 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
569 json_response(conn, :no_content, "")
573 @doc "Create an account registration invite token"
574 def create_invite_token(conn, params) do
578 if params["max_use"],
579 do: Map.put(opts, :max_use, params["max_use"]),
583 if params["expires_at"],
584 do: Map.put(opts, :expires_at, params["expires_at"]),
587 {:ok, invite} = UserInviteToken.create_invite(opts)
589 json(conn, AccountView.render("invite.json", %{invite: invite}))
592 @doc "Get list of created invites"
593 def invites(conn, _params) do
594 invites = UserInviteToken.list_invites()
597 |> put_view(AccountView)
598 |> render("invites.json", %{invites: invites})
601 @doc "Revokes invite by token"
602 def revoke_invite(conn, %{"token" => token}) do
603 with {:ok, invite} <- UserInviteToken.find_by_token(token),
604 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
606 |> put_view(AccountView)
607 |> render("invite.json", %{invite: updated_invite})
609 nil -> {:error, :not_found}
613 @doc "Get a password reset token (base64 string) for given nickname"
614 def get_password_reset(conn, %{"nickname" => nickname}) do
615 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
616 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
621 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
625 @doc "Force password reset for a given user"
626 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
627 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
629 Enum.map(users, &User.force_password_reset_async/1)
631 ModerationLog.insert_log(%{
634 action: "force_password_reset"
637 json_response(conn, :no_content, "")
640 def list_reports(conn, params) do
641 {page, page_size} = page_params(params)
644 |> put_view(ReportView)
645 |> render("index.json", %{reports: Utils.get_reports(params, page, page_size)})
648 def list_grouped_reports(conn, _params) do
649 reports = Utils.get_reported_activities()
652 |> put_view(ReportView)
653 |> render("index_grouped.json", Utils.get_reports_grouped_by_status(reports))
656 def report_show(conn, %{"id" => id}) do
657 with %Activity{} = report <- Activity.get_by_id(id) do
659 |> put_view(ReportView)
660 |> render("show.json", Report.extract_report_info(report))
662 _ -> {:error, :not_found}
666 def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
669 |> Enum.map(fn report ->
670 with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do
671 ModerationLog.insert_log(%{
672 action: "report_update",
679 {:error, message} -> %{id: report["id"], error: message}
683 case Enum.any?(result, &Map.has_key?(&1, :error)) do
684 true -> json_response(conn, :bad_request, result)
685 false -> json_response(conn, :no_content, "")
689 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
690 with false <- is_nil(params["status"]),
691 %Activity{} <- Activity.get_by_id(id) do
694 |> Map.put("in_reply_to_status_id", id)
695 |> Map.put("visibility", "direct")
697 {:ok, activity} = CommonAPI.post(user, params)
699 ModerationLog.insert_log(%{
700 action: "report_response",
703 text: params["status"]
707 |> put_view(StatusView)
708 |> render("show.json", %{activity: activity})
718 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
719 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
720 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
722 ModerationLog.insert_log(%{
723 action: "status_update",
726 sensitive: sensitive,
727 visibility: params["visibility"]
731 |> put_view(StatusView)
732 |> render("show.json", %{activity: activity})
736 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
737 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
738 ModerationLog.insert_log(%{
739 action: "status_delete",
748 def list_log(conn, params) do
749 {page, page_size} = page_params(params)
752 ModerationLog.get_all(%{
754 page_size: page_size,
755 start_date: params["start_date"],
756 end_date: params["end_date"],
757 user_id: params["user_id"],
758 search: params["search"]
762 |> put_view(ModerationLogView)
763 |> render("index.json", %{log: log})
766 def migrate_to_db(conn, _params) do
767 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
771 def migrate_from_db(conn, _params) do
772 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
776 def config_show(conn, _params) do
777 configs = Pleroma.Repo.all(Config)
780 |> put_view(ConfigView)
781 |> render("index.json", %{configs: configs})
784 def config_update(conn, %{"configs" => configs}) do
786 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
789 %{"group" => group, "key" => key, "delete" => "true"} = params ->
790 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
793 %{"group" => group, "key" => key, "value" => value} ->
794 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
797 |> Enum.reject(&is_nil(&1))
799 Pleroma.Config.TransferTask.load_and_update_env()
800 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
807 |> put_view(ConfigView)
808 |> render("index.json", %{configs: updated})
811 def reload_emoji(conn, _params) do
812 Pleroma.Emoji.reload()
817 def errors(conn, {:error, :not_found}) do
819 |> put_status(:not_found)
820 |> json(dgettext("errors", "Not found"))
823 def errors(conn, {:error, reason}) do
825 |> put_status(:bad_request)
829 def errors(conn, {:param_cast, _}) do
831 |> put_status(:bad_request)
832 |> json(dgettext("errors", "Invalid parameters"))
835 def errors(conn, _) do
837 |> put_status(:internal_server_error)
838 |> json(dgettext("errors", "Something went wrong"))
841 defp page_params(params) do
842 {get_page(params["page"]), get_page_size(params["page_size"])}
845 defp get_page(page_string) when is_nil(page_string), do: 1
847 defp get_page(page_string) do
848 case Integer.parse(page_string) do
854 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
856 defp get_page_size(page_size_string) do
857 case Integer.parse(page_size_string) do
858 {page_size, _} -> page_size
859 :error -> @users_page_size