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,
60 %{scopes: ["read:reports"]} when action in [:list_reports, :report_show]
65 %{scopes: ["write:reports"]}
66 when action in [:report_update_state, :report_respond]
71 %{scopes: ["read:statuses"]} when action == :list_user_statuses
76 %{scopes: ["write:statuses"]}
77 when action in [:status_update, :status_delete]
83 when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log]
89 when action in [:relay_follow, :relay_unfollow, :config_update]
94 action_fallback(:errors)
96 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
97 user = User.get_cached_by_nickname(nickname)
100 ModerationLog.insert_log(%{
110 def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
111 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
114 ModerationLog.insert_log(%{
124 def user_follow(%{assigns: %{user: admin}} = conn, %{
125 "follower" => follower_nick,
126 "followed" => followed_nick
128 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
129 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
130 User.follow(follower, followed)
132 ModerationLog.insert_log(%{
144 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
145 "follower" => follower_nick,
146 "followed" => followed_nick
148 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
149 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
150 User.unfollow(follower, followed)
152 ModerationLog.insert_log(%{
164 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
166 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
172 password_confirmation: password,
176 User.register_changeset(%User{}, user_data, need_confirmation: false)
178 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
179 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
182 case Pleroma.Repo.transaction(changesets) do
187 |> Enum.map(fn user ->
188 {:ok, user} = User.post_register_action(user)
192 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
194 ModerationLog.insert_log(%{
196 subjects: Map.values(users),
203 {:error, id, changeset, _} ->
205 Enum.map(changesets.operations, fn
206 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
207 AccountView.render("create-error.json", %{changeset: changeset})
209 {_, {:changeset, current_changeset, _}} ->
210 AccountView.render("create-error.json", %{changeset: current_changeset})
214 |> put_status(:conflict)
219 def user_show(conn, %{"nickname" => nickname}) do
220 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
222 |> put_view(AccountView)
223 |> render("show.json", %{user: user})
225 _ -> {:error, :not_found}
229 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
230 godmode = params["godmode"] == "true" || params["godmode"] == true
232 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
233 {_, page_size} = page_params(params)
236 ActivityPub.fetch_user_activities(user, nil, %{
237 "limit" => page_size,
242 |> put_view(StatusView)
243 |> render("index.json", %{activities: activities, as: :activity})
245 _ -> {:error, :not_found}
249 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
250 user = User.get_cached_by_nickname(nickname)
252 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
254 action = if user.deactivated, do: "activate", else: "deactivate"
256 ModerationLog.insert_log(%{
263 |> put_view(AccountView)
264 |> render("show.json", %{user: updated_user})
267 def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
268 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
269 {:ok, updated_users} = User.deactivate(users, false)
271 ModerationLog.insert_log(%{
278 |> put_view(AccountView)
279 |> render("index.json", %{users: Keyword.values(updated_users)})
282 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
283 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
284 {:ok, updated_users} = User.deactivate(users, true)
286 ModerationLog.insert_log(%{
293 |> put_view(AccountView)
294 |> render("index.json", %{users: Keyword.values(updated_users)})
297 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
298 with {:ok, _} <- User.tag(nicknames, tags) do
299 ModerationLog.insert_log(%{
301 nicknames: nicknames,
306 json_response(conn, :no_content, "")
310 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
311 with {:ok, _} <- User.untag(nicknames, tags) do
312 ModerationLog.insert_log(%{
314 nicknames: nicknames,
319 json_response(conn, :no_content, "")
323 def list_users(conn, params) do
324 {page, page_size} = page_params(params)
325 filters = maybe_parse_filters(params["filters"])
328 query: params["query"],
330 page_size: page_size,
331 tags: params["tags"],
332 name: params["name"],
333 email: params["email"]
336 with {:ok, users, _count} <- Search.user(Map.merge(search_params, filters)),
337 {:ok, users, count} <- filter_relay_user(users),
341 AccountView.render("index.json",
349 defp filter_relay_user(users) do
350 filtered_users = Enum.reject(users, &(&1.ap_id == Relay.relay_ap_id()))
352 {:ok, filtered_users, length(filtered_users)}
355 @filters ~w(local external active deactivated is_admin is_moderator)
357 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
358 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
360 defp maybe_parse_filters(filters) do
363 |> Enum.filter(&Enum.member?(@filters, &1))
364 |> Enum.map(&String.to_atom(&1))
365 |> Enum.into(%{}, &{&1, true})
368 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
369 "permission_group" => permission_group,
370 "nicknames" => nicknames
372 when permission_group in ["moderator", "admin"] do
373 update = %{:"is_#{permission_group}" => true}
375 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
377 for u <- users, do: User.admin_api_update(u, update)
379 ModerationLog.insert_log(%{
383 permission: permission_group
389 def right_add_multiple(conn, _) do
390 render_error(conn, :not_found, "No such permission_group")
393 def right_add(%{assigns: %{user: admin}} = conn, %{
394 "permission_group" => permission_group,
395 "nickname" => nickname
397 when permission_group in ["moderator", "admin"] do
398 fields = %{:"is_#{permission_group}" => true}
402 |> User.get_cached_by_nickname()
403 |> User.admin_api_update(fields)
405 ModerationLog.insert_log(%{
409 permission: permission_group
415 def right_add(conn, _) do
416 render_error(conn, :not_found, "No such permission_group")
419 def right_get(conn, %{"nickname" => nickname}) do
420 user = User.get_cached_by_nickname(nickname)
424 is_moderator: user.is_moderator,
425 is_admin: user.is_admin
429 def right_delete_multiple(
430 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
432 "permission_group" => permission_group,
433 "nicknames" => nicknames
436 when permission_group in ["moderator", "admin"] do
437 with false <- Enum.member?(nicknames, admin_nickname) do
438 update = %{:"is_#{permission_group}" => false}
440 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
442 for u <- users, do: User.admin_api_update(u, update)
444 ModerationLog.insert_log(%{
448 permission: permission_group
453 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
457 def right_delete_multiple(conn, _) do
458 render_error(conn, :not_found, "No such permission_group")
462 %{assigns: %{user: admin}} = conn,
464 "permission_group" => permission_group,
465 "nickname" => nickname
468 when permission_group in ["moderator", "admin"] do
469 fields = %{:"is_#{permission_group}" => false}
473 |> User.get_cached_by_nickname()
474 |> User.admin_api_update(fields)
476 ModerationLog.insert_log(%{
480 permission: permission_group
486 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
487 render_error(conn, :forbidden, "You can't revoke your own admin status.")
490 def relay_list(conn, _params) do
491 with {:ok, list} <- Relay.list() do
492 json(conn, %{relays: list})
500 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
501 with {:ok, _message} <- Relay.follow(target) do
502 ModerationLog.insert_log(%{
503 action: "relay_follow",
517 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
518 with {:ok, _message} <- Relay.unfollow(target) do
519 ModerationLog.insert_log(%{
520 action: "relay_unfollow",
534 @doc "Sends registration invite via email"
535 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
537 Pleroma.Config.get([:instance, :invites_enabled]) &&
538 !Pleroma.Config.get([:instance, :registrations_open]),
539 {:ok, invite_token} <- UserInviteToken.create_invite(),
541 Pleroma.Emails.UserEmail.user_invitation_email(
547 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
548 json_response(conn, :no_content, "")
552 @doc "Create an account registration invite token"
553 def create_invite_token(conn, params) do
557 if params["max_use"],
558 do: Map.put(opts, :max_use, params["max_use"]),
562 if params["expires_at"],
563 do: Map.put(opts, :expires_at, params["expires_at"]),
566 {:ok, invite} = UserInviteToken.create_invite(opts)
568 json(conn, AccountView.render("invite.json", %{invite: invite}))
571 @doc "Get list of created invites"
572 def invites(conn, _params) do
573 invites = UserInviteToken.list_invites()
576 |> put_view(AccountView)
577 |> render("invites.json", %{invites: invites})
580 @doc "Revokes invite by token"
581 def revoke_invite(conn, %{"token" => token}) do
582 with {:ok, invite} <- UserInviteToken.find_by_token(token),
583 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
585 |> put_view(AccountView)
586 |> render("invite.json", %{invite: updated_invite})
588 nil -> {:error, :not_found}
592 @doc "Get a password reset token (base64 string) for given nickname"
593 def get_password_reset(conn, %{"nickname" => nickname}) do
594 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
595 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
600 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
604 @doc "Force password reset for a given user"
605 def force_password_reset(conn, %{"nickname" => nickname}) do
606 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
608 User.force_password_reset_async(user)
610 json_response(conn, :no_content, "")
613 def list_reports(conn, params) do
614 {page, page_size} = page_params(params)
618 |> Map.put("type", "Flag")
619 |> Map.put("skip_preload", true)
620 |> Map.put("total", true)
621 |> Map.put("limit", page_size)
622 |> Map.put("offset", (page - 1) * page_size)
624 reports = ActivityPub.fetch_activities([], params, :offset)
627 |> put_view(ReportView)
628 |> render("index.json", %{reports: reports})
631 def report_show(conn, %{"id" => id}) do
632 with %Activity{} = report <- Activity.get_by_id(id) do
634 |> put_view(ReportView)
635 |> render("show.json", Report.extract_report_info(report))
637 _ -> {:error, :not_found}
641 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
642 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
643 ModerationLog.insert_log(%{
644 action: "report_update",
650 |> put_view(ReportView)
651 |> render("show.json", Report.extract_report_info(report))
655 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
656 with false <- is_nil(params["status"]),
657 %Activity{} <- Activity.get_by_id(id) do
660 |> Map.put("in_reply_to_status_id", id)
661 |> Map.put("visibility", "direct")
663 {:ok, activity} = CommonAPI.post(user, params)
665 ModerationLog.insert_log(%{
666 action: "report_response",
669 text: params["status"]
673 |> put_view(StatusView)
674 |> render("show.json", %{activity: activity})
684 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
685 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
686 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
688 ModerationLog.insert_log(%{
689 action: "status_update",
692 sensitive: sensitive,
693 visibility: params["visibility"]
697 |> put_view(StatusView)
698 |> render("show.json", %{activity: activity})
702 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
703 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
704 ModerationLog.insert_log(%{
705 action: "status_delete",
714 def list_log(conn, params) do
715 {page, page_size} = page_params(params)
718 ModerationLog.get_all(%{
720 page_size: page_size,
721 start_date: params["start_date"],
722 end_date: params["end_date"],
723 user_id: params["user_id"],
724 search: params["search"]
728 |> put_view(ModerationLogView)
729 |> render("index.json", %{log: log})
732 def migrate_to_db(conn, _params) do
733 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
737 def migrate_from_db(conn, _params) do
738 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
742 def config_show(conn, _params) do
743 configs = Pleroma.Repo.all(Config)
746 |> put_view(ConfigView)
747 |> render("index.json", %{configs: configs})
750 def config_update(conn, %{"configs" => configs}) do
752 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
755 %{"group" => group, "key" => key, "delete" => "true"} = params ->
756 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
759 %{"group" => group, "key" => key, "value" => value} ->
760 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
763 |> Enum.reject(&is_nil(&1))
765 Pleroma.Config.TransferTask.load_and_update_env()
766 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
773 |> put_view(ConfigView)
774 |> render("index.json", %{configs: updated})
777 def reload_emoji(conn, _params) do
778 Pleroma.Emoji.reload()
783 def errors(conn, {:error, :not_found}) do
785 |> put_status(:not_found)
786 |> json(dgettext("errors", "Not found"))
789 def errors(conn, {:error, reason}) do
791 |> put_status(:bad_request)
795 def errors(conn, {:param_cast, _}) do
797 |> put_status(:bad_request)
798 |> json(dgettext("errors", "Invalid parameters"))
801 def errors(conn, _) do
803 |> put_status(:internal_server_error)
804 |> json(dgettext("errors", "Something went wrong"))
807 defp page_params(params) do
808 {get_page(params["page"]), get_page_size(params["page_size"])}
811 defp get_page(page_string) when is_nil(page_string), do: 1
813 defp get_page(page_string) do
814 case Integer.parse(page_string) do
820 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
822 defp get_page_size(page_size_string) do
823 case Integer.parse(page_size_string) do
824 {page_size, _} -> page_size
825 :error -> @users_page_size