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 "Create an account registration invite token"
466 def create_invite_token(conn, params) do
470 if params["max_use"],
471 do: Map.put(opts, :max_use, params["max_use"]),
475 if params["expires_at"],
476 do: Map.put(opts, :expires_at, params["expires_at"]),
479 {:ok, invite} = UserInviteToken.create_invite(opts)
481 json(conn, AccountView.render("invite.json", %{invite: invite}))
484 @doc "Get list of created invites"
485 def invites(conn, _params) do
486 invites = UserInviteToken.list_invites()
489 |> json(AccountView.render("invites.json", %{invites: invites}))
492 @doc "Revokes invite by token"
493 def revoke_invite(conn, %{"token" => token}) do
494 with {:ok, invite} <- UserInviteToken.find_by_token(token),
495 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
497 |> json(AccountView.render("invite.json", %{invite: updated_invite}))
499 nil -> {:error, :not_found}
503 @doc "Get a password reset token (base64 string) for given nickname"
504 def get_password_reset(conn, %{"nickname" => nickname}) do
505 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
506 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
512 def list_reports(conn, params) do
515 |> Map.put("type", "Flag")
516 |> Map.put("skip_preload", true)
517 |> Map.put("total", true)
519 reports = ActivityPub.fetch_activities([], params)
522 |> put_view(ReportView)
523 |> render("index.json", %{reports: reports})
526 def report_show(conn, %{"id" => id}) do
527 with %Activity{} = report <- Activity.get_by_id(id) do
529 |> put_view(ReportView)
530 |> render("show.json", %{report: report})
532 _ -> {:error, :not_found}
536 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
537 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
538 ModerationLog.insert_log(%{
539 action: "report_update",
545 |> put_view(ReportView)
546 |> render("show.json", %{report: report})
550 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
551 with false <- is_nil(params["status"]),
552 %Activity{} <- Activity.get_by_id(id) do
555 |> Map.put("in_reply_to_status_id", id)
556 |> Map.put("visibility", "direct")
558 {:ok, activity} = CommonAPI.post(user, params)
560 ModerationLog.insert_log(%{
561 action: "report_response",
564 text: params["status"]
568 |> put_view(StatusView)
569 |> render("status.json", %{activity: activity})
579 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
580 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
581 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
583 ModerationLog.insert_log(%{
584 action: "status_update",
587 sensitive: sensitive,
588 visibility: params["visibility"]
592 |> put_view(StatusView)
593 |> render("status.json", %{activity: activity})
597 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
598 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
599 ModerationLog.insert_log(%{
600 action: "status_delete",
609 def list_log(conn, params) do
610 {page, page_size} = page_params(params)
612 log = ModerationLog.get_all(page, page_size)
615 |> put_view(ModerationLogView)
616 |> render("index.json", %{log: log})
619 def migrate_to_db(conn, _params) do
620 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
624 def migrate_from_db(conn, _params) do
625 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
629 def config_show(conn, _params) do
630 configs = Pleroma.Repo.all(Config)
633 |> put_view(ConfigView)
634 |> render("index.json", %{configs: configs})
637 def config_update(conn, %{"configs" => configs}) do
639 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
642 %{"group" => group, "key" => key, "delete" => "true"} = params ->
643 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
646 %{"group" => group, "key" => key, "value" => value} ->
647 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
650 |> Enum.reject(&is_nil(&1))
652 Pleroma.Config.TransferTask.load_and_update_env()
653 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
660 |> put_view(ConfigView)
661 |> render("index.json", %{configs: updated})
664 def errors(conn, {:error, :not_found}) do
666 |> put_status(:not_found)
667 |> json(dgettext("errors", "Not found"))
670 def errors(conn, {:error, reason}) do
672 |> put_status(:bad_request)
676 def errors(conn, {:param_cast, _}) do
678 |> put_status(:bad_request)
679 |> json(dgettext("errors", "Invalid parameters"))
682 def errors(conn, _) do
684 |> put_status(:internal_server_error)
685 |> json(dgettext("errors", "Something went wrong"))
688 defp page_params(params) do
689 {get_page(params["page"]), get_page_size(params["page_size"])}
692 defp get_page(page_string) when is_nil(page_string), do: 1
694 defp get_page(page_string) do
695 case Integer.parse(page_string) do
701 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
703 defp get_page_size(page_size_string) do
704 case Integer.parse(page_size_string) do
705 {page_size, _} -> page_size
706 :error -> @users_page_size