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 reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do
486 |> Enum.map(fn report ->
487 with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do
488 ModerationLog.insert_log(%{
489 action: "report_update",
496 {:error, message} -> %{id: report["id"], error: message}
500 case Enum.any?(result, &Map.has_key?(&1, :error)) do
501 true -> json_response(conn, :bad_request, result)
502 false -> json_response(conn, :no_content, "")
506 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
507 with false <- is_nil(params["status"]),
508 %Activity{} <- Activity.get_by_id(id) do
511 |> Map.put("in_reply_to_status_id", id)
512 |> Map.put("visibility", "direct")
514 {:ok, activity} = CommonAPI.post(user, params)
516 ModerationLog.insert_log(%{
517 action: "report_response",
520 text: params["status"]
524 |> put_view(StatusView)
525 |> render("show.json", %{activity: activity})
535 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
536 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
537 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
539 ModerationLog.insert_log(%{
540 action: "status_update",
543 sensitive: sensitive,
544 visibility: params["visibility"]
548 |> put_view(StatusView)
549 |> render("show.json", %{activity: activity})
553 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
554 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
555 ModerationLog.insert_log(%{
556 action: "status_delete",
565 def list_log(conn, params) do
566 {page, page_size} = page_params(params)
569 ModerationLog.get_all(%{
571 page_size: page_size,
572 start_date: params["start_date"],
573 end_date: params["end_date"],
574 user_id: params["user_id"],
575 search: params["search"]
579 |> put_view(ModerationLogView)
580 |> render("index.json", %{log: log})
583 def migrate_to_db(conn, _params) do
584 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
588 def migrate_from_db(conn, _params) do
589 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
593 def config_show(conn, _params) do
594 configs = Pleroma.Repo.all(Config)
597 |> put_view(ConfigView)
598 |> render("index.json", %{configs: configs})
601 def config_update(conn, %{"configs" => configs}) do
603 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
606 %{"group" => group, "key" => key, "delete" => "true"} = params ->
607 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
610 %{"group" => group, "key" => key, "value" => value} ->
611 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
614 |> Enum.reject(&is_nil(&1))
616 Pleroma.Config.TransferTask.load_and_update_env()
617 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
624 |> put_view(ConfigView)
625 |> render("index.json", %{configs: updated})
628 def reload_emoji(conn, _params) do
629 Pleroma.Emoji.reload()
634 def errors(conn, {:error, :not_found}) do
636 |> put_status(:not_found)
637 |> json(dgettext("errors", "Not found"))
640 def errors(conn, {:error, reason}) do
642 |> put_status(:bad_request)
646 def errors(conn, {:param_cast, _}) do
648 |> put_status(:bad_request)
649 |> json(dgettext("errors", "Invalid parameters"))
652 def errors(conn, _) do
654 |> put_status(:internal_server_error)
655 |> json(dgettext("errors", "Something went wrong"))
658 defp page_params(params) do
659 {get_page(params["page"]), get_page_size(params["page_size"])}
662 defp get_page(page_string) when is_nil(page_string), do: 1
664 defp get_page(page_string) do
665 case Integer.parse(page_string) do
671 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
673 defp get_page_size(page_size_string) do
674 case Integer.parse(page_size_string) do
675 {page_size, _} -> page_size
676 :error -> @users_page_size