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,
60 %{scopes: ["read:reports"]} when action in [:list_reports, :report_show]
65 %{scopes: ["write:reports"]}
66 when action in [:report_update_state, :report_respond]
71 %{scopes: ["read:statuses"]} when action == :list_user_statuses
76 %{scopes: ["write:statuses"]}
77 when action in [:status_update, :status_delete]
83 when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log]
89 when action in [:relay_follow, :relay_unfollow, :config_update]
94 action_fallback(:errors)
96 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
97 user = User.get_cached_by_nickname(nickname)
100 ModerationLog.insert_log(%{
110 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
111 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
114 ModerationLog.insert_log(%{
124 def user_follow(%{assigns: %{user: admin}} = conn, %{
125 "follower" => follower_nick,
126 "followed" => followed_nick
128 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
129 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
130 User.follow(follower, followed)
132 ModerationLog.insert_log(%{
144 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
145 "follower" => follower_nick,
146 "followed" => followed_nick
148 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
149 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
150 User.unfollow(follower, followed)
152 ModerationLog.insert_log(%{
164 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
166 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
172 password_confirmation: password,
176 User.register_changeset(%User{}, user_data, need_confirmation: false)
178 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
179 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
182 case Pleroma.Repo.transaction(changesets) do
187 |> Enum.map(fn user ->
188 {:ok, user} = User.post_register_action(user)
192 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
194 ModerationLog.insert_log(%{
196 subjects: Map.values(users),
203 {:error, id, changeset, _} ->
205 Enum.map(changesets.operations, fn
206 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
207 AccountView.render("create-error.json", %{changeset: changeset})
209 {_, {:changeset, current_changeset, _}} ->
210 AccountView.render("create-error.json", %{changeset: current_changeset})
214 |> put_status(:conflict)
219 def user_show(conn, %{"nickname" => nickname}) do
220 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
222 |> put_view(AccountView)
223 |> render("show.json", %{user: user})
225 _ -> {:error, :not_found}
229 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
230 godmode = params["godmode"] == "true" || params["godmode"] == true
232 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
233 {_, page_size} = page_params(params)
236 ActivityPub.fetch_user_activities(user, nil, %{
237 "limit" => page_size,
242 |> put_view(StatusView)
243 |> render("index.json", %{activities: activities, as: :activity})
245 _ -> {:error, :not_found}
249 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
250 user = User.get_cached_by_nickname(nickname)
252 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
254 action = if user.deactivated, do: "activate", else: "deactivate"
256 ModerationLog.insert_log(%{
263 |> put_view(AccountView)
264 |> render("show.json", %{user: updated_user})
267 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
268 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
269 {:ok, updated_users} = User.deactivate(users, false)
271 ModerationLog.insert_log(%{
278 |> put_view(AccountView)
279 |> render("index.json", %{users: Keyword.values(updated_users)})
282 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
283 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
284 {:ok, updated_users} = User.deactivate(users, true)
286 ModerationLog.insert_log(%{
293 |> put_view(AccountView)
294 |> render("index.json", %{users: Keyword.values(updated_users)})
297 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
298 with {:ok, _} <- User.tag(nicknames, tags) do
299 ModerationLog.insert_log(%{
301 nicknames: nicknames,
306 json_response(conn, :no_content, "")
310 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
311 with {:ok, _} <- User.untag(nicknames, tags) do
312 ModerationLog.insert_log(%{
314 nicknames: nicknames,
319 json_response(conn, :no_content, "")
323 def list_users(conn, params) do
324 {page, page_size} = page_params(params)
325 filters = maybe_parse_filters(params["filters"])
328 query: params["query"],
330 page_size: page_size,
331 tags: params["tags"],
332 name: params["name"],
333 email: params["email"]
336 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
340 AccountView.render("index.json",
348 @filters ~w(local external active deactivated is_admin is_moderator)
350 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
351 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
353 defp maybe_parse_filters(filters) do
356 |> Enum.filter(&Enum.member?(@filters, &1))
357 |> Enum.map(&String.to_atom(&1))
358 |> Enum.into(%{}, &{&1, true})
361 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
362 "permission_group" => permission_group,
363 "nicknames" => nicknames
365 when permission_group in ["moderator", "admin"] do
366 update = %{:"is_#{permission_group}" => true}
368 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
370 for u <- users, do: User.admin_api_update(u, update)
372 ModerationLog.insert_log(%{
376 permission: permission_group
382 def right_add_multiple(conn, _) do
383 render_error(conn, :not_found, "No such permission_group")
386 def right_add(%{assigns: %{user: admin}} = conn, %{
387 "permission_group" => permission_group,
388 "nickname" => nickname
390 when permission_group in ["moderator", "admin"] do
391 fields = %{:"is_#{permission_group}" => true}
395 |> User.get_cached_by_nickname()
396 |> User.admin_api_update(fields)
398 ModerationLog.insert_log(%{
402 permission: permission_group
408 def right_add(conn, _) do
409 render_error(conn, :not_found, "No such permission_group")
412 def right_get(conn, %{"nickname" => nickname}) do
413 user = User.get_cached_by_nickname(nickname)
417 is_moderator: user.is_moderator,
418 is_admin: user.is_admin
422 def right_delete_multiple(
423 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
425 "permission_group" => permission_group,
426 "nicknames" => nicknames
429 when permission_group in ["moderator", "admin"] do
430 with false <- Enum.member?(nicknames, admin_nickname) do
431 update = %{:"is_#{permission_group}" => false}
433 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
435 for u <- users, do: User.admin_api_update(u, update)
437 ModerationLog.insert_log(%{
441 permission: permission_group
446 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
450 def right_delete_multiple(conn, _) do
451 render_error(conn, :not_found, "No such permission_group")
455 %{assigns: %{user: admin}} = conn,
457 "permission_group" => permission_group,
458 "nickname" => nickname
461 when permission_group in ["moderator", "admin"] do
462 fields = %{:"is_#{permission_group}" => false}
466 |> User.get_cached_by_nickname()
467 |> User.admin_api_update(fields)
469 ModerationLog.insert_log(%{
473 permission: permission_group
479 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
480 render_error(conn, :forbidden, "You can't revoke your own admin status.")
483 def relay_list(conn, _params) do
484 with {:ok, list} <- Relay.list() do
485 json(conn, %{relays: list})
493 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
494 with {:ok, _message} <- Relay.follow(target) do
495 ModerationLog.insert_log(%{
496 action: "relay_follow",
510 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
511 with {:ok, _message} <- Relay.unfollow(target) do
512 ModerationLog.insert_log(%{
513 action: "relay_unfollow",
527 @doc "Sends registration invite via email"
528 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
530 Pleroma.Config.get([:instance, :invites_enabled]) &&
531 !Pleroma.Config.get([:instance, :registrations_open]),
532 {:ok, invite_token} <- UserInviteToken.create_invite(),
534 Pleroma.Emails.UserEmail.user_invitation_email(
540 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
541 json_response(conn, :no_content, "")
545 @doc "Create an account registration invite token"
546 def create_invite_token(conn, params) do
550 if params["max_use"],
551 do: Map.put(opts, :max_use, params["max_use"]),
555 if params["expires_at"],
556 do: Map.put(opts, :expires_at, params["expires_at"]),
559 {:ok, invite} = UserInviteToken.create_invite(opts)
561 json(conn, AccountView.render("invite.json", %{invite: invite}))
564 @doc "Get list of created invites"
565 def invites(conn, _params) do
566 invites = UserInviteToken.list_invites()
569 |> put_view(AccountView)
570 |> render("invites.json", %{invites: invites})
573 @doc "Revokes invite by token"
574 def revoke_invite(conn, %{"token" => token}) do
575 with {:ok, invite} <- UserInviteToken.find_by_token(token),
576 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
578 |> put_view(AccountView)
579 |> render("invite.json", %{invite: updated_invite})
581 nil -> {:error, :not_found}
585 @doc "Get a password reset token (base64 string) for given nickname"
586 def get_password_reset(conn, %{"nickname" => nickname}) do
587 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
588 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
593 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
597 @doc "Force password reset for a given user"
598 def force_password_reset(conn, %{"nickname" => nickname}) do
599 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
601 User.force_password_reset_async(user)
603 json_response(conn, :no_content, "")
606 def list_reports(conn, params) do
607 {page, page_size} = page_params(params)
611 |> Map.put("type", "Flag")
612 |> Map.put("skip_preload", true)
613 |> Map.put("total", true)
614 |> Map.put("limit", page_size)
615 |> Map.put("offset", (page - 1) * page_size)
617 reports = ActivityPub.fetch_activities([], params, :offset)
620 |> put_view(ReportView)
621 |> render("index.json", %{reports: reports})
624 def report_show(conn, %{"id" => id}) do
625 with %Activity{} = report <- Activity.get_by_id(id) do
627 |> put_view(ReportView)
628 |> render("show.json", Report.extract_report_info(report))
630 _ -> {:error, :not_found}
634 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
635 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
636 ModerationLog.insert_log(%{
637 action: "report_update",
643 |> put_view(ReportView)
644 |> render("show.json", Report.extract_report_info(report))
648 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
649 with false <- is_nil(params["status"]),
650 %Activity{} <- Activity.get_by_id(id) do
653 |> Map.put("in_reply_to_status_id", id)
654 |> Map.put("visibility", "direct")
656 {:ok, activity} = CommonAPI.post(user, params)
658 ModerationLog.insert_log(%{
659 action: "report_response",
662 text: params["status"]
666 |> put_view(StatusView)
667 |> render("show.json", %{activity: activity})
677 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
678 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
679 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
681 ModerationLog.insert_log(%{
682 action: "status_update",
685 sensitive: sensitive,
686 visibility: params["visibility"]
690 |> put_view(StatusView)
691 |> render("show.json", %{activity: activity})
695 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
696 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
697 ModerationLog.insert_log(%{
698 action: "status_delete",
707 def list_log(conn, params) do
708 {page, page_size} = page_params(params)
711 ModerationLog.get_all(%{
713 page_size: page_size,
714 start_date: params["start_date"],
715 end_date: params["end_date"],
716 user_id: params["user_id"],
717 search: params["search"]
721 |> put_view(ModerationLogView)
722 |> render("index.json", %{log: log})
725 def migrate_to_db(conn, _params) do
726 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
730 def migrate_from_db(conn, _params) do
731 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
735 def config_show(conn, _params) do
736 configs = Pleroma.Repo.all(Config)
739 |> put_view(ConfigView)
740 |> render("index.json", %{configs: configs})
743 def config_update(conn, %{"configs" => configs}) do
745 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
748 %{"group" => group, "key" => key, "delete" => "true"} = params ->
749 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
752 %{"group" => group, "key" => key, "value" => value} ->
753 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
756 |> Enum.reject(&is_nil(&1))
758 Pleroma.Config.TransferTask.load_and_update_env()
759 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
766 |> put_view(ConfigView)
767 |> render("index.json", %{configs: updated})
770 def reload_emoji(conn, _params) do
771 Pleroma.Emoji.reload()
776 def errors(conn, {:error, :not_found}) do
778 |> put_status(:not_found)
779 |> json(dgettext("errors", "Not found"))
782 def errors(conn, {:error, reason}) do
784 |> put_status(:bad_request)
788 def errors(conn, {:param_cast, _}) do
790 |> put_status(:bad_request)
791 |> json(dgettext("errors", "Invalid parameters"))
794 def errors(conn, _) do
796 |> put_status(:internal_server_error)
797 |> json(dgettext("errors", "Something went wrong"))
800 defp page_params(params) do
801 {get_page(params["page"]), get_page_size(params["page_size"])}
804 defp get_page(page_string) when is_nil(page_string), do: 1
806 defp get_page(page_string) do
807 case Integer.parse(page_string) do
813 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
815 defp get_page_size(page_size_string) do
816 case Integer.parse(page_size_string) do
817 {page_size, _} -> page_size
818 :error -> @users_page_size