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.Report
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]
29 action_fallback(:errors)
31 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
32 user = User.get_cached_by_nickname(nickname)
35 ModerationLog.insert_log(%{
45 def user_follow(%{assigns: %{user: admin}} = conn, %{
46 "follower" => follower_nick,
47 "followed" => followed_nick
49 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
50 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
51 User.follow(follower, followed)
53 ModerationLog.insert_log(%{
65 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
66 "follower" => follower_nick,
67 "followed" => followed_nick
69 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
70 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
71 User.unfollow(follower, followed)
73 ModerationLog.insert_log(%{
85 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
87 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
93 password_confirmation: password,
97 User.register_changeset(%User{}, user_data, need_confirmation: false)
99 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
100 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
103 case Pleroma.Repo.transaction(changesets) do
108 |> Enum.map(fn user ->
109 {:ok, user} = User.post_register_action(user)
113 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
115 ModerationLog.insert_log(%{
117 subjects: Map.values(users),
124 {:error, id, changeset, _} ->
126 Enum.map(changesets.operations, fn
127 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
128 AccountView.render("create-error.json", %{changeset: changeset})
130 {_, {:changeset, current_changeset, _}} ->
131 AccountView.render("create-error.json", %{changeset: current_changeset})
135 |> put_status(:conflict)
140 def user_show(conn, %{"nickname" => nickname}) do
141 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
143 |> put_view(AccountView)
144 |> render("show.json", %{user: user})
146 _ -> {:error, :not_found}
150 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
151 godmode = params["godmode"] == "true" || params["godmode"] == true
153 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
154 {_, page_size} = page_params(params)
157 ActivityPub.fetch_user_activities(user, nil, %{
158 "limit" => page_size,
163 |> put_view(StatusView)
164 |> render("index.json", %{activities: activities, as: :activity})
166 _ -> {:error, :not_found}
170 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
171 user = User.get_cached_by_nickname(nickname)
173 {:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
175 action = if user.info.deactivated, do: "activate", else: "deactivate"
177 ModerationLog.insert_log(%{
184 |> put_view(AccountView)
185 |> render("show.json", %{user: updated_user})
188 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
189 with {:ok, _} <- User.tag(nicknames, tags) do
190 ModerationLog.insert_log(%{
192 nicknames: nicknames,
197 json_response(conn, :no_content, "")
201 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
202 with {:ok, _} <- User.untag(nicknames, tags) do
203 ModerationLog.insert_log(%{
205 nicknames: nicknames,
210 json_response(conn, :no_content, "")
214 def list_users(conn, params) do
215 {page, page_size} = page_params(params)
216 filters = maybe_parse_filters(params["filters"])
219 query: params["query"],
221 page_size: page_size,
222 tags: params["tags"],
223 name: params["name"],
224 email: params["email"]
227 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
231 AccountView.render("index.json",
239 @filters ~w(local external active deactivated is_admin is_moderator)
241 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
242 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
244 defp maybe_parse_filters(filters) do
247 |> Enum.filter(&Enum.member?(@filters, &1))
248 |> Enum.map(&String.to_atom(&1))
249 |> Enum.into(%{}, &{&1, true})
252 def right_add(%{assigns: %{user: admin}} = conn, %{
253 "permission_group" => permission_group,
254 "nickname" => nickname
256 when permission_group in ["moderator", "admin"] do
257 info = Map.put(%{}, "is_" <> permission_group, true)
261 |> User.get_cached_by_nickname()
262 |> User.update_info(&User.Info.admin_api_update(&1, info))
264 ModerationLog.insert_log(%{
268 permission: permission_group
274 def right_add(conn, _) do
275 render_error(conn, :not_found, "No such permission_group")
278 def right_get(conn, %{"nickname" => nickname}) do
279 user = User.get_cached_by_nickname(nickname)
283 is_moderator: user.info.is_moderator,
284 is_admin: user.info.is_admin
288 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
289 render_error(conn, :forbidden, "You can't revoke your own admin status.")
293 %{assigns: %{user: admin}} = conn,
295 "permission_group" => permission_group,
296 "nickname" => nickname
299 when permission_group in ["moderator", "admin"] do
300 info = Map.put(%{}, "is_" <> permission_group, false)
304 |> User.get_cached_by_nickname()
305 |> User.update_info(&User.Info.admin_api_update(&1, info))
307 ModerationLog.insert_log(%{
311 permission: permission_group
317 def right_delete(conn, _) do
318 render_error(conn, :not_found, "No such permission_group")
321 def set_activation_status(%{assigns: %{user: admin}} = conn, %{
322 "nickname" => nickname,
325 with {:ok, status} <- Ecto.Type.cast(:boolean, status),
326 %User{} = user <- User.get_cached_by_nickname(nickname),
327 {:ok, _} <- User.deactivate(user, !status) do
328 action = if(user.info.deactivated, do: "activate", else: "deactivate")
330 ModerationLog.insert_log(%{
336 json_response(conn, :no_content, "")
340 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
341 with {:ok, _message} <- Relay.follow(target) do
342 ModerationLog.insert_log(%{
343 action: "relay_follow",
357 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
358 with {:ok, _message} <- Relay.unfollow(target) do
359 ModerationLog.insert_log(%{
360 action: "relay_unfollow",
374 @doc "Sends registration invite via email"
375 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
377 Pleroma.Config.get([:instance, :invites_enabled]) &&
378 !Pleroma.Config.get([:instance, :registrations_open]),
379 {:ok, invite_token} <- UserInviteToken.create_invite(),
381 Pleroma.Emails.UserEmail.user_invitation_email(
387 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
388 json_response(conn, :no_content, "")
392 @doc "Create an account registration invite token"
393 def create_invite_token(conn, params) do
397 if params["max_use"],
398 do: Map.put(opts, :max_use, params["max_use"]),
402 if params["expires_at"],
403 do: Map.put(opts, :expires_at, params["expires_at"]),
406 {:ok, invite} = UserInviteToken.create_invite(opts)
408 json(conn, AccountView.render("invite.json", %{invite: invite}))
411 @doc "Get list of created invites"
412 def invites(conn, _params) do
413 invites = UserInviteToken.list_invites()
416 |> put_view(AccountView)
417 |> render("invites.json", %{invites: invites})
420 @doc "Revokes invite by token"
421 def revoke_invite(conn, %{"token" => token}) do
422 with {:ok, invite} <- UserInviteToken.find_by_token(token),
423 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
425 |> put_view(AccountView)
426 |> render("invite.json", %{invite: updated_invite})
428 nil -> {:error, :not_found}
432 @doc "Get a password reset token (base64 string) for given nickname"
433 def get_password_reset(conn, %{"nickname" => nickname}) do
434 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
435 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
441 @doc "Force password reset for a given user"
442 def force_password_reset(conn, %{"nickname" => nickname}) do
443 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
445 User.force_password_reset_async(user)
447 json_response(conn, :no_content, "")
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.extract_report_info(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.extract_report_info(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