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(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
599 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
601 Enum.map(users, &User.force_password_reset_async/1)
603 ModerationLog.insert_log(%{
606 action: "force_password_reset"
609 json_response(conn, :no_content, "")
612 def list_reports(conn, params) do
613 {page, page_size} = page_params(params)
617 |> Map.put("type", "Flag")
618 |> Map.put("skip_preload", true)
619 |> Map.put("total", true)
620 |> Map.put("limit", page_size)
621 |> Map.put("offset", (page - 1) * page_size)
623 reports = ActivityPub.fetch_activities([], params, :offset)
626 |> put_view(ReportView)
627 |> render("index.json", %{reports: reports})
630 def report_show(conn, %{"id" => id}) do
631 with %Activity{} = report <- Activity.get_by_id(id) do
633 |> put_view(ReportView)
634 |> render("show.json", Report.extract_report_info(report))
636 _ -> {:error, :not_found}
640 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
641 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
642 ModerationLog.insert_log(%{
643 action: "report_update",
649 |> put_view(ReportView)
650 |> render("show.json", Report.extract_report_info(report))
654 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
655 with false <- is_nil(params["status"]),
656 %Activity{} <- Activity.get_by_id(id) do
659 |> Map.put("in_reply_to_status_id", id)
660 |> Map.put("visibility", "direct")
662 {:ok, activity} = CommonAPI.post(user, params)
664 ModerationLog.insert_log(%{
665 action: "report_response",
668 text: params["status"]
672 |> put_view(StatusView)
673 |> render("show.json", %{activity: activity})
683 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
684 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
685 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
687 ModerationLog.insert_log(%{
688 action: "status_update",
691 sensitive: sensitive,
692 visibility: params["visibility"]
696 |> put_view(StatusView)
697 |> render("show.json", %{activity: activity})
701 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
702 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
703 ModerationLog.insert_log(%{
704 action: "status_delete",
713 def list_log(conn, params) do
714 {page, page_size} = page_params(params)
717 ModerationLog.get_all(%{
719 page_size: page_size,
720 start_date: params["start_date"],
721 end_date: params["end_date"],
722 user_id: params["user_id"],
723 search: params["search"]
727 |> put_view(ModerationLogView)
728 |> render("index.json", %{log: log})
731 def migrate_to_db(conn, _params) do
732 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
736 def migrate_from_db(conn, _params) do
737 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
741 def config_show(conn, _params) do
742 configs = Pleroma.Repo.all(Config)
745 |> put_view(ConfigView)
746 |> render("index.json", %{configs: configs})
749 def config_update(conn, %{"configs" => configs}) do
751 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
754 %{"group" => group, "key" => key, "delete" => "true"} = params ->
755 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
758 %{"group" => group, "key" => key, "value" => value} ->
759 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
762 |> Enum.reject(&is_nil(&1))
764 Pleroma.Config.TransferTask.load_and_update_env()
765 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
772 |> put_view(ConfigView)
773 |> render("index.json", %{configs: updated})
776 def reload_emoji(conn, _params) do
777 Pleroma.Emoji.reload()
782 def errors(conn, {:error, :not_found}) do
784 |> put_status(:not_found)
785 |> json(dgettext("errors", "Not found"))
788 def errors(conn, {:error, reason}) do
790 |> put_status(:bad_request)
794 def errors(conn, {:param_cast, _}) do
796 |> put_status(:bad_request)
797 |> json(dgettext("errors", "Invalid parameters"))
800 def errors(conn, _) do
802 |> put_status(:internal_server_error)
803 |> json(dgettext("errors", "Something went wrong"))
806 defp page_params(params) do
807 {get_page(params["page"]), get_page_size(params["page_size"])}
810 defp get_page(page_string) when is_nil(page_string), do: 1
812 defp get_page(page_string) do
813 case Integer.parse(page_string) do
819 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
821 defp get_page_size(page_size_string) do
822 case Integer.parse(page_size_string) do
823 {page_size, _} -> page_size
824 :error -> @users_page_size