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.AdminAPI.AccountView
15 alias Pleroma.Web.AdminAPI.Config
16 alias Pleroma.Web.AdminAPI.ConfigView
17 alias Pleroma.Web.AdminAPI.ModerationLogView
18 alias Pleroma.Web.AdminAPI.Report
19 alias Pleroma.Web.AdminAPI.ReportView
20 alias Pleroma.Web.AdminAPI.Search
21 alias Pleroma.Web.CommonAPI
22 alias Pleroma.Web.Endpoint
23 alias Pleroma.Web.MastodonAPI.StatusView
24 alias Pleroma.Web.Router
26 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
32 %{scopes: ["read:accounts"]}
33 when action in [:list_users, :user_show, :right_get, :invites]
38 %{scopes: ["write:accounts"]}
48 :user_toggle_activation,
55 :set_activation_status
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.info.deactivated)
255 action = if user.info.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)),
341 AccountView.render("index.json",
349 @filters ~w(local external active deactivated is_admin is_moderator)
351 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
352 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
354 defp maybe_parse_filters(filters) do
357 |> Enum.filter(&Enum.member?(@filters, &1))
358 |> Enum.map(&String.to_atom(&1))
359 |> Enum.into(%{}, &{&1, true})
362 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
363 "permission_group" => permission_group,
364 "nicknames" => nicknames
366 when permission_group in ["moderator", "admin"] do
367 info = Map.put(%{}, "is_" <> permission_group, true)
369 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
371 User.update_info(users, &User.Info.admin_api_update(&1, info))
373 ModerationLog.insert_log(%{
377 permission: permission_group
383 def right_add_multiple(conn, _) do
384 render_error(conn, :not_found, "No such permission_group")
387 def right_add(%{assigns: %{user: admin}} = conn, %{
388 "permission_group" => permission_group,
389 "nickname" => nickname
391 when permission_group in ["moderator", "admin"] do
392 info = Map.put(%{}, "is_" <> permission_group, true)
396 |> User.get_cached_by_nickname()
397 |> User.update_info(&User.Info.admin_api_update(&1, info))
399 ModerationLog.insert_log(%{
403 permission: permission_group
409 def right_add(conn, _) do
410 render_error(conn, :not_found, "No such permission_group")
413 def right_get(conn, %{"nickname" => nickname}) do
414 user = User.get_cached_by_nickname(nickname)
418 is_moderator: user.info.is_moderator,
419 is_admin: user.info.is_admin
423 def right_delete_multiple(
424 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
426 "permission_group" => permission_group,
427 "nicknames" => nicknames
430 when permission_group in ["moderator", "admin"] do
431 with false <- Enum.member?(nicknames, admin_nickname) do
432 info = Map.put(%{}, "is_" <> permission_group, false)
434 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
436 User.update_info(users, &User.Info.admin_api_update(&1, info))
438 ModerationLog.insert_log(%{
442 permission: permission_group
447 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
451 def right_delete_multiple(conn, _) do
452 render_error(conn, :not_found, "No such permission_group")
456 %{assigns: %{user: admin}} = conn,
458 "permission_group" => permission_group,
459 "nickname" => nickname
462 when permission_group in ["moderator", "admin"] do
463 info = Map.put(%{}, "is_" <> permission_group, false)
467 |> User.get_cached_by_nickname()
468 |> User.update_info(&User.Info.admin_api_update(&1, info))
470 ModerationLog.insert_log(%{
474 permission: permission_group
480 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
481 render_error(conn, :forbidden, "You can't revoke your own admin status.")
484 def relay_list(conn, _params) do
485 with {:ok, list} <- Relay.list() do
486 json(conn, %{relays: list})
494 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
495 with {:ok, _message} <- Relay.follow(target) do
496 ModerationLog.insert_log(%{
497 action: "relay_follow",
511 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
512 with {:ok, _message} <- Relay.unfollow(target) do
513 ModerationLog.insert_log(%{
514 action: "relay_unfollow",
528 @doc "Sends registration invite via email"
529 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
531 Pleroma.Config.get([:instance, :invites_enabled]) &&
532 !Pleroma.Config.get([:instance, :registrations_open]),
533 {:ok, invite_token} <- UserInviteToken.create_invite(),
535 Pleroma.Emails.UserEmail.user_invitation_email(
541 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
542 json_response(conn, :no_content, "")
546 @doc "Create an account registration invite token"
547 def create_invite_token(conn, params) do
551 if params["max_use"],
552 do: Map.put(opts, :max_use, params["max_use"]),
556 if params["expires_at"],
557 do: Map.put(opts, :expires_at, params["expires_at"]),
560 {:ok, invite} = UserInviteToken.create_invite(opts)
562 json(conn, AccountView.render("invite.json", %{invite: invite}))
565 @doc "Get list of created invites"
566 def invites(conn, _params) do
567 invites = UserInviteToken.list_invites()
570 |> put_view(AccountView)
571 |> render("invites.json", %{invites: invites})
574 @doc "Revokes invite by token"
575 def revoke_invite(conn, %{"token" => token}) do
576 with {:ok, invite} <- UserInviteToken.find_by_token(token),
577 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
579 |> put_view(AccountView)
580 |> render("invite.json", %{invite: updated_invite})
582 nil -> {:error, :not_found}
586 @doc "Get a password reset token (base64 string) for given nickname"
587 def get_password_reset(conn, %{"nickname" => nickname}) do
588 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
589 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
594 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
598 @doc "Force password reset for a given user"
599 def force_password_reset(conn, %{"nickname" => nickname}) do
600 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
602 User.force_password_reset_async(user)
604 json_response(conn, :no_content, "")
607 def list_reports(conn, params) do
608 {page, page_size} = page_params(params)
612 |> Map.put("type", "Flag")
613 |> Map.put("skip_preload", true)
614 |> Map.put("total", true)
615 |> Map.put("limit", page_size)
616 |> Map.put("offset", (page - 1) * page_size)
618 reports = ActivityPub.fetch_activities([], params, :offset)
621 |> put_view(ReportView)
622 |> render("index.json", %{reports: reports})
625 def report_show(conn, %{"id" => id}) do
626 with %Activity{} = report <- Activity.get_by_id(id) do
628 |> put_view(ReportView)
629 |> render("show.json", Report.extract_report_info(report))
631 _ -> {:error, :not_found}
635 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
636 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
637 ModerationLog.insert_log(%{
638 action: "report_update",
644 |> put_view(ReportView)
645 |> render("show.json", Report.extract_report_info(report))
649 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
650 with false <- is_nil(params["status"]),
651 %Activity{} <- Activity.get_by_id(id) do
654 |> Map.put("in_reply_to_status_id", id)
655 |> Map.put("visibility", "direct")
657 {:ok, activity} = CommonAPI.post(user, params)
659 ModerationLog.insert_log(%{
660 action: "report_response",
663 text: params["status"]
667 |> put_view(StatusView)
668 |> render("show.json", %{activity: activity})
678 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
679 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
680 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
682 ModerationLog.insert_log(%{
683 action: "status_update",
686 sensitive: sensitive,
687 visibility: params["visibility"]
691 |> put_view(StatusView)
692 |> render("show.json", %{activity: activity})
696 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
697 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
698 ModerationLog.insert_log(%{
699 action: "status_delete",
708 def list_log(conn, params) do
709 {page, page_size} = page_params(params)
712 ModerationLog.get_all(%{
714 page_size: page_size,
715 start_date: params["start_date"],
716 end_date: params["end_date"],
717 user_id: params["user_id"],
718 search: params["search"]
722 |> put_view(ModerationLogView)
723 |> render("index.json", %{log: log})
726 def migrate_to_db(conn, _params) do
727 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
731 def migrate_from_db(conn, _params) do
732 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
736 def config_show(conn, _params) do
737 configs = Pleroma.Repo.all(Config)
740 |> put_view(ConfigView)
741 |> render("index.json", %{configs: configs})
744 def config_update(conn, %{"configs" => configs}) do
746 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
749 %{"group" => group, "key" => key, "delete" => "true"} = params ->
750 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
753 %{"group" => group, "key" => key, "value" => value} ->
754 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
757 |> Enum.reject(&is_nil(&1))
759 Pleroma.Config.TransferTask.load_and_update_env()
760 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
767 |> put_view(ConfigView)
768 |> render("index.json", %{configs: updated})
771 def reload_emoji(conn, _params) do
772 Pleroma.Emoji.reload()
777 def errors(conn, {:error, :not_found}) do
779 |> put_status(:not_found)
780 |> json(dgettext("errors", "Not found"))
783 def errors(conn, {:error, reason}) do
785 |> put_status(:bad_request)
789 def errors(conn, {:param_cast, _}) do
791 |> put_status(:bad_request)
792 |> json(dgettext("errors", "Invalid parameters"))
795 def errors(conn, _) do
797 |> put_status(:internal_server_error)
798 |> json(dgettext("errors", "Something went wrong"))
801 defp page_params(params) do
802 {get_page(params["page"]), get_page_size(params["page_size"])}
805 defp get_page(page_string) when is_nil(page_string), do: 1
807 defp get_page(page_string) do
808 case Integer.parse(page_string) do
814 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
816 defp get_page_size(page_size_string) do
817 case Integer.parse(page_size_string) do
818 {page_size, _} -> page_size
819 :error -> @users_page_size