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_follow(%{assigns: %{user: admin}} = conn, %{
112 "follower" => follower_nick,
113 "followed" => followed_nick
115 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
116 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
117 User.follow(follower, followed)
119 ModerationLog.insert_log(%{
131 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
132 "follower" => follower_nick,
133 "followed" => followed_nick
135 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
136 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
137 User.unfollow(follower, followed)
139 ModerationLog.insert_log(%{
151 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
153 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
159 password_confirmation: password,
163 User.register_changeset(%User{}, user_data, need_confirmation: false)
165 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
166 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
169 case Pleroma.Repo.transaction(changesets) do
174 |> Enum.map(fn user ->
175 {:ok, user} = User.post_register_action(user)
179 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
181 ModerationLog.insert_log(%{
183 subjects: Map.values(users),
190 {:error, id, changeset, _} ->
192 Enum.map(changesets.operations, fn
193 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
194 AccountView.render("create-error.json", %{changeset: changeset})
196 {_, {:changeset, current_changeset, _}} ->
197 AccountView.render("create-error.json", %{changeset: current_changeset})
201 |> put_status(:conflict)
206 def user_show(conn, %{"nickname" => nickname}) do
207 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
209 |> put_view(AccountView)
210 |> render("show.json", %{user: user})
212 _ -> {:error, :not_found}
216 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
217 godmode = params["godmode"] == "true" || params["godmode"] == true
219 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
220 {_, page_size} = page_params(params)
223 ActivityPub.fetch_user_activities(user, nil, %{
224 "limit" => page_size,
229 |> put_view(StatusView)
230 |> render("index.json", %{activities: activities, as: :activity})
232 _ -> {:error, :not_found}
236 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
237 user = User.get_cached_by_nickname(nickname)
239 {:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
241 action = if user.info.deactivated, do: "activate", else: "deactivate"
243 ModerationLog.insert_log(%{
250 |> put_view(AccountView)
251 |> render("show.json", %{user: updated_user})
254 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
255 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
256 {:ok, updated_users} = User.deactivate(users, false)
258 ModerationLog.insert_log(%{
265 |> put_view(AccountView)
266 |> render("index.json", %{users: Keyword.values(updated_users)})
269 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
270 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
271 {:ok, updated_users} = User.deactivate(users, true)
273 ModerationLog.insert_log(%{
280 |> put_view(AccountView)
281 |> render("index.json", %{users: Keyword.values(updated_users)})
284 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
285 with {:ok, _} <- User.tag(nicknames, tags) do
286 ModerationLog.insert_log(%{
288 nicknames: nicknames,
293 json_response(conn, :no_content, "")
297 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
298 with {:ok, _} <- User.untag(nicknames, tags) do
299 ModerationLog.insert_log(%{
301 nicknames: nicknames,
306 json_response(conn, :no_content, "")
310 def list_users(conn, params) do
311 {page, page_size} = page_params(params)
312 filters = maybe_parse_filters(params["filters"])
315 query: params["query"],
317 page_size: page_size,
318 tags: params["tags"],
319 name: params["name"],
320 email: params["email"]
323 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
327 AccountView.render("index.json",
335 @filters ~w(local external active deactivated is_admin is_moderator)
337 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
338 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
340 defp maybe_parse_filters(filters) do
343 |> Enum.filter(&Enum.member?(@filters, &1))
344 |> Enum.map(&String.to_atom(&1))
345 |> Enum.into(%{}, &{&1, true})
348 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
349 "permission_group" => permission_group,
350 "nicknames" => nicknames
352 when permission_group in ["moderator", "admin"] do
353 info = Map.put(%{}, "is_" <> permission_group, true)
355 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
357 User.update_info(users, &User.Info.admin_api_update(&1, info))
359 ModerationLog.insert_log(%{
363 permission: permission_group
369 def right_add_multiple(conn, _) do
370 render_error(conn, :not_found, "No such permission_group")
373 def right_add(%{assigns: %{user: admin}} = conn, %{
374 "permission_group" => permission_group,
375 "nickname" => nickname
377 when permission_group in ["moderator", "admin"] do
378 info = Map.put(%{}, "is_" <> permission_group, true)
382 |> User.get_cached_by_nickname()
383 |> User.update_info(&User.Info.admin_api_update(&1, info))
385 ModerationLog.insert_log(%{
389 permission: permission_group
395 def right_add(conn, _) do
396 render_error(conn, :not_found, "No such permission_group")
399 def right_get(conn, %{"nickname" => nickname}) do
400 user = User.get_cached_by_nickname(nickname)
404 is_moderator: user.info.is_moderator,
405 is_admin: user.info.is_admin
409 def right_delete_multiple(
410 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
412 "permission_group" => permission_group,
413 "nicknames" => nicknames
416 when permission_group in ["moderator", "admin"] do
417 with false <- Enum.member?(nicknames, admin_nickname) do
418 info = Map.put(%{}, "is_" <> permission_group, false)
420 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
422 User.update_info(users, &User.Info.admin_api_update(&1, info))
424 ModerationLog.insert_log(%{
428 permission: permission_group
433 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
437 def right_delete_multiple(conn, _) do
438 render_error(conn, :not_found, "No such permission_group")
442 %{assigns: %{user: admin}} = conn,
444 "permission_group" => permission_group,
445 "nickname" => nickname
448 when permission_group in ["moderator", "admin"] do
449 info = Map.put(%{}, "is_" <> permission_group, false)
453 |> User.get_cached_by_nickname()
454 |> User.update_info(&User.Info.admin_api_update(&1, info))
456 ModerationLog.insert_log(%{
460 permission: permission_group
466 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
467 render_error(conn, :forbidden, "You can't revoke your own admin status.")
470 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
471 with {:ok, _message} <- Relay.follow(target) do
472 ModerationLog.insert_log(%{
473 action: "relay_follow",
487 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
488 with {:ok, _message} <- Relay.unfollow(target) do
489 ModerationLog.insert_log(%{
490 action: "relay_unfollow",
504 @doc "Sends registration invite via email"
505 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
507 Pleroma.Config.get([:instance, :invites_enabled]) &&
508 !Pleroma.Config.get([:instance, :registrations_open]),
509 {:ok, invite_token} <- UserInviteToken.create_invite(),
511 Pleroma.Emails.UserEmail.user_invitation_email(
517 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
518 json_response(conn, :no_content, "")
522 @doc "Create an account registration invite token"
523 def create_invite_token(conn, params) do
527 if params["max_use"],
528 do: Map.put(opts, :max_use, params["max_use"]),
532 if params["expires_at"],
533 do: Map.put(opts, :expires_at, params["expires_at"]),
536 {:ok, invite} = UserInviteToken.create_invite(opts)
538 json(conn, AccountView.render("invite.json", %{invite: invite}))
541 @doc "Get list of created invites"
542 def invites(conn, _params) do
543 invites = UserInviteToken.list_invites()
546 |> put_view(AccountView)
547 |> render("invites.json", %{invites: invites})
550 @doc "Revokes invite by token"
551 def revoke_invite(conn, %{"token" => token}) do
552 with {:ok, invite} <- UserInviteToken.find_by_token(token),
553 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
555 |> put_view(AccountView)
556 |> render("invite.json", %{invite: updated_invite})
558 nil -> {:error, :not_found}
562 @doc "Get a password reset token (base64 string) for given nickname"
563 def get_password_reset(conn, %{"nickname" => nickname}) do
564 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
565 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
570 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
574 @doc "Force password reset for a given user"
575 def force_password_reset(conn, %{"nickname" => nickname}) do
576 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
578 User.force_password_reset_async(user)
580 json_response(conn, :no_content, "")
583 def list_reports(conn, params) do
584 {page, page_size} = page_params(params)
588 |> Map.put("type", "Flag")
589 |> Map.put("skip_preload", true)
590 |> Map.put("total", true)
591 |> Map.put("limit", page_size)
592 |> Map.put("offset", (page - 1) * page_size)
594 reports = ActivityPub.fetch_activities([], params, :offset)
597 |> put_view(ReportView)
598 |> render("index.json", %{reports: reports})
601 def report_show(conn, %{"id" => id}) do
602 with %Activity{} = report <- Activity.get_by_id(id) do
604 |> put_view(ReportView)
605 |> render("show.json", Report.extract_report_info(report))
607 _ -> {:error, :not_found}
611 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
612 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
613 ModerationLog.insert_log(%{
614 action: "report_update",
620 |> put_view(ReportView)
621 |> render("show.json", Report.extract_report_info(report))
625 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
626 with false <- is_nil(params["status"]),
627 %Activity{} <- Activity.get_by_id(id) do
630 |> Map.put("in_reply_to_status_id", id)
631 |> Map.put("visibility", "direct")
633 {:ok, activity} = CommonAPI.post(user, params)
635 ModerationLog.insert_log(%{
636 action: "report_response",
639 text: params["status"]
643 |> put_view(StatusView)
644 |> render("show.json", %{activity: activity})
654 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
655 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
656 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
658 ModerationLog.insert_log(%{
659 action: "status_update",
662 sensitive: sensitive,
663 visibility: params["visibility"]
667 |> put_view(StatusView)
668 |> render("show.json", %{activity: activity})
672 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
673 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
674 ModerationLog.insert_log(%{
675 action: "status_delete",
684 def list_log(conn, params) do
685 {page, page_size} = page_params(params)
688 ModerationLog.get_all(%{
690 page_size: page_size,
691 start_date: params["start_date"],
692 end_date: params["end_date"],
693 user_id: params["user_id"],
694 search: params["search"]
698 |> put_view(ModerationLogView)
699 |> render("index.json", %{log: log})
702 def migrate_to_db(conn, _params) do
703 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
707 def migrate_from_db(conn, _params) do
708 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
712 def config_show(conn, _params) do
713 configs = Pleroma.Repo.all(Config)
716 |> put_view(ConfigView)
717 |> render("index.json", %{configs: configs})
720 def config_update(conn, %{"configs" => configs}) do
722 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
725 %{"group" => group, "key" => key, "delete" => "true"} = params ->
726 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
729 %{"group" => group, "key" => key, "value" => value} ->
730 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
733 |> Enum.reject(&is_nil(&1))
735 Pleroma.Config.TransferTask.load_and_update_env()
736 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
743 |> put_view(ConfigView)
744 |> render("index.json", %{configs: updated})
747 def reload_emoji(conn, _params) do
748 Pleroma.Emoji.reload()
753 def errors(conn, {:error, :not_found}) do
755 |> put_status(:not_found)
756 |> json(dgettext("errors", "Not found"))
759 def errors(conn, {:error, reason}) do
761 |> put_status(:bad_request)
765 def errors(conn, {:param_cast, _}) do
767 |> put_status(:bad_request)
768 |> json(dgettext("errors", "Invalid parameters"))
771 def errors(conn, _) do
773 |> put_status(:internal_server_error)
774 |> json(dgettext("errors", "Something went wrong"))
777 defp page_params(params) do
778 {get_page(params["page"]), get_page_size(params["page_size"])}
781 defp get_page(page_string) when is_nil(page_string), do: 1
783 defp get_page(page_string) do
784 case Integer.parse(page_string) do
790 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
792 defp get_page_size(page_size_string) do
793 case Integer.parse(page_size_string) do
794 {page_size, _} -> page_size
795 :error -> @users_page_size