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 user = User.get_cached_by_nickname(nickname)
261 |> Map.put("is_" <> permission_group, true)
263 info_cng = User.Info.admin_api_update(user.info, info)
267 |> Ecto.Changeset.change()
268 |> Ecto.Changeset.put_embed(:info, info_cng)
270 ModerationLog.insert_log(%{
274 permission: permission_group
277 {:ok, _user} = User.update_and_set_cache(cng)
282 def right_add(conn, _) do
283 render_error(conn, :not_found, "No such permission_group")
286 def right_get(conn, %{"nickname" => nickname}) do
287 user = User.get_cached_by_nickname(nickname)
291 is_moderator: user.info.is_moderator,
292 is_admin: user.info.is_admin
297 %{assigns: %{user: %User{:nickname => admin_nickname} = admin}} = conn,
299 "permission_group" => permission_group,
300 "nickname" => nickname
303 when permission_group in ["moderator", "admin"] do
304 if admin_nickname == nickname do
305 render_error(conn, :forbidden, "You can't revoke your own admin status.")
307 user = User.get_cached_by_nickname(nickname)
311 |> Map.put("is_" <> permission_group, false)
313 info_cng = User.Info.admin_api_update(user.info, info)
316 Ecto.Changeset.change(user)
317 |> Ecto.Changeset.put_embed(:info, info_cng)
319 {:ok, _user} = User.update_and_set_cache(cng)
321 ModerationLog.insert_log(%{
325 permission: permission_group
332 def right_delete(conn, _) do
333 render_error(conn, :not_found, "No such permission_group")
336 def set_activation_status(%{assigns: %{user: admin}} = conn, %{
337 "nickname" => nickname,
340 with {:ok, status} <- Ecto.Type.cast(:boolean, status),
341 %User{} = user <- User.get_cached_by_nickname(nickname),
342 {:ok, _} <- User.deactivate(user, !status) do
343 action = if(user.info.deactivated, do: "activate", else: "deactivate")
345 ModerationLog.insert_log(%{
351 json_response(conn, :no_content, "")
355 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
356 with {:ok, _message} <- Relay.follow(target) do
357 ModerationLog.insert_log(%{
358 action: "relay_follow",
372 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
373 with {:ok, _message} <- Relay.unfollow(target) do
374 ModerationLog.insert_log(%{
375 action: "relay_unfollow",
389 @doc "Sends registration invite via email"
390 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
392 Pleroma.Config.get([:instance, :invites_enabled]) &&
393 !Pleroma.Config.get([:instance, :registrations_open]),
394 {:ok, invite_token} <- UserInviteToken.create_invite(),
396 Pleroma.Emails.UserEmail.user_invitation_email(
402 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
403 json_response(conn, :no_content, "")
407 @doc "Create an account registration invite token"
408 def create_invite_token(conn, params) do
412 if params["max_use"],
413 do: Map.put(opts, :max_use, params["max_use"]),
417 if params["expires_at"],
418 do: Map.put(opts, :expires_at, params["expires_at"]),
421 {:ok, invite} = UserInviteToken.create_invite(opts)
423 json(conn, AccountView.render("invite.json", %{invite: invite}))
426 @doc "Get list of created invites"
427 def invites(conn, _params) do
428 invites = UserInviteToken.list_invites()
431 |> put_view(AccountView)
432 |> render("invites.json", %{invites: invites})
435 @doc "Revokes invite by token"
436 def revoke_invite(conn, %{"token" => token}) do
437 with {:ok, invite} <- UserInviteToken.find_by_token(token),
438 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
440 |> put_view(AccountView)
441 |> render("invite.json", %{invite: updated_invite})
443 nil -> {:error, :not_found}
447 @doc "Get a password reset token (base64 string) for given nickname"
448 def get_password_reset(conn, %{"nickname" => nickname}) do
449 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
450 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
456 def list_reports(conn, params) do
459 |> Map.put("type", "Flag")
460 |> Map.put("skip_preload", true)
461 |> Map.put("total", true)
463 reports = ActivityPub.fetch_activities([], params)
466 |> put_view(ReportView)
467 |> render("index.json", %{reports: reports})
470 def report_show(conn, %{"id" => id}) do
471 with %Activity{} = report <- Activity.get_by_id(id) do
473 |> put_view(ReportView)
474 |> render("show.json", Report.extract_report_info(report))
476 _ -> {:error, :not_found}
480 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
481 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
482 ModerationLog.insert_log(%{
483 action: "report_update",
489 |> put_view(ReportView)
490 |> render("show.json", Report.extract_report_info(report))
494 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
495 with false <- is_nil(params["status"]),
496 %Activity{} <- Activity.get_by_id(id) do
499 |> Map.put("in_reply_to_status_id", id)
500 |> Map.put("visibility", "direct")
502 {:ok, activity} = CommonAPI.post(user, params)
504 ModerationLog.insert_log(%{
505 action: "report_response",
508 text: params["status"]
512 |> put_view(StatusView)
513 |> render("status.json", %{activity: activity})
523 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
524 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
525 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
527 ModerationLog.insert_log(%{
528 action: "status_update",
531 sensitive: sensitive,
532 visibility: params["visibility"]
536 |> put_view(StatusView)
537 |> render("status.json", %{activity: activity})
541 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
542 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
543 ModerationLog.insert_log(%{
544 action: "status_delete",
553 def list_log(conn, params) do
554 {page, page_size} = page_params(params)
556 log = ModerationLog.get_all(page, page_size)
559 |> put_view(ModerationLogView)
560 |> render("index.json", %{log: log})
563 def migrate_to_db(conn, _params) do
564 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
568 def migrate_from_db(conn, _params) do
569 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
573 def config_show(conn, _params) do
574 configs = Pleroma.Repo.all(Config)
577 |> put_view(ConfigView)
578 |> render("index.json", %{configs: configs})
581 def config_update(conn, %{"configs" => configs}) do
583 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
586 %{"group" => group, "key" => key, "delete" => "true"} = params ->
587 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
590 %{"group" => group, "key" => key, "value" => value} ->
591 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
594 |> Enum.reject(&is_nil(&1))
596 Pleroma.Config.TransferTask.load_and_update_env()
597 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
604 |> put_view(ConfigView)
605 |> render("index.json", %{configs: updated})
608 def reload_emoji(conn, _params) do
609 Pleroma.Emoji.reload()
614 def errors(conn, {:error, :not_found}) do
616 |> put_status(:not_found)
617 |> json(dgettext("errors", "Not found"))
620 def errors(conn, {:error, reason}) do
622 |> put_status(:bad_request)
626 def errors(conn, {:param_cast, _}) do
628 |> put_status(:bad_request)
629 |> json(dgettext("errors", "Invalid parameters"))
632 def errors(conn, _) do
634 |> put_status(:internal_server_error)
635 |> json(dgettext("errors", "Something went wrong"))
638 defp page_params(params) do
639 {get_page(params["page"]), get_page_size(params["page_size"])}
642 defp get_page(page_string) when is_nil(page_string), do: 1
644 defp get_page(page_string) do
645 case Integer.parse(page_string) do
651 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
653 defp get_page_size(page_size_string) do
654 case Integer.parse(page_size_string) do
655 {page_size, _} -> page_size
656 :error -> @users_page_size