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
468 |> Map.put("type", "Flag")
469 |> Map.put("skip_preload", true)
470 |> Map.put("total", true)
472 reports = ActivityPub.fetch_activities([], params)
475 |> put_view(ReportView)
476 |> render("index.json", %{reports: reports})
479 def report_show(conn, %{"id" => id}) do
480 with %Activity{} = report <- Activity.get_by_id(id) do
482 |> put_view(ReportView)
483 |> render("show.json", Report.extract_report_info(report))
485 _ -> {:error, :not_found}
489 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
490 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
491 ModerationLog.insert_log(%{
492 action: "report_update",
498 |> put_view(ReportView)
499 |> render("show.json", Report.extract_report_info(report))
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("status.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("status.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)
565 log = ModerationLog.get_all(page, page_size)
568 |> put_view(ModerationLogView)
569 |> render("index.json", %{log: log})
572 def migrate_to_db(conn, _params) do
573 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
577 def migrate_from_db(conn, _params) do
578 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
582 def config_show(conn, _params) do
583 configs = Pleroma.Repo.all(Config)
586 |> put_view(ConfigView)
587 |> render("index.json", %{configs: configs})
590 def config_update(conn, %{"configs" => configs}) do
592 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
595 %{"group" => group, "key" => key, "delete" => "true"} = params ->
596 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
599 %{"group" => group, "key" => key, "value" => value} ->
600 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
603 |> Enum.reject(&is_nil(&1))
605 Pleroma.Config.TransferTask.load_and_update_env()
606 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
613 |> put_view(ConfigView)
614 |> render("index.json", %{configs: updated})
617 def reload_emoji(conn, _params) do
618 Pleroma.Emoji.reload()
623 def errors(conn, {:error, :not_found}) do
625 |> put_status(:not_found)
626 |> json(dgettext("errors", "Not found"))
629 def errors(conn, {:error, reason}) do
631 |> put_status(:bad_request)
635 def errors(conn, {:param_cast, _}) do
637 |> put_status(:bad_request)
638 |> json(dgettext("errors", "Invalid parameters"))
641 def errors(conn, _) do
643 |> put_status(:internal_server_error)
644 |> json(dgettext("errors", "Something went wrong"))
647 defp page_params(params) do
648 {get_page(params["page"]), get_page_size(params["page_size"])}
651 defp get_page(page_string) when is_nil(page_string), do: 1
653 defp get_page(page_string) do
654 case Integer.parse(page_string) do
660 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
662 defp get_page_size(page_size_string) do
663 case Integer.parse(page_size_string) do
664 {page_size, _} -> page_size
665 :error -> @users_page_size