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
10 alias Pleroma.UserInviteToken
11 alias Pleroma.Web.ActivityPub.ActivityPub
12 alias Pleroma.Web.ActivityPub.Relay
13 alias Pleroma.Web.AdminAPI.AccountView
14 alias Pleroma.Web.AdminAPI.Config
15 alias Pleroma.Web.AdminAPI.ConfigView
16 alias Pleroma.Web.AdminAPI.ModerationLogView
17 alias Pleroma.Web.AdminAPI.ReportView
18 alias Pleroma.Web.AdminAPI.Search
19 alias Pleroma.Web.CommonAPI
20 alias Pleroma.Web.Endpoint
21 alias Pleroma.Web.MastodonAPI.StatusView
22 alias Pleroma.Web.Router
24 import Pleroma.Web.ControllerHelper, only: [json_response: 3]
30 action_fallback(:errors)
32 def user_delete(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
33 user = User.get_cached_by_nickname(nickname)
36 ModerationLog.insert_log(%{
46 def user_follow(%{assigns: %{user: admin}} = conn, %{
47 "follower" => follower_nick,
48 "followed" => followed_nick
50 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
51 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
52 User.follow(follower, followed)
54 ModerationLog.insert_log(%{
66 def user_unfollow(%{assigns: %{user: admin}} = conn, %{
67 "follower" => follower_nick,
68 "followed" => followed_nick
70 with %User{} = follower <- User.get_cached_by_nickname(follower_nick),
71 %User{} = followed <- User.get_cached_by_nickname(followed_nick) do
72 User.unfollow(follower, followed)
74 ModerationLog.insert_log(%{
86 def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do
88 Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} ->
94 password_confirmation: password,
98 User.register_changeset(%User{}, user_data, need_confirmation: false)
100 |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi ->
101 Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset)
104 case Pleroma.Repo.transaction(changesets) do
109 |> Enum.map(fn user ->
110 {:ok, user} = User.post_register_action(user)
114 |> Enum.map(&AccountView.render("created.json", %{user: &1}))
116 ModerationLog.insert_log(%{
118 subjects: Map.values(users),
125 {:error, id, changeset, _} ->
127 Enum.map(changesets.operations, fn
128 {current_id, {:changeset, _current_changeset, _}} when current_id == id ->
129 AccountView.render("create-error.json", %{changeset: changeset})
131 {_, {:changeset, current_changeset, _}} ->
132 AccountView.render("create-error.json", %{changeset: current_changeset})
136 |> put_status(:conflict)
141 def user_show(conn, %{"nickname" => nickname}) do
142 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
144 |> json(AccountView.render("show.json", %{user: user}))
146 _ -> {:error, :not_found}
150 def list_user_statuses(conn, %{"nickname" => nickname} = params) do
151 godmode = params["godmode"] == "true" || params["godmode"] == true
153 with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do
154 {_, page_size} = page_params(params)
157 ActivityPub.fetch_user_activities(user, nil, %{
158 "limit" => page_size,
163 |> json(StatusView.render("index.json", %{activities: activities, as: :activity}))
165 _ -> {:error, :not_found}
169 def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do
170 user = User.get_cached_by_nickname(nickname)
172 {:ok, updated_user} = User.deactivate(user, !user.info.deactivated)
174 action = if user.info.deactivated, do: "activate", else: "deactivate"
176 ModerationLog.insert_log(%{
183 |> json(AccountView.render("show.json", %{user: updated_user}))
186 def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
187 with {:ok, _} <- User.tag(nicknames, tags) do
188 ModerationLog.insert_log(%{
190 nicknames: nicknames,
195 json_response(conn, :no_content, "")
199 def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do
200 with {:ok, _} <- User.untag(nicknames, tags) do
201 ModerationLog.insert_log(%{
203 nicknames: nicknames,
208 json_response(conn, :no_content, "")
212 def list_users(conn, params) do
213 {page, page_size} = page_params(params)
214 filters = maybe_parse_filters(params["filters"])
217 query: params["query"],
219 page_size: page_size,
220 tags: params["tags"],
221 name: params["name"],
222 email: params["email"]
225 with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)),
229 AccountView.render("index.json",
237 @filters ~w(local external active deactivated is_admin is_moderator)
239 @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{}
240 defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{}
242 defp maybe_parse_filters(filters) do
245 |> Enum.filter(&Enum.member?(@filters, &1))
246 |> Enum.map(&String.to_atom(&1))
247 |> Enum.into(%{}, &{&1, true})
250 def right_add(%{assigns: %{user: admin}} = conn, %{
251 "permission_group" => permission_group,
252 "nickname" => nickname
254 when permission_group in ["moderator", "admin"] do
255 user = User.get_cached_by_nickname(nickname)
259 |> Map.put("is_" <> permission_group, true)
261 info_cng = User.Info.admin_api_update(user.info, info)
265 |> Ecto.Changeset.change()
266 |> Ecto.Changeset.put_embed(:info, info_cng)
268 ModerationLog.insert_log(%{
272 permission: permission_group
275 {:ok, _user} = User.update_and_set_cache(cng)
280 def right_add(conn, _) do
281 render_error(conn, :not_found, "No such permission_group")
284 def right_get(conn, %{"nickname" => nickname}) do
285 user = User.get_cached_by_nickname(nickname)
289 is_moderator: user.info.is_moderator,
290 is_admin: user.info.is_admin
295 %{assigns: %{user: %User{:nickname => admin_nickname} = admin}} = conn,
297 "permission_group" => permission_group,
298 "nickname" => nickname
301 when permission_group in ["moderator", "admin"] do
302 if admin_nickname == nickname do
303 render_error(conn, :forbidden, "You can't revoke your own admin status.")
305 user = User.get_cached_by_nickname(nickname)
309 |> Map.put("is_" <> permission_group, false)
311 info_cng = User.Info.admin_api_update(user.info, info)
314 Ecto.Changeset.change(user)
315 |> Ecto.Changeset.put_embed(:info, info_cng)
317 {:ok, _user} = User.update_and_set_cache(cng)
319 ModerationLog.insert_log(%{
323 permission: permission_group
330 def right_delete(conn, _) do
331 render_error(conn, :not_found, "No such permission_group")
334 def set_activation_status(%{assigns: %{user: admin}} = conn, %{
335 "nickname" => nickname,
338 with {:ok, status} <- Ecto.Type.cast(:boolean, status),
339 %User{} = user <- User.get_cached_by_nickname(nickname),
340 {:ok, _} <- User.deactivate(user, !status) do
341 action = if(user.info.deactivated, do: "activate", else: "deactivate")
343 ModerationLog.insert_log(%{
349 json_response(conn, :no_content, "")
353 def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
354 with {:ok, _message} <- Relay.follow(target) do
355 ModerationLog.insert_log(%{
356 action: "relay_follow",
370 def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do
371 with {:ok, _message} <- Relay.unfollow(target) do
372 ModerationLog.insert_log(%{
373 action: "relay_unfollow",
387 @doc "Sends registration invite via email"
388 def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
390 Pleroma.Config.get([:instance, :invites_enabled]) &&
391 !Pleroma.Config.get([:instance, :registrations_open]),
392 {:ok, invite_token} <- UserInviteToken.create_invite(),
394 Pleroma.Emails.UserEmail.user_invitation_email(
400 {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
401 json_response(conn, :no_content, "")
405 @doc "Create an account registration invite token"
406 def create_invite_token(conn, params) do
410 if params["max_use"],
411 do: Map.put(opts, :max_use, params["max_use"]),
415 if params["expires_at"],
416 do: Map.put(opts, :expires_at, params["expires_at"]),
419 {:ok, invite} = UserInviteToken.create_invite(opts)
421 json(conn, AccountView.render("invite.json", %{invite: invite}))
424 @doc "Get list of created invites"
425 def invites(conn, _params) do
426 invites = UserInviteToken.list_invites()
429 |> json(AccountView.render("invites.json", %{invites: invites}))
432 @doc "Revokes invite by token"
433 def revoke_invite(conn, %{"token" => token}) do
434 with {:ok, invite} <- UserInviteToken.find_by_token(token),
435 {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do
437 |> json(AccountView.render("invite.json", %{invite: updated_invite}))
439 nil -> {:error, :not_found}
443 @doc "Get a password reset token (base64 string) for given nickname"
444 def get_password_reset(conn, %{"nickname" => nickname}) do
445 (%User{local: true} = user) = User.get_cached_by_nickname(nickname)
446 {:ok, token} = Pleroma.PasswordResetToken.create_token(user)
451 link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token)
455 def list_reports(conn, params) do
458 |> Map.put("type", "Flag")
459 |> Map.put("skip_preload", true)
460 |> Map.put("total", true)
462 reports = ActivityPub.fetch_activities([], params)
465 |> put_view(ReportView)
466 |> render("index.json", %{reports: reports})
469 def report_show(conn, %{"id" => id}) do
470 with %Activity{} = report <- Activity.get_by_id(id) do
472 |> put_view(ReportView)
473 |> render("show.json", %{report: report})
475 _ -> {:error, :not_found}
479 def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do
480 with {:ok, report} <- CommonAPI.update_report_state(id, state) do
481 ModerationLog.insert_log(%{
482 action: "report_update",
488 |> put_view(ReportView)
489 |> render("show.json", %{report: report})
493 def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do
494 with false <- is_nil(params["status"]),
495 %Activity{} <- Activity.get_by_id(id) do
498 |> Map.put("in_reply_to_status_id", id)
499 |> Map.put("visibility", "direct")
501 {:ok, activity} = CommonAPI.post(user, params)
503 ModerationLog.insert_log(%{
504 action: "report_response",
507 text: params["status"]
511 |> put_view(StatusView)
512 |> render("status.json", %{activity: activity})
522 def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do
523 with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
524 {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"])
526 ModerationLog.insert_log(%{
527 action: "status_update",
530 sensitive: sensitive,
531 visibility: params["visibility"]
535 |> put_view(StatusView)
536 |> render("status.json", %{activity: activity})
540 def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do
541 with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
542 ModerationLog.insert_log(%{
543 action: "status_delete",
552 def list_log(conn, params) do
553 {page, page_size} = page_params(params)
555 log = ModerationLog.get_all(page, page_size)
558 |> put_view(ModerationLogView)
559 |> render("index.json", %{log: log})
562 def migrate_to_db(conn, _params) do
563 Mix.Tasks.Pleroma.Config.run(["migrate_to_db"])
567 def migrate_from_db(conn, _params) do
568 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"])
572 def config_show(conn, _params) do
573 configs = Pleroma.Repo.all(Config)
576 |> put_view(ConfigView)
577 |> render("index.json", %{configs: configs})
580 def config_update(conn, %{"configs" => configs}) do
582 if Pleroma.Config.get([:instance, :dynamic_configuration]) do
585 %{"group" => group, "key" => key, "delete" => "true"} = params ->
586 {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]})
589 %{"group" => group, "key" => key, "value" => value} ->
590 {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value})
593 |> Enum.reject(&is_nil(&1))
595 Pleroma.Config.TransferTask.load_and_update_env()
596 Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"])
603 |> put_view(ConfigView)
604 |> render("index.json", %{configs: updated})
607 def errors(conn, {:error, :not_found}) do
609 |> put_status(:not_found)
610 |> json(dgettext("errors", "Not found"))
613 def errors(conn, {:error, reason}) do
615 |> put_status(:bad_request)
619 def errors(conn, {:param_cast, _}) do
621 |> put_status(:bad_request)
622 |> json(dgettext("errors", "Invalid parameters"))
625 def errors(conn, _) do
627 |> put_status(:internal_server_error)
628 |> json(dgettext("errors", "Something went wrong"))
631 defp page_params(params) do
632 {get_page(params["page"]), get_page_size(params["page_size"])}
635 defp get_page(page_string) when is_nil(page_string), do: 1
637 defp get_page(page_string) do
638 case Integer.parse(page_string) do
644 defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size
646 defp get_page_size(page_size_string) do
647 case Integer.parse(page_size_string) do
648 {page_size, _} -> page_size
649 :error -> @users_page_size