X-Git-Url: https://git.squeep.com/?a=blobdiff_plain;ds=sidebyside;f=lib%2Fpleroma%2Fweb%2Fadmin_api%2Fadmin_api_controller.ex;h=47b7d2da38ef73c443332b96fa5d9c75912d4d4f;hb=2019f3b3ff365b61c14f01c736a6dcb68cb36624;hp=b6d3f79c8a0145123c28a3c55b3e025f5ea5b548;hpb=5c6c3026ea7bd20cb2c10f6e2e34a54734d2509e;p=akkoma diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex index b6d3f79c8..47b7d2da3 100644 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ b/lib/pleroma/web/admin_api/admin_api_controller.ex @@ -1,18 +1,25 @@ # Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors +# Copyright © 2017-2020 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only defmodule Pleroma.Web.AdminAPI.AdminAPIController do use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [json_response: 3] + alias Pleroma.Activity + alias Pleroma.Config + alias Pleroma.ConfigDB alias Pleroma.ModerationLog alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.ReportNote + alias Pleroma.Stats alias Pleroma.User alias Pleroma.UserInviteToken alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.Relay + alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.AdminAPI.AccountView - alias Pleroma.Web.AdminAPI.Config alias Pleroma.Web.AdminAPI.ConfigView alias Pleroma.Web.AdminAPI.ModerationLogView alias Pleroma.Web.AdminAPI.Report @@ -23,26 +30,22 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do alias Pleroma.Web.MastodonAPI.StatusView alias Pleroma.Web.Router - import Pleroma.Web.ControllerHelper, only: [json_response: 3] - require Logger + @descriptions_json Pleroma.Docs.JSON.compile() + @users_page_size 50 + plug( OAuthScopesPlug, - %{scopes: ["read:accounts"]} - when action in [:list_users, :user_show, :right_get, :invites] + %{scopes: ["read:accounts"], admin: true} + when action in [:list_users, :user_show, :right_get] ) plug( OAuthScopesPlug, - %{scopes: ["write:accounts"]} + %{scopes: ["write:accounts"], admin: true} when action in [ - :get_invite_token, - :revoke_invite, - :email_invite, :get_password_reset, - :user_follow, - :user_unfollow, :user_delete, :users_create, :user_toggle_activation, @@ -51,46 +54,59 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do :tag_users, :untag_users, :right_add, - :right_delete, - :set_activation_status + :right_delete ] ) + plug(OAuthScopesPlug, %{scopes: ["read:invites"], admin: true} when action == :invites) + plug( OAuthScopesPlug, - %{scopes: ["read:reports"]} when action in [:list_reports, :report_show] + %{scopes: ["write:invites"], admin: true} + when action in [:create_invite_token, :revoke_invite, :email_invite] ) plug( OAuthScopesPlug, - %{scopes: ["write:reports"]} - when action in [:report_update_state, :report_respond] + %{scopes: ["write:follows"], admin: true} + when action in [:user_follow, :user_unfollow, :relay_follow, :relay_unfollow] ) plug( OAuthScopesPlug, - %{scopes: ["read:statuses"]} when action == :list_user_statuses + %{scopes: ["read:reports"], admin: true} + when action in [:list_reports, :report_show] ) plug( OAuthScopesPlug, - %{scopes: ["write:statuses"]} - when action in [:status_update, :status_delete] + %{scopes: ["write:reports"], admin: true} + when action in [:reports_update] ) plug( OAuthScopesPlug, - %{scopes: ["read"]} - when action in [:config_show, :migrate_to_db, :migrate_from_db, :list_log] + %{scopes: ["read:statuses"], admin: true} + when action == :list_user_statuses ) plug( OAuthScopesPlug, - %{scopes: ["write"]} - when action in [:relay_follow, :relay_unfollow, :config_update] + %{scopes: ["write:statuses"], admin: true} + when action in [:status_update, :status_delete] ) - @users_page_size 50 + plug( + OAuthScopesPlug, + %{scopes: ["read"], admin: true} + when action in [:config_show, :list_log, :stats] + ) + + plug( + OAuthScopesPlug, + %{scopes: ["write"], admin: true} + when action == :config_update + ) action_fallback(:errors) @@ -227,7 +243,25 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end end + def list_instance_statuses(conn, %{"instance" => instance} = params) do + with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true + {page, page_size} = page_params(params) + + activities = + ActivityPub.fetch_statuses(nil, %{ + "instance" => instance, + "limit" => page_size, + "offset" => (page - 1) * page_size, + "exclude_reblogs" => !with_reblogs && "true" + }) + + conn + |> put_view(Pleroma.Web.AdminAPI.StatusView) + |> render("index.json", %{activities: activities, as: :activity}) + end + def list_user_statuses(conn, %{"nickname" => nickname} = params) do + with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true godmode = params["godmode"] == "true" || params["godmode"] == true with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do @@ -236,7 +270,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do activities = ActivityPub.fetch_user_activities(user, nil, %{ "limit" => page_size, - "godmode" => godmode + "godmode" => godmode, + "exclude_reblogs" => !with_reblogs && "true" }) conn @@ -250,9 +285,9 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do def user_toggle_activation(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do user = User.get_cached_by_nickname(nickname) - {:ok, updated_user} = User.deactivate(user, !user.info.deactivated) + {:ok, updated_user} = User.deactivate(user, !user.deactivated) - action = if user.info.deactivated, do: "activate", else: "deactivate" + action = if user.deactivated, do: "activate", else: "deactivate" ModerationLog.insert_log(%{ actor: admin, @@ -335,6 +370,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do } with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)), + {:ok, users, count} <- filter_service_users(users, count), do: conn |> json( @@ -346,6 +382,18 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do ) end + defp filter_service_users(users, count) do + filtered_users = Enum.reject(users, &service_user?/1) + count = if Enum.any?(users, &service_user?/1), do: length(filtered_users), else: count + + {:ok, filtered_users, count} + end + + defp service_user?(user) do + String.match?(user.ap_id, ~r/.*\/relay$/) or + String.match?(user.ap_id, ~r/.*\/internal\/fetch$/) + end + @filters ~w(local external active deactivated is_admin is_moderator) @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{} @@ -364,11 +412,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do "nicknames" => nicknames }) when permission_group in ["moderator", "admin"] do - info = Map.put(%{}, "is_" <> permission_group, true) + update = %{:"is_#{permission_group}" => true} users = nicknames |> Enum.map(&User.get_cached_by_nickname/1) - User.update_info(users, &User.Info.admin_api_update(&1, info)) + for u <- users, do: User.admin_api_update(u, update) ModerationLog.insert_log(%{ action: "grant", @@ -377,7 +425,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do permission: permission_group }) - json(conn, info) + json(conn, update) end def right_add_multiple(conn, _) do @@ -389,12 +437,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do "nickname" => nickname }) when permission_group in ["moderator", "admin"] do - info = Map.put(%{}, "is_" <> permission_group, true) + fields = %{:"is_#{permission_group}" => true} {:ok, user} = nickname |> User.get_cached_by_nickname() - |> User.update_info(&User.Info.admin_api_update(&1, info)) + |> User.admin_api_update(fields) ModerationLog.insert_log(%{ action: "grant", @@ -403,7 +451,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do permission: permission_group }) - json(conn, info) + json(conn, fields) end def right_add(conn, _) do @@ -415,8 +463,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do conn |> json(%{ - is_moderator: user.info.is_moderator, - is_admin: user.info.is_admin + is_moderator: user.is_moderator, + is_admin: user.is_admin }) end @@ -429,11 +477,11 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do ) when permission_group in ["moderator", "admin"] do with false <- Enum.member?(nicknames, admin_nickname) do - info = Map.put(%{}, "is_" <> permission_group, false) + update = %{:"is_#{permission_group}" => false} users = nicknames |> Enum.map(&User.get_cached_by_nickname/1) - User.update_info(users, &User.Info.admin_api_update(&1, info)) + for u <- users, do: User.admin_api_update(u, update) ModerationLog.insert_log(%{ action: "revoke", @@ -442,7 +490,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do permission: permission_group }) - json(conn, info) + json(conn, update) else _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.") end @@ -460,12 +508,12 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do } ) when permission_group in ["moderator", "admin"] do - info = Map.put(%{}, "is_" <> permission_group, false) + fields = %{:"is_#{permission_group}" => false} {:ok, user} = nickname |> User.get_cached_by_nickname() - |> User.update_info(&User.Info.admin_api_update(&1, info)) + |> User.admin_api_update(fields) ModerationLog.insert_log(%{ action: "revoke", @@ -474,7 +522,7 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do permission: permission_group }) - json(conn, info) + json(conn, fields) end def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do @@ -528,8 +576,8 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do @doc "Sends registration invite via email" def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do with true <- - Pleroma.Config.get([:instance, :invites_enabled]) && - !Pleroma.Config.get([:instance, :registrations_open]), + Config.get([:instance, :invites_enabled]) && + !Config.get([:instance, :registrations_open]), {:ok, invite_token} <- UserInviteToken.create_invite(), email <- Pleroma.Emails.UserEmail.user_invitation_email( @@ -596,10 +644,16 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end @doc "Force password reset for a given user" - def force_password_reset(conn, %{"nickname" => nickname}) do - (%User{local: true} = user) = User.get_cached_by_nickname(nickname) + def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do + users = nicknames |> Enum.map(&User.get_cached_by_nickname/1) - User.force_password_reset_async(user) + Enum.each(users, &User.force_password_reset_async/1) + + ModerationLog.insert_log(%{ + actor: admin, + subject: users, + action: "force_password_reset" + }) json_response(conn, :no_content, "") end @@ -607,21 +661,21 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do def list_reports(conn, params) do {page, page_size} = page_params(params) - params = - params - |> Map.put("type", "Flag") - |> Map.put("skip_preload", true) - |> Map.put("total", true) - |> Map.put("limit", page_size) - |> Map.put("offset", (page - 1) * page_size) - - reports = ActivityPub.fetch_activities([], params, :offset) + reports = Utils.get_reports(params, page, page_size) conn |> put_view(ReportView) |> render("index.json", %{reports: reports}) end + def list_grouped_reports(conn, _params) do + statuses = Utils.get_reported_activities() + + conn + |> put_view(ReportView) + |> render("index_grouped.json", Utils.get_reports_grouped_by_status(statuses)) + end + def report_show(conn, %{"id" => id}) do with %Activity{} = report <- Activity.get_by_id(id) do conn @@ -632,49 +686,85 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do end end - def report_update_state(%{assigns: %{user: admin}} = conn, %{"id" => id, "state" => state}) do - with {:ok, report} <- CommonAPI.update_report_state(id, state) do - ModerationLog.insert_log(%{ - action: "report_update", - actor: admin, - subject: report - }) + def reports_update(%{assigns: %{user: admin}} = conn, %{"reports" => reports}) do + result = + reports + |> Enum.map(fn report -> + with {:ok, activity} <- CommonAPI.update_report_state(report["id"], report["state"]) do + ModerationLog.insert_log(%{ + action: "report_update", + actor: admin, + subject: activity + }) + + activity + else + {:error, message} -> %{id: report["id"], error: message} + end + end) - conn - |> put_view(ReportView) - |> render("show.json", Report.extract_report_info(report)) + case Enum.any?(result, &Map.has_key?(&1, :error)) do + true -> json_response(conn, :bad_request, result) + false -> json_response(conn, :no_content, "") end end - def report_respond(%{assigns: %{user: user}} = conn, %{"id" => id} = params) do - with false <- is_nil(params["status"]), - %Activity{} <- Activity.get_by_id(id) do - params = - params - |> Map.put("in_reply_to_status_id", id) - |> Map.put("visibility", "direct") + def report_notes_create(%{assigns: %{user: user}} = conn, %{ + "id" => report_id, + "content" => content + }) do + with {:ok, _} <- ReportNote.create(user.id, report_id, content) do + ModerationLog.insert_log(%{ + action: "report_note", + actor: user, + subject: Activity.get_by_id(report_id), + text: content + }) - {:ok, activity} = CommonAPI.post(user, params) + json_response(conn, :no_content, "") + else + _ -> json_response(conn, :bad_request, "") + end + end + def report_notes_delete(%{assigns: %{user: user}} = conn, %{ + "id" => note_id, + "report_id" => report_id + }) do + with {:ok, note} <- ReportNote.destroy(note_id) do ModerationLog.insert_log(%{ - action: "report_response", + action: "report_note_delete", actor: user, - subject: activity, - text: params["status"] + subject: Activity.get_by_id(report_id), + text: note.content }) - conn - |> put_view(StatusView) - |> render("show.json", %{activity: activity}) + json_response(conn, :no_content, "") else - true -> - {:param_cast, nil} - - nil -> - {:error, :not_found} + _ -> json_response(conn, :bad_request, "") end end + def list_statuses(%{assigns: %{user: admin}} = conn, params) do + godmode = params["godmode"] == "true" || params["godmode"] == true + local_only = params["local_only"] == "true" || params["local_only"] == true + with_reblogs = params["with_reblogs"] == "true" || params["with_reblogs"] == true + {page, page_size} = page_params(params) + + activities = + ActivityPub.fetch_statuses(admin, %{ + "godmode" => godmode, + "local_only" => local_only, + "limit" => page_size, + "offset" => (page - 1) * page_size, + "exclude_reblogs" => !with_reblogs && "true" + }) + + conn + |> put_view(Pleroma.Web.AdminAPI.StatusView) + |> render("index.json", %{activities: activities, as: :activity}) + end + def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do {:ok, sensitive} = Ecto.Type.cast(:boolean, params["sensitive"]) @@ -723,49 +813,135 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do |> render("index.json", %{log: log}) end - def migrate_to_db(conn, _params) do - Mix.Tasks.Pleroma.Config.run(["migrate_to_db"]) - json(conn, %{}) + def config_descriptions(conn, _params) do + conn + |> Plug.Conn.put_resp_content_type("application/json") + |> Plug.Conn.send_resp(200, @descriptions_json) end - def migrate_from_db(conn, _params) do - Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "true"]) - json(conn, %{}) + def config_show(conn, %{"only_db" => true}) do + with :ok <- configurable_from_database(conn) do + configs = Pleroma.Repo.all(ConfigDB) + + conn + |> put_view(ConfigView) + |> render("index.json", %{configs: configs}) + end end def config_show(conn, _params) do - configs = Pleroma.Repo.all(Config) + with :ok <- configurable_from_database(conn) do + configs = ConfigDB.get_all_as_keyword() + + merged = + Config.Holder.default_config() + |> ConfigDB.merge(configs) + |> Enum.map(fn {group, values} -> + Enum.map(values, fn {key, value} -> + db = + if configs[group][key] do + ConfigDB.get_db_keys(configs[group][key], key) + end + + db_value = configs[group][key] + + merged_value = + if !is_nil(db_value) and Keyword.keyword?(db_value) and + ConfigDB.sub_key_full_update?(group, key, Keyword.keys(db_value)) do + ConfigDB.merge_group(group, key, value, db_value) + else + value + end + + setting = %{ + group: ConfigDB.convert(group), + key: ConfigDB.convert(key), + value: ConfigDB.convert(merged_value) + } + + if db, do: Map.put(setting, :db, db), else: setting + end) + end) + |> List.flatten() - conn - |> put_view(ConfigView) - |> render("index.json", %{configs: configs}) + response = %{configs: merged} + + response = + if Restarter.Pleroma.need_reboot?() do + Map.put(response, :need_reboot, true) + else + response + end + + json(conn, response) + end end def config_update(conn, %{"configs" => configs}) do - updated = - if Pleroma.Config.get([:instance, :dynamic_configuration]) do - updated = - Enum.map(configs, fn - %{"group" => group, "key" => key, "delete" => "true"} = params -> - {:ok, config} = Config.delete(%{group: group, key: key, subkeys: params["subkeys"]}) - config - - %{"group" => group, "key" => key, "value" => value} -> - {:ok, config} = Config.update_or_create(%{group: group, key: key, value: value}) - config + with :ok <- configurable_from_database(conn) do + {_errors, results} = + Enum.map(configs, fn + %{"group" => group, "key" => key, "delete" => true} = params -> + ConfigDB.delete(%{group: group, key: key, subkeys: params["subkeys"]}) + + %{"group" => group, "key" => key, "value" => value} -> + ConfigDB.update_or_create(%{group: group, key: key, value: value}) + end) + |> Enum.split_with(fn result -> elem(result, 0) == :error end) + + {deleted, updated} = + results + |> Enum.map(fn {:ok, config} -> + Map.put(config, :db, ConfigDB.get_db_keys(config)) + end) + |> Enum.split_with(fn config -> + Ecto.get_meta(config, :state) == :deleted + end) + + Config.TransferTask.load_and_update_env(deleted, false) + + need_reboot? = + Restarter.Pleroma.need_reboot?() || + Enum.any?(updated, fn config -> + group = ConfigDB.from_string(config.group) + key = ConfigDB.from_string(config.key) + value = ConfigDB.from_binary(config.value) + Config.TransferTask.pleroma_need_restart?(group, key, value) end) - |> Enum.reject(&is_nil(&1)) - Pleroma.Config.TransferTask.load_and_update_env() - Mix.Tasks.Pleroma.Config.run(["migrate_from_db", Pleroma.Config.get(:env), "false"]) - updated - else - [] - end + response = %{configs: updated} - conn - |> put_view(ConfigView) - |> render("index.json", %{configs: updated}) + response = + if need_reboot? do + Restarter.Pleroma.need_reboot() + Map.put(response, :need_reboot, need_reboot?) + else + response + end + + conn + |> put_view(ConfigView) + |> render("index.json", response) + end + end + + def restart(conn, _params) do + with :ok <- configurable_from_database(conn) do + Restarter.Pleroma.restart(Config.get(:env), 50) + + json(conn, %{}) + end + end + + defp configurable_from_database(conn) do + if Config.get(:configurable_from_database) do + :ok + else + errors( + conn, + {:error, "To use this endpoint you need to enable configuration from database."} + ) + end end def reload_emoji(conn, _params) do @@ -774,6 +950,41 @@ defmodule Pleroma.Web.AdminAPI.AdminAPIController do conn |> json("ok") end + def confirm_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do + users = nicknames |> Enum.map(&User.get_cached_by_nickname/1) + + User.toggle_confirmation(users) + + ModerationLog.insert_log(%{ + actor: admin, + subject: users, + action: "confirm_email" + }) + + conn |> json("") + end + + def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do + users = nicknames |> Enum.map(&User.get_cached_by_nickname/1) + + User.try_send_confirmation_email(users) + + ModerationLog.insert_log(%{ + actor: admin, + subject: users, + action: "resend_confirmation_email" + }) + + conn |> json("") + end + + def stats(conn, _) do + count = Stats.get_status_visibility_count() + + conn + |> json(%{"status_visibility" => count}) + end + def errors(conn, {:error, :not_found}) do conn |> put_status(:not_found)