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,
55 :set_activation_status
61 %{scopes: ["read:reports"]} when action in [:list_reports, :report_show]
66 %{scopes: ["write:reports"]}
67 when action in [:report_update_state, :report_respond]
72 %{scopes: ["read:statuses"]} when action == :list_user_statuses
77 %{scopes: ["write:statuses"]}
78 when action in [:status_update, :status_delete]
84 when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log]
90 when action in [:relay_follow, :relay_unfollow, :config_update]
95 action_fallback(:errors)
97 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
98 user = User.get_cached_by_nickname(nickname)
101 ModerationLog.insert_log(%{
111 def user_follow(%{assigns: %{user: admin}} = conn, %{
112 "follower" => follower_nick,
113 "followed" => followed_nick
115 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
116 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
117 User.follow(follower, followed)
119 ModerationLog.insert_log(%{
131 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
132 "follower" => follower_nick,
133 "followed" => followed_nick
135 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
136 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
137 User.unfollow(follower, followed)
139 ModerationLog.insert_log(%{
151 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
153 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
159 password_confirmation: password,
163 User.register_changeset(%User{}, user_data, need_confirmation: false)
165 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
166 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
169 case Pleroma.Repo.transaction(changesets) do
174 |> Enum.map(fn user ->
175 {:ok, user} = User.post_register_action(user)
179 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
181 ModerationLog.insert_log(%{
183 subjects: Map.values(users),
190 {:error, id, changeset, _} ->
192 Enum.map(changesets.operations, fn
193 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
194 AccountView.render("create-error.json", %{changeset: changeset})
196 {_, {:changeset, current_changeset, _}} ->
197 AccountView.render("create-error.json", %{changeset: current_changeset})
201 |> put_status(:conflict)
206 def user_show(conn, %{"nickname" => nickname}) do
207 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
209 |> put_view(AccountView)
210 |> render("show.json", %{user: user})
212 _ -> {:error, :not_found}
216 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
217 godmode = params["godmode"] == "true" || params["godmode"] == true
219 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
220 {_, page_size} = page_params(params)
223 ActivityPub.fetch_user_activities(user, nil, %{
224 "limit" => page_size,
229 |> put_view(StatusView)
230 |> render("index.json", %{activities: activities, as: :activity})
232 _ -> {:error, :not_found}
236 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
237 user = User.get_cached_by_nickname(nickname)
239 {:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
241 action = if user.info.deactivated, do: "activate", else: "deactivate"
243 ModerationLog.insert_log(%{
250 |> put_view(AccountView)
251 |> render("show.json", %{user: updated_user})
254 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
255 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
256 {:ok, updated_users} = User.deactivate(users, false)
258 ModerationLog.insert_log(%{
265 |> put_view(AccountView)
266 |> render("index.json", %{users: Keyword.values(updated_users)})
269 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
270 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
271 {:ok, updated_users} = User.deactivate(users, true)
273 ModerationLog.insert_log(%{
280 |> put_view(AccountView)
281 |> render("index.json", %{users: Keyword.values(updated_users)})
284 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
285 with {:ok, _} <- User.tag(nicknames, tags) do
286 ModerationLog.insert_log(%{
288 nicknames: nicknames,
293 json_response(conn, :no_content, "")
297 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
298 with {:ok, _} <- User.untag(nicknames, tags) do
299 ModerationLog.insert_log(%{
301 nicknames: nicknames,
306 json_response(conn, :no_content, "")
310 def list_users(conn, params) do
311 {page, page_size} = page_params(params)
312 filters = maybe_parse_filters(params["filters"])
315 query: params["query"],
317 page_size: page_size,
318 tags: params["tags"],
319 name: params["name"],
320 email: params["email"]
323 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
327 AccountView.render("index.json",
335 @filters ~w(local external active deactivated is_admin is_moderator)
337 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
338 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
340 defp maybe_parse_filters(filters) do
343 |> Enum.filter(&Enum.member?(@filters, &1))
344 |> Enum.map(&String.to_atom(&1))
345 |> Enum.into(%{}, &{&1, true})
348 def right_add(%{assigns: %{user: admin}} = conn, %{
349 "permission_group" => permission_group,
350 "nicknames" => nicknames
352 when permission_group in ["moderator", "admin"] do
353 info = Map.put(%{}, "is_" <> permission_group, true)
355 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
357 User.update_info(users, &User.Info.admin_api_update(&1, info))
359 ModerationLog.insert_log(%{
363 permission: permission_group
369 def right_add(conn, _) do
370 render_error(conn, :not_found, "No such permission_group")
373 def right_get(conn, %{"nickname" => nickname}) do
374 user = User.get_cached_by_nickname(nickname)
378 is_moderator: user.info.is_moderator,
379 is_admin: user.info.is_admin
384 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
386 "permission_group" => permission_group,
387 "nicknames" => nicknames
390 when permission_group in ["moderator", "admin"] do
391 with false <- Enum.member?(nicknames, admin_nickname) do
392 info = Map.put(%{}, "is_" <> permission_group, false)
394 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
396 User.update_info(users, &User.Info.admin_api_update(&1, info))
398 ModerationLog.insert_log(%{
402 permission: permission_group
407 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
411 def right_delete(conn, _) do
412 render_error(conn, :not_found, "No such permission_group")
415 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
416 with {:ok, _message} <- Relay.follow(target) do
417 ModerationLog.insert_log(%{
418 action: "relay_follow",
432 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
433 with {:ok, _message} <- Relay.unfollow(target) do
434 ModerationLog.insert_log(%{
435 action: "relay_unfollow",
449 @doc "Sends registration invite via email"
450 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
452 Pleroma.Config.get([:instance, :invites_enabled]) &&
453 !Pleroma.Config.get([:instance, :registrations_open]),
454 {:ok, invite_token} <- UserInviteToken.create_invite(),
456 Pleroma.Emails.UserEmail.user_invitation_email(
462 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
463 json_response(conn, :no_content, "")
467 @doc "Create an account registration invite token"
468 def create_invite_token(conn, params) do
472 if params["max_use"],
473 do: Map.put(opts, :max_use, params["max_use"]),
477 if params["expires_at"],
478 do: Map.put(opts, :expires_at, params["expires_at"]),
481 {:ok, invite} = UserInviteToken.create_invite(opts)
483 json(conn, AccountView.render("invite.json", %{invite: invite}))
486 @doc "Get list of created invites"
487 def invites(conn, _params) do
488 invites = UserInviteToken.list_invites()
491 |> put_view(AccountView)
492 |> render("invites.json", %{invites: invites})
495 @doc "Revokes invite by token"
496 def revoke_invite(conn, %{"token" => token}) do
497 with {:ok, invite} <- UserInviteToken.find_by_token(token),
498 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
500 |> put_view(AccountView)
501 |> render("invite.json", %{invite: updated_invite})
503 nil -> {:error, :not_found}
507 @doc "Get a password reset token (base64 string) for given nickname"
508 def get_password_reset(conn, %{"nickname" => nickname}) do
509 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
510 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
515 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
519 @doc "Force password reset for a given user"
520 def force_password_reset(conn, %{"nickname" => nickname}) do
521 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
523 User.force_password_reset_async(user)
525 json_response(conn, :no_content, "")
528 def list_reports(conn, params) do
529 {page, page_size} = page_params(params)
533 |> Map.put("type", "Flag")
534 |> Map.put("skip_preload", true)
535 |> Map.put("total", true)
536 |> Map.put("limit", page_size)
537 |> Map.put("offset", (page - 1) * page_size)
539 reports = ActivityPub.fetch_activities([], params, :offset)
542 |> put_view(ReportView)
543 |> render("index.json", %{reports: reports})
546 def report_show(conn, %{"id" => id}) do
547 with %Activity{} = report <- Activity.get_by_id(id) do
549 |> put_view(ReportView)
550 |> render("show.json", Report.extract_report_info(report))
552 _ -> {:error, :not_found}
556 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
557 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
558 ModerationLog.insert_log(%{
559 action: "report_update",
565 |> put_view(ReportView)
566 |> render("show.json", Report.extract_report_info(report))
570 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
571 with false <- is_nil(params["status"]),
572 %Activity{} <- Activity.get_by_id(id) do
575 |> Map.put("in_reply_to_status_id", id)
576 |> Map.put("visibility", "direct")
578 {:ok, activity} = CommonAPI.post(user, params)
580 ModerationLog.insert_log(%{
581 action: "report_response",
584 text: params["status"]
588 |> put_view(StatusView)
589 |> render("show.json", %{activity: activity})
599 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
600 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
601 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
603 ModerationLog.insert_log(%{
604 action: "status_update",
607 sensitive: sensitive,
608 visibility: params["visibility"]
612 |> put_view(StatusView)
613 |> render("show.json", %{activity: activity})
617 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
618 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
619 ModerationLog.insert_log(%{
620 action: "status_delete",
629 def list_log(conn, params) do
630 {page, page_size} = page_params(params)
633 ModerationLog.get_all(%{
635 page_size: page_size,
636 start_date: params["start_date"],
637 end_date: params["end_date"],
638 user_id: params["user_id"],
639 search: params["search"]
643 |> put_view(ModerationLogView)
644 |> render("index.json", %{log: log})
647 def migrate_to_db(conn, _params) do
648 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
652 def migrate_from_db(conn, _params) do
653 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
657 def config_show(conn, _params) do
658 configs = Pleroma.Repo.all(Config)
661 |> put_view(ConfigView)
662 |> render("index.json", %{configs: configs})
665 def config_update(conn, %{"configs" => configs}) do
667 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
670 %{"group" => group, "key" => key, "delete" => "true"} = params ->
671 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
674 %{"group" => group, "key" => key, "value" => value} ->
675 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
678 |> Enum.reject(&is_nil(&1))
680 Pleroma.Config.TransferTask.load_and_update_env()
681 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
688 |> put_view(ConfigView)
689 |> render("index.json", %{configs: updated})
692 def reload_emoji(conn, _params) do
693 Pleroma.Emoji.reload()
698 def errors(conn, {:error, :not_found}) do
700 |> put_status(:not_found)
701 |> json(dgettext("errors", "Not found"))
704 def errors(conn, {:error, reason}) do
706 |> put_status(:bad_request)
710 def errors(conn, {:param_cast, _}) do
712 |> put_status(:bad_request)
713 |> json(dgettext("errors", "Invalid parameters"))
716 def errors(conn, _) do
718 |> put_status(:internal_server_error)
719 |> json(dgettext("errors", "Something went wrong"))
722 defp page_params(params) do
723 {get_page(params["page"]), get_page_size(params["page_size"])}
726 defp get_page(page_string) when is_nil(page_string), do: 1
728 defp get_page(page_string) do
729 case Integer.parse(page_string) do
735 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
737 defp get_page_size(page_size_string) do
738 case Integer.parse(page_size_string) do
739 {page_size, _} -> page_size
740 :error -> @users_page_size