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
10 alias Pleroma.UserInviteToken
11 alias Pleroma.Web.ActivityPub.ActivityPub
12 alias Pleroma.Web.ActivityPub.Relay
13 alias Pleroma.Web.ActivityPub.Utils
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 action_fallback(:errors)
34 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
35 user = User.get_cached_by_nickname(nickname)
38 ModerationLog.insert_log(%{
48 def user_follow(%{assigns: %{user: admin}} = conn, %{
49 "follower" => follower_nick,
50 "followed" => followed_nick
52 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
53 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
54 User.follow(follower, followed)
56 ModerationLog.insert_log(%{
68 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
69 "follower" => follower_nick,
70 "followed" => followed_nick
72 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
73 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
74 User.unfollow(follower, followed)
76 ModerationLog.insert_log(%{
88 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
90 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
96 password_confirmation: password,
100 User.register_changeset(%User{}, user_data, need_confirmation: false)
102 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
103 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
106 case Pleroma.Repo.transaction(changesets) do
111 |> Enum.map(fn user ->
112 {:ok, user} = User.post_register_action(user)
116 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
118 ModerationLog.insert_log(%{
120 subjects: Map.values(users),
127 {:error, id, changeset, _} ->
129 Enum.map(changesets.operations, fn
130 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
131 AccountView.render("create-error.json", %{changeset: changeset})
133 {_, {:changeset, current_changeset, _}} ->
134 AccountView.render("create-error.json", %{changeset: current_changeset})
138 |> put_status(:conflict)
143 def user_show(conn, %{"nickname" => nickname}) do
144 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
146 |> put_view(AccountView)
147 |> render("show.json", %{user: user})
149 _ -> {:error, :not_found}
153 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
154 godmode = params["godmode"] == "true" || params["godmode"] == true
156 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
157 {_, page_size} = page_params(params)
160 ActivityPub.fetch_user_activities(user, nil, %{
161 "limit" => page_size,
166 |> put_view(StatusView)
167 |> render("index.json", %{activities: activities, as: :activity})
169 _ -> {:error, :not_found}
173 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
174 user = User.get_cached_by_nickname(nickname)
176 {:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
178 action = if user.info.deactivated, do: "activate", else: "deactivate"
180 ModerationLog.insert_log(%{
187 |> put_view(AccountView)
188 |> render("show.json", %{user: updated_user})
191 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
192 with {:ok, _} <- User.tag(nicknames, tags) do
193 ModerationLog.insert_log(%{
195 nicknames: nicknames,
200 json_response(conn, :no_content, "")
204 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
205 with {:ok, _} <- User.untag(nicknames, tags) do
206 ModerationLog.insert_log(%{
208 nicknames: nicknames,
213 json_response(conn, :no_content, "")
217 def list_users(conn, params) do
218 {page, page_size} = page_params(params)
219 filters = maybe_parse_filters(params["filters"])
222 query: params["query"],
224 page_size: page_size,
225 tags: params["tags"],
226 name: params["name"],
227 email: params["email"]
230 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
234 AccountView.render("index.json",
242 @filters ~w(local external active deactivated is_admin is_moderator)
244 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
245 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
247 defp maybe_parse_filters(filters) do
250 |> Enum.filter(&Enum.member?(@filters, &1))
251 |> Enum.map(&String.to_atom(&1))
252 |> Enum.into(%{}, &{&1, true})
255 def right_add(%{assigns: %{user: admin}} = conn, %{
256 "permission_group" => permission_group,
257 "nickname" => nickname
259 when permission_group in ["moderator", "admin"] do
260 info = Map.put(%{}, "is_" <> permission_group, true)
264 |> User.get_cached_by_nickname()
265 |> User.update_info(&User.Info.admin_api_update(&1, info))
267 ModerationLog.insert_log(%{
271 permission: permission_group
277 def right_add(conn, _) do
278 render_error(conn, :not_found, "No such permission_group")
281 def right_get(conn, %{"nickname" => nickname}) do
282 user = User.get_cached_by_nickname(nickname)
286 is_moderator: user.info.is_moderator,
287 is_admin: user.info.is_admin
291 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
292 render_error(conn, :forbidden, "You can't revoke your own admin status.")
296 %{assigns: %{user: admin}} = conn,
298 "permission_group" => permission_group,
299 "nickname" => nickname
302 when permission_group in ["moderator", "admin"] do
303 info = Map.put(%{}, "is_" <> permission_group, false)
307 |> User.get_cached_by_nickname()
308 |> User.update_info(&User.Info.admin_api_update(&1, info))
310 ModerationLog.insert_log(%{
314 permission: permission_group
320 def right_delete(conn, _) do
321 render_error(conn, :not_found, "No such permission_group")
324 def set_activation_status(%{assigns: %{user: admin}} = conn, %{
325 "nickname" => nickname,
328 with {:ok, status} <- Ecto.Type.cast(:boolean, status),
329 %User{} = user <- User.get_cached_by_nickname(nickname),
330 {:ok, _} <- User.deactivate(user, !status) do
331 action = if(user.info.deactivated, do: "activate", else: "deactivate")
333 ModerationLog.insert_log(%{
339 json_response(conn, :no_content, "")
343 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
344 with {:ok, _message} <- Relay.follow(target) do
345 ModerationLog.insert_log(%{
346 action: "relay_follow",
360 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
361 with {:ok, _message} <- Relay.unfollow(target) do
362 ModerationLog.insert_log(%{
363 action: "relay_unfollow",
377 @doc "Sends registration invite via email"
378 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
380 Pleroma.Config.get([:instance, :invites_enabled]) &&
381 !Pleroma.Config.get([:instance, :registrations_open]),
382 {:ok, invite_token} <- UserInviteToken.create_invite(),
384 Pleroma.Emails.UserEmail.user_invitation_email(
390 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
391 json_response(conn, :no_content, "")
395 @doc "Create an account registration invite token"
396 def create_invite_token(conn, params) do
400 if params["max_use"],
401 do: Map.put(opts, :max_use, params["max_use"]),
405 if params["expires_at"],
406 do: Map.put(opts, :expires_at, params["expires_at"]),
409 {:ok, invite} = UserInviteToken.create_invite(opts)
411 json(conn, AccountView.render("invite.json", %{invite: invite}))
414 @doc "Get list of created invites"
415 def invites(conn, _params) do
416 invites = UserInviteToken.list_invites()
419 |> put_view(AccountView)
420 |> render("invites.json", %{invites: invites})
423 @doc "Revokes invite by token"
424 def revoke_invite(conn, %{"token" => token}) do
425 with {:ok, invite} <- UserInviteToken.find_by_token(token),
426 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
428 |> put_view(AccountView)
429 |> render("invite.json", %{invite: updated_invite})
431 nil -> {:error, :not_found}
435 @doc "Get a password reset token (base64 string) for given nickname"
436 def get_password_reset(conn, %{"nickname" => nickname}) do
437 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
438 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
443 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
447 @doc "Force password reset for a given user"
448 def force_password_reset(conn, %{"nickname" => nickname}) do
449 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
451 User.force_password_reset_async(user)
453 json_response(conn, :no_content, "")
456 def list_reports(conn, params) do
457 {page, page_size} = page_params(params)
460 |> put_view(ReportView)
461 |> render("index.json", %{reports: Utils.get_reports(params, page, page_size)})
464 def list_grouped_reports(conn, _params) do
466 |> put_view(ReportView)
467 |> render("index_grouped.json", Utils.get_reports_grouped_by_status())
470 def report_show(conn, %{"id" => id}) do
471 with %Activity{} = report <- Activity.get_by_id(id) do
473 |> put_view(ReportView)
474 |> render("show.json", Report.extract_report_info(report))
476 _ -> {:error, :not_found}
480 def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
483 |> Enum.map(fn report ->
484 with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do
485 ModerationLog.insert_log(%{
486 action: "report_update",
493 {:error, message} -> %{id: report["id"], error: message}
497 case Enum.any?(result, &Map.has_key?(&1, :error)) do
498 true -> json_response(conn, :bad_request, result)
499 false -> json_response(conn, :no_content, "")
503 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
504 with false <- is_nil(params["status"]),
505 %Activity{} <- Activity.get_by_id(id) do
508 |> Map.put("in_reply_to_status_id", id)
509 |> Map.put("visibility", "direct")
511 {:ok, activity} = CommonAPI.post(user, params)
513 ModerationLog.insert_log(%{
514 action: "report_response",
517 text: params["status"]
521 |> put_view(StatusView)
522 |> render("show.json", %{activity: activity})
532 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
533 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
534 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
536 ModerationLog.insert_log(%{
537 action: "status_update",
540 sensitive: sensitive,
541 visibility: params["visibility"]
545 |> put_view(StatusView)
546 |> render("show.json", %{activity: activity})
550 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
551 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
552 ModerationLog.insert_log(%{
553 action: "status_delete",
562 def list_log(conn, params) do
563 {page, page_size} = page_params(params)
566 ModerationLog.get_all(%{
568 page_size: page_size,
569 start_date: params["start_date"],
570 end_date: params["end_date"],
571 user_id: params["user_id"],
572 search: params["search"]
576 |> put_view(ModerationLogView)
577 |> render("index.json", %{log: log})
580 def migrate_to_db(conn, _params) do
581 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
585 def migrate_from_db(conn, _params) do
586 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
590 def config_show(conn, _params) do
591 configs = Pleroma.Repo.all(Config)
594 |> put_view(ConfigView)
595 |> render("index.json", %{configs: configs})
598 def config_update(conn, %{"configs" => configs}) do
600 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
603 %{"group" => group, "key" => key, "delete" => "true"} = params ->
604 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
607 %{"group" => group, "key" => key, "value" => value} ->
608 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
611 |> Enum.reject(&is_nil(&1))
613 Pleroma.Config.TransferTask.load_and_update_env()
614 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
621 |> put_view(ConfigView)
622 |> render("index.json", %{configs: updated})
625 def reload_emoji(conn, _params) do
626 Pleroma.Emoji.reload()
631 def errors(conn, {:error, :not_found}) do
633 |> put_status(:not_found)
634 |> json(dgettext("errors", "Not found"))
637 def errors(conn, {:error, reason}) do
639 |> put_status(:bad_request)
643 def errors(conn, {:param_cast, _}) do
645 |> put_status(:bad_request)
646 |> json(dgettext("errors", "Invalid parameters"))
649 def errors(conn, _) do
651 |> put_status(:internal_server_error)
652 |> json(dgettext("errors", "Something went wrong"))
655 defp page_params(params) do
656 {get_page(params["page"]), get_page_size(params["page_size"])}
659 defp get_page(page_string) when is_nil(page_string), do: 1
661 defp get_page(page_string) do
662 case Integer.parse(page_string) do
668 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
670 defp get_page_size(page_size_string) do
671 case Integer.parse(page_size_string) do
672 {page_size, _} -> page_size
673 :error -> @users_page_size