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
9 alias Pleroma.Plugs.OAuthScopesPlug
11 alias Pleroma.UserInviteToken
12 alias Pleroma.Web.ActivityPub.ActivityPub
13 alias Pleroma.Web.ActivityPub.Relay
14 alias Pleroma.Web.AdminAPI.AccountView
15 alias Pleroma.Web.AdminAPI.Config
16 alias Pleroma.Web.AdminAPI.ConfigView
17 alias Pleroma.Web.AdminAPI.ModerationLogView
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 %{scopes: ["read:accounts"]}
30 when action in [:list_users, :user_show, :right_get, :invites]
35 %{scopes: ["write:accounts"]}
45 :user_toggle_activation,
50 :set_activation_status
56 %{scopes: ["read:reports"]} when action in [:list_reports, :report_show]
61 %{scopes: ["write:reports"]}
62 when action in [:report_update_state, :report_respond]
67 %{scopes: ["read:statuses"]} when action == :list_user_statuses
72 %{scopes: ["write:statuses"]}
73 when action in [:status_update, :status_delete]
79 when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log]
85 when action in [:relay_follow, :relay_unfollow, :config_update]
90 action_fallback(:errors)
92 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
93 user = User.get_cached_by_nickname(nickname)
96 ModerationLog.insert_log(%{
106 def user_follow(%{assigns: %{user: admin}} = conn, %{
107 "follower" => follower_nick,
108 "followed" => followed_nick
110 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
111 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
112 User.follow(follower, followed)
114 ModerationLog.insert_log(%{
126 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
127 "follower" => follower_nick,
128 "followed" => followed_nick
130 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
131 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
132 User.unfollow(follower, followed)
134 ModerationLog.insert_log(%{
146 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
148 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
154 password_confirmation: password,
158 User.register_changeset(%User{}, user_data, need_confirmation: false)
160 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
161 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
164 case Pleroma.Repo.transaction(changesets) do
169 |> Enum.map(fn user ->
170 {:ok, user} = User.post_register_action(user)
174 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
176 ModerationLog.insert_log(%{
178 subjects: Map.values(users),
185 {:error, id, changeset, _} ->
187 Enum.map(changesets.operations, fn
188 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
189 AccountView.render("create-error.json", %{changeset: changeset})
191 {_, {:changeset, current_changeset, _}} ->
192 AccountView.render("create-error.json", %{changeset: current_changeset})
196 |> put_status(:conflict)
201 def user_show(conn, %{"nickname" => nickname}) do
202 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
204 |> json(AccountView.render("show.json", %{user: user}))
206 _ -> {:error, :not_found}
210 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
211 godmode = params["godmode"] == "true" || params["godmode"] == true
213 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
214 {_, page_size} = page_params(params)
217 ActivityPub.fetch_user_activities(user, nil, %{
218 "limit" => page_size,
223 |> json(StatusView.render("index.json", %{activities: activities, as: :activity}))
225 _ -> {:error, :not_found}
229 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
230 user = User.get_cached_by_nickname(nickname)
232 {:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
234 action = if user.info.deactivated, do: "activate", else: "deactivate"
236 ModerationLog.insert_log(%{
243 |> json(AccountView.render("show.json", %{user: updated_user}))
246 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
247 with {:ok, _} <- User.tag(nicknames, tags) do
248 ModerationLog.insert_log(%{
250 nicknames: nicknames,
255 json_response(conn, :no_content, "")
259 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
260 with {:ok, _} <- User.untag(nicknames, tags) do
261 ModerationLog.insert_log(%{
263 nicknames: nicknames,
268 json_response(conn, :no_content, "")
272 def list_users(conn, params) do
273 {page, page_size} = page_params(params)
274 filters = maybe_parse_filters(params["filters"])
277 query: params["query"],
279 page_size: page_size,
280 tags: params["tags"],
281 name: params["name"],
282 email: params["email"]
285 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
289 AccountView.render("index.json",
297 @filters ~w(local external active deactivated is_admin is_moderator)
299 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
300 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
302 defp maybe_parse_filters(filters) do
305 |> Enum.filter(&Enum.member?(@filters, &1))
306 |> Enum.map(&String.to_atom(&1))
307 |> Enum.into(%{}, &{&1, true})
310 def right_add(%{assigns: %{user: admin}} = conn, %{
311 "permission_group" => permission_group,
312 "nickname" => nickname
314 when permission_group in ["moderator", "admin"] do
315 user = User.get_cached_by_nickname(nickname)
319 |> Map.put("is_" <> permission_group, true)
321 info_cng = User.Info.admin_api_update(user.info, info)
325 |> Ecto.Changeset.change()
326 |> Ecto.Changeset.put_embed(:info, info_cng)
328 ModerationLog.insert_log(%{
332 permission: permission_group
335 {:ok, _user} = User.update_and_set_cache(cng)
340 def right_add(conn, _) do
341 render_error(conn, :not_found, "No such permission_group")
344 def right_get(conn, %{"nickname" => nickname}) do
345 user = User.get_cached_by_nickname(nickname)
349 is_moderator: user.info.is_moderator,
350 is_admin: user.info.is_admin
355 %{assigns: %{user: %User{:nickname => admin_nickname} = admin}} = conn,
357 "permission_group" => permission_group,
358 "nickname" => nickname
361 when permission_group in ["moderator", "admin"] do
362 if admin_nickname == nickname do
363 render_error(conn, :forbidden, "You can't revoke your own admin status.")
365 user = User.get_cached_by_nickname(nickname)
369 |> Map.put("is_" <> permission_group, false)
371 info_cng = User.Info.admin_api_update(user.info, info)
374 Ecto.Changeset.change(user)
375 |> Ecto.Changeset.put_embed(:info, info_cng)
377 {:ok, _user} = User.update_and_set_cache(cng)
379 ModerationLog.insert_log(%{
383 permission: permission_group
390 def right_delete(conn, _) do
391 render_error(conn, :not_found, "No such permission_group")
394 def set_activation_status(%{assigns: %{user: admin}} = conn, %{
395 "nickname" => nickname,
398 with {:ok, status} <- Ecto.Type.cast(:boolean, status),
399 %User{} = user <- User.get_cached_by_nickname(nickname),
400 {:ok, _} <- User.deactivate(user, !status) do
401 action = if(user.info.deactivated, do: "activate", else: "deactivate")
403 ModerationLog.insert_log(%{
409 json_response(conn, :no_content, "")
413 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
414 with {:ok, _message} <- Relay.follow(target) do
415 ModerationLog.insert_log(%{
416 action: "relay_follow",
430 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
431 with {:ok, _message} <- Relay.unfollow(target) do
432 ModerationLog.insert_log(%{
433 action: "relay_unfollow",
447 @doc "Sends registration invite via email"
448 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
450 Pleroma.Config.get([:instance, :invites_enabled]) &&
451 !Pleroma.Config.get([:instance, :registrations_open]),
452 {:ok, invite_token} <- UserInviteToken.create_invite(),
454 Pleroma.Emails.UserEmail.user_invitation_email(
460 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
461 json_response(conn, :no_content, "")
465 @doc "Get a account registration invite token (base64 string)"
466 def get_invite_token(conn, params) do
467 options = params["invite"] || %{}
468 {:ok, invite} = UserInviteToken.create_invite(options)
471 |> json(invite.token)
474 @doc "Get list of created invites"
475 def invites(conn, _params) do
476 invites = UserInviteToken.list_invites()
479 |> json(AccountView.render("invites.json", %{invites: invites}))
482 @doc "Revokes invite by token"
483 def revoke_invite(conn, %{"token" => token}) do
484 with {:ok, invite} <- UserInviteToken.find_by_token(token),
485 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
487 |> json(AccountView.render("invite.json", %{invite: updated_invite}))
489 nil -> {:error, :not_found}
493 @doc "Get a password reset token (base64 string) for given nickname"
494 def get_password_reset(conn, %{"nickname" => nickname}) do
495 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
496 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
502 def list_reports(conn, params) do
505 |> Map.put("type", "Flag")
506 |> Map.put("skip_preload", true)
507 |> Map.put("total", true)
509 reports = ActivityPub.fetch_activities([], params)
512 |> put_view(ReportView)
513 |> render("index.json", %{reports: reports})
516 def report_show(conn, %{"id" => id}) do
517 with %Activity{} = report <- Activity.get_by_id(id) do
519 |> put_view(ReportView)
520 |> render("show.json", %{report: report})
522 _ -> {:error, :not_found}
526 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
527 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
528 ModerationLog.insert_log(%{
529 action: "report_update",
535 |> put_view(ReportView)
536 |> render("show.json", %{report: report})
540 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
541 with false <- is_nil(params["status"]),
542 %Activity{} <- Activity.get_by_id(id) do
545 |> Map.put("in_reply_to_status_id", id)
546 |> Map.put("visibility", "direct")
548 {:ok, activity} = CommonAPI.post(user, params)
550 ModerationLog.insert_log(%{
551 action: "report_response",
554 text: params["status"]
558 |> put_view(StatusView)
559 |> render("status.json", %{activity: activity})
569 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
570 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
571 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
573 ModerationLog.insert_log(%{
574 action: "status_update",
577 sensitive: sensitive,
578 visibility: params["visibility"]
582 |> put_view(StatusView)
583 |> render("status.json", %{activity: activity})
587 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
588 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
589 ModerationLog.insert_log(%{
590 action: "status_delete",
599 def list_log(conn, params) do
600 {page, page_size} = page_params(params)
602 log = ModerationLog.get_all(page, page_size)
605 |> put_view(ModerationLogView)
606 |> render("index.json", %{log: log})
609 def migrate_to_db(conn, _params) do
610 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
614 def migrate_from_db(conn, _params) do
615 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
619 def config_show(conn, _params) do
620 configs = Pleroma.Repo.all(Config)
623 |> put_view(ConfigView)
624 |> render("index.json", %{configs: configs})
627 def config_update(conn, %{"configs" => configs}) do
629 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
632 %{"group" => group, "key" => key, "delete" => "true"} = params ->
633 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
636 %{"group" => group, "key" => key, "value" => value} ->
637 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
640 |> Enum.reject(&is_nil(&1))
642 Pleroma.Config.TransferTask.load_and_update_env()
643 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
650 |> put_view(ConfigView)
651 |> render("index.json", %{configs: updated})
654 def errors(conn, {:error, :not_found}) do
656 |> put_status(:not_found)
657 |> json(dgettext("errors", "Not found"))
660 def errors(conn, {:error, reason}) do
662 |> put_status(:bad_request)
666 def errors(conn, {:param_cast, _}) do
668 |> put_status(:bad_request)
669 |> json(dgettext("errors", "Invalid parameters"))
672 def errors(conn, _) do
674 |> put_status(:internal_server_error)
675 |> json(dgettext("errors", "Something went wrong"))
678 defp page_params(params) do
679 {get_page(params["page"]), get_page_size(params["page_size"])}
682 defp get_page(page_string) when is_nil(page_string), do: 1
684 defp get_page(page_string) do
685 case Integer.parse(page_string) do
691 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
693 defp get_page_size(page_size_string) do
694 case Integer.parse(page_size_string) do
695 {page_size, _} -> page_size
696 :error -> @users_page_size