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
10 alias Pleroma.UserInviteToken
11 alias Pleroma.Web.ActivityPub.ActivityPub
12 alias Pleroma.Web.ActivityPub.Relay
13 alias Pleroma.Web.AdminAPI.AccountView
14 alias Pleroma.Web.AdminAPI.Config
15 alias Pleroma.Web.AdminAPI.ConfigView
16 alias Pleroma.Web.AdminAPI.ModerationLogView
17 alias Pleroma.Web.AdminAPI.ReportView
18 alias Pleroma.Web.AdminAPI.Search
19 alias Pleroma.Web.CommonAPI
20 alias Pleroma.Web.MastodonAPI.StatusView
22 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
28 action_fallback(:errors)
30 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
31 user = User.get_cached_by_nickname(nickname)
34 ModerationLog.insert_log(%{
44 def user_follow(%{assigns: %{user: admin}} = conn, %{
45 "follower" => follower_nick,
46 "followed" => followed_nick
48 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
49 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
50 User.follow(follower, followed)
52 ModerationLog.insert_log(%{
64 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
65 "follower" => follower_nick,
66 "followed" => followed_nick
68 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
69 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
70 User.unfollow(follower, followed)
72 ModerationLog.insert_log(%{
84 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
86 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
92 password_confirmation: password,
96 User.register_changeset(%User{}, user_data, need_confirmation: false)
98 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
99 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
102 case Pleroma.Repo.transaction(changesets) do
107 |> Enum.map(fn user ->
108 {:ok, user} = User.post_register_action(user)
112 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
114 ModerationLog.insert_log(%{
116 subjects: Map.values(users),
123 {:error, id, changeset, _} ->
125 Enum.map(changesets.operations, fn
126 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
127 AccountView.render("create-error.json", %{changeset: changeset})
129 {_, {:changeset, current_changeset, _}} ->
130 AccountView.render("create-error.json", %{changeset: current_changeset})
134 |> put_status(:conflict)
139 def user_show(conn, %{"nickname" => nickname}) do
140 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
142 |> json(AccountView.render("show.json", %{user: user}))
144 _ -> {:error, :not_found}
148 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
149 godmode = params["godmode"] == "true" || params["godmode"] == true
151 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
152 {_, page_size} = page_params(params)
155 ActivityPub.fetch_user_activities(user, nil, %{
156 "limit" => page_size,
161 |> json(StatusView.render("index.json", %{activities: activities, as: :activity}))
163 _ -> {:error, :not_found}
167 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
168 user = User.get_cached_by_nickname(nickname)
170 {:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
172 action = if user.info.deactivated, do: "activate", else: "deactivate"
174 ModerationLog.insert_log(%{
181 |> json(AccountView.render("show.json", %{user: updated_user}))
184 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
185 with {:ok, _} <- User.tag(nicknames, tags) do
186 ModerationLog.insert_log(%{
188 nicknames: nicknames,
193 json_response(conn, :no_content, "")
197 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
198 with {:ok, _} <- User.untag(nicknames, tags) do
199 ModerationLog.insert_log(%{
201 nicknames: nicknames,
206 json_response(conn, :no_content, "")
210 def list_users(conn, params) do
211 {page, page_size} = page_params(params)
212 filters = maybe_parse_filters(params["filters"])
215 query: params["query"],
217 page_size: page_size,
218 tags: params["tags"],
219 name: params["name"],
220 email: params["email"]
223 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
227 AccountView.render("index.json",
235 @filters ~w(local external active deactivated is_admin is_moderator)
237 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
238 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
240 defp maybe_parse_filters(filters) do
243 |> Enum.filter(&Enum.member?(@filters, &1))
244 |> Enum.map(&String.to_atom(&1))
245 |> Enum.into(%{}, &{&1, true})
248 def right_add(%{assigns: %{user: admin}} = conn, %{
249 "permission_group" => permission_group,
250 "nickname" => nickname
252 when permission_group in ["moderator", "admin"] do
253 user = User.get_cached_by_nickname(nickname)
257 |> Map.put("is_" <> permission_group, true)
259 info_cng = User.Info.admin_api_update(user.info, info)
263 |> Ecto.Changeset.change()
264 |> Ecto.Changeset.put_embed(:info, info_cng)
266 ModerationLog.insert_log(%{
270 permission: permission_group
273 {:ok, _user} = User.update_and_set_cache(cng)
278 def right_add(conn, _) do
279 render_error(conn, :not_found, "No such permission_group")
282 def right_get(conn, %{"nickname" => nickname}) do
283 user = User.get_cached_by_nickname(nickname)
287 is_moderator: user.info.is_moderator,
288 is_admin: user.info.is_admin
293 %{assigns: %{user: %User{:nickname => admin_nickname} = admin}} = conn,
295 "permission_group" => permission_group,
296 "nickname" => nickname
299 when permission_group in ["moderator", "admin"] do
300 if admin_nickname == nickname do
301 render_error(conn, :forbidden, "You can't revoke your own admin status.")
303 user = User.get_cached_by_nickname(nickname)
307 |> Map.put("is_" <> permission_group, false)
309 info_cng = User.Info.admin_api_update(user.info, info)
312 Ecto.Changeset.change(user)
313 |> Ecto.Changeset.put_embed(:info, info_cng)
315 {:ok, _user} = User.update_and_set_cache(cng)
317 ModerationLog.insert_log(%{
321 permission: permission_group
328 def right_delete(conn, _) do
329 render_error(conn, :not_found, "No such permission_group")
332 def set_activation_status(%{assigns: %{user: admin}} = conn, %{
333 "nickname" => nickname,
336 with {:ok, status} <- Ecto.Type.cast(:boolean, status),
337 %User{} = user <- User.get_cached_by_nickname(nickname),
338 {:ok, _} <- User.deactivate(user, !status) do
339 action = if(user.info.deactivated, do: "activate", else: "deactivate")
341 ModerationLog.insert_log(%{
347 json_response(conn, :no_content, "")
351 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
352 with {:ok, _message} <- Relay.follow(target) do
353 ModerationLog.insert_log(%{
354 action: "relay_follow",
368 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
369 with {:ok, _message} <- Relay.unfollow(target) do
370 ModerationLog.insert_log(%{
371 action: "relay_unfollow",
385 @doc "Sends registration invite via email"
386 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
388 Pleroma.Config.get([:instance, :invites_enabled]) &&
389 !Pleroma.Config.get([:instance, :registrations_open]),
390 {:ok, invite_token} <- UserInviteToken.create_invite(),
392 Pleroma.Emails.UserEmail.user_invitation_email(
398 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
399 json_response(conn, :no_content, "")
403 @doc "Create an account registration invite token"
404 def create_invite_token(conn, params) do
408 if params["max_use"],
409 do: Map.put(opts, :max_use, params["max_use"]),
413 if params["expires_at"],
414 do: Map.put(opts, :expires_at, params["expires_at"]),
417 {:ok, invite} = UserInviteToken.create_invite(opts)
419 json(conn, AccountView.render("invite.json", %{invite: invite}))
422 @doc "Get list of created invites"
423 def invites(conn, _params) do
424 invites = UserInviteToken.list_invites()
427 |> json(AccountView.render("invites.json", %{invites: invites}))
430 @doc "Revokes invite by token"
431 def revoke_invite(conn, %{"token" => token}) do
432 with {:ok, invite} <- UserInviteToken.find_by_token(token),
433 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
435 |> json(AccountView.render("invite.json", %{invite: updated_invite}))
437 nil -> {:error, :not_found}
441 @doc "Get a password reset token (base64 string) for given nickname"
442 def get_password_reset(conn, %{"nickname" => nickname}) do
443 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
444 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
450 def list_reports(conn, params) do
453 |> Map.put("type", "Flag")
454 |> Map.put("skip_preload", true)
455 |> Map.put("total", true)
457 reports = ActivityPub.fetch_activities([], params)
460 |> put_view(ReportView)
461 |> render("index.json", %{reports: reports})
464 def report_show(conn, %{"id" => id}) do
465 with %Activity{} = report <- Activity.get_by_id(id) do
467 |> put_view(ReportView)
468 |> render("show.json", %{report: report})
470 _ -> {:error, :not_found}
474 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
475 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
476 ModerationLog.insert_log(%{
477 action: "report_update",
483 |> put_view(ReportView)
484 |> render("show.json", %{report: report})
488 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
489 with false <- is_nil(params["status"]),
490 %Activity{} <- Activity.get_by_id(id) do
493 |> Map.put("in_reply_to_status_id", id)
494 |> Map.put("visibility", "direct")
496 {:ok, activity} = CommonAPI.post(user, params)
498 ModerationLog.insert_log(%{
499 action: "report_response",
502 text: params["status"]
506 |> put_view(StatusView)
507 |> render("status.json", %{activity: activity})
517 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
518 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
519 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
521 ModerationLog.insert_log(%{
522 action: "status_update",
525 sensitive: sensitive,
526 visibility: params["visibility"]
530 |> put_view(StatusView)
531 |> render("status.json", %{activity: activity})
535 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
536 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
537 ModerationLog.insert_log(%{
538 action: "status_delete",
547 def list_log(conn, params) do
548 {page, page_size} = page_params(params)
550 log = ModerationLog.get_all(page, page_size)
553 |> put_view(ModerationLogView)
554 |> render("index.json", %{log: log})
557 def migrate_to_db(conn, _params) do
558 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
562 def migrate_from_db(conn, _params) do
563 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
567 def config_show(conn, _params) do
568 configs = Pleroma.Repo.all(Config)
571 |> put_view(ConfigView)
572 |> render("index.json", %{configs: configs})
575 def config_update(conn, %{"configs" => configs}) do
577 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
580 %{"group" => group, "key" => key, "delete" => "true"} = params ->
581 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
584 %{"group" => group, "key" => key, "value" => value} ->
585 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
588 |> Enum.reject(&is_nil(&1))
590 Pleroma.Config.TransferTask.load_and_update_env()
591 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
598 |> put_view(ConfigView)
599 |> render("index.json", %{configs: updated})
602 def reload_emoji(conn, _params) do
603 Pleroma.Emoji.reload()
608 def errors(conn, {:error, :not_found}) do
610 |> put_status(:not_found)
611 |> json(dgettext("errors", "Not found"))
614 def errors(conn, {:error, reason}) do
616 |> put_status(:bad_request)
620 def errors(conn, {:param_cast, _}) do
622 |> put_status(:bad_request)
623 |> json(dgettext("errors", "Invalid parameters"))
626 def errors(conn, _) do
628 |> put_status(:internal_server_error)
629 |> json(dgettext("errors", "Something went wrong"))
632 defp page_params(params) do
633 {get_page(params["page"]), get_page_size(params["page_size"])}
636 defp get_page(page_string) when is_nil(page_string), do: 1
638 defp get_page(page_string) do
639 case Integer.parse(page_string) do
645 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
647 defp get_page_size(page_size_string) do
648 case Integer.parse(page_size_string) do
649 {page_size, _} -> page_size
650 :error -> @users_page_size