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_relay_user(users, count),
342 AccountView.render("index.json",
350 defp filter_relay_user(users, count) do
351 filtered_users = Enum.reject(users, &relay_user?/1)
352 count = if Enum.any?(users, &relay_user?/1), do: length(filtered_users), else: count
354 {:ok, filtered_users, count}
357 defp relay_user?(user) do
358 user.ap_id == Relay.relay_ap_id()
361 @filters ~w(local external active deactivated is_admin is_moderator)
363 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
364 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
366 defp maybe_parse_filters(filters) do
369 |> Enum.filter(&Enum.member?(@filters, &1))
370 |> Enum.map(&String.to_atom(&1))
371 |> Enum.into(%{}, &{&1, true})
374 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
375 "permission_group" => permission_group,
376 "nicknames" => nicknames
378 when permission_group in ["moderator", "admin"] do
379 update = %{:"is_#{permission_group}" => true}
381 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
383 for u <- users, do: User.admin_api_update(u, update)
385 ModerationLog.insert_log(%{
389 permission: permission_group
395 def right_add_multiple(conn, _) do
396 render_error(conn, :not_found, "No such permission_group")
399 def right_add(%{assigns: %{user: admin}} = conn, %{
400 "permission_group" => permission_group,
401 "nickname" => nickname
403 when permission_group in ["moderator", "admin"] do
404 fields = %{:"is_#{permission_group}" => true}
408 |> User.get_cached_by_nickname()
409 |> User.admin_api_update(fields)
411 ModerationLog.insert_log(%{
415 permission: permission_group
421 def right_add(conn, _) do
422 render_error(conn, :not_found, "No such permission_group")
425 def right_get(conn, %{"nickname" => nickname}) do
426 user = User.get_cached_by_nickname(nickname)
430 is_moderator: user.is_moderator,
431 is_admin: user.is_admin
435 def right_delete_multiple(
436 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
438 "permission_group" => permission_group,
439 "nicknames" => nicknames
442 when permission_group in ["moderator", "admin"] do
443 with false <- Enum.member?(nicknames, admin_nickname) do
444 update = %{:"is_#{permission_group}" => false}
446 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
448 for u <- users, do: User.admin_api_update(u, update)
450 ModerationLog.insert_log(%{
454 permission: permission_group
459 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
463 def right_delete_multiple(conn, _) do
464 render_error(conn, :not_found, "No such permission_group")
468 %{assigns: %{user: admin}} = conn,
470 "permission_group" => permission_group,
471 "nickname" => nickname
474 when permission_group in ["moderator", "admin"] do
475 fields = %{:"is_#{permission_group}" => false}
479 |> User.get_cached_by_nickname()
480 |> User.admin_api_update(fields)
482 ModerationLog.insert_log(%{
486 permission: permission_group
492 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
493 render_error(conn, :forbidden, "You can't revoke your own admin status.")
496 def relay_list(conn, _params) do
497 with {:ok, list} <- Relay.list() do
498 json(conn, %{relays: list})
506 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
507 with {:ok, _message} <- Relay.follow(target) do
508 ModerationLog.insert_log(%{
509 action: "relay_follow",
523 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
524 with {:ok, _message} <- Relay.unfollow(target) do
525 ModerationLog.insert_log(%{
526 action: "relay_unfollow",
540 @doc "Sends registration invite via email"
541 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
543 Pleroma.Config.get([:instance, :invites_enabled]) &&
544 !Pleroma.Config.get([:instance, :registrations_open]),
545 {:ok, invite_token} <- UserInviteToken.create_invite(),
547 Pleroma.Emails.UserEmail.user_invitation_email(
553 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
554 json_response(conn, :no_content, "")
558 @doc "Create an account registration invite token"
559 def create_invite_token(conn, params) do
563 if params["max_use"],
564 do: Map.put(opts, :max_use, params["max_use"]),
568 if params["expires_at"],
569 do: Map.put(opts, :expires_at, params["expires_at"]),
572 {:ok, invite} = UserInviteToken.create_invite(opts)
574 json(conn, AccountView.render("invite.json", %{invite: invite}))
577 @doc "Get list of created invites"
578 def invites(conn, _params) do
579 invites = UserInviteToken.list_invites()
582 |> put_view(AccountView)
583 |> render("invites.json", %{invites: invites})
586 @doc "Revokes invite by token"
587 def revoke_invite(conn, %{"token" => token}) do
588 with {:ok, invite} <- UserInviteToken.find_by_token(token),
589 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
591 |> put_view(AccountView)
592 |> render("invite.json", %{invite: updated_invite})
594 nil -> {:error, :not_found}
598 @doc "Get a password reset token (base64 string) for given nickname"
599 def get_password_reset(conn, %{"nickname" => nickname}) do
600 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
601 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
606 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
610 @doc "Force password reset for a given user"
611 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
612 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
614 Enum.map(users, &User.force_password_reset_async/1)
616 ModerationLog.insert_log(%{
619 action: "force_password_reset"
622 json_response(conn, :no_content, "")
625 def list_reports(conn, params) do
626 {page, page_size} = page_params(params)
629 |> put_view(ReportView)
630 |> render("index.json", %{reports: Utils.get_reports(params, page, page_size)})
633 def list_grouped_reports(conn, _params) do
634 reports = Utils.get_reported_activities()
637 |> put_view(ReportView)
638 |> render("index_grouped.json", Utils.get_reports_grouped_by_status(reports))
641 def report_show(conn, %{"id" => id}) do
642 with %Activity{} = report <- Activity.get_by_id(id) do
644 |> put_view(ReportView)
645 |> render("show.json", Report.extract_report_info(report))
647 _ -> {:error, :not_found}
651 def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
654 |> Enum.map(fn report ->
655 with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do
656 ModerationLog.insert_log(%{
657 action: "report_update",
664 {:error, message} -> %{id: report["id"], error: message}
668 case Enum.any?(result, &Map.has_key?(&1, :error)) do
669 true -> json_response(conn, :bad_request, result)
670 false -> json_response(conn, :no_content, "")
674 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
675 with false <- is_nil(params["status"]),
676 %Activity{} <- Activity.get_by_id(id) do
679 |> Map.put("in_reply_to_status_id", id)
680 |> Map.put("visibility", "direct")
682 {:ok, activity} = CommonAPI.post(user, params)
684 ModerationLog.insert_log(%{
685 action: "report_response",
688 text: params["status"]
692 |> put_view(StatusView)
693 |> render("show.json", %{activity: activity})
703 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
704 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
705 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
707 ModerationLog.insert_log(%{
708 action: "status_update",
711 sensitive: sensitive,
712 visibility: params["visibility"]
716 |> put_view(StatusView)
717 |> render("show.json", %{activity: activity})
721 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
722 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
723 ModerationLog.insert_log(%{
724 action: "status_delete",
733 def list_log(conn, params) do
734 {page, page_size} = page_params(params)
737 ModerationLog.get_all(%{
739 page_size: page_size,
740 start_date: params["start_date"],
741 end_date: params["end_date"],
742 user_id: params["user_id"],
743 search: params["search"]
747 |> put_view(ModerationLogView)
748 |> render("index.json", %{log: log})
751 def migrate_to_db(conn, _params) do
752 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
756 def migrate_from_db(conn, _params) do
757 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
761 def config_show(conn, _params) do
762 configs = Pleroma.Repo.all(Config)
765 |> put_view(ConfigView)
766 |> render("index.json", %{configs: configs})
769 def config_update(conn, %{"configs" => configs}) do
771 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
774 %{"group" => group, "key" => key, "delete" => "true"} = params ->
775 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
778 %{"group" => group, "key" => key, "value" => value} ->
779 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
782 |> Enum.reject(&is_nil(&1))
784 Pleroma.Config.TransferTask.load_and_update_env()
785 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
792 |> put_view(ConfigView)
793 |> render("index.json", %{configs: updated})
796 def reload_emoji(conn, _params) do
797 Pleroma.Emoji.reload()
802 def errors(conn, {:error, :not_found}) do
804 |> put_status(:not_found)
805 |> json(dgettext("errors", "Not found"))
808 def errors(conn, {:error, reason}) do
810 |> put_status(:bad_request)
814 def errors(conn, {:param_cast, _}) do
816 |> put_status(:bad_request)
817 |> json(dgettext("errors", "Invalid parameters"))
820 def errors(conn, _) do
822 |> put_status(:internal_server_error)
823 |> json(dgettext("errors", "Something went wrong"))
826 defp page_params(params) do
827 {get_page(params["page"]), get_page_size(params["page_size"])}
830 defp get_page(page_string) when is_nil(page_string), do: 1
832 defp get_page(page_string) do
833 case Integer.parse(page_string) do
839 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
841 defp get_page_size(page_size_string) do
842 case Integer.parse(page_size_string) do
843 {page_size, _} -> page_size
844 :error -> @users_page_size