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(conn, %{"nickname" => nickname}) do
611 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
613 User.force_password_reset_async(user)
615 json_response(conn, :no_content, "")
618 def list_reports(conn, params) do
619 {page, page_size} = page_params(params)
623 |> Map.put("type", "Flag")
624 |> Map.put("skip_preload", true)
625 |> Map.put("total", true)
626 |> Map.put("limit", page_size)
627 |> Map.put("offset", (page - 1) * page_size)
629 reports = ActivityPub.fetch_activities([], params, :offset)
632 |> put_view(ReportView)
633 |> render("index.json", %{reports: reports})
636 def report_show(conn, %{"id" => id}) do
637 with %Activity{} = report <- Activity.get_by_id(id) do
639 |> put_view(ReportView)
640 |> render("show.json", Report.extract_report_info(report))
642 _ -> {:error, :not_found}
646 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
647 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
648 ModerationLog.insert_log(%{
649 action: "report_update",
655 |> put_view(ReportView)
656 |> render("show.json", Report.extract_report_info(report))
660 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
661 with false <- is_nil(params["status"]),
662 %Activity{} <- Activity.get_by_id(id) do
665 |> Map.put("in_reply_to_status_id", id)
666 |> Map.put("visibility", "direct")
668 {:ok, activity} = CommonAPI.post(user, params)
670 ModerationLog.insert_log(%{
671 action: "report_response",
674 text: params["status"]
678 |> put_view(StatusView)
679 |> render("show.json", %{activity: activity})
689 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
690 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
691 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
693 ModerationLog.insert_log(%{
694 action: "status_update",
697 sensitive: sensitive,
698 visibility: params["visibility"]
702 |> put_view(StatusView)
703 |> render("show.json", %{activity: activity})
707 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
708 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
709 ModerationLog.insert_log(%{
710 action: "status_delete",
719 def list_log(conn, params) do
720 {page, page_size} = page_params(params)
723 ModerationLog.get_all(%{
725 page_size: page_size,
726 start_date: params["start_date"],
727 end_date: params["end_date"],
728 user_id: params["user_id"],
729 search: params["search"]
733 |> put_view(ModerationLogView)
734 |> render("index.json", %{log: log})
737 def migrate_to_db(conn, _params) do
738 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
742 def migrate_from_db(conn, _params) do
743 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
747 def config_show(conn, _params) do
748 configs = Pleroma.Repo.all(Config)
751 |> put_view(ConfigView)
752 |> render("index.json", %{configs: configs})
755 def config_update(conn, %{"configs" => configs}) do
757 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
760 %{"group" => group, "key" => key, "delete" => "true"} = params ->
761 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
764 %{"group" => group, "key" => key, "value" => value} ->
765 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
768 |> Enum.reject(&is_nil(&1))
770 Pleroma.Config.TransferTask.load_and_update_env()
771 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
778 |> put_view(ConfigView)
779 |> render("index.json", %{configs: updated})
782 def reload_emoji(conn, _params) do
783 Pleroma.Emoji.reload()
788 def errors(conn, {:error, :not_found}) do
790 |> put_status(:not_found)
791 |> json(dgettext("errors", "Not found"))
794 def errors(conn, {:error, reason}) do
796 |> put_status(:bad_request)
800 def errors(conn, {:param_cast, _}) do
802 |> put_status(:bad_request)
803 |> json(dgettext("errors", "Invalid parameters"))
806 def errors(conn, _) do
808 |> put_status(:internal_server_error)
809 |> json(dgettext("errors", "Something went wrong"))
812 defp page_params(params) do
813 {get_page(params["page"]), get_page_size(params["page_size"])}
816 defp get_page(page_string) when is_nil(page_string), do: 1
818 defp get_page(page_string) do
819 case Integer.parse(page_string) do
825 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
827 defp get_page_size(page_size_string) do
828 case Integer.parse(page_size_string) do
829 {page_size, _} -> page_size
830 :error -> @users_page_size