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.ReportView
19 alias Pleroma.Web.AdminAPI.Search
20 alias Pleroma.Web.CommonAPI
21 alias Pleroma.Web.MastodonAPI.StatusView
23 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
27 plug(OAuthScopesPlug, %{scopes: ["read:statuses"]} when action == :list_user_statuses)
31 %{scopes: ["write:statuses"]} when action in [:status_update, :status_delete]
63 :user_toggle_activation,
68 :set_activation_status,
79 action_fallback(:errors)
81 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
82 user = User.get_cached_by_nickname(nickname)
85 ModerationLog.insert_log(%{
95 def user_follow(%{assigns: %{user: admin}} = conn, %{
96 "follower" => follower_nick,
97 "followed" => followed_nick
99 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
100 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
101 User.follow(follower, followed)
103 ModerationLog.insert_log(%{
115 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
116 "follower" => follower_nick,
117 "followed" => followed_nick
119 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
120 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
121 User.unfollow(follower, followed)
123 ModerationLog.insert_log(%{
135 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
137 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
143 password_confirmation: password,
147 User.register_changeset(%User{}, user_data, need_confirmation: false)
149 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
150 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
153 case Pleroma.Repo.transaction(changesets) do
158 |> Enum.map(fn user ->
159 {:ok, user} = User.post_register_action(user)
163 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
165 ModerationLog.insert_log(%{
167 subjects: Map.values(users),
174 {:error, id, changeset, _} ->
176 Enum.map(changesets.operations, fn
177 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
178 AccountView.render("create-error.json", %{changeset: changeset})
180 {_, {:changeset, current_changeset, _}} ->
181 AccountView.render("create-error.json", %{changeset: current_changeset})
185 |> put_status(:conflict)
190 def user_show(conn, %{"nickname" => nickname}) do
191 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
193 |> json(AccountView.render("show.json", %{user: user}))
195 _ -> {:error, :not_found}
199 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
200 godmode = params["godmode"] == "true" || params["godmode"] == true
202 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
203 {_, page_size} = page_params(params)
206 ActivityPub.fetch_user_activities(user, nil, %{
207 "limit" => page_size,
212 |> json(StatusView.render("index.json", %{activities: activities, as: :activity}))
214 _ -> {:error, :not_found}
218 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
219 user = User.get_cached_by_nickname(nickname)
221 {:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
223 action = if user.info.deactivated, do: "activate", else: "deactivate"
225 ModerationLog.insert_log(%{
232 |> json(AccountView.render("show.json", %{user: updated_user}))
235 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
236 with {:ok, _} <- User.tag(nicknames, tags) do
237 ModerationLog.insert_log(%{
239 nicknames: nicknames,
244 json_response(conn, :no_content, "")
248 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
249 with {:ok, _} <- User.untag(nicknames, tags) do
250 ModerationLog.insert_log(%{
252 nicknames: nicknames,
257 json_response(conn, :no_content, "")
261 def list_users(conn, params) do
262 {page, page_size} = page_params(params)
263 filters = maybe_parse_filters(params["filters"])
266 query: params["query"],
268 page_size: page_size,
269 tags: params["tags"],
270 name: params["name"],
271 email: params["email"]
274 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
278 AccountView.render("index.json",
286 @filters ~w(local external active deactivated is_admin is_moderator)
288 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
289 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
291 defp maybe_parse_filters(filters) do
294 |> Enum.filter(&Enum.member?(@filters, &1))
295 |> Enum.map(&String.to_atom(&1))
296 |> Enum.into(%{}, &{&1, true})
299 def right_add(%{assigns: %{user: admin}} = conn, %{
300 "permission_group" => permission_group,
301 "nickname" => nickname
303 when permission_group in ["moderator", "admin"] do
304 user = User.get_cached_by_nickname(nickname)
308 |> Map.put("is_" <> permission_group, true)
310 info_cng = User.Info.admin_api_update(user.info, info)
314 |> Ecto.Changeset.change()
315 |> Ecto.Changeset.put_embed(:info, info_cng)
317 ModerationLog.insert_log(%{
321 permission: permission_group
324 {:ok, _user} = User.update_and_set_cache(cng)
329 def right_add(conn, _) do
330 render_error(conn, :not_found, "No such permission_group")
333 def right_get(conn, %{"nickname" => nickname}) do
334 user = User.get_cached_by_nickname(nickname)
338 is_moderator: user.info.is_moderator,
339 is_admin: user.info.is_admin
344 %{assigns: %{user: %User{:nickname => admin_nickname} = admin}} = conn,
346 "permission_group" => permission_group,
347 "nickname" => nickname
350 when permission_group in ["moderator", "admin"] do
351 if admin_nickname == nickname do
352 render_error(conn, :forbidden, "You can't revoke your own admin status.")
354 user = User.get_cached_by_nickname(nickname)
358 |> Map.put("is_" <> permission_group, false)
360 info_cng = User.Info.admin_api_update(user.info, info)
363 Ecto.Changeset.change(user)
364 |> Ecto.Changeset.put_embed(:info, info_cng)
366 {:ok, _user} = User.update_and_set_cache(cng)
368 ModerationLog.insert_log(%{
372 permission: permission_group
379 def right_delete(conn, _) do
380 render_error(conn, :not_found, "No such permission_group")
383 def set_activation_status(%{assigns: %{user: admin}} = conn, %{
384 "nickname" => nickname,
387 with {:ok, status} <- Ecto.Type.cast(:boolean, status),
388 %User{} = user <- User.get_cached_by_nickname(nickname),
389 {:ok, _} <- User.deactivate(user, !status) do
390 action = if(user.info.deactivated, do: "activate", else: "deactivate")
392 ModerationLog.insert_log(%{
398 json_response(conn, :no_content, "")
402 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
403 with {:ok, _message} <- Relay.follow(target) do
404 ModerationLog.insert_log(%{
405 action: "relay_follow",
419 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
420 with {:ok, _message} <- Relay.unfollow(target) do
421 ModerationLog.insert_log(%{
422 action: "relay_unfollow",
436 @doc "Sends registration invite via email"
437 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
439 Pleroma.Config.get([:instance, :invites_enabled]) &&
440 !Pleroma.Config.get([:instance, :registrations_open]),
441 {:ok, invite_token} <- UserInviteToken.create_invite(),
443 Pleroma.Emails.UserEmail.user_invitation_email(
449 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
450 json_response(conn, :no_content, "")
454 @doc "Get a account registeration invite token (base64 string)"
455 def get_invite_token(conn, params) do
456 options = params["invite"] || %{}
457 {:ok, invite} = UserInviteToken.create_invite(options)
460 |> json(invite.token)
463 @doc "Get list of created invites"
464 def invites(conn, _params) do
465 invites = UserInviteToken.list_invites()
468 |> json(AccountView.render("invites.json", %{invites: invites}))
471 @doc "Revokes invite by token"
472 def revoke_invite(conn, %{"token" => token}) do
473 with {:ok, invite} <- UserInviteToken.find_by_token(token),
474 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
476 |> json(AccountView.render("invite.json", %{invite: updated_invite}))
478 nil -> {:error, :not_found}
482 @doc "Get a password reset token (base64 string) for given nickname"
483 def get_password_reset(conn, %{"nickname" => nickname}) do
484 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
485 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
491 def list_reports(conn, params) do
494 |> Map.put("type", "Flag")
495 |> Map.put("skip_preload", true)
499 |> ActivityPub.fetch_activities(params)
503 |> put_view(ReportView)
504 |> render("index.json", %{reports: reports})
507 def report_show(conn, %{"id" => id}) do
508 with %Activity{} = report <- Activity.get_by_id(id) do
510 |> put_view(ReportView)
511 |> render("show.json", %{report: report})
513 _ -> {:error, :not_found}
517 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
518 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
519 ModerationLog.insert_log(%{
520 action: "report_update",
526 |> put_view(ReportView)
527 |> render("show.json", %{report: report})
531 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
532 with false <- is_nil(params["status"]),
533 %Activity{} <- Activity.get_by_id(id) do
536 |> Map.put("in_reply_to_status_id", id)
537 |> Map.put("visibility", "direct")
539 {:ok, activity} = CommonAPI.post(user, params)
541 ModerationLog.insert_log(%{
542 action: "report_response",
545 text: params["status"]
549 |> put_view(StatusView)
550 |> render("status.json", %{activity: activity})
560 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
561 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
562 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
564 ModerationLog.insert_log(%{
565 action: "status_update",
568 sensitive: sensitive,
569 visibility: params["visibility"]
573 |> put_view(StatusView)
574 |> render("status.json", %{activity: activity})
578 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
579 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
580 ModerationLog.insert_log(%{
581 action: "status_delete",
590 def list_log(conn, params) do
591 {page, page_size} = page_params(params)
593 log = ModerationLog.get_all(page, page_size)
596 |> put_view(ModerationLogView)
597 |> render("index.json", %{log: log})
600 def migrate_to_db(conn, _params) do
601 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
605 def migrate_from_db(conn, _params) do
606 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
610 def config_show(conn, _params) do
611 configs = Pleroma.Repo.all(Config)
614 |> put_view(ConfigView)
615 |> render("index.json", %{configs: configs})
618 def config_update(conn, %{"configs" => configs}) do
620 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
623 %{"group" => group, "key" => key, "delete" => "true"} = params ->
624 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
627 %{"group" => group, "key" => key, "value" => value} ->
628 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
631 |> Enum.reject(&is_nil(&1))
633 Pleroma.Config.TransferTask.load_and_update_env()
634 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
641 |> put_view(ConfigView)
642 |> render("index.json", %{configs: updated})
645 def errors(conn, {:error, :not_found}) do
647 |> put_status(:not_found)
648 |> json(dgettext("errors", "Not found"))
651 def errors(conn, {:error, reason}) do
653 |> put_status(:bad_request)
657 def errors(conn, {:param_cast, _}) do
659 |> put_status(:bad_request)
660 |> json(dgettext("errors", "Invalid parameters"))
663 def errors(conn, _) do
665 |> put_status(:internal_server_error)
666 |> json(dgettext("errors", "Something went wrong"))
669 defp page_params(params) do
670 {get_page(params["page"]), get_page_size(params["page_size"])}
673 defp get_page(page_string) when is_nil(page_string), do: 1
675 defp get_page(page_string) do
676 case Integer.parse(page_string) do
682 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
684 defp get_page_size(page_size_string) do
685 case Integer.parse(page_size_string) do
686 {page_size, _} -> page_size
687 :error -> @users_page_size