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.AdminAPI.AccountView
14 alias Pleroma.Web.AdminAPI.Config
15 alias Pleroma.Web.AdminAPI.ConfigView
16 alias Pleroma.Web.AdminAPI.ModerationLogView
17 alias Pleroma.Web.AdminAPI.Report
18 alias Pleroma.Web.AdminAPI.ReportView
19 alias Pleroma.Web.AdminAPI.Search
20 alias Pleroma.Web.CommonAPI
21 alias Pleroma.Web.Endpoint
22 alias Pleroma.Web.MastodonAPI.StatusView
23 alias Pleroma.Web.Router
25 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
31 action_fallback(:errors)
33 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
34 user = User.get_cached_by_nickname(nickname)
37 ModerationLog.insert_log(%{
47 def user_follow(%{assigns: %{user: admin}} = conn, %{
48 "follower" => follower_nick,
49 "followed" => followed_nick
51 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
52 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
53 User.follow(follower, followed)
55 ModerationLog.insert_log(%{
67 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
68 "follower" => follower_nick,
69 "followed" => followed_nick
71 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
72 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
73 User.unfollow(follower, followed)
75 ModerationLog.insert_log(%{
87 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
89 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
95 password_confirmation: password,
99 User.register_changeset(%User{}, user_data, need_confirmation: false)
101 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
102 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
105 case Pleroma.Repo.transaction(changesets) do
110 |> Enum.map(fn user ->
111 {:ok, user} = User.post_register_action(user)
115 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
117 ModerationLog.insert_log(%{
119 subjects: Map.values(users),
126 {:error, id, changeset, _} ->
128 Enum.map(changesets.operations, fn
129 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
130 AccountView.render("create-error.json", %{changeset: changeset})
132 {_, {:changeset, current_changeset, _}} ->
133 AccountView.render("create-error.json", %{changeset: current_changeset})
137 |> put_status(:conflict)
142 def user_show(conn, %{"nickname" => nickname}) do
143 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
145 |> put_view(AccountView)
146 |> render("show.json", %{user: user})
148 _ -> {:error, :not_found}
152 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
153 godmode = params["godmode"] == "true" || params["godmode"] == true
155 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
156 {_, page_size} = page_params(params)
159 ActivityPub.fetch_user_activities(user, nil, %{
160 "limit" => page_size,
165 |> put_view(StatusView)
166 |> render("index.json", %{activities: activities, as: :activity})
168 _ -> {:error, :not_found}
172 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
173 user = User.get_cached_by_nickname(nickname)
175 {:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
177 action = if user.info.deactivated, do: "activate", else: "deactivate"
179 ModerationLog.insert_log(%{
186 |> put_view(AccountView)
187 |> render("show.json", %{user: updated_user})
190 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
191 with {:ok, _} <- User.tag(nicknames, tags) do
192 ModerationLog.insert_log(%{
194 nicknames: nicknames,
199 json_response(conn, :no_content, "")
203 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
204 with {:ok, _} <- User.untag(nicknames, tags) do
205 ModerationLog.insert_log(%{
207 nicknames: nicknames,
212 json_response(conn, :no_content, "")
216 def list_users(conn, params) do
217 {page, page_size} = page_params(params)
218 filters = maybe_parse_filters(params["filters"])
221 query: params["query"],
223 page_size: page_size,
224 tags: params["tags"],
225 name: params["name"],
226 email: params["email"]
229 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
233 AccountView.render("index.json",
241 @filters ~w(local external active deactivated is_admin is_moderator)
243 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
244 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
246 defp maybe_parse_filters(filters) do
249 |> Enum.filter(&Enum.member?(@filters, &1))
250 |> Enum.map(&String.to_atom(&1))
251 |> Enum.into(%{}, &{&1, true})
254 def right_add(%{assigns: %{user: admin}} = conn, %{
255 "permission_group" => permission_group,
256 "nickname" => nickname
258 when permission_group in ["moderator", "admin"] do
259 user = User.get_cached_by_nickname(nickname)
263 |> Map.put("is_" <> permission_group, true)
265 info_cng = User.Info.admin_api_update(user.info, info)
269 |> Ecto.Changeset.change()
270 |> Ecto.Changeset.put_embed(:info, info_cng)
272 ModerationLog.insert_log(%{
276 permission: permission_group
279 {:ok, _user} = User.update_and_set_cache(cng)
284 def right_add(conn, _) do
285 render_error(conn, :not_found, "No such permission_group")
288 def right_get(conn, %{"nickname" => nickname}) do
289 user = User.get_cached_by_nickname(nickname)
293 is_moderator: user.info.is_moderator,
294 is_admin: user.info.is_admin
299 %{assigns: %{user: %User{:nickname => admin_nickname} = admin}} = conn,
301 "permission_group" => permission_group,
302 "nickname" => nickname
305 when permission_group in ["moderator", "admin"] do
306 if admin_nickname == nickname do
307 render_error(conn, :forbidden, "You can't revoke your own admin status.")
309 user = User.get_cached_by_nickname(nickname)
313 |> Map.put("is_" <> permission_group, false)
315 info_cng = User.Info.admin_api_update(user.info, info)
318 Ecto.Changeset.change(user)
319 |> Ecto.Changeset.put_embed(:info, info_cng)
321 {:ok, _user} = User.update_and_set_cache(cng)
323 ModerationLog.insert_log(%{
327 permission: permission_group
334 def right_delete(conn, _) do
335 render_error(conn, :not_found, "No such permission_group")
338 def set_activation_status(%{assigns: %{user: admin}} = conn, %{
339 "nickname" => nickname,
342 with {:ok, status} <- Ecto.Type.cast(:boolean, status),
343 %User{} = user <- User.get_cached_by_nickname(nickname),
344 {:ok, _} <- User.deactivate(user, !status) do
345 action = if(user.info.deactivated, do: "activate", else: "deactivate")
347 ModerationLog.insert_log(%{
353 json_response(conn, :no_content, "")
357 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
358 with {:ok, _message} <- Relay.follow(target) do
359 ModerationLog.insert_log(%{
360 action: "relay_follow",
374 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
375 with {:ok, _message} <- Relay.unfollow(target) do
376 ModerationLog.insert_log(%{
377 action: "relay_unfollow",
391 @doc "Sends registration invite via email"
392 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
394 Pleroma.Config.get([:instance, :invites_enabled]) &&
395 !Pleroma.Config.get([:instance, :registrations_open]),
396 {:ok, invite_token} <- UserInviteToken.create_invite(),
398 Pleroma.Emails.UserEmail.user_invitation_email(
404 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
405 json_response(conn, :no_content, "")
409 @doc "Create an account registration invite token"
410 def create_invite_token(conn, params) do
414 if params["max_use"],
415 do: Map.put(opts, :max_use, params["max_use"]),
419 if params["expires_at"],
420 do: Map.put(opts, :expires_at, params["expires_at"]),
423 {:ok, invite} = UserInviteToken.create_invite(opts)
425 json(conn, AccountView.render("invite.json", %{invite: invite}))
428 @doc "Get list of created invites"
429 def invites(conn, _params) do
430 invites = UserInviteToken.list_invites()
433 |> put_view(AccountView)
434 |> render("invites.json", %{invites: invites})
437 @doc "Revokes invite by token"
438 def revoke_invite(conn, %{"token" => token}) do
439 with {:ok, invite} <- UserInviteToken.find_by_token(token),
440 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
442 |> put_view(AccountView)
443 |> render("invite.json", %{invite: updated_invite})
445 nil -> {:error, :not_found}
449 @doc "Get a password reset token (base64 string) for given nickname"
450 def get_password_reset(conn, %{"nickname" => nickname}) do
451 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
452 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
457 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
461 @doc "Force password reset for a given user"
462 def force_password_reset(conn, %{"nickname" => nickname}) do
463 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
465 User.force_password_reset_async(user)
467 json_response(conn, :no_content, "")
470 def list_reports(conn, params) do
473 |> Map.put("type", "Flag")
474 |> Map.put("skip_preload", true)
475 |> Map.put("total", true)
477 reports = ActivityPub.fetch_activities([], params)
480 |> put_view(ReportView)
481 |> render("index.json", %{reports: reports})
484 def report_show(conn, %{"id" => id}) do
485 with %Activity{} = report <- Activity.get_by_id(id) do
487 |> put_view(ReportView)
488 |> render("show.json", Report.extract_report_info(report))
490 _ -> {:error, :not_found}
494 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
495 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
496 ModerationLog.insert_log(%{
497 action: "report_update",
503 |> put_view(ReportView)
504 |> render("show.json", Report.extract_report_info(report))
508 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
509 with false <- is_nil(params["status"]),
510 %Activity{} <- Activity.get_by_id(id) do
513 |> Map.put("in_reply_to_status_id", id)
514 |> Map.put("visibility", "direct")
516 {:ok, activity} = CommonAPI.post(user, params)
518 ModerationLog.insert_log(%{
519 action: "report_response",
522 text: params["status"]
526 |> put_view(StatusView)
527 |> render("status.json", %{activity: activity})
537 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
538 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
539 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
541 ModerationLog.insert_log(%{
542 action: "status_update",
545 sensitive: sensitive,
546 visibility: params["visibility"]
550 |> put_view(StatusView)
551 |> render("status.json", %{activity: activity})
555 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
556 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
557 ModerationLog.insert_log(%{
558 action: "status_delete",
567 def list_log(conn, params) do
568 {page, page_size} = page_params(params)
570 log = ModerationLog.get_all(page, page_size)
573 |> put_view(ModerationLogView)
574 |> render("index.json", %{log: log})
577 def migrate_to_db(conn, _params) do
578 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
582 def migrate_from_db(conn, _params) do
583 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
587 def config_show(conn, _params) do
588 configs = Pleroma.Repo.all(Config)
591 |> put_view(ConfigView)
592 |> render("index.json", %{configs: configs})
595 def config_update(conn, %{"configs" => configs}) do
597 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
600 %{"group" => group, "key" => key, "delete" => "true"} = params ->
601 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
604 %{"group" => group, "key" => key, "value" => value} ->
605 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
608 |> Enum.reject(&is_nil(&1))
610 Pleroma.Config.TransferTask.load_and_update_env()
611 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
618 |> put_view(ConfigView)
619 |> render("index.json", %{configs: updated})
622 def reload_emoji(conn, _params) do
623 Pleroma.Emoji.reload()
628 def errors(conn, {:error, :not_found}) do
630 |> put_status(:not_found)
631 |> json(dgettext("errors", "Not found"))
634 def errors(conn, {:error, reason}) do
636 |> put_status(:bad_request)
640 def errors(conn, {:param_cast, _}) do
642 |> put_status(:bad_request)
643 |> json(dgettext("errors", "Invalid parameters"))
646 def errors(conn, _) do
648 |> put_status(:internal_server_error)
649 |> json(dgettext("errors", "Something went wrong"))
652 defp page_params(params) do
653 {get_page(params["page"]), get_page_size(params["page_size"])}
656 defp get_page(page_string) when is_nil(page_string), do: 1
658 defp get_page(page_string) do
659 case Integer.parse(page_string) do
665 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
667 defp get_page_size(page_size_string) do
668 case Integer.parse(page_size_string) do
669 {page_size, _} -> page_size
670 :error -> @users_page_size