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)),
337 {:ok, users, count} <- filter_relay_user(users, count),
341 AccountView.render("index.json",
349 defp filter_relay_user(users, count) do
350 filtered_users = Enum.reject(users, &relay_user?/1)
351 count = if Enum.any?(users, &relay_user?/1), do: length(filtered_users), else: count
353 {:ok, filtered_users, count}
356 defp relay_user?(user) do
357 user.ap_id == Relay.relay_ap_id()
360 @filters ~w(local external active deactivated is_admin is_moderator)
362 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
363 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
365 defp maybe_parse_filters(filters) do
368 |> Enum.filter(&Enum.member?(@filters, &1))
369 |> Enum.map(&String.to_atom(&1))
370 |> Enum.into(%{}, &{&1, true})
373 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
374 "permission_group" => permission_group,
375 "nicknames" => nicknames
377 when permission_group in ["moderator", "admin"] do
378 update = %{:"is_#{permission_group}" => true}
380 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
382 for u <- users, do: User.admin_api_update(u, update)
384 ModerationLog.insert_log(%{
388 permission: permission_group
394 def right_add_multiple(conn, _) do
395 render_error(conn, :not_found, "No such permission_group")
398 def right_add(%{assigns: %{user: admin}} = conn, %{
399 "permission_group" => permission_group,
400 "nickname" => nickname
402 when permission_group in ["moderator", "admin"] do
403 fields = %{:"is_#{permission_group}" => true}
407 |> User.get_cached_by_nickname()
408 |> User.admin_api_update(fields)
410 ModerationLog.insert_log(%{
414 permission: permission_group
420 def right_add(conn, _) do
421 render_error(conn, :not_found, "No such permission_group")
424 def right_get(conn, %{"nickname" => nickname}) do
425 user = User.get_cached_by_nickname(nickname)
429 is_moderator: user.is_moderator,
430 is_admin: user.is_admin
434 def right_delete_multiple(
435 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
437 "permission_group" => permission_group,
438 "nicknames" => nicknames
441 when permission_group in ["moderator", "admin"] do
442 with false <- Enum.member?(nicknames, admin_nickname) do
443 update = %{:"is_#{permission_group}" => false}
445 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
447 for u <- users, do: User.admin_api_update(u, update)
449 ModerationLog.insert_log(%{
453 permission: permission_group
458 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
462 def right_delete_multiple(conn, _) do
463 render_error(conn, :not_found, "No such permission_group")
467 %{assigns: %{user: admin}} = conn,
469 "permission_group" => permission_group,
470 "nickname" => nickname
473 when permission_group in ["moderator", "admin"] do
474 fields = %{:"is_#{permission_group}" => false}
478 |> User.get_cached_by_nickname()
479 |> User.admin_api_update(fields)
481 ModerationLog.insert_log(%{
485 permission: permission_group
491 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
492 render_error(conn, :forbidden, "You can't revoke your own admin status.")
495 def relay_list(conn, _params) do
496 with {:ok, list} <- Relay.list() do
497 json(conn, %{relays: list})
505 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
506 with {:ok, _message} <- Relay.follow(target) do
507 ModerationLog.insert_log(%{
508 action: "relay_follow",
522 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
523 with {:ok, _message} <- Relay.unfollow(target) do
524 ModerationLog.insert_log(%{
525 action: "relay_unfollow",
539 @doc "Sends registration invite via email"
540 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
542 Pleroma.Config.get([:instance, :invites_enabled]) &&
543 !Pleroma.Config.get([:instance, :registrations_open]),
544 {:ok, invite_token} <- UserInviteToken.create_invite(),
546 Pleroma.Emails.UserEmail.user_invitation_email(
552 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
553 json_response(conn, :no_content, "")
557 @doc "Create an account registration invite token"
558 def create_invite_token(conn, params) do
562 if params["max_use"],
563 do: Map.put(opts, :max_use, params["max_use"]),
567 if params["expires_at"],
568 do: Map.put(opts, :expires_at, params["expires_at"]),
571 {:ok, invite} = UserInviteToken.create_invite(opts)
573 json(conn, AccountView.render("invite.json", %{invite: invite}))
576 @doc "Get list of created invites"
577 def invites(conn, _params) do
578 invites = UserInviteToken.list_invites()
581 |> put_view(AccountView)
582 |> render("invites.json", %{invites: invites})
585 @doc "Revokes invite by token"
586 def revoke_invite(conn, %{"token" => token}) do
587 with {:ok, invite} <- UserInviteToken.find_by_token(token),
588 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
590 |> put_view(AccountView)
591 |> render("invite.json", %{invite: updated_invite})
593 nil -> {:error, :not_found}
597 @doc "Get a password reset token (base64 string) for given nickname"
598 def get_password_reset(conn, %{"nickname" => nickname}) do
599 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
600 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
605 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
609 @doc "Force password reset for a given user"
610 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
611 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
613 Enum.map(users, &User.force_password_reset_async/1)
615 ModerationLog.insert_log(%{
618 action: "force_password_reset"
621 json_response(conn, :no_content, "")
624 def list_reports(conn, params) do
625 {page, page_size} = page_params(params)
629 |> Map.put("type", "Flag")
630 |> Map.put("skip_preload", true)
631 |> Map.put("total", true)
632 |> Map.put("limit", page_size)
633 |> Map.put("offset", (page - 1) * page_size)
635 reports = ActivityPub.fetch_activities([], params, :offset)
638 |> put_view(ReportView)
639 |> render("index.json", %{reports: reports})
642 def report_show(conn, %{"id" => id}) do
643 with %Activity{} = report <- Activity.get_by_id(id) do
645 |> put_view(ReportView)
646 |> render("show.json", Report.extract_report_info(report))
648 _ -> {:error, :not_found}
652 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
653 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
654 ModerationLog.insert_log(%{
655 action: "report_update",
661 |> put_view(ReportView)
662 |> render("show.json", Report.extract_report_info(report))
666 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
667 with false <- is_nil(params["status"]),
668 %Activity{} <- Activity.get_by_id(id) do
671 |> Map.put("in_reply_to_status_id", id)
672 |> Map.put("visibility", "direct")
674 {:ok, activity} = CommonAPI.post(user, params)
676 ModerationLog.insert_log(%{
677 action: "report_response",
680 text: params["status"]
684 |> put_view(StatusView)
685 |> render("show.json", %{activity: activity})
695 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
696 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
697 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
699 ModerationLog.insert_log(%{
700 action: "status_update",
703 sensitive: sensitive,
704 visibility: params["visibility"]
708 |> put_view(StatusView)
709 |> render("show.json", %{activity: activity})
713 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
714 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
715 ModerationLog.insert_log(%{
716 action: "status_delete",
725 def list_log(conn, params) do
726 {page, page_size} = page_params(params)
729 ModerationLog.get_all(%{
731 page_size: page_size,
732 start_date: params["start_date"],
733 end_date: params["end_date"],
734 user_id: params["user_id"],
735 search: params["search"]
739 |> put_view(ModerationLogView)
740 |> render("index.json", %{log: log})
743 def migrate_to_db(conn, _params) do
744 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
748 def migrate_from_db(conn, _params) do
749 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
753 def config_show(conn, _params) do
754 configs = Pleroma.Repo.all(Config)
757 |> put_view(ConfigView)
758 |> render("index.json", %{configs: configs})
761 def config_update(conn, %{"configs" => configs}) do
763 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
766 %{"group" => group, "key" => key, "delete" => "true"} = params ->
767 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
770 %{"group" => group, "key" => key, "value" => value} ->
771 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
774 |> Enum.reject(&is_nil(&1))
776 Pleroma.Config.TransferTask.load_and_update_env()
777 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
784 |> put_view(ConfigView)
785 |> render("index.json", %{configs: updated})
788 def reload_emoji(conn, _params) do
789 Pleroma.Emoji.reload()
794 def errors(conn, {:error, :not_found}) do
796 |> put_status(:not_found)
797 |> json(dgettext("errors", "Not found"))
800 def errors(conn, {:error, reason}) do
802 |> put_status(:bad_request)
806 def errors(conn, {:param_cast, _}) do
808 |> put_status(:bad_request)
809 |> json(dgettext("errors", "Invalid parameters"))
812 def errors(conn, _) do
814 |> put_status(:internal_server_error)
815 |> json(dgettext("errors", "Something went wrong"))
818 defp page_params(params) do
819 {get_page(params["page"]), get_page_size(params["page_size"])}
822 defp get_page(page_string) when is_nil(page_string), do: 1
824 defp get_page(page_string) do
825 case Integer.parse(page_string) do
831 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
833 defp get_page_size(page_size_string) do
834 case Integer.parse(page_size_string) do
835 {page_size, _} -> page_size
836 :error -> @users_page_size