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.MastodonAPI.StatusView
23 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
29 action_fallback(:errors)
31 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
32 user = User.get_cached_by_nickname(nickname)
35 ModerationLog.insert_log(%{
45 def user_follow(%{assigns: %{user: admin}} = conn, %{
46 "follower" => follower_nick,
47 "followed" => followed_nick
49 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
50 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
51 User.follow(follower, followed)
53 ModerationLog.insert_log(%{
65 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
66 "follower" => follower_nick,
67 "followed" => followed_nick
69 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
70 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
71 User.unfollow(follower, followed)
73 ModerationLog.insert_log(%{
85 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
87 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
93 password_confirmation: password,
97 User.register_changeset(%User{}, user_data, need_confirmation: false)
99 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
100 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
103 case Pleroma.Repo.transaction(changesets) do
108 |> Enum.map(fn user ->
109 {:ok, user} = User.post_register_action(user)
113 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
115 ModerationLog.insert_log(%{
117 subjects: Map.values(users),
124 {:error, id, changeset, _} ->
126 Enum.map(changesets.operations, fn
127 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
128 AccountView.render("create-error.json", %{changeset: changeset})
130 {_, {:changeset, current_changeset, _}} ->
131 AccountView.render("create-error.json", %{changeset: current_changeset})
135 |> put_status(:conflict)
140 def user_show(conn, %{"nickname" => nickname}) do
141 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
143 |> put_view(AccountView)
144 |> render("show.json", %{user: user})
146 _ -> {:error, :not_found}
150 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
151 godmode = params["godmode"] == "true" || params["godmode"] == true
153 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
154 {_, page_size} = page_params(params)
157 ActivityPub.fetch_user_activities(user, nil, %{
158 "limit" => page_size,
163 |> put_view(StatusView)
164 |> render("index.json", %{activities: activities, as: :activity})
166 _ -> {:error, :not_found}
170 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
171 user = User.get_cached_by_nickname(nickname)
173 {:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
175 action = if user.info.deactivated, do: "activate", else: "deactivate"
177 ModerationLog.insert_log(%{
184 |> put_view(AccountView)
185 |> render("show.json", %{user: updated_user})
188 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
189 with {:ok, _} <- User.tag(nicknames, tags) do
190 ModerationLog.insert_log(%{
192 nicknames: nicknames,
197 json_response(conn, :no_content, "")
201 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
202 with {:ok, _} <- User.untag(nicknames, tags) do
203 ModerationLog.insert_log(%{
205 nicknames: nicknames,
210 json_response(conn, :no_content, "")
214 def list_users(conn, params) do
215 {page, page_size} = page_params(params)
216 filters = maybe_parse_filters(params["filters"])
219 query: params["query"],
221 page_size: page_size,
222 tags: params["tags"],
223 name: params["name"],
224 email: params["email"]
227 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
231 AccountView.render("index.json",
239 @filters ~w(local external active deactivated is_admin is_moderator)
241 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
242 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
244 defp maybe_parse_filters(filters) do
247 |> Enum.filter(&Enum.member?(@filters, &1))
248 |> Enum.map(&String.to_atom(&1))
249 |> Enum.into(%{}, &{&1, true})
252 def right_add(%{assigns: %{user: admin}} = conn, %{
253 "permission_group" => permission_group,
254 "nickname" => nickname
256 when permission_group in ["moderator", "admin"] do
257 user = User.get_cached_by_nickname(nickname)
261 |> Map.put("is_" <> permission_group, true)
263 info_cng = User.Info.admin_api_update(user.info, info)
267 |> Ecto.Changeset.change()
268 |> Ecto.Changeset.put_embed(:info, info_cng)
270 ModerationLog.insert_log(%{
274 permission: permission_group
277 {:ok, _user} = User.update_and_set_cache(cng)
282 def right_add(conn, _) do
283 render_error(conn, :not_found, "No such permission_group")
286 def right_get(conn, %{"nickname" => nickname}) do
287 user = User.get_cached_by_nickname(nickname)
291 is_moderator: user.info.is_moderator,
292 is_admin: user.info.is_admin
297 %{assigns: %{user: %User{:nickname => admin_nickname} = admin}} = conn,
299 "permission_group" => permission_group,
300 "nickname" => nickname
303 when permission_group in ["moderator", "admin"] do
304 if admin_nickname == nickname do
305 render_error(conn, :forbidden, "You can't revoke your own admin status.")
307 user = User.get_cached_by_nickname(nickname)
311 |> Map.put("is_" <> permission_group, false)
313 info_cng = User.Info.admin_api_update(user.info, info)
316 Ecto.Changeset.change(user)
317 |> Ecto.Changeset.put_embed(:info, info_cng)
319 {:ok, _user} = User.update_and_set_cache(cng)
321 ModerationLog.insert_log(%{
325 permission: permission_group
332 def right_delete(conn, _) do
333 render_error(conn, :not_found, "No such permission_group")
336 def set_activation_status(%{assigns: %{user: admin}} = conn, %{
337 "nickname" => nickname,
340 with {:ok, status} <- Ecto.Type.cast(:boolean, status),
341 %User{} = user <- User.get_cached_by_nickname(nickname),
342 {:ok, _} <- User.deactivate(user, !status) do
343 action = if(user.info.deactivated, do: "activate", else: "deactivate")
345 ModerationLog.insert_log(%{
351 json_response(conn, :no_content, "")
355 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
356 with {:ok, _message} <- Relay.follow(target) do
357 ModerationLog.insert_log(%{
358 action: "relay_follow",
372 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
373 with {:ok, _message} <- Relay.unfollow(target) do
374 ModerationLog.insert_log(%{
375 action: "relay_unfollow",
389 @doc "Sends registration invite via email"
390 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
392 Pleroma.Config.get([:instance, :invites_enabled]) &&
393 !Pleroma.Config.get([:instance, :registrations_open]),
394 {:ok, invite_token} <- UserInviteToken.create_invite(),
396 Pleroma.Emails.UserEmail.user_invitation_email(
402 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
403 json_response(conn, :no_content, "")
407 @doc "Create an account registration invite token"
408 def create_invite_token(conn, params) do
412 if params["max_use"],
413 do: Map.put(opts, :max_use, params["max_use"]),
417 if params["expires_at"],
418 do: Map.put(opts, :expires_at, params["expires_at"]),
421 {:ok, invite} = UserInviteToken.create_invite(opts)
423 json(conn, AccountView.render("invite.json", %{invite: invite}))
426 @doc "Get list of created invites"
427 def invites(conn, _params) do
428 invites = UserInviteToken.list_invites()
431 |> put_view(AccountView)
432 |> render("invites.json", %{invites: invites})
435 @doc "Revokes invite by token"
436 def revoke_invite(conn, %{"token" => token}) do
437 with {:ok, invite} <- UserInviteToken.find_by_token(token),
438 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
440 |> put_view(AccountView)
441 |> render("invite.json", %{invite: updated_invite})
443 nil -> {:error, :not_found}
447 @doc "Get a password reset token (base64 string) for given nickname"
448 def get_password_reset(conn, %{"nickname" => nickname}) do
449 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
450 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
456 @doc "Force password reset for a given user"
457 def force_password_reset(conn, %{"nickname" => nickname}) do
458 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
460 User.force_password_reset_async(user)
462 json_response(conn, :no_content, "")
465 def list_reports(conn, params) do
466 {page, page_size} = page_params(params)
470 |> Map.put("type", "Flag")
471 |> Map.put("skip_preload", true)
472 |> Map.put("total", true)
473 |> Map.put("limit", page_size)
474 |> Map.put("offset", (page - 1) * page_size)
476 reports = ActivityPub.fetch_activities([], params, :offset)
479 |> put_view(ReportView)
480 |> render("index.json", %{reports: reports})
483 def report_show(conn, %{"id" => id}) do
484 with %Activity{} = report <- Activity.get_by_id(id) do
486 |> put_view(ReportView)
487 |> render("show.json", Report.extract_report_info(report))
489 _ -> {:error, :not_found}
493 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
494 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
495 ModerationLog.insert_log(%{
496 action: "report_update",
502 |> put_view(ReportView)
503 |> render("show.json", Report.extract_report_info(report))
507 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
508 with false <- is_nil(params["status"]),
509 %Activity{} <- Activity.get_by_id(id) do
512 |> Map.put("in_reply_to_status_id", id)
513 |> Map.put("visibility", "direct")
515 {:ok, activity} = CommonAPI.post(user, params)
517 ModerationLog.insert_log(%{
518 action: "report_response",
521 text: params["status"]
525 |> put_view(StatusView)
526 |> render("status.json", %{activity: activity})
536 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
537 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
538 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
540 ModerationLog.insert_log(%{
541 action: "status_update",
544 sensitive: sensitive,
545 visibility: params["visibility"]
549 |> put_view(StatusView)
550 |> render("status.json", %{activity: activity})
554 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
555 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
556 ModerationLog.insert_log(%{
557 action: "status_delete",
566 def list_log(conn, params) do
567 {page, page_size} = page_params(params)
569 log = ModerationLog.get_all(page, page_size)
572 |> put_view(ModerationLogView)
573 |> render("index.json", %{log: log})
576 def migrate_to_db(conn, _params) do
577 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
581 def migrate_from_db(conn, _params) do
582 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
586 def config_show(conn, _params) do
587 configs = Pleroma.Repo.all(Config)
590 |> put_view(ConfigView)
591 |> render("index.json", %{configs: configs})
594 def config_update(conn, %{"configs" => configs}) do
596 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
599 %{"group" => group, "key" => key, "delete" => "true"} = params ->
600 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
603 %{"group" => group, "key" => key, "value" => value} ->
604 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
607 |> Enum.reject(&is_nil(&1))
609 Pleroma.Config.TransferTask.load_and_update_env()
610 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
617 |> put_view(ConfigView)
618 |> render("index.json", %{configs: updated})
621 def reload_emoji(conn, _params) do
622 Pleroma.Emoji.reload()
627 def errors(conn, {:error, :not_found}) do
629 |> put_status(:not_found)
630 |> json(dgettext("errors", "Not found"))
633 def errors(conn, {:error, reason}) do
635 |> put_status(:bad_request)
639 def errors(conn, {:param_cast, _}) do
641 |> put_status(:bad_request)
642 |> json(dgettext("errors", "Invalid parameters"))
645 def errors(conn, _) do
647 |> put_status(:internal_server_error)
648 |> json(dgettext("errors", "Something went wrong"))
651 defp page_params(params) do
652 {get_page(params["page"]), get_page_size(params["page_size"])}
655 defp get_page(page_string) when is_nil(page_string), do: 1
657 defp get_page(page_string) do
658 case Integer.parse(page_string) do
664 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
666 defp get_page_size(page_size_string) do
667 case Integer.parse(page_size_string) do
668 {page_size, _} -> page_size
669 :error -> @users_page_size