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_instance_statuses(conn, %{"instance" => instance} = params) do
230 {page, page_size} = page_params(params)
233 ActivityPub.fetch_instance_activities(%{
234 "instance" => instance,
235 "limit" => page_size,
236 "offset" => (page - 1) * page_size
240 |> put_view(StatusView)
241 |> render("index.json", %{activities: activities, as: :activity})
244 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
245 godmode = params["godmode"] == "true" || params["godmode"] == true
247 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
248 {_, page_size} = page_params(params)
251 ActivityPub.fetch_user_activities(user, nil, %{
252 "limit" => page_size,
257 |> put_view(StatusView)
258 |> render("index.json", %{activities: activities, as: :activity})
260 _ -> {:error, :not_found}
264 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
265 user = User.get_cached_by_nickname(nickname)
267 {:ok, updated_user} = User.deactivate(user, !user.deactivated)
269 action = if user.deactivated, do: "activate", else: "deactivate"
271 ModerationLog.insert_log(%{
278 |> put_view(AccountView)
279 |> render("show.json", %{user: updated_user})
282 def user_activate(%{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, false)
286 ModerationLog.insert_log(%{
293 |> put_view(AccountView)
294 |> render("index.json", %{users: Keyword.values(updated_users)})
297 def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
298 users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
299 {:ok, updated_users} = User.deactivate(users, true)
301 ModerationLog.insert_log(%{
308 |> put_view(AccountView)
309 |> render("index.json", %{users: Keyword.values(updated_users)})
312 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
313 with {:ok, _} <- User.tag(nicknames, tags) do
314 ModerationLog.insert_log(%{
316 nicknames: nicknames,
321 json_response(conn, :no_content, "")
325 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
326 with {:ok, _} <- User.untag(nicknames, tags) do
327 ModerationLog.insert_log(%{
329 nicknames: nicknames,
334 json_response(conn, :no_content, "")
338 def list_users(conn, params) do
339 {page, page_size} = page_params(params)
340 filters = maybe_parse_filters(params["filters"])
343 query: params["query"],
345 page_size: page_size,
346 tags: params["tags"],
347 name: params["name"],
348 email: params["email"]
351 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
352 {:ok, users, count} <- filter_relay_user(users, count),
356 AccountView.render("index.json",
364 defp filter_relay_user(users, count) do
365 filtered_users = Enum.reject(users, &relay_user?/1)
366 count = if Enum.any?(users, &relay_user?/1), do: length(filtered_users), else: count
368 {:ok, filtered_users, count}
371 defp relay_user?(user) do
372 user.ap_id == Relay.relay_ap_id()
375 @filters ~w(local external active deactivated is_admin is_moderator)
377 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
378 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
380 defp maybe_parse_filters(filters) do
383 |> Enum.filter(&Enum.member?(@filters, &1))
384 |> Enum.map(&String.to_atom(&1))
385 |> Enum.into(%{}, &{&1, true})
388 def right_add_multiple(%{assigns: %{user: admin}} = conn, %{
389 "permission_group" => permission_group,
390 "nicknames" => nicknames
392 when permission_group in ["moderator", "admin"] do
393 update = %{:"is_#{permission_group}" => true}
395 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
397 for u <- users, do: User.admin_api_update(u, update)
399 ModerationLog.insert_log(%{
403 permission: permission_group
409 def right_add_multiple(conn, _) do
410 render_error(conn, :not_found, "No such permission_group")
413 def right_add(%{assigns: %{user: admin}} = conn, %{
414 "permission_group" => permission_group,
415 "nickname" => nickname
417 when permission_group in ["moderator", "admin"] do
418 fields = %{:"is_#{permission_group}" => true}
422 |> User.get_cached_by_nickname()
423 |> User.admin_api_update(fields)
425 ModerationLog.insert_log(%{
429 permission: permission_group
435 def right_add(conn, _) do
436 render_error(conn, :not_found, "No such permission_group")
439 def right_get(conn, %{"nickname" => nickname}) do
440 user = User.get_cached_by_nickname(nickname)
444 is_moderator: user.is_moderator,
445 is_admin: user.is_admin
449 def right_delete_multiple(
450 %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn,
452 "permission_group" => permission_group,
453 "nicknames" => nicknames
456 when permission_group in ["moderator", "admin"] do
457 with false <- Enum.member?(nicknames, admin_nickname) do
458 update = %{:"is_#{permission_group}" => false}
460 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
462 for u <- users, do: User.admin_api_update(u, update)
464 ModerationLog.insert_log(%{
468 permission: permission_group
473 _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.")
477 def right_delete_multiple(conn, _) do
478 render_error(conn, :not_found, "No such permission_group")
482 %{assigns: %{user: admin}} = conn,
484 "permission_group" => permission_group,
485 "nickname" => nickname
488 when permission_group in ["moderator", "admin"] do
489 fields = %{:"is_#{permission_group}" => false}
493 |> User.get_cached_by_nickname()
494 |> User.admin_api_update(fields)
496 ModerationLog.insert_log(%{
500 permission: permission_group
506 def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do
507 render_error(conn, :forbidden, "You can't revoke your own admin status.")
510 def relay_list(conn, _params) do
511 with {:ok, list} <- Relay.list() do
512 json(conn, %{relays: list})
520 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
521 with {:ok, _message} <- Relay.follow(target) do
522 ModerationLog.insert_log(%{
523 action: "relay_follow",
537 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
538 with {:ok, _message} <- Relay.unfollow(target) do
539 ModerationLog.insert_log(%{
540 action: "relay_unfollow",
554 @doc "Sends registration invite via email"
555 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
557 Pleroma.Config.get([:instance, :invites_enabled]) &&
558 !Pleroma.Config.get([:instance, :registrations_open]),
559 {:ok, invite_token} <- UserInviteToken.create_invite(),
561 Pleroma.Emails.UserEmail.user_invitation_email(
567 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
568 json_response(conn, :no_content, "")
572 @doc "Create an account registration invite token"
573 def create_invite_token(conn, params) do
577 if params["max_use"],
578 do: Map.put(opts, :max_use, params["max_use"]),
582 if params["expires_at"],
583 do: Map.put(opts, :expires_at, params["expires_at"]),
586 {:ok, invite} = UserInviteToken.create_invite(opts)
588 json(conn, AccountView.render("invite.json", %{invite: invite}))
591 @doc "Get list of created invites"
592 def invites(conn, _params) do
593 invites = UserInviteToken.list_invites()
596 |> put_view(AccountView)
597 |> render("invites.json", %{invites: invites})
600 @doc "Revokes invite by token"
601 def revoke_invite(conn, %{"token" => token}) do
602 with {:ok, invite} <- UserInviteToken.find_by_token(token),
603 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
605 |> put_view(AccountView)
606 |> render("invite.json", %{invite: updated_invite})
608 nil -> {:error, :not_found}
612 @doc "Get a password reset token (base64 string) for given nickname"
613 def get_password_reset(conn, %{"nickname" => nickname}) do
614 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
615 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
620 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
624 @doc "Force password reset for a given user"
625 def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do
626 users = nicknames |> Enum.map(&User.get_cached_by_nickname/1)
628 Enum.map(users, &User.force_password_reset_async/1)
630 ModerationLog.insert_log(%{
633 action: "force_password_reset"
636 json_response(conn, :no_content, "")
639 def list_reports(conn, params) do
640 {page, page_size} = page_params(params)
644 |> Map.put("type", "Flag")
645 |> Map.put("skip_preload", true)
646 |> Map.put("total", true)
647 |> Map.put("limit", page_size)
648 |> Map.put("offset", (page - 1) * page_size)
650 reports = ActivityPub.fetch_activities([], params, :offset)
653 |> put_view(ReportView)
654 |> render("index.json", %{reports: reports})
657 def report_show(conn, %{"id" => id}) do
658 with %Activity{} = report <- Activity.get_by_id(id) do
660 |> put_view(ReportView)
661 |> render("show.json", Report.extract_report_info(report))
663 _ -> {:error, :not_found}
667 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
668 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
669 ModerationLog.insert_log(%{
670 action: "report_update",
676 |> put_view(ReportView)
677 |> render("show.json", Report.extract_report_info(report))
681 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
682 with false <- is_nil(params["status"]),
683 %Activity{} <- Activity.get_by_id(id) do
686 |> Map.put("in_reply_to_status_id", id)
687 |> Map.put("visibility", "direct")
689 {:ok, activity} = CommonAPI.post(user, params)
691 ModerationLog.insert_log(%{
692 action: "report_response",
695 text: params["status"]
699 |> put_view(StatusView)
700 |> render("show.json", %{activity: activity})
710 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
711 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
712 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
714 ModerationLog.insert_log(%{
715 action: "status_update",
718 sensitive: sensitive,
719 visibility: params["visibility"]
723 |> put_view(StatusView)
724 |> render("show.json", %{activity: activity})
728 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
729 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
730 ModerationLog.insert_log(%{
731 action: "status_delete",
740 def list_log(conn, params) do
741 {page, page_size} = page_params(params)
744 ModerationLog.get_all(%{
746 page_size: page_size,
747 start_date: params["start_date"],
748 end_date: params["end_date"],
749 user_id: params["user_id"],
750 search: params["search"]
754 |> put_view(ModerationLogView)
755 |> render("index.json", %{log: log})
758 def migrate_to_db(conn, _params) do
759 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
763 def migrate_from_db(conn, _params) do
764 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
768 def config_show(conn, _params) do
769 configs = Pleroma.Repo.all(Config)
772 |> put_view(ConfigView)
773 |> render("index.json", %{configs: configs})
776 def config_update(conn, %{"configs" => configs}) do
778 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
781 %{"group" => group, "key" => key, "delete" => "true"} = params ->
782 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
785 %{"group" => group, "key" => key, "value" => value} ->
786 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
789 |> Enum.reject(&is_nil(&1))
791 Pleroma.Config.TransferTask.load_and_update_env()
792 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
799 |> put_view(ConfigView)
800 |> render("index.json", %{configs: updated})
803 def reload_emoji(conn, _params) do
804 Pleroma.Emoji.reload()
809 def errors(conn, {:error, :not_found}) do
811 |> put_status(:not_found)
812 |> json(dgettext("errors", "Not found"))
815 def errors(conn, {:error, reason}) do
817 |> put_status(:bad_request)
821 def errors(conn, {:param_cast, _}) do
823 |> put_status(:bad_request)
824 |> json(dgettext("errors", "Invalid parameters"))
827 def errors(conn, _) do
829 |> put_status(:internal_server_error)
830 |> json(dgettext("errors", "Something went wrong"))
833 defp page_params(params) do
834 {get_page(params["page"]), get_page_size(params["page_size"])}
837 defp get_page(page_string) when is_nil(page_string), do: 1
839 defp get_page(page_string) do
840 case Integer.parse(page_string) do
846 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
848 defp get_page_size(page_size_string) do
849 case Integer.parse(page_size_string) do
850 {page_size, _} -> page_size
851 :error -> @users_page_size