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,
55 :set_activation_status
61 %{scopes: ["read:reports"]} when action in [:list_reports, :report_show]
66 %{scopes: ["write:reports"]}
67 when action in [:report_update_state, :report_respond]
72 %{scopes: ["read:statuses"]} when action == :list_user_statuses
77 %{scopes: ["write:statuses"]}
78 when action in [:status_update, :status_delete]
84 when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log]
90 when action in [:relay_follow, :relay_unfollow, :config_update]
95 action_fallback(:errors)
97 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
98 user = User.get_cached_by_nickname(nickname)
101 ModerationLog.insert_log(%{
111 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
112 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
115 ModerationLog.insert_log(%{
125 def user_follow(%{assigns: %{user: admin}} = conn, %{
126 "follower" => follower_nick,
127 "followed" => followed_nick
129 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
130 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
131 User.follow(follower, followed)
133 ModerationLog.insert_log(%{
145 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
146 "follower" => follower_nick,
147 "followed" => followed_nick
149 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
150 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
151 User.unfollow(follower, followed)
153 ModerationLog.insert_log(%{
165 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
167 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
173 password_confirmation: password,
177 User.register_changeset(%User{}, user_data, need_confirmation: false)
179 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
180 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
183 case Pleroma.Repo.transaction(changesets) do
188 |> Enum.map(fn user ->
189 {:ok, user} = User.post_register_action(user)
193 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
195 ModerationLog.insert_log(%{
197 subjects: Map.values(users),
204 {:error, id, changeset, _} ->
206 Enum.map(changesets.operations, fn
207 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
208 AccountView.render("create-error.json", %{changeset: changeset})
210 {_, {:changeset, current_changeset, _}} ->
211 AccountView.render("create-error.json", %{changeset: current_changeset})
215 |> put_status(:conflict)
220 def user_show(conn, %{"nickname" => nickname}) do
221 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
223 |> put_view(AccountView)
224 |> render("show.json", %{user: user})
226 _ -> {:error, :not_found}
230 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
231 godmode = params["godmode"] == "true" || params["godmode"] == true
233 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
234 {_, page_size} = page_params(params)
237 ActivityPub.fetch_user_activities(user, nil, %{
238 "limit" => page_size,
243 |> put_view(StatusView)
244 |> render("index.json", %{activities: activities, as: :activity})
246 _ -> {:error, :not_found}
250 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
251 user = User.get_cached_by_nickname(nickname)
253 {:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
255 action = if user.info.deactivated, do: "activate", else: "deactivate"
257 ModerationLog.insert_log(%{
264 |> put_view(AccountView)
265 |> render("show.json", %{user: updated_user})
268 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
269 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
270 {:ok, updated_users} = User.deactivate(users, false)
272 ModerationLog.insert_log(%{
279 |> put_view(AccountView)
280 |> render("index.json", %{users: Keyword.values(updated_users)})
283 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
284 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
285 {:ok, updated_users} = User.deactivate(users, true)
287 ModerationLog.insert_log(%{
294 |> put_view(AccountView)
295 |> render("index.json", %{users: Keyword.values(updated_users)})
298 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
299 with {:ok, _} <- User.tag(nicknames, tags) do
300 ModerationLog.insert_log(%{
302 nicknames: nicknames,
307 json_response(conn, :no_content, "")
311 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
312 with {:ok, _} <- User.untag(nicknames, tags) do
313 ModerationLog.insert_log(%{
315 nicknames: nicknames,
320 json_response(conn, :no_content, "")
324 def list_users(conn, params) do
325 {page, page_size} = page_params(params)
326 filters = maybe_parse_filters(params["filters"])
329 query: params["query"],
331 page_size: page_size,
332 tags: params["tags"],
333 name: params["name"],
334 email: params["email"]
337 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
341 AccountView.render("index.json",
349 @filters ~w(local external active deactivated is_admin is_moderator)
351 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
352 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
354 defp maybe_parse_filters(filters) do
357 |> Enum.filter(&Enum.member?(@filters, &1))
358 |> Enum.map(&String.to_atom(&1))
359 |> Enum.into(%{}, &{&1, true})
362 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
363 "permission_group" => permission_group,
364 "nicknames" => nicknames
366 when permission_group in ["moderator", "admin"] do
367 info = Map.put(%{}, "is_" <> permission_group, true)
369 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
371 User.update_info(users, &User.Info.admin_api_update(&1, info))
373 ModerationLog.insert_log(%{
377 permission: permission_group
383 def right_add_multiple(conn, _) do
384 render_error(conn, :not_found, "No such permission_group")
387 def right_add(%{assigns: %{user: admin}} = conn, %{
388 "permission_group" => permission_group,
389 "nickname" => nickname
391 when permission_group in ["moderator", "admin"] do
392 info = Map.put(%{}, "is_" <> permission_group, true)
396 |> User.get_cached_by_nickname()
397 |> User.update_info(&User.Info.admin_api_update(&1, info))
399 ModerationLog.insert_log(%{
403 permission: permission_group
409 def right_add(conn, _) do
410 render_error(conn, :not_found, "No such permission_group")
413 def right_get(conn, %{"nickname" => nickname}) do
414 user = User.get_cached_by_nickname(nickname)
418 is_moderator: user.info.is_moderator,
419 is_admin: user.info.is_admin
423 def right_delete_multiple(
424 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
426 "permission_group" => permission_group,
427 "nicknames" => nicknames
430 when permission_group in ["moderator", "admin"] do
431 with false <- Enum.member?(nicknames, admin_nickname) do
432 info = Map.put(%{}, "is_" <> permission_group, false)
434 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
436 User.update_info(users, &User.Info.admin_api_update(&1, info))
438 ModerationLog.insert_log(%{
442 permission: permission_group
447 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
451 def right_delete_multiple(conn, _) do
452 render_error(conn, :not_found, "No such permission_group")
456 %{assigns: %{user: admin}} = conn,
458 "permission_group" => permission_group,
459 "nickname" => nickname
462 when permission_group in ["moderator", "admin"] do
463 info = Map.put(%{}, "is_" <> permission_group, false)
467 |> User.get_cached_by_nickname()
468 |> User.update_info(&User.Info.admin_api_update(&1, info))
470 ModerationLog.insert_log(%{
474 permission: permission_group
480 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
481 render_error(conn, :forbidden, "You can't revoke your own admin status.")
484 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
485 with {:ok, _message} <- Relay.follow(target) do
486 ModerationLog.insert_log(%{
487 action: "relay_follow",
501 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
502 with {:ok, _message} <- Relay.unfollow(target) do
503 ModerationLog.insert_log(%{
504 action: "relay_unfollow",
518 @doc "Sends registration invite via email"
519 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
521 Pleroma.Config.get([:instance, :invites_enabled]) &&
522 !Pleroma.Config.get([:instance, :registrations_open]),
523 {:ok, invite_token} <- UserInviteToken.create_invite(),
525 Pleroma.Emails.UserEmail.user_invitation_email(
531 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
532 json_response(conn, :no_content, "")
536 @doc "Create an account registration invite token"
537 def create_invite_token(conn, params) do
541 if params["max_use"],
542 do: Map.put(opts, :max_use, params["max_use"]),
546 if params["expires_at"],
547 do: Map.put(opts, :expires_at, params["expires_at"]),
550 {:ok, invite} = UserInviteToken.create_invite(opts)
552 json(conn, AccountView.render("invite.json", %{invite: invite}))
555 @doc "Get list of created invites"
556 def invites(conn, _params) do
557 invites = UserInviteToken.list_invites()
560 |> put_view(AccountView)
561 |> render("invites.json", %{invites: invites})
564 @doc "Revokes invite by token"
565 def revoke_invite(conn, %{"token" => token}) do
566 with {:ok, invite} <- UserInviteToken.find_by_token(token),
567 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
569 |> put_view(AccountView)
570 |> render("invite.json", %{invite: updated_invite})
572 nil -> {:error, :not_found}
576 @doc "Get a password reset token (base64 string) for given nickname"
577 def get_password_reset(conn, %{"nickname" => nickname}) do
578 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
579 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
584 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
588 @doc "Force password reset for a given user"
589 def force_password_reset(conn, %{"nickname" => nickname}) do
590 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
592 User.force_password_reset_async(user)
594 json_response(conn, :no_content, "")
597 def list_reports(conn, params) do
598 {page, page_size} = page_params(params)
602 |> Map.put("type", "Flag")
603 |> Map.put("skip_preload", true)
604 |> Map.put("total", true)
605 |> Map.put("limit", page_size)
606 |> Map.put("offset", (page - 1) * page_size)
608 reports = ActivityPub.fetch_activities([], params, :offset)
611 |> put_view(ReportView)
612 |> render("index.json", %{reports: reports})
615 def report_show(conn, %{"id" => id}) do
616 with %Activity{} = report <- Activity.get_by_id(id) do
618 |> put_view(ReportView)
619 |> render("show.json", Report.extract_report_info(report))
621 _ -> {:error, :not_found}
625 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
626 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
627 ModerationLog.insert_log(%{
628 action: "report_update",
634 |> put_view(ReportView)
635 |> render("show.json", Report.extract_report_info(report))
639 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
640 with false <- is_nil(params["status"]),
641 %Activity{} <- Activity.get_by_id(id) do
644 |> Map.put("in_reply_to_status_id", id)
645 |> Map.put("visibility", "direct")
647 {:ok, activity} = CommonAPI.post(user, params)
649 ModerationLog.insert_log(%{
650 action: "report_response",
653 text: params["status"]
657 |> put_view(StatusView)
658 |> render("show.json", %{activity: activity})
668 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
669 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
670 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
672 ModerationLog.insert_log(%{
673 action: "status_update",
676 sensitive: sensitive,
677 visibility: params["visibility"]
681 |> put_view(StatusView)
682 |> render("show.json", %{activity: activity})
686 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
687 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
688 ModerationLog.insert_log(%{
689 action: "status_delete",
698 def list_log(conn, params) do
699 {page, page_size} = page_params(params)
702 ModerationLog.get_all(%{
704 page_size: page_size,
705 start_date: params["start_date"],
706 end_date: params["end_date"],
707 user_id: params["user_id"],
708 search: params["search"]
712 |> put_view(ModerationLogView)
713 |> render("index.json", %{log: log})
716 def migrate_to_db(conn, _params) do
717 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
721 def migrate_from_db(conn, _params) do
722 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
726 def config_show(conn, _params) do
727 configs = Pleroma.Repo.all(Config)
730 |> put_view(ConfigView)
731 |> render("index.json", %{configs: configs})
734 def config_update(conn, %{"configs" => configs}) do
736 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
739 %{"group" => group, "key" => key, "delete" => "true"} = params ->
740 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
743 %{"group" => group, "key" => key, "value" => value} ->
744 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
747 |> Enum.reject(&is_nil(&1))
749 Pleroma.Config.TransferTask.load_and_update_env()
750 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
757 |> put_view(ConfigView)
758 |> render("index.json", %{configs: updated})
761 def reload_emoji(conn, _params) do
762 Pleroma.Emoji.reload()
767 def errors(conn, {:error, :not_found}) do
769 |> put_status(:not_found)
770 |> json(dgettext("errors", "Not found"))
773 def errors(conn, {:error, reason}) do
775 |> put_status(:bad_request)
779 def errors(conn, {:param_cast, _}) do
781 |> put_status(:bad_request)
782 |> json(dgettext("errors", "Invalid parameters"))
785 def errors(conn, _) do
787 |> put_status(:internal_server_error)
788 |> json(dgettext("errors", "Something went wrong"))
791 defp page_params(params) do
792 {get_page(params["page"]), get_page_size(params["page_size"])}
795 defp get_page(page_string) when is_nil(page_string), do: 1
797 defp get_page(page_string) do
798 case Integer.parse(page_string) do
804 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
806 defp get_page_size(page_size_string) do
807 case Integer.parse(page_size_string) do
808 {page_size, _} -> page_size
809 :error -> @users_page_size