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.Endpoint
22 alias Pleroma.Web.MastodonAPI.StatusView
23 alias Pleroma.Web.Router
25 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
31 action_fallback(:errors)
33 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
34 user = User.get_cached_by_nickname(nickname)
37 ModerationLog.insert_log(%{
47 def user_follow(%{assigns: %{user: admin}} = conn, %{
48 "follower" => follower_nick,
49 "followed" => followed_nick
51 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
52 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
53 User.follow(follower, followed)
55 ModerationLog.insert_log(%{
67 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
68 "follower" => follower_nick,
69 "followed" => followed_nick
71 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
72 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
73 User.unfollow(follower, followed)
75 ModerationLog.insert_log(%{
87 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
89 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
95 password_confirmation: password,
99 User.register_changeset(%User{}, user_data, need_confirmation: false)
101 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
102 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
105 case Pleroma.Repo.transaction(changesets) do
110 |> Enum.map(fn user ->
111 {:ok, user} = User.post_register_action(user)
115 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
117 ModerationLog.insert_log(%{
119 subjects: Map.values(users),
126 {:error, id, changeset, _} ->
128 Enum.map(changesets.operations, fn
129 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
130 AccountView.render("create-error.json", %{changeset: changeset})
132 {_, {:changeset, current_changeset, _}} ->
133 AccountView.render("create-error.json", %{changeset: current_changeset})
137 |> put_status(:conflict)
142 def user_show(conn, %{"nickname" => nickname}) do
143 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
145 |> put_view(AccountView)
146 |> render("show.json", %{user: user})
148 _ -> {:error, :not_found}
152 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
153 godmode = params["godmode"] == "true" || params["godmode"] == true
155 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
156 {_, page_size} = page_params(params)
159 ActivityPub.fetch_user_activities(user, nil, %{
160 "limit" => page_size,
165 |> put_view(StatusView)
166 |> render("index.json", %{activities: activities, as: :activity})
168 _ -> {:error, :not_found}
172 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
173 user = User.get_cached_by_nickname(nickname)
175 {:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
177 action = if user.info.deactivated, do: "activate", else: "deactivate"
179 ModerationLog.insert_log(%{
186 |> put_view(AccountView)
187 |> render("show.json", %{user: updated_user})
190 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
191 with {:ok, _} <- User.tag(nicknames, tags) do
192 ModerationLog.insert_log(%{
194 nicknames: nicknames,
199 json_response(conn, :no_content, "")
203 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
204 with {:ok, _} <- User.untag(nicknames, tags) do
205 ModerationLog.insert_log(%{
207 nicknames: nicknames,
212 json_response(conn, :no_content, "")
216 def list_users(conn, params) do
217 {page, page_size} = page_params(params)
218 filters = maybe_parse_filters(params["filters"])
221 query: params["query"],
223 page_size: page_size,
224 tags: params["tags"],
225 name: params["name"],
226 email: params["email"]
229 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
233 AccountView.render("index.json",
241 @filters ~w(local external active deactivated is_admin is_moderator)
243 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
244 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
246 defp maybe_parse_filters(filters) do
249 |> Enum.filter(&Enum.member?(@filters, &1))
250 |> Enum.map(&String.to_atom(&1))
251 |> Enum.into(%{}, &{&1, true})
254 def right_add(%{assigns: %{user: admin}} = conn, %{
255 "permission_group" => permission_group,
256 "nickname" => nickname
258 when permission_group in ["moderator", "admin"] do
259 info = Map.put(%{}, "is_" <> permission_group, true)
263 |> User.get_cached_by_nickname()
264 |> User.update_info(&User.Info.admin_api_update(&1, info))
266 ModerationLog.insert_log(%{
270 permission: permission_group
276 def right_add(conn, _) do
277 render_error(conn, :not_found, "No such permission_group")
280 def right_get(conn, %{"nickname" => nickname}) do
281 user = User.get_cached_by_nickname(nickname)
285 is_moderator: user.info.is_moderator,
286 is_admin: user.info.is_admin
290 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
291 render_error(conn, :forbidden, "You can't revoke your own admin status.")
295 %{assigns: %{user: admin}} = conn,
297 "permission_group" => permission_group,
298 "nickname" => nickname
301 when permission_group in ["moderator", "admin"] do
302 info = Map.put(%{}, "is_" <> permission_group, false)
306 |> User.get_cached_by_nickname()
307 |> User.update_info(&User.Info.admin_api_update(&1, info))
309 ModerationLog.insert_log(%{
313 permission: permission_group
319 def right_delete(conn, _) do
320 render_error(conn, :not_found, "No such permission_group")
323 def set_activation_status(%{assigns: %{user: admin}} = conn, %{
324 "nickname" => nickname,
327 with {:ok, status} <- Ecto.Type.cast(:boolean, status),
328 %User{} = user <- User.get_cached_by_nickname(nickname),
329 {:ok, _} <- User.deactivate(user, !status) do
330 action = if(user.info.deactivated, do: "activate", else: "deactivate")
332 ModerationLog.insert_log(%{
338 json_response(conn, :no_content, "")
342 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
343 with {:ok, _message} <- Relay.follow(target) do
344 ModerationLog.insert_log(%{
345 action: "relay_follow",
359 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
360 with {:ok, _message} <- Relay.unfollow(target) do
361 ModerationLog.insert_log(%{
362 action: "relay_unfollow",
376 @doc "Sends registration invite via email"
377 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
379 Pleroma.Config.get([:instance, :invites_enabled]) &&
380 !Pleroma.Config.get([:instance, :registrations_open]),
381 {:ok, invite_token} <- UserInviteToken.create_invite(),
383 Pleroma.Emails.UserEmail.user_invitation_email(
389 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
390 json_response(conn, :no_content, "")
394 @doc "Create an account registration invite token"
395 def create_invite_token(conn, params) do
399 if params["max_use"],
400 do: Map.put(opts, :max_use, params["max_use"]),
404 if params["expires_at"],
405 do: Map.put(opts, :expires_at, params["expires_at"]),
408 {:ok, invite} = UserInviteToken.create_invite(opts)
410 json(conn, AccountView.render("invite.json", %{invite: invite}))
413 @doc "Get list of created invites"
414 def invites(conn, _params) do
415 invites = UserInviteToken.list_invites()
418 |> put_view(AccountView)
419 |> render("invites.json", %{invites: invites})
422 @doc "Revokes invite by token"
423 def revoke_invite(conn, %{"token" => token}) do
424 with {:ok, invite} <- UserInviteToken.find_by_token(token),
425 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
427 |> put_view(AccountView)
428 |> render("invite.json", %{invite: updated_invite})
430 nil -> {:error, :not_found}
434 @doc "Get a password reset token (base64 string) for given nickname"
435 def get_password_reset(conn, %{"nickname" => nickname}) do
436 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
437 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
442 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
446 @doc "Force password reset for a given user"
447 def force_password_reset(conn, %{"nickname" => nickname}) do
448 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
450 User.force_password_reset_async(user)
452 json_response(conn, :no_content, "")
455 def list_reports(conn, params) do
456 {page, page_size} = page_params(params)
460 |> Map.put("type", "Flag")
461 |> Map.put("skip_preload", true)
462 |> Map.put("total", true)
463 |> Map.put("limit", page_size)
464 |> Map.put("offset", (page - 1) * page_size)
466 reports = ActivityPub.fetch_activities([], params, :offset)
469 |> put_view(ReportView)
470 |> render("index.json", %{reports: reports})
473 def report_show(conn, %{"id" => id}) do
474 with %Activity{} = report <- Activity.get_by_id(id) do
476 |> put_view(ReportView)
477 |> render("show.json", Report.extract_report_info(report))
479 _ -> {:error, :not_found}
483 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
484 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
485 ModerationLog.insert_log(%{
486 action: "report_update",
492 |> put_view(ReportView)
493 |> render("show.json", Report.extract_report_info(report))
497 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
498 with false <- is_nil(params["status"]),
499 %Activity{} <- Activity.get_by_id(id) do
502 |> Map.put("in_reply_to_status_id", id)
503 |> Map.put("visibility", "direct")
505 {:ok, activity} = CommonAPI.post(user, params)
507 ModerationLog.insert_log(%{
508 action: "report_response",
511 text: params["status"]
515 |> put_view(StatusView)
516 |> render("show.json", %{activity: activity})
526 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
527 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
528 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
530 ModerationLog.insert_log(%{
531 action: "status_update",
534 sensitive: sensitive,
535 visibility: params["visibility"]
539 |> put_view(StatusView)
540 |> render("show.json", %{activity: activity})
544 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
545 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
546 ModerationLog.insert_log(%{
547 action: "status_delete",
556 def list_log(conn, params) do
557 {page, page_size} = page_params(params)
560 ModerationLog.get_all(%{
562 page_size: page_size,
563 start_date: params["start_date"],
564 end_date: params["end_date"],
565 user_id: params["user_id"],
566 search: params["search"]
570 |> put_view(ModerationLogView)
571 |> render("index.json", %{log: log})
574 def migrate_to_db(conn, _params) do
575 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
579 def migrate_from_db(conn, _params) do
580 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
584 def config_show(conn, _params) do
585 configs = Pleroma.Repo.all(Config)
588 |> put_view(ConfigView)
589 |> render("index.json", %{configs: configs})
592 def config_update(conn, %{"configs" => configs}) do
594 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
597 %{"group" => group, "key" => key, "delete" => "true"} = params ->
598 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
601 %{"group" => group, "key" => key, "value" => value} ->
602 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
605 |> Enum.reject(&is_nil(&1))
607 Pleroma.Config.TransferTask.load_and_update_env()
608 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
615 |> put_view(ConfigView)
616 |> render("index.json", %{configs: updated})
619 def reload_emoji(conn, _params) do
620 Pleroma.Emoji.reload()
625 def errors(conn, {:error, :not_found}) do
627 |> put_status(:not_found)
628 |> json(dgettext("errors", "Not found"))
631 def errors(conn, {:error, reason}) do
633 |> put_status(:bad_request)
637 def errors(conn, {:param_cast, _}) do
639 |> put_status(:bad_request)
640 |> json(dgettext("errors", "Invalid parameters"))
643 def errors(conn, _) do
645 |> put_status(:internal_server_error)
646 |> json(dgettext("errors", "Something went wrong"))
649 defp page_params(params) do
650 {get_page(params["page"]), get_page_size(params["page_size"])}
653 defp get_page(page_string) when is_nil(page_string), do: 1
655 defp get_page(page_string) do
656 case Integer.parse(page_string) do
662 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
664 defp get_page_size(page_size_string) do
665 case Integer.parse(page_size_string) do
666 {page_size, _} -> page_size
667 :error -> @users_page_size