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,
54 :set_activation_status
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_follow(%{assigns: %{user: admin}} = conn, %{
111 "follower" => follower_nick,
112 "followed" => followed_nick
114 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
115 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
116 User.follow(follower, followed)
118 ModerationLog.insert_log(%{
130 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
131 "follower" => follower_nick,
132 "followed" => followed_nick
134 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
135 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
136 User.unfollow(follower, followed)
138 ModerationLog.insert_log(%{
150 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
152 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
158 password_confirmation: password,
162 User.register_changeset(%User{}, user_data, need_confirmation: false)
164 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
165 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
168 case Pleroma.Repo.transaction(changesets) do
173 |> Enum.map(fn user ->
174 {:ok, user} = User.post_register_action(user)
178 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
180 ModerationLog.insert_log(%{
182 subjects: Map.values(users),
189 {:error, id, changeset, _} ->
191 Enum.map(changesets.operations, fn
192 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
193 AccountView.render("create-error.json", %{changeset: changeset})
195 {_, {:changeset, current_changeset, _}} ->
196 AccountView.render("create-error.json", %{changeset: current_changeset})
200 |> put_status(:conflict)
205 def user_show(conn, %{"nickname" => nickname}) do
206 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
208 |> put_view(AccountView)
209 |> render("show.json", %{user: user})
211 _ -> {:error, :not_found}
215 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
216 godmode = params["godmode"] == "true" || params["godmode"] == true
218 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
219 {_, page_size} = page_params(params)
222 ActivityPub.fetch_user_activities(user, nil, %{
223 "limit" => page_size,
228 |> put_view(StatusView)
229 |> render("index.json", %{activities: activities, as: :activity})
231 _ -> {:error, :not_found}
235 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
236 user = User.get_cached_by_nickname(nickname)
238 {:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
240 action = if user.info.deactivated, do: "activate", else: "deactivate"
242 ModerationLog.insert_log(%{
249 |> put_view(AccountView)
250 |> render("show.json", %{user: updated_user})
253 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
254 with {:ok, _} <- User.tag(nicknames, tags) do
255 ModerationLog.insert_log(%{
257 nicknames: nicknames,
262 json_response(conn, :no_content, "")
266 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
267 with {:ok, _} <- User.untag(nicknames, tags) do
268 ModerationLog.insert_log(%{
270 nicknames: nicknames,
275 json_response(conn, :no_content, "")
279 def list_users(conn, params) do
280 {page, page_size} = page_params(params)
281 filters = maybe_parse_filters(params["filters"])
284 query: params["query"],
286 page_size: page_size,
287 tags: params["tags"],
288 name: params["name"],
289 email: params["email"]
292 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
296 AccountView.render("index.json",
304 @filters ~w(local external active deactivated is_admin is_moderator)
306 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
307 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
309 defp maybe_parse_filters(filters) do
312 |> Enum.filter(&Enum.member?(@filters, &1))
313 |> Enum.map(&String.to_atom(&1))
314 |> Enum.into(%{}, &{&1, true})
317 def right_add(%{assigns: %{user: admin}} = conn, %{
318 "permission_group" => permission_group,
319 "nickname" => nickname
321 when permission_group in ["moderator", "admin"] do
322 info = Map.put(%{}, "is_" <> permission_group, true)
326 |> User.get_cached_by_nickname()
327 |> User.update_info(&User.Info.admin_api_update(&1, info))
329 ModerationLog.insert_log(%{
333 permission: permission_group
339 def right_add(conn, _) do
340 render_error(conn, :not_found, "No such permission_group")
343 def right_get(conn, %{"nickname" => nickname}) do
344 user = User.get_cached_by_nickname(nickname)
348 is_moderator: user.info.is_moderator,
349 is_admin: user.info.is_admin
353 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
354 render_error(conn, :forbidden, "You can't revoke your own admin status.")
358 %{assigns: %{user: admin}} = conn,
360 "permission_group" => permission_group,
361 "nickname" => nickname
364 when permission_group in ["moderator", "admin"] do
365 info = Map.put(%{}, "is_" <> permission_group, false)
369 |> User.get_cached_by_nickname()
370 |> User.update_info(&User.Info.admin_api_update(&1, info))
372 ModerationLog.insert_log(%{
376 permission: permission_group
382 def right_delete(conn, _) do
383 render_error(conn, :not_found, "No such permission_group")
386 def set_activation_status(%{assigns: %{user: admin}} = conn, %{
387 "nickname" => nickname,
390 with {:ok, status} <- Ecto.Type.cast(:boolean, status),
391 %User{} = user <- User.get_cached_by_nickname(nickname),
392 {:ok, _} <- User.deactivate(user, !status) do
393 action = if(user.info.deactivated, do: "activate", else: "deactivate")
395 ModerationLog.insert_log(%{
401 json_response(conn, :no_content, "")
405 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
406 with {:ok, _message} <- Relay.follow(target) do
407 ModerationLog.insert_log(%{
408 action: "relay_follow",
422 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
423 with {:ok, _message} <- Relay.unfollow(target) do
424 ModerationLog.insert_log(%{
425 action: "relay_unfollow",
439 @doc "Sends registration invite via email"
440 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
442 Pleroma.Config.get([:instance, :invites_enabled]) &&
443 !Pleroma.Config.get([:instance, :registrations_open]),
444 {:ok, invite_token} <- UserInviteToken.create_invite(),
446 Pleroma.Emails.UserEmail.user_invitation_email(
452 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
453 json_response(conn, :no_content, "")
457 @doc "Create an account registration invite token"
458 def create_invite_token(conn, params) do
462 if params["max_use"],
463 do: Map.put(opts, :max_use, params["max_use"]),
467 if params["expires_at"],
468 do: Map.put(opts, :expires_at, params["expires_at"]),
471 {:ok, invite} = UserInviteToken.create_invite(opts)
473 json(conn, AccountView.render("invite.json", %{invite: invite}))
476 @doc "Get list of created invites"
477 def invites(conn, _params) do
478 invites = UserInviteToken.list_invites()
481 |> put_view(AccountView)
482 |> render("invites.json", %{invites: invites})
485 @doc "Revokes invite by token"
486 def revoke_invite(conn, %{"token" => token}) do
487 with {:ok, invite} <- UserInviteToken.find_by_token(token),
488 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
490 |> put_view(AccountView)
491 |> render("invite.json", %{invite: updated_invite})
493 nil -> {:error, :not_found}
497 @doc "Get a password reset token (base64 string) for given nickname"
498 def get_password_reset(conn, %{"nickname" => nickname}) do
499 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
500 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
505 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
509 @doc "Force password reset for a given user"
510 def force_password_reset(conn, %{"nickname" => nickname}) do
511 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
513 User.force_password_reset_async(user)
515 json_response(conn, :no_content, "")
518 def list_reports(conn, params) do
519 {page, page_size} = page_params(params)
522 |> put_view(ReportView)
523 |> render("index.json", %{reports: Utils.get_reports(params, page, page_size)})
526 def list_grouped_reports(conn, _params) do
528 |> put_view(ReportView)
529 |> render("index_grouped.json", Utils.get_reports_grouped_by_status())
532 def report_show(conn, %{"id" => id}) do
533 with %Activity{} = report <- Activity.get_by_id(id) do
535 |> put_view(ReportView)
536 |> render("show.json", Report.extract_report_info(report))
538 _ -> {:error, :not_found}
542 def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
545 |> Enum.map(fn report ->
546 with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do
547 ModerationLog.insert_log(%{
548 action: "report_update",
555 {:error, message} -> %{id: report["id"], error: message}
559 case Enum.any?(result, &Map.has_key?(&1, :error)) do
560 true -> json_response(conn, :bad_request, result)
561 false -> json_response(conn, :no_content, "")
565 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
566 with false <- is_nil(params["status"]),
567 %Activity{} <- Activity.get_by_id(id) do
570 |> Map.put("in_reply_to_status_id", id)
571 |> Map.put("visibility", "direct")
573 {:ok, activity} = CommonAPI.post(user, params)
575 ModerationLog.insert_log(%{
576 action: "report_response",
579 text: params["status"]
583 |> put_view(StatusView)
584 |> render("show.json", %{activity: activity})
594 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
595 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
596 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
598 ModerationLog.insert_log(%{
599 action: "status_update",
602 sensitive: sensitive,
603 visibility: params["visibility"]
607 |> put_view(StatusView)
608 |> render("show.json", %{activity: activity})
612 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
613 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
614 ModerationLog.insert_log(%{
615 action: "status_delete",
624 def list_log(conn, params) do
625 {page, page_size} = page_params(params)
628 ModerationLog.get_all(%{
630 page_size: page_size,
631 start_date: params["start_date"],
632 end_date: params["end_date"],
633 user_id: params["user_id"],
634 search: params["search"]
638 |> put_view(ModerationLogView)
639 |> render("index.json", %{log: log})
642 def migrate_to_db(conn, _params) do
643 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
647 def migrate_from_db(conn, _params) do
648 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
652 def config_show(conn, _params) do
653 configs = Pleroma.Repo.all(Config)
656 |> put_view(ConfigView)
657 |> render("index.json", %{configs: configs})
660 def config_update(conn, %{"configs" => configs}) do
662 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
665 %{"group" => group, "key" => key, "delete" => "true"} = params ->
666 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
669 %{"group" => group, "key" => key, "value" => value} ->
670 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
673 |> Enum.reject(&is_nil(&1))
675 Pleroma.Config.TransferTask.load_and_update_env()
676 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
683 |> put_view(ConfigView)
684 |> render("index.json", %{configs: updated})
687 def reload_emoji(conn, _params) do
688 Pleroma.Emoji.reload()
693 def errors(conn, {:error, :not_found}) do
695 |> put_status(:not_found)
696 |> json(dgettext("errors", "Not found"))
699 def errors(conn, {:error, reason}) do
701 |> put_status(:bad_request)
705 def errors(conn, {:param_cast, _}) do
707 |> put_status(:bad_request)
708 |> json(dgettext("errors", "Invalid parameters"))
711 def errors(conn, _) do
713 |> put_status(:internal_server_error)
714 |> json(dgettext("errors", "Something went wrong"))
717 defp page_params(params) do
718 {get_page(params["page"]), get_page_size(params["page_size"])}
721 defp get_page(page_string) when is_nil(page_string), do: 1
723 defp get_page(page_string) do
724 case Integer.parse(page_string) do
730 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
732 defp get_page_size(page_size_string) do
733 case Integer.parse(page_size_string) do
734 {page_size, _} -> page_size
735 :error -> @users_page_size