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.Report
19 alias Pleroma.Web.AdminAPI.ReportView
20 alias Pleroma.Web.AdminAPI.Search
21 alias Pleroma.Web.CommonAPI
22 alias Pleroma.Web.Endpoint
23 alias Pleroma.Web.MastodonAPI.StatusView
24 alias Pleroma.Web.Router
26 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
32 %{scopes: ["read:accounts"]}
33 when action in [:list_users, :user_show, :right_get, :invites]
38 %{scopes: ["write:accounts"]}
48 :user_toggle_activation,
53 :set_activation_status
59 %{scopes: ["read:reports"]} when action in [:list_reports, :report_show]
64 %{scopes: ["write:reports"]}
65 when action in [:report_update_state, :report_respond]
70 %{scopes: ["read:statuses"]} when action == :list_user_statuses
75 %{scopes: ["write:statuses"]}
76 when action in [:status_update, :status_delete]
82 when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log]
88 when action in [:relay_follow, :relay_unfollow, :config_update]
93 action_fallback(:errors)
95 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
96 user = User.get_cached_by_nickname(nickname)
99 ModerationLog.insert_log(%{
109 def user_follow(%{assigns: %{user: admin}} = conn, %{
110 "follower" => follower_nick,
111 "followed" => followed_nick
113 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
114 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
115 User.follow(follower, followed)
117 ModerationLog.insert_log(%{
129 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
130 "follower" => follower_nick,
131 "followed" => followed_nick
133 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
134 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
135 User.unfollow(follower, followed)
137 ModerationLog.insert_log(%{
149 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
151 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
157 password_confirmation: password,
161 User.register_changeset(%User{}, user_data, need_confirmation: false)
163 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
164 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
167 case Pleroma.Repo.transaction(changesets) do
172 |> Enum.map(fn user ->
173 {:ok, user} = User.post_register_action(user)
177 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
179 ModerationLog.insert_log(%{
181 subjects: Map.values(users),
188 {:error, id, changeset, _} ->
190 Enum.map(changesets.operations, fn
191 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
192 AccountView.render("create-error.json", %{changeset: changeset})
194 {_, {:changeset, current_changeset, _}} ->
195 AccountView.render("create-error.json", %{changeset: current_changeset})
199 |> put_status(:conflict)
204 def user_show(conn, %{"nickname" => nickname}) do
205 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
207 |> put_view(AccountView)
208 |> render("show.json", %{user: user})
210 _ -> {:error, :not_found}
214 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
215 godmode = params["godmode"] == "true" || params["godmode"] == true
217 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
218 {_, page_size} = page_params(params)
221 ActivityPub.fetch_user_activities(user, nil, %{
222 "limit" => page_size,
227 |> put_view(StatusView)
228 |> render("index.json", %{activities: activities, as: :activity})
230 _ -> {:error, :not_found}
234 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
235 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
236 {:ok, updated_users} = User.deactivate(users, false)
238 ModerationLog.insert_log(%{
245 |> put_view(AccountView)
246 |> render("index.json", %{users: Keyword.values(updated_users)})
249 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
250 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
251 {:ok, updated_users} = User.deactivate(users, true)
253 ModerationLog.insert_log(%{
260 |> put_view(AccountView)
261 |> render("index.json", %{users: Keyword.values(updated_users)})
264 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
265 with {:ok, _} <- User.tag(nicknames, tags) do
266 ModerationLog.insert_log(%{
268 nicknames: nicknames,
273 json_response(conn, :no_content, "")
277 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
278 with {:ok, _} <- User.untag(nicknames, tags) do
279 ModerationLog.insert_log(%{
281 nicknames: nicknames,
286 json_response(conn, :no_content, "")
290 def list_users(conn, params) do
291 {page, page_size} = page_params(params)
292 filters = maybe_parse_filters(params["filters"])
295 query: params["query"],
297 page_size: page_size,
298 tags: params["tags"],
299 name: params["name"],
300 email: params["email"]
303 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
307 AccountView.render("index.json",
315 @filters ~w(local external active deactivated is_admin is_moderator)
317 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
318 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
320 defp maybe_parse_filters(filters) do
323 |> Enum.filter(&Enum.member?(@filters, &1))
324 |> Enum.map(&String.to_atom(&1))
325 |> Enum.into(%{}, &{&1, true})
328 def right_add(%{assigns: %{user: admin}} = conn, %{
329 "permission_group" => permission_group,
330 "nicknames" => nicknames
332 when permission_group in ["moderator", "admin"] do
333 info = Map.put(%{}, "is_" <> permission_group, true)
335 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
337 User.update_info(users, &User.Info.admin_api_update(&1, info))
339 ModerationLog.insert_log(%{
343 permission: permission_group
349 def right_add(conn, _) do
350 render_error(conn, :not_found, "No such permission_group")
353 def right_get(conn, %{"nickname" => nickname}) do
354 user = User.get_cached_by_nickname(nickname)
358 is_moderator: user.info.is_moderator,
359 is_admin: user.info.is_admin
364 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
366 "permission_group" => permission_group,
367 "nicknames" => nicknames
370 when permission_group in ["moderator", "admin"] do
371 with false <- Enum.member?(nicknames, admin_nickname) do
372 info = Map.put(%{}, "is_" <> permission_group, false)
374 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
376 User.update_info(users, &User.Info.admin_api_update(&1, info))
378 ModerationLog.insert_log(%{
382 permission: permission_group
387 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
391 def right_delete(conn, _) do
392 render_error(conn, :not_found, "No such permission_group")
395 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
396 with {:ok, _message} <- Relay.follow(target) do
397 ModerationLog.insert_log(%{
398 action: "relay_follow",
412 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
413 with {:ok, _message} <- Relay.unfollow(target) do
414 ModerationLog.insert_log(%{
415 action: "relay_unfollow",
429 @doc "Sends registration invite via email"
430 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
432 Pleroma.Config.get([:instance, :invites_enabled]) &&
433 !Pleroma.Config.get([:instance, :registrations_open]),
434 {:ok, invite_token} <- UserInviteToken.create_invite(),
436 Pleroma.Emails.UserEmail.user_invitation_email(
442 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
443 json_response(conn, :no_content, "")
447 @doc "Create an account registration invite token"
448 def create_invite_token(conn, params) do
452 if params["max_use"],
453 do: Map.put(opts, :max_use, params["max_use"]),
457 if params["expires_at"],
458 do: Map.put(opts, :expires_at, params["expires_at"]),
461 {:ok, invite} = UserInviteToken.create_invite(opts)
463 json(conn, AccountView.render("invite.json", %{invite: invite}))
466 @doc "Get list of created invites"
467 def invites(conn, _params) do
468 invites = UserInviteToken.list_invites()
471 |> put_view(AccountView)
472 |> render("invites.json", %{invites: invites})
475 @doc "Revokes invite by token"
476 def revoke_invite(conn, %{"token" => token}) do
477 with {:ok, invite} <- UserInviteToken.find_by_token(token),
478 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
480 |> put_view(AccountView)
481 |> render("invite.json", %{invite: updated_invite})
483 nil -> {:error, :not_found}
487 @doc "Get a password reset token (base64 string) for given nickname"
488 def get_password_reset(conn, %{"nickname" => nickname}) do
489 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
490 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
495 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
499 @doc "Force password reset for a given user"
500 def force_password_reset(conn, %{"nickname" => nickname}) do
501 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
503 User.force_password_reset_async(user)
505 json_response(conn, :no_content, "")
508 def list_reports(conn, params) do
509 {page, page_size} = page_params(params)
513 |> Map.put("type", "Flag")
514 |> Map.put("skip_preload", true)
515 |> Map.put("total", true)
516 |> Map.put("limit", page_size)
517 |> Map.put("offset", (page - 1) * page_size)
519 reports = ActivityPub.fetch_activities([], params, :offset)
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.extract_report_info(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.extract_report_info(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("show.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("show.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)
613 ModerationLog.get_all(%{
615 page_size: page_size,
616 start_date: params["start_date"],
617 end_date: params["end_date"],
618 user_id: params["user_id"],
619 search: params["search"]
623 |> put_view(ModerationLogView)
624 |> render("index.json", %{log: log})
627 def migrate_to_db(conn, _params) do
628 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
632 def migrate_from_db(conn, _params) do
633 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
637 def config_show(conn, _params) do
638 configs = Pleroma.Repo.all(Config)
641 |> put_view(ConfigView)
642 |> render("index.json", %{configs: configs})
645 def config_update(conn, %{"configs" => configs}) do
647 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
650 %{"group" => group, "key" => key, "delete" => "true"} = params ->
651 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
654 %{"group" => group, "key" => key, "value" => value} ->
655 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
658 |> Enum.reject(&is_nil(&1))
660 Pleroma.Config.TransferTask.load_and_update_env()
661 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
668 |> put_view(ConfigView)
669 |> render("index.json", %{configs: updated})
672 def reload_emoji(conn, _params) do
673 Pleroma.Emoji.reload()
678 def errors(conn, {:error, :not_found}) do
680 |> put_status(:not_found)
681 |> json(dgettext("errors", "Not found"))
684 def errors(conn, {:error, reason}) do
686 |> put_status(:bad_request)
690 def errors(conn, {:param_cast, _}) do
692 |> put_status(:bad_request)
693 |> json(dgettext("errors", "Invalid parameters"))
696 def errors(conn, _) do
698 |> put_status(:internal_server_error)
699 |> json(dgettext("errors", "Something went wrong"))
702 defp page_params(params) do
703 {get_page(params["page"]), get_page_size(params["page_size"])}
706 defp get_page(page_string) when is_nil(page_string), do: 1
708 defp get_page(page_string) do
709 case Integer.parse(page_string) do
715 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
717 defp get_page_size(page_size_string) do
718 case Integer.parse(page_size_string) do
719 {page_size, _} -> page_size
720 :error -> @users_page_size