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.ActivityPub.Utils
15 alias Pleroma.Web.AdminAPI.AccountView
16 alias Pleroma.Web.AdminAPI.Config
17 alias Pleroma.Web.AdminAPI.ConfigView
18 alias Pleroma.Web.AdminAPI.ModerationLogView
19 alias Pleroma.Web.AdminAPI.Report
20 alias Pleroma.Web.AdminAPI.ReportView
21 alias Pleroma.Web.AdminAPI.Search
22 alias Pleroma.Web.CommonAPI
23 alias Pleroma.Web.Endpoint
24 alias Pleroma.Web.MastodonAPI.StatusView
25 alias Pleroma.Web.Router
27 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
33 %{scopes: ["read:accounts"]}
34 when action in [:list_users, :user_show, :right_get, :invites]
39 %{scopes: ["write:accounts"]}
49 :user_toggle_activation,
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_instance_statuses(conn, %{"instance" => instance} = params) do
231 {page, page_size} = page_params(params)
234 ActivityPub.fetch_instance_activities(%{
235 "instance" => instance,
236 "limit" => page_size,
237 "offset" => (page - 1) * page_size
241 |> put_view(StatusView)
242 |> render("index.json", %{activities: activities, as: :activity})
245 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
246 godmode = params["godmode"] == "true" || params["godmode"] == true
248 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
249 {_, page_size} = page_params(params)
252 ActivityPub.fetch_user_activities(user, nil, %{
253 "limit" => page_size,
258 |> put_view(StatusView)
259 |> render("index.json", %{activities: activities, as: :activity})
261 _ -> {:error, :not_found}
265 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
266 user = User.get_cached_by_nickname(nickname)
268 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
270 action = if user.deactivated, do: "activate", else: "deactivate"
272 ModerationLog.insert_log(%{
279 |> put_view(AccountView)
280 |> render("show.json", %{user: updated_user})
283 def user_activate(%{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, false)
287 ModerationLog.insert_log(%{
294 |> put_view(AccountView)
295 |> render("index.json", %{users: Keyword.values(updated_users)})
298 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
299 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
300 {:ok, updated_users} = User.deactivate(users, true)
302 ModerationLog.insert_log(%{
309 |> put_view(AccountView)
310 |> render("index.json", %{users: Keyword.values(updated_users)})
313 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
314 with {:ok, _} <- User.tag(nicknames, tags) do
315 ModerationLog.insert_log(%{
317 nicknames: nicknames,
322 json_response(conn, :no_content, "")
326 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
327 with {:ok, _} <- User.untag(nicknames, tags) do
328 ModerationLog.insert_log(%{
330 nicknames: nicknames,
335 json_response(conn, :no_content, "")
339 def list_users(conn, params) do
340 {page, page_size} = page_params(params)
341 filters = maybe_parse_filters(params["filters"])
344 query: params["query"],
346 page_size: page_size,
347 tags: params["tags"],
348 name: params["name"],
349 email: params["email"]
352 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
353 {:ok, users, count} <- filter_service_users(users, count),
357 AccountView.render("index.json",
365 defp filter_service_users(users, count) do
366 filtered_users = Enum.reject(users, &service_user?/1)
367 count = if Enum.any?(users, &service_user?/1), do: length(filtered_users), else: count
369 {:ok, filtered_users, count}
372 defp service_user?(user) do
373 String.match?(user.ap_id, ~r/.*\/relay$/) or
374 String.match?(user.ap_id, ~r/.*\/internal\/fetch$/)
377 @filters ~w(local external active deactivated is_admin is_moderator)
379 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
380 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
382 defp maybe_parse_filters(filters) do
385 |> Enum.filter(&Enum.member?(@filters, &1))
386 |> Enum.map(&String.to_atom(&1))
387 |> Enum.into(%{}, &{&1, true})
390 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
391 "permission_group" => permission_group,
392 "nicknames" => nicknames
394 when permission_group in ["moderator", "admin"] do
395 update = %{:"is_#{permission_group}" => true}
397 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
399 for u <- users, do: User.admin_api_update(u, update)
401 ModerationLog.insert_log(%{
405 permission: permission_group
411 def right_add_multiple(conn, _) do
412 render_error(conn, :not_found, "No such permission_group")
415 def right_add(%{assigns: %{user: admin}} = conn, %{
416 "permission_group" => permission_group,
417 "nickname" => nickname
419 when permission_group in ["moderator", "admin"] do
420 fields = %{:"is_#{permission_group}" => true}
424 |> User.get_cached_by_nickname()
425 |> User.admin_api_update(fields)
427 ModerationLog.insert_log(%{
431 permission: permission_group
437 def right_add(conn, _) do
438 render_error(conn, :not_found, "No such permission_group")
441 def right_get(conn, %{"nickname" => nickname}) do
442 user = User.get_cached_by_nickname(nickname)
446 is_moderator: user.is_moderator,
447 is_admin: user.is_admin
451 def right_delete_multiple(
452 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
454 "permission_group" => permission_group,
455 "nicknames" => nicknames
458 when permission_group in ["moderator", "admin"] do
459 with false <- Enum.member?(nicknames, admin_nickname) do
460 update = %{:"is_#{permission_group}" => false}
462 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
464 for u <- users, do: User.admin_api_update(u, update)
466 ModerationLog.insert_log(%{
470 permission: permission_group
475 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
479 def right_delete_multiple(conn, _) do
480 render_error(conn, :not_found, "No such permission_group")
484 %{assigns: %{user: admin}} = conn,
486 "permission_group" => permission_group,
487 "nickname" => nickname
490 when permission_group in ["moderator", "admin"] do
491 fields = %{:"is_#{permission_group}" => false}
495 |> User.get_cached_by_nickname()
496 |> User.admin_api_update(fields)
498 ModerationLog.insert_log(%{
502 permission: permission_group
508 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
509 render_error(conn, :forbidden, "You can't revoke your own admin status.")
512 def relay_list(conn, _params) do
513 with {:ok, list} <- Relay.list() do
514 json(conn, %{relays: list})
522 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
523 with {:ok, _message} <- Relay.follow(target) do
524 ModerationLog.insert_log(%{
525 action: "relay_follow",
539 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
540 with {:ok, _message} <- Relay.unfollow(target) do
541 ModerationLog.insert_log(%{
542 action: "relay_unfollow",
556 @doc "Sends registration invite via email"
557 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
559 Pleroma.Config.get([:instance, :invites_enabled]) &&
560 !Pleroma.Config.get([:instance, :registrations_open]),
561 {:ok, invite_token} <- UserInviteToken.create_invite(),
563 Pleroma.Emails.UserEmail.user_invitation_email(
569 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
570 json_response(conn, :no_content, "")
574 @doc "Create an account registration invite token"
575 def create_invite_token(conn, params) do
579 if params["max_use"],
580 do: Map.put(opts, :max_use, params["max_use"]),
584 if params["expires_at"],
585 do: Map.put(opts, :expires_at, params["expires_at"]),
588 {:ok, invite} = UserInviteToken.create_invite(opts)
590 json(conn, AccountView.render("invite.json", %{invite: invite}))
593 @doc "Get list of created invites"
594 def invites(conn, _params) do
595 invites = UserInviteToken.list_invites()
598 |> put_view(AccountView)
599 |> render("invites.json", %{invites: invites})
602 @doc "Revokes invite by token"
603 def revoke_invite(conn, %{"token" => token}) do
604 with {:ok, invite} <- UserInviteToken.find_by_token(token),
605 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
607 |> put_view(AccountView)
608 |> render("invite.json", %{invite: updated_invite})
610 nil -> {:error, :not_found}
614 @doc "Get a password reset token (base64 string) for given nickname"
615 def get_password_reset(conn, %{"nickname" => nickname}) do
616 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
617 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
622 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
626 @doc "Force password reset for a given user"
627 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
628 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
630 Enum.map(users, &User.force_password_reset_async/1)
632 ModerationLog.insert_log(%{
635 action: "force_password_reset"
638 json_response(conn, :no_content, "")
641 def list_reports(conn, params) do
642 {page, page_size} = page_params(params)
645 |> put_view(ReportView)
646 |> render("index.json", %{reports: Utils.get_reports(params, page, page_size)})
649 def list_grouped_reports(conn, _params) do
650 statuses = Utils.get_reported_activities()
653 |> put_view(ReportView)
654 |> render("index_grouped.json", Utils.get_reports_grouped_by_status(statuses))
657 def report_show(conn, %{"id" => id}) do
658 with %Activity{} = report <- Activity.get_by_id(id) do
660 |> put_view(ReportView)
661 |> render("show.json", Report.extract_report_info(report))
663 _ -> {:error, :not_found}
667 def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
670 |> Enum.map(fn report ->
671 with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do
672 ModerationLog.insert_log(%{
673 action: "report_update",
680 {:error, message} -> %{id: report["id"], error: message}
684 case Enum.any?(result, &Map.has_key?(&1, :error)) do
685 true -> json_response(conn, :bad_request, result)
686 false -> json_response(conn, :no_content, "")
690 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
691 with false <- is_nil(params["status"]),
692 %Activity{} <- Activity.get_by_id(id) do
695 |> Map.put("in_reply_to_status_id", id)
696 |> Map.put("visibility", "direct")
698 {:ok, activity} = CommonAPI.post(user, params)
700 ModerationLog.insert_log(%{
701 action: "report_response",
704 text: params["status"]
708 |> put_view(StatusView)
709 |> render("show.json", %{activity: activity})
719 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
720 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
721 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
723 ModerationLog.insert_log(%{
724 action: "status_update",
727 sensitive: sensitive,
728 visibility: params["visibility"]
732 |> put_view(StatusView)
733 |> render("show.json", %{activity: activity})
737 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
738 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
739 ModerationLog.insert_log(%{
740 action: "status_delete",
749 def list_log(conn, params) do
750 {page, page_size} = page_params(params)
753 ModerationLog.get_all(%{
755 page_size: page_size,
756 start_date: params["start_date"],
757 end_date: params["end_date"],
758 user_id: params["user_id"],
759 search: params["search"]
763 |> put_view(ModerationLogView)
764 |> render("index.json", %{log: log})
767 def migrate_to_db(conn, _params) do
768 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
772 def migrate_from_db(conn, _params) do
773 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
777 def config_show(conn, _params) do
778 configs = Pleroma.Repo.all(Config)
781 |> put_view(ConfigView)
782 |> render("index.json", %{configs: configs})
785 def config_update(conn, %{"configs" => configs}) do
787 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
790 %{"group" => group, "key" => key, "delete" => "true"} = params ->
791 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
794 %{"group" => group, "key" => key, "value" => value} ->
795 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
798 |> Enum.reject(&is_nil(&1))
800 Pleroma.Config.TransferTask.load_and_update_env()
801 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
808 |> put_view(ConfigView)
809 |> render("index.json", %{configs: updated})
812 def reload_emoji(conn, _params) do
813 Pleroma.Emoji.reload()
818 def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
819 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
821 User.toggle_confirmation(users)
823 ModerationLog.insert_log(%{
826 action: "confirm_email"
832 def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
833 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
835 User.try_send_confirmation_email(users)
837 ModerationLog.insert_log(%{
840 action: "resend_confirmation_email"
846 def errors(conn, {:error, :not_found}) do
848 |> put_status(:not_found)
849 |> json(dgettext("errors", "Not found"))
852 def errors(conn, {:error, reason}) do
854 |> put_status(:bad_request)
858 def errors(conn, {:param_cast, _}) do
860 |> put_status(:bad_request)
861 |> json(dgettext("errors", "Invalid parameters"))
864 def errors(conn, _) do
866 |> put_status(:internal_server_error)
867 |> json(dgettext("errors", "Something went wrong"))
870 defp page_params(params) do
871 {get_page(params["page"]), get_page_size(params["page_size"])}
874 defp get_page(page_string) when is_nil(page_string), do: 1
876 defp get_page(page_string) do
877 case Integer.parse(page_string) do
883 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
885 defp get_page_size(page_size_string) do
886 case Integer.parse(page_size_string) do
887 {page_size, _} -> page_size
888 :error -> @users_page_size