From 9de9760aa696657400c762d46dced273c3475be4 Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Wed, 20 May 2020 18:00:41 +0400 Subject: Move status actions to AdminAPI.StatusController --- lib/pleroma/web/admin_api/admin_api_controller.ex | 1207 -------------------- .../admin_api/controllers/admin_api_controller.ex | 1103 ++++++++++++++++++ .../admin_api/controllers/fallback_controller.ex | 31 + .../web/admin_api/controllers/status_controller.ex | 112 ++ lib/pleroma/web/router.ex | 8 +- 5 files changed, 1250 insertions(+), 1211 deletions(-) delete mode 100644 lib/pleroma/web/admin_api/admin_api_controller.ex create mode 100644 lib/pleroma/web/admin_api/controllers/admin_api_controller.ex create mode 100644 lib/pleroma/web/admin_api/controllers/fallback_controller.ex create mode 100644 lib/pleroma/web/admin_api/controllers/status_controller.ex (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex deleted file mode 100644 index 647ceb3ba..000000000 --- a/lib/pleroma/web/admin_api/admin_api_controller.ex +++ /dev/null @@ -1,1207 +0,0 @@ -# Pleroma: A lightweight social networking server -# 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.MFA - 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.Builder - alias Pleroma.Web.ActivityPub.Pipeline - alias Pleroma.Web.ActivityPub.Relay - alias Pleroma.Web.ActivityPub.Utils - alias Pleroma.Web.AdminAPI - alias Pleroma.Web.AdminAPI.AccountView - alias Pleroma.Web.AdminAPI.ConfigView - alias Pleroma.Web.AdminAPI.ModerationLogView - alias Pleroma.Web.AdminAPI.Report - alias Pleroma.Web.AdminAPI.ReportView - alias Pleroma.Web.AdminAPI.Search - alias Pleroma.Web.CommonAPI - alias Pleroma.Web.Endpoint - alias Pleroma.Web.MastodonAPI - alias Pleroma.Web.MastodonAPI.AppView - alias Pleroma.Web.OAuth.App - alias Pleroma.Web.Router - - require Logger - - @descriptions Pleroma.Docs.JSON.compile() - @users_page_size 50 - - plug( - OAuthScopesPlug, - %{scopes: ["read:accounts"], admin: true} - when action in [:list_users, :user_show, :right_get, :show_user_credentials] - ) - - plug( - OAuthScopesPlug, - %{scopes: ["write:accounts"], admin: true} - when action in [ - :get_password_reset, - :force_password_reset, - :user_delete, - :users_create, - :user_toggle_activation, - :user_activate, - :user_deactivate, - :tag_users, - :untag_users, - :right_add, - :right_add_multiple, - :right_delete, - :disable_mfa, - :right_delete_multiple, - :update_user_credentials - ] - ) - - plug(OAuthScopesPlug, %{scopes: ["read:invites"], admin: true} when action == :invites) - - plug( - OAuthScopesPlug, - %{scopes: ["write:invites"], admin: true} - when action in [:create_invite_token, :revoke_invite, :email_invite] - ) - - plug( - OAuthScopesPlug, - %{scopes: ["write:follows"], admin: true} - when action in [:user_follow, :user_unfollow, :relay_follow, :relay_unfollow] - ) - - plug( - OAuthScopesPlug, - %{scopes: ["read:reports"], admin: true} - when action in [:list_reports, :report_show] - ) - - plug( - OAuthScopesPlug, - %{scopes: ["write:reports"], admin: true} - when action in [:reports_update, :report_notes_create, :report_notes_delete] - ) - - plug( - OAuthScopesPlug, - %{scopes: ["read:statuses"], admin: true} - when action in [:list_statuses, :list_user_statuses, :list_instance_statuses, :status_show] - ) - - plug( - OAuthScopesPlug, - %{scopes: ["write:statuses"], admin: true} - when action in [:status_update, :status_delete] - ) - - plug( - OAuthScopesPlug, - %{scopes: ["read"], admin: true} - when action in [ - :config_show, - :list_log, - :stats, - :relay_list, - :config_descriptions, - :need_reboot - ] - ) - - plug( - OAuthScopesPlug, - %{scopes: ["write"], admin: true} - when action in [ - :restart, - :config_update, - :resend_confirmation_email, - :confirm_email, - :oauth_app_create, - :oauth_app_list, - :oauth_app_update, - :oauth_app_delete, - :reload_emoji - ] - ) - - action_fallback(:errors) - - def user_delete(conn, %{"nickname" => nickname}) do - user_delete(conn, %{"nicknames" => [nickname]}) - end - - def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do - users = - nicknames - |> Enum.map(&User.get_cached_by_nickname/1) - - users - |> Enum.each(fn user -> - {:ok, delete_data, _} = Builder.delete(admin, user.ap_id) - Pipeline.common_pipeline(delete_data, local: true) - end) - - ModerationLog.insert_log(%{ - actor: admin, - subject: users, - action: "delete" - }) - - conn - |> json(nicknames) - end - - def user_follow(%{assigns: %{user: admin}} = conn, %{ - "follower" => follower_nick, - "followed" => followed_nick - }) do - with %User{} = follower <- User.get_cached_by_nickname(follower_nick), - %User{} = followed <- User.get_cached_by_nickname(followed_nick) do - User.follow(follower, followed) - - ModerationLog.insert_log(%{ - actor: admin, - followed: followed, - follower: follower, - action: "follow" - }) - end - - conn - |> json("ok") - end - - def user_unfollow(%{assigns: %{user: admin}} = conn, %{ - "follower" => follower_nick, - "followed" => followed_nick - }) do - with %User{} = follower <- User.get_cached_by_nickname(follower_nick), - %User{} = followed <- User.get_cached_by_nickname(followed_nick) do - User.unfollow(follower, followed) - - ModerationLog.insert_log(%{ - actor: admin, - followed: followed, - follower: follower, - action: "unfollow" - }) - end - - conn - |> json("ok") - end - - def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do - changesets = - Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} -> - user_data = %{ - nickname: nickname, - name: nickname, - email: email, - password: password, - password_confirmation: password, - bio: "." - } - - User.register_changeset(%User{}, user_data, need_confirmation: false) - end) - |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi -> - Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset) - end) - - case Pleroma.Repo.transaction(changesets) do - {:ok, users} -> - res = - users - |> Map.values() - |> Enum.map(fn user -> - {:ok, user} = User.post_register_action(user) - - user - end) - |> Enum.map(&AccountView.render("created.json", %{user: &1})) - - ModerationLog.insert_log(%{ - actor: admin, - subjects: Map.values(users), - action: "create" - }) - - conn - |> json(res) - - {:error, id, changeset, _} -> - res = - Enum.map(changesets.operations, fn - {current_id, {:changeset, _current_changeset, _}} when current_id == id -> - AccountView.render("create-error.json", %{changeset: changeset}) - - {_, {:changeset, current_changeset, _}} -> - AccountView.render("create-error.json", %{changeset: current_changeset}) - end) - - conn - |> put_status(:conflict) - |> json(res) - end - end - - def user_show(conn, %{"nickname" => nickname}) do - with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do - conn - |> put_view(AccountView) - |> render("show.json", %{user: user}) - else - _ -> {:error, :not_found} - 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(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 - {_, page_size} = page_params(params) - - activities = - ActivityPub.fetch_user_activities(user, nil, %{ - "limit" => page_size, - "godmode" => godmode, - "exclude_reblogs" => !with_reblogs && "true" - }) - - conn - |> put_view(MastodonAPI.StatusView) - |> render("index.json", %{activities: activities, as: :activity}) - else - _ -> {:error, :not_found} - end - end - - 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.deactivated) - - action = if user.deactivated, do: "activate", else: "deactivate" - - ModerationLog.insert_log(%{ - actor: admin, - subject: [user], - action: action - }) - - conn - |> put_view(AccountView) - |> render("show.json", %{user: updated_user}) - end - - def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do - users = Enum.map(nicknames, &User.get_cached_by_nickname/1) - {:ok, updated_users} = User.deactivate(users, false) - - ModerationLog.insert_log(%{ - actor: admin, - subject: users, - action: "activate" - }) - - conn - |> put_view(AccountView) - |> render("index.json", %{users: Keyword.values(updated_users)}) - end - - def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do - users = Enum.map(nicknames, &User.get_cached_by_nickname/1) - {:ok, updated_users} = User.deactivate(users, true) - - ModerationLog.insert_log(%{ - actor: admin, - subject: users, - action: "deactivate" - }) - - conn - |> put_view(AccountView) - |> render("index.json", %{users: Keyword.values(updated_users)}) - end - - def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do - with {:ok, _} <- User.tag(nicknames, tags) do - ModerationLog.insert_log(%{ - actor: admin, - nicknames: nicknames, - tags: tags, - action: "tag" - }) - - json_response(conn, :no_content, "") - end - end - - def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do - with {:ok, _} <- User.untag(nicknames, tags) do - ModerationLog.insert_log(%{ - actor: admin, - nicknames: nicknames, - tags: tags, - action: "untag" - }) - - json_response(conn, :no_content, "") - end - end - - def list_users(conn, params) do - {page, page_size} = page_params(params) - filters = maybe_parse_filters(params["filters"]) - - search_params = %{ - query: params["query"], - page: page, - page_size: page_size, - tags: params["tags"], - name: params["name"], - email: params["email"] - } - - with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do - json( - conn, - AccountView.render("index.json", users: users, count: count, page_size: page_size) - ) - end - end - - @filters ~w(local external active deactivated is_admin is_moderator) - - @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{} - defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{} - - defp maybe_parse_filters(filters) do - filters - |> String.split(",") - |> Enum.filter(&Enum.member?(@filters, &1)) - |> Enum.map(&String.to_atom(&1)) - |> Enum.into(%{}, &{&1, true}) - end - - def right_add_multiple(%{assigns: %{user: admin}} = conn, %{ - "permission_group" => permission_group, - "nicknames" => nicknames - }) - when permission_group in ["moderator", "admin"] do - update = %{:"is_#{permission_group}" => true} - - users = nicknames |> Enum.map(&User.get_cached_by_nickname/1) - - for u <- users, do: User.admin_api_update(u, update) - - ModerationLog.insert_log(%{ - action: "grant", - actor: admin, - subject: users, - permission: permission_group - }) - - json(conn, update) - end - - def right_add_multiple(conn, _) do - render_error(conn, :not_found, "No such permission_group") - end - - def right_add(%{assigns: %{user: admin}} = conn, %{ - "permission_group" => permission_group, - "nickname" => nickname - }) - when permission_group in ["moderator", "admin"] do - fields = %{:"is_#{permission_group}" => true} - - {:ok, user} = - nickname - |> User.get_cached_by_nickname() - |> User.admin_api_update(fields) - - ModerationLog.insert_log(%{ - action: "grant", - actor: admin, - subject: [user], - permission: permission_group - }) - - json(conn, fields) - end - - def right_add(conn, _) do - render_error(conn, :not_found, "No such permission_group") - end - - def right_get(conn, %{"nickname" => nickname}) do - user = User.get_cached_by_nickname(nickname) - - conn - |> json(%{ - is_moderator: user.is_moderator, - is_admin: user.is_admin - }) - end - - def right_delete_multiple( - %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn, - %{ - "permission_group" => permission_group, - "nicknames" => nicknames - } - ) - when permission_group in ["moderator", "admin"] do - with false <- Enum.member?(nicknames, admin_nickname) do - update = %{:"is_#{permission_group}" => false} - - users = nicknames |> Enum.map(&User.get_cached_by_nickname/1) - - for u <- users, do: User.admin_api_update(u, update) - - ModerationLog.insert_log(%{ - action: "revoke", - actor: admin, - subject: users, - permission: permission_group - }) - - json(conn, update) - else - _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.") - end - end - - def right_delete_multiple(conn, _) do - render_error(conn, :not_found, "No such permission_group") - end - - def right_delete( - %{assigns: %{user: admin}} = conn, - %{ - "permission_group" => permission_group, - "nickname" => nickname - } - ) - when permission_group in ["moderator", "admin"] do - fields = %{:"is_#{permission_group}" => false} - - {:ok, user} = - nickname - |> User.get_cached_by_nickname() - |> User.admin_api_update(fields) - - ModerationLog.insert_log(%{ - action: "revoke", - actor: admin, - subject: [user], - permission: permission_group - }) - - json(conn, fields) - end - - def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do - render_error(conn, :forbidden, "You can't revoke your own admin status.") - end - - def relay_list(conn, _params) do - with {:ok, list} <- Relay.list() do - json(conn, %{relays: list}) - else - _ -> - conn - |> put_status(500) - end - end - - def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do - with {:ok, _message} <- Relay.follow(target) do - ModerationLog.insert_log(%{ - action: "relay_follow", - actor: admin, - target: target - }) - - json(conn, target) - else - _ -> - conn - |> put_status(500) - |> json(target) - end - end - - def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do - with {:ok, _message} <- Relay.unfollow(target) do - ModerationLog.insert_log(%{ - action: "relay_unfollow", - actor: admin, - target: target - }) - - json(conn, target) - else - _ -> - conn - |> put_status(500) - |> json(target) - end - end - - @doc "Sends registration invite via email" - def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do - with {_, false} <- {:registrations_open, Config.get([:instance, :registrations_open])}, - {_, true} <- {:invites_enabled, Config.get([:instance, :invites_enabled])}, - {:ok, invite_token} <- UserInviteToken.create_invite(), - email <- - Pleroma.Emails.UserEmail.user_invitation_email( - user, - invite_token, - email, - params["name"] - ), - {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do - json_response(conn, :no_content, "") - else - {:registrations_open, _} -> - errors( - conn, - {:error, "To send invites you need to set the `registrations_open` option to false."} - ) - - {:invites_enabled, _} -> - errors( - conn, - {:error, "To send invites you need to set the `invites_enabled` option to true."} - ) - end - end - - @doc "Create an account registration invite token" - def create_invite_token(conn, params) do - opts = %{} - - opts = - if params["max_use"], - do: Map.put(opts, :max_use, params["max_use"]), - else: opts - - opts = - if params["expires_at"], - do: Map.put(opts, :expires_at, params["expires_at"]), - else: opts - - {:ok, invite} = UserInviteToken.create_invite(opts) - - json(conn, AccountView.render("invite.json", %{invite: invite})) - end - - @doc "Get list of created invites" - def invites(conn, _params) do - invites = UserInviteToken.list_invites() - - conn - |> put_view(AccountView) - |> render("invites.json", %{invites: invites}) - end - - @doc "Revokes invite by token" - def revoke_invite(conn, %{"token" => token}) do - with {:ok, invite} <- UserInviteToken.find_by_token(token), - {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do - conn - |> put_view(AccountView) - |> render("invite.json", %{invite: updated_invite}) - else - nil -> {:error, :not_found} - end - end - - @doc "Get a password reset token (base64 string) for given nickname" - def get_password_reset(conn, %{"nickname" => nickname}) do - (%User{local: true} = user) = User.get_cached_by_nickname(nickname) - {:ok, token} = Pleroma.PasswordResetToken.create_token(user) - - conn - |> json(%{ - token: token.token, - link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token) - }) - end - - @doc "Force password reset for a given user" - def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do - users = nicknames |> Enum.map(&User.get_cached_by_nickname/1) - - 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 - - @doc "Disable mfa for user's account." - def disable_mfa(conn, %{"nickname" => nickname}) do - case User.get_by_nickname(nickname) do - %User{} = user -> - MFA.disable(user) - json(conn, nickname) - - _ -> - {:error, :not_found} - end - end - - @doc "Show a given user's credentials" - def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do - with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do - conn - |> put_view(AccountView) - |> render("credentials.json", %{user: user, for: admin}) - else - _ -> {:error, :not_found} - end - end - - @doc "Updates a given user" - def update_user_credentials( - %{assigns: %{user: admin}} = conn, - %{"nickname" => nickname} = params - ) do - with {_, user} <- {:user, User.get_cached_by_nickname(nickname)}, - {:ok, _user} <- - User.update_as_admin(user, params) do - ModerationLog.insert_log(%{ - actor: admin, - subject: [user], - action: "updated_users" - }) - - if params["password"] do - User.force_password_reset_async(user) - end - - ModerationLog.insert_log(%{ - actor: admin, - subject: [user], - action: "force_password_reset" - }) - - json(conn, %{status: "success"}) - else - {:error, changeset} -> - {_, {error, _}} = Enum.at(changeset.errors, 0) - json(conn, %{error: "New password #{error}."}) - - _ -> - json(conn, %{error: "Unable to change password."}) - end - end - - def list_reports(conn, params) do - {page, page_size} = page_params(params) - - reports = Utils.get_reports(params, page, page_size) - - conn - |> put_view(ReportView) - |> render("index.json", %{reports: reports}) - end - - def report_show(conn, %{"id" => id}) do - with %Activity{} = report <- Activity.get_by_id(id) do - conn - |> put_view(ReportView) - |> render("show.json", Report.extract_report_info(report)) - else - _ -> {:error, :not_found} - end - end - - 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) - - 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_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 - }) - - 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_note_delete", - actor: user, - subject: Activity.get_by_id(report_id), - text: note.content - }) - - json_response(conn, :no_content, "") - else - _ -> 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(nil, %{ - "godmode" => godmode, - "local_only" => local_only, - "limit" => page_size, - "offset" => (page - 1) * page_size, - "exclude_reblogs" => !with_reblogs && "true" - }) - - conn - |> put_view(AdminAPI.StatusView) - |> render("index.json", %{activities: activities, as: :activity}) - end - - def status_show(conn, %{"id" => id}) do - with %Activity{} = activity <- Activity.get_by_id(id) do - conn - |> put_view(MastodonAPI.StatusView) - |> render("show.json", %{activity: activity}) - else - _ -> errors(conn, {:error, :not_found}) - end - end - - def status_update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do - params = - params - |> Map.take(["sensitive", "visibility"]) - |> Map.new(fn {key, value} -> {String.to_existing_atom(key), value} end) - - with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do - {:ok, sensitive} = Ecto.Type.cast(:boolean, params[:sensitive]) - - ModerationLog.insert_log(%{ - action: "status_update", - actor: admin, - subject: activity, - sensitive: sensitive, - visibility: params[:visibility] - }) - - conn - |> put_view(MastodonAPI.StatusView) - |> render("show.json", %{activity: activity}) - end - end - - def status_delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do - with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do - ModerationLog.insert_log(%{ - action: "status_delete", - actor: user, - subject_id: id - }) - - json(conn, %{}) - end - end - - def list_log(conn, params) do - {page, page_size} = page_params(params) - - log = - ModerationLog.get_all(%{ - page: page, - page_size: page_size, - start_date: params["start_date"], - end_date: params["end_date"], - user_id: params["user_id"], - search: params["search"] - }) - - conn - |> put_view(ModerationLogView) - |> render("index.json", %{log: log}) - end - - def config_descriptions(conn, _params) do - descriptions = Enum.filter(@descriptions, &whitelisted_config?/1) - - json(conn, descriptions) - end - - 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 - 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() - - json(conn, %{configs: merged, need_reboot: Restarter.Pleroma.need_reboot?()}) - end - end - - def config_update(conn, %{"configs" => configs}) do - with :ok <- configurable_from_database(conn) do - {_errors, results} = - configs - |> Enum.filter(&whitelisted_config?/1) - |> Enum.map(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) - - if !Restarter.Pleroma.need_reboot?() do - changed_reboot_settings? = - (updated ++ deleted) - |> Enum.any?(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) - - if changed_reboot_settings?, do: Restarter.Pleroma.need_reboot() - end - - conn - |> put_view(ConfigView) - |> render("index.json", %{configs: updated, need_reboot: Restarter.Pleroma.need_reboot?()}) - 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 - - def need_reboot(conn, _params) do - json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()}) - 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 - - defp whitelisted_config?(group, key) do - if whitelisted_configs = Config.get(:database_config_whitelist) do - Enum.any?(whitelisted_configs, fn - {whitelisted_group} -> - group == inspect(whitelisted_group) - - {whitelisted_group, whitelisted_key} -> - group == inspect(whitelisted_group) && key == inspect(whitelisted_key) - end) - else - true - end - end - - defp whitelisted_config?(%{"group" => group, "key" => key}) do - whitelisted_config?(group, key) - end - - defp whitelisted_config?(%{:group => group} = config) do - whitelisted_config?(group, config[:key]) - end - - def reload_emoji(conn, _params) do - Pleroma.Emoji.reload() - - 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 oauth_app_create(conn, params) do - params = - if params["name"] do - Map.put(params, "client_name", params["name"]) - else - params - end - - result = - case App.create(params) do - {:ok, app} -> - AppView.render("show.json", %{app: app, admin: true}) - - {:error, changeset} -> - App.errors(changeset) - end - - json(conn, result) - end - - def oauth_app_update(conn, params) do - params = - if params["name"] do - Map.put(params, "client_name", params["name"]) - else - params - end - - with {:ok, app} <- App.update(params) do - json(conn, AppView.render("show.json", %{app: app, admin: true})) - else - {:error, changeset} -> - json(conn, App.errors(changeset)) - - nil -> - json_response(conn, :bad_request, "") - end - end - - def oauth_app_list(conn, params) do - {page, page_size} = page_params(params) - - search_params = %{ - client_name: params["name"], - client_id: params["client_id"], - page: page, - page_size: page_size - } - - search_params = - if Map.has_key?(params, "trusted") do - Map.put(search_params, :trusted, params["trusted"]) - else - search_params - end - - with {:ok, apps, count} <- App.search(search_params) do - json( - conn, - AppView.render("index.json", - apps: apps, - count: count, - page_size: page_size, - admin: true - ) - ) - end - end - - def oauth_app_delete(conn, params) do - with {:ok, _app} <- App.destroy(params["id"]) do - json_response(conn, :no_content, "") - else - _ -> json_response(conn, :bad_request, "") - end - end - - def stats(conn, _) do - count = Stats.get_status_visibility_count() - - conn - |> json(%{"status_visibility" => count}) - end - - defp errors(conn, {:error, :not_found}) do - conn - |> put_status(:not_found) - |> json(dgettext("errors", "Not found")) - end - - defp errors(conn, {:error, reason}) do - conn - |> put_status(:bad_request) - |> json(reason) - end - - defp errors(conn, {:param_cast, _}) do - conn - |> put_status(:bad_request) - |> json(dgettext("errors", "Invalid parameters")) - end - - defp errors(conn, _) do - conn - |> put_status(:internal_server_error) - |> json(dgettext("errors", "Something went wrong")) - end - - defp page_params(params) do - {get_page(params["page"]), get_page_size(params["page_size"])} - end - - defp get_page(page_string) when is_nil(page_string), do: 1 - - defp get_page(page_string) do - case Integer.parse(page_string) do - {page, _} -> page - :error -> 1 - end - end - - defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size - - defp get_page_size(page_size_string) do - case Integer.parse(page_size_string) do - {page_size, _} -> page_size - :error -> @users_page_size - end - end -end diff --git a/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex new file mode 100644 index 000000000..6b1d64a2e --- /dev/null +++ b/lib/pleroma/web/admin_api/controllers/admin_api_controller.ex @@ -0,0 +1,1103 @@ +# Pleroma: A lightweight social networking server +# 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.MFA + 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.Builder + alias Pleroma.Web.ActivityPub.Pipeline + alias Pleroma.Web.ActivityPub.Relay + alias Pleroma.Web.ActivityPub.Utils + alias Pleroma.Web.AdminAPI + alias Pleroma.Web.AdminAPI.AccountView + alias Pleroma.Web.AdminAPI.ConfigView + alias Pleroma.Web.AdminAPI.ModerationLogView + alias Pleroma.Web.AdminAPI.Report + alias Pleroma.Web.AdminAPI.ReportView + alias Pleroma.Web.AdminAPI.Search + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.Endpoint + alias Pleroma.Web.MastodonAPI + alias Pleroma.Web.MastodonAPI.AppView + alias Pleroma.Web.OAuth.App + alias Pleroma.Web.Router + + require Logger + + @descriptions Pleroma.Docs.JSON.compile() + @users_page_size 50 + + plug( + OAuthScopesPlug, + %{scopes: ["read:accounts"], admin: true} + when action in [:list_users, :user_show, :right_get, :show_user_credentials] + ) + + plug( + OAuthScopesPlug, + %{scopes: ["write:accounts"], admin: true} + when action in [ + :get_password_reset, + :force_password_reset, + :user_delete, + :users_create, + :user_toggle_activation, + :user_activate, + :user_deactivate, + :tag_users, + :untag_users, + :right_add, + :right_add_multiple, + :right_delete, + :disable_mfa, + :right_delete_multiple, + :update_user_credentials + ] + ) + + plug(OAuthScopesPlug, %{scopes: ["read:invites"], admin: true} when action == :invites) + + plug( + OAuthScopesPlug, + %{scopes: ["write:invites"], admin: true} + when action in [:create_invite_token, :revoke_invite, :email_invite] + ) + + plug( + OAuthScopesPlug, + %{scopes: ["write:follows"], admin: true} + when action in [:user_follow, :user_unfollow, :relay_follow, :relay_unfollow] + ) + + plug( + OAuthScopesPlug, + %{scopes: ["read:reports"], admin: true} + when action in [:list_reports, :report_show] + ) + + plug( + OAuthScopesPlug, + %{scopes: ["write:reports"], admin: true} + when action in [:reports_update, :report_notes_create, :report_notes_delete] + ) + + plug( + OAuthScopesPlug, + %{scopes: ["read:statuses"], admin: true} + when action in [:list_user_statuses, :list_instance_statuses] + ) + + plug( + OAuthScopesPlug, + %{scopes: ["read"], admin: true} + when action in [ + :config_show, + :list_log, + :stats, + :relay_list, + :config_descriptions, + :need_reboot + ] + ) + + plug( + OAuthScopesPlug, + %{scopes: ["write"], admin: true} + when action in [ + :restart, + :config_update, + :resend_confirmation_email, + :confirm_email, + :oauth_app_create, + :oauth_app_list, + :oauth_app_update, + :oauth_app_delete, + :reload_emoji + ] + ) + + action_fallback(AdminAPI.FallbackController) + + def user_delete(conn, %{"nickname" => nickname}) do + user_delete(conn, %{"nicknames" => [nickname]}) + end + + def user_delete(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do + users = + nicknames + |> Enum.map(&User.get_cached_by_nickname/1) + + users + |> Enum.each(fn user -> + {:ok, delete_data, _} = Builder.delete(admin, user.ap_id) + Pipeline.common_pipeline(delete_data, local: true) + end) + + ModerationLog.insert_log(%{ + actor: admin, + subject: users, + action: "delete" + }) + + conn + |> json(nicknames) + end + + def user_follow(%{assigns: %{user: admin}} = conn, %{ + "follower" => follower_nick, + "followed" => followed_nick + }) do + with %User{} = follower <- User.get_cached_by_nickname(follower_nick), + %User{} = followed <- User.get_cached_by_nickname(followed_nick) do + User.follow(follower, followed) + + ModerationLog.insert_log(%{ + actor: admin, + followed: followed, + follower: follower, + action: "follow" + }) + end + + conn + |> json("ok") + end + + def user_unfollow(%{assigns: %{user: admin}} = conn, %{ + "follower" => follower_nick, + "followed" => followed_nick + }) do + with %User{} = follower <- User.get_cached_by_nickname(follower_nick), + %User{} = followed <- User.get_cached_by_nickname(followed_nick) do + User.unfollow(follower, followed) + + ModerationLog.insert_log(%{ + actor: admin, + followed: followed, + follower: follower, + action: "unfollow" + }) + end + + conn + |> json("ok") + end + + def users_create(%{assigns: %{user: admin}} = conn, %{"users" => users}) do + changesets = + Enum.map(users, fn %{"nickname" => nickname, "email" => email, "password" => password} -> + user_data = %{ + nickname: nickname, + name: nickname, + email: email, + password: password, + password_confirmation: password, + bio: "." + } + + User.register_changeset(%User{}, user_data, need_confirmation: false) + end) + |> Enum.reduce(Ecto.Multi.new(), fn changeset, multi -> + Ecto.Multi.insert(multi, Ecto.UUID.generate(), changeset) + end) + + case Pleroma.Repo.transaction(changesets) do + {:ok, users} -> + res = + users + |> Map.values() + |> Enum.map(fn user -> + {:ok, user} = User.post_register_action(user) + + user + end) + |> Enum.map(&AccountView.render("created.json", %{user: &1})) + + ModerationLog.insert_log(%{ + actor: admin, + subjects: Map.values(users), + action: "create" + }) + + conn + |> json(res) + + {:error, id, changeset, _} -> + res = + Enum.map(changesets.operations, fn + {current_id, {:changeset, _current_changeset, _}} when current_id == id -> + AccountView.render("create-error.json", %{changeset: changeset}) + + {_, {:changeset, current_changeset, _}} -> + AccountView.render("create-error.json", %{changeset: current_changeset}) + end) + + conn + |> put_status(:conflict) + |> json(res) + end + end + + def user_show(conn, %{"nickname" => nickname}) do + with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do + conn + |> put_view(AccountView) + |> render("show.json", %{user: user}) + else + _ -> {:error, :not_found} + 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(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 + {_, page_size} = page_params(params) + + activities = + ActivityPub.fetch_user_activities(user, nil, %{ + "limit" => page_size, + "godmode" => godmode, + "exclude_reblogs" => !with_reblogs && "true" + }) + + conn + |> put_view(MastodonAPI.StatusView) + |> render("index.json", %{activities: activities, as: :activity}) + else + _ -> {:error, :not_found} + end + end + + 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.deactivated) + + action = if user.deactivated, do: "activate", else: "deactivate" + + ModerationLog.insert_log(%{ + actor: admin, + subject: [user], + action: action + }) + + conn + |> put_view(AccountView) + |> render("show.json", %{user: updated_user}) + end + + def user_activate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do + users = Enum.map(nicknames, &User.get_cached_by_nickname/1) + {:ok, updated_users} = User.deactivate(users, false) + + ModerationLog.insert_log(%{ + actor: admin, + subject: users, + action: "activate" + }) + + conn + |> put_view(AccountView) + |> render("index.json", %{users: Keyword.values(updated_users)}) + end + + def user_deactivate(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do + users = Enum.map(nicknames, &User.get_cached_by_nickname/1) + {:ok, updated_users} = User.deactivate(users, true) + + ModerationLog.insert_log(%{ + actor: admin, + subject: users, + action: "deactivate" + }) + + conn + |> put_view(AccountView) + |> render("index.json", %{users: Keyword.values(updated_users)}) + end + + def tag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do + with {:ok, _} <- User.tag(nicknames, tags) do + ModerationLog.insert_log(%{ + actor: admin, + nicknames: nicknames, + tags: tags, + action: "tag" + }) + + json_response(conn, :no_content, "") + end + end + + def untag_users(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames, "tags" => tags}) do + with {:ok, _} <- User.untag(nicknames, tags) do + ModerationLog.insert_log(%{ + actor: admin, + nicknames: nicknames, + tags: tags, + action: "untag" + }) + + json_response(conn, :no_content, "") + end + end + + def list_users(conn, params) do + {page, page_size} = page_params(params) + filters = maybe_parse_filters(params["filters"]) + + search_params = %{ + query: params["query"], + page: page, + page_size: page_size, + tags: params["tags"], + name: params["name"], + email: params["email"] + } + + with {:ok, users, count} <- Search.user(Map.merge(search_params, filters)) do + json( + conn, + AccountView.render("index.json", users: users, count: count, page_size: page_size) + ) + end + end + + @filters ~w(local external active deactivated is_admin is_moderator) + + @spec maybe_parse_filters(String.t()) :: %{required(String.t()) => true} | %{} + defp maybe_parse_filters(filters) when is_nil(filters) or filters == "", do: %{} + + defp maybe_parse_filters(filters) do + filters + |> String.split(",") + |> Enum.filter(&Enum.member?(@filters, &1)) + |> Enum.map(&String.to_atom(&1)) + |> Enum.into(%{}, &{&1, true}) + end + + def right_add_multiple(%{assigns: %{user: admin}} = conn, %{ + "permission_group" => permission_group, + "nicknames" => nicknames + }) + when permission_group in ["moderator", "admin"] do + update = %{:"is_#{permission_group}" => true} + + users = nicknames |> Enum.map(&User.get_cached_by_nickname/1) + + for u <- users, do: User.admin_api_update(u, update) + + ModerationLog.insert_log(%{ + action: "grant", + actor: admin, + subject: users, + permission: permission_group + }) + + json(conn, update) + end + + def right_add_multiple(conn, _) do + render_error(conn, :not_found, "No such permission_group") + end + + def right_add(%{assigns: %{user: admin}} = conn, %{ + "permission_group" => permission_group, + "nickname" => nickname + }) + when permission_group in ["moderator", "admin"] do + fields = %{:"is_#{permission_group}" => true} + + {:ok, user} = + nickname + |> User.get_cached_by_nickname() + |> User.admin_api_update(fields) + + ModerationLog.insert_log(%{ + action: "grant", + actor: admin, + subject: [user], + permission: permission_group + }) + + json(conn, fields) + end + + def right_add(conn, _) do + render_error(conn, :not_found, "No such permission_group") + end + + def right_get(conn, %{"nickname" => nickname}) do + user = User.get_cached_by_nickname(nickname) + + conn + |> json(%{ + is_moderator: user.is_moderator, + is_admin: user.is_admin + }) + end + + def right_delete_multiple( + %{assigns: %{user: %{nickname: admin_nickname} = admin}} = conn, + %{ + "permission_group" => permission_group, + "nicknames" => nicknames + } + ) + when permission_group in ["moderator", "admin"] do + with false <- Enum.member?(nicknames, admin_nickname) do + update = %{:"is_#{permission_group}" => false} + + users = nicknames |> Enum.map(&User.get_cached_by_nickname/1) + + for u <- users, do: User.admin_api_update(u, update) + + ModerationLog.insert_log(%{ + action: "revoke", + actor: admin, + subject: users, + permission: permission_group + }) + + json(conn, update) + else + _ -> render_error(conn, :forbidden, "You can't revoke your own admin/moderator status.") + end + end + + def right_delete_multiple(conn, _) do + render_error(conn, :not_found, "No such permission_group") + end + + def right_delete( + %{assigns: %{user: admin}} = conn, + %{ + "permission_group" => permission_group, + "nickname" => nickname + } + ) + when permission_group in ["moderator", "admin"] do + fields = %{:"is_#{permission_group}" => false} + + {:ok, user} = + nickname + |> User.get_cached_by_nickname() + |> User.admin_api_update(fields) + + ModerationLog.insert_log(%{ + action: "revoke", + actor: admin, + subject: [user], + permission: permission_group + }) + + json(conn, fields) + end + + def right_delete(%{assigns: %{user: %{nickname: nickname}}} = conn, %{"nickname" => nickname}) do + render_error(conn, :forbidden, "You can't revoke your own admin status.") + end + + def relay_list(conn, _params) do + with {:ok, list} <- Relay.list() do + json(conn, %{relays: list}) + else + _ -> + conn + |> put_status(500) + end + end + + def relay_follow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do + with {:ok, _message} <- Relay.follow(target) do + ModerationLog.insert_log(%{ + action: "relay_follow", + actor: admin, + target: target + }) + + json(conn, target) + else + _ -> + conn + |> put_status(500) + |> json(target) + end + end + + def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target}) do + with {:ok, _message} <- Relay.unfollow(target) do + ModerationLog.insert_log(%{ + action: "relay_unfollow", + actor: admin, + target: target + }) + + json(conn, target) + else + _ -> + conn + |> put_status(500) + |> json(target) + end + end + + @doc "Sends registration invite via email" + def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do + with {_, false} <- {:registrations_open, Config.get([:instance, :registrations_open])}, + {_, true} <- {:invites_enabled, Config.get([:instance, :invites_enabled])}, + {:ok, invite_token} <- UserInviteToken.create_invite(), + email <- + Pleroma.Emails.UserEmail.user_invitation_email( + user, + invite_token, + email, + params["name"] + ), + {:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do + json_response(conn, :no_content, "") + else + {:registrations_open, _} -> + {:error, "To send invites you need to set the `registrations_open` option to false."} + + {:invites_enabled, _} -> + {:error, "To send invites you need to set the `invites_enabled` option to true."} + end + end + + @doc "Create an account registration invite token" + def create_invite_token(conn, params) do + opts = %{} + + opts = + if params["max_use"], + do: Map.put(opts, :max_use, params["max_use"]), + else: opts + + opts = + if params["expires_at"], + do: Map.put(opts, :expires_at, params["expires_at"]), + else: opts + + {:ok, invite} = UserInviteToken.create_invite(opts) + + json(conn, AccountView.render("invite.json", %{invite: invite})) + end + + @doc "Get list of created invites" + def invites(conn, _params) do + invites = UserInviteToken.list_invites() + + conn + |> put_view(AccountView) + |> render("invites.json", %{invites: invites}) + end + + @doc "Revokes invite by token" + def revoke_invite(conn, %{"token" => token}) do + with {:ok, invite} <- UserInviteToken.find_by_token(token), + {:ok, updated_invite} = UserInviteToken.update_invite(invite, %{used: true}) do + conn + |> put_view(AccountView) + |> render("invite.json", %{invite: updated_invite}) + else + nil -> {:error, :not_found} + end + end + + @doc "Get a password reset token (base64 string) for given nickname" + def get_password_reset(conn, %{"nickname" => nickname}) do + (%User{local: true} = user) = User.get_cached_by_nickname(nickname) + {:ok, token} = Pleroma.PasswordResetToken.create_token(user) + + conn + |> json(%{ + token: token.token, + link: Router.Helpers.reset_password_url(Endpoint, :reset, token.token) + }) + end + + @doc "Force password reset for a given user" + def force_password_reset(%{assigns: %{user: admin}} = conn, %{"nicknames" => nicknames}) do + users = nicknames |> Enum.map(&User.get_cached_by_nickname/1) + + 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 + + @doc "Disable mfa for user's account." + def disable_mfa(conn, %{"nickname" => nickname}) do + case User.get_by_nickname(nickname) do + %User{} = user -> + MFA.disable(user) + json(conn, nickname) + + _ -> + {:error, :not_found} + end + end + + @doc "Show a given user's credentials" + def show_user_credentials(%{assigns: %{user: admin}} = conn, %{"nickname" => nickname}) do + with %User{} = user <- User.get_cached_by_nickname_or_id(nickname) do + conn + |> put_view(AccountView) + |> render("credentials.json", %{user: user, for: admin}) + else + _ -> {:error, :not_found} + end + end + + @doc "Updates a given user" + def update_user_credentials( + %{assigns: %{user: admin}} = conn, + %{"nickname" => nickname} = params + ) do + with {_, user} <- {:user, User.get_cached_by_nickname(nickname)}, + {:ok, _user} <- + User.update_as_admin(user, params) do + ModerationLog.insert_log(%{ + actor: admin, + subject: [user], + action: "updated_users" + }) + + if params["password"] do + User.force_password_reset_async(user) + end + + ModerationLog.insert_log(%{ + actor: admin, + subject: [user], + action: "force_password_reset" + }) + + json(conn, %{status: "success"}) + else + {:error, changeset} -> + {_, {error, _}} = Enum.at(changeset.errors, 0) + json(conn, %{error: "New password #{error}."}) + + _ -> + json(conn, %{error: "Unable to change password."}) + end + end + + def list_reports(conn, params) do + {page, page_size} = page_params(params) + + reports = Utils.get_reports(params, page, page_size) + + conn + |> put_view(ReportView) + |> render("index.json", %{reports: reports}) + end + + def report_show(conn, %{"id" => id}) do + with %Activity{} = report <- Activity.get_by_id(id) do + conn + |> put_view(ReportView) + |> render("show.json", Report.extract_report_info(report)) + else + _ -> {:error, :not_found} + end + end + + 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) + + 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_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 + }) + + 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_note_delete", + actor: user, + subject: Activity.get_by_id(report_id), + text: note.content + }) + + json_response(conn, :no_content, "") + else + _ -> json_response(conn, :bad_request, "") + end + end + + def list_log(conn, params) do + {page, page_size} = page_params(params) + + log = + ModerationLog.get_all(%{ + page: page, + page_size: page_size, + start_date: params["start_date"], + end_date: params["end_date"], + user_id: params["user_id"], + search: params["search"] + }) + + conn + |> put_view(ModerationLogView) + |> render("index.json", %{log: log}) + end + + def config_descriptions(conn, _params) do + descriptions = Enum.filter(@descriptions, &whitelisted_config?/1) + + json(conn, descriptions) + end + + def config_show(conn, %{"only_db" => true}) do + with :ok <- configurable_from_database() do + configs = Pleroma.Repo.all(ConfigDB) + + conn + |> put_view(ConfigView) + |> render("index.json", %{configs: configs}) + end + end + + def config_show(conn, _params) do + with :ok <- configurable_from_database() 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() + + json(conn, %{configs: merged, need_reboot: Restarter.Pleroma.need_reboot?()}) + end + end + + def config_update(conn, %{"configs" => configs}) do + with :ok <- configurable_from_database() do + {_errors, results} = + configs + |> Enum.filter(&whitelisted_config?/1) + |> Enum.map(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) + + if !Restarter.Pleroma.need_reboot?() do + changed_reboot_settings? = + (updated ++ deleted) + |> Enum.any?(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) + + if changed_reboot_settings?, do: Restarter.Pleroma.need_reboot() + end + + conn + |> put_view(ConfigView) + |> render("index.json", %{configs: updated, need_reboot: Restarter.Pleroma.need_reboot?()}) + end + end + + def restart(conn, _params) do + with :ok <- configurable_from_database() do + Restarter.Pleroma.restart(Config.get(:env), 50) + + json(conn, %{}) + end + end + + def need_reboot(conn, _params) do + json(conn, %{need_reboot: Restarter.Pleroma.need_reboot?()}) + end + + defp configurable_from_database do + if Config.get(:configurable_from_database) do + :ok + else + {:error, "To use this endpoint you need to enable configuration from database."} + end + end + + defp whitelisted_config?(group, key) do + if whitelisted_configs = Config.get(:database_config_whitelist) do + Enum.any?(whitelisted_configs, fn + {whitelisted_group} -> + group == inspect(whitelisted_group) + + {whitelisted_group, whitelisted_key} -> + group == inspect(whitelisted_group) && key == inspect(whitelisted_key) + end) + else + true + end + end + + defp whitelisted_config?(%{"group" => group, "key" => key}) do + whitelisted_config?(group, key) + end + + defp whitelisted_config?(%{:group => group} = config) do + whitelisted_config?(group, config[:key]) + end + + def reload_emoji(conn, _params) do + Pleroma.Emoji.reload() + + 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 oauth_app_create(conn, params) do + params = + if params["name"] do + Map.put(params, "client_name", params["name"]) + else + params + end + + result = + case App.create(params) do + {:ok, app} -> + AppView.render("show.json", %{app: app, admin: true}) + + {:error, changeset} -> + App.errors(changeset) + end + + json(conn, result) + end + + def oauth_app_update(conn, params) do + params = + if params["name"] do + Map.put(params, "client_name", params["name"]) + else + params + end + + with {:ok, app} <- App.update(params) do + json(conn, AppView.render("show.json", %{app: app, admin: true})) + else + {:error, changeset} -> + json(conn, App.errors(changeset)) + + nil -> + json_response(conn, :bad_request, "") + end + end + + def oauth_app_list(conn, params) do + {page, page_size} = page_params(params) + + search_params = %{ + client_name: params["name"], + client_id: params["client_id"], + page: page, + page_size: page_size + } + + search_params = + if Map.has_key?(params, "trusted") do + Map.put(search_params, :trusted, params["trusted"]) + else + search_params + end + + with {:ok, apps, count} <- App.search(search_params) do + json( + conn, + AppView.render("index.json", + apps: apps, + count: count, + page_size: page_size, + admin: true + ) + ) + end + end + + def oauth_app_delete(conn, params) do + with {:ok, _app} <- App.destroy(params["id"]) do + json_response(conn, :no_content, "") + else + _ -> json_response(conn, :bad_request, "") + end + end + + def stats(conn, _) do + count = Stats.get_status_visibility_count() + + conn + |> json(%{"status_visibility" => count}) + end + + defp page_params(params) do + {get_page(params["page"]), get_page_size(params["page_size"])} + end + + defp get_page(page_string) when is_nil(page_string), do: 1 + + defp get_page(page_string) do + case Integer.parse(page_string) do + {page, _} -> page + :error -> 1 + end + end + + defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size + + defp get_page_size(page_size_string) do + case Integer.parse(page_size_string) do + {page_size, _} -> page_size + :error -> @users_page_size + end + end +end diff --git a/lib/pleroma/web/admin_api/controllers/fallback_controller.ex b/lib/pleroma/web/admin_api/controllers/fallback_controller.ex new file mode 100644 index 000000000..9f7bb92ce --- /dev/null +++ b/lib/pleroma/web/admin_api/controllers/fallback_controller.ex @@ -0,0 +1,31 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.FallbackController do + use Pleroma.Web, :controller + + def call(conn, {:error, :not_found}) do + conn + |> put_status(:not_found) + |> json(dgettext("errors", "Not found")) + end + + def call(conn, {:error, reason}) do + conn + |> put_status(:bad_request) + |> json(reason) + end + + def call(conn, {:param_cast, _}) do + conn + |> put_status(:bad_request) + |> json(dgettext("errors", "Invalid parameters")) + end + + def call(conn, _) do + conn + |> put_status(:internal_server_error) + |> json(dgettext("errors", "Something went wrong")) + end +end diff --git a/lib/pleroma/web/admin_api/controllers/status_controller.ex b/lib/pleroma/web/admin_api/controllers/status_controller.ex new file mode 100644 index 000000000..1e9763979 --- /dev/null +++ b/lib/pleroma/web/admin_api/controllers/status_controller.ex @@ -0,0 +1,112 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.AdminAPI.StatusController do + use Pleroma.Web, :controller + + alias Pleroma.Activity + alias Pleroma.ModerationLog + alias Pleroma.Plugs.OAuthScopesPlug + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.CommonAPI + alias Pleroma.Web.MastodonAPI + + require Logger + + @users_page_size 50 + + plug(OAuthScopesPlug, %{scopes: ["read:statuses"], admin: true} when action in [:index, :show]) + + plug( + OAuthScopesPlug, + %{scopes: ["write:statuses"], admin: true} when action in [:update, :delete] + ) + + action_fallback(Pleroma.Web.AdminAPI.FallbackController) + + def index(%{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(nil, %{ + "godmode" => godmode, + "local_only" => local_only, + "limit" => page_size, + "offset" => (page - 1) * page_size, + "exclude_reblogs" => !with_reblogs && "true" + }) + + render(conn, "index.json", %{activities: activities, as: :activity}) + end + + def show(conn, %{"id" => id}) do + with %Activity{} = activity <- Activity.get_by_id(id) do + conn + |> put_view(MastodonAPI.StatusView) + |> render("show.json", %{activity: activity}) + else + nil -> {:error, :not_found} + end + end + + def update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do + params = + params + |> Map.take(["sensitive", "visibility"]) + |> Map.new(fn {key, value} -> {String.to_existing_atom(key), value} end) + + with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do + {:ok, sensitive} = Ecto.Type.cast(:boolean, params[:sensitive]) + + ModerationLog.insert_log(%{ + action: "status_update", + actor: admin, + subject: activity, + sensitive: sensitive, + visibility: params[:visibility] + }) + + conn + |> put_view(MastodonAPI.StatusView) + |> render("show.json", %{activity: activity}) + end + end + + def delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do + with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do + ModerationLog.insert_log(%{ + action: "status_delete", + actor: user, + subject_id: id + }) + + json(conn, %{}) + end + end + + defp page_params(params) do + {get_page(params["page"]), get_page_size(params["page_size"])} + end + + defp get_page(page_string) when is_nil(page_string), do: 1 + + defp get_page(page_string) do + case Integer.parse(page_string) do + {page, _} -> page + :error -> 1 + end + end + + defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size + + defp get_page_size(page_size_string) do + case Integer.parse(page_size_string) do + {page_size, _} -> page_size + :error -> @users_page_size + end + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index 4cacf6255..9e99ab218 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -189,10 +189,10 @@ defmodule Pleroma.Web.Router do post("/reports/:id/notes", AdminAPIController, :report_notes_create) delete("/reports/:report_id/notes/:id", AdminAPIController, :report_notes_delete) - get("/statuses/:id", AdminAPIController, :status_show) - put("/statuses/:id", AdminAPIController, :status_update) - delete("/statuses/:id", AdminAPIController, :status_delete) - get("/statuses", AdminAPIController, :list_statuses) + get("/statuses/:id", StatusController, :show) + put("/statuses/:id", StatusController, :update) + delete("/statuses/:id", StatusController, :delete) + get("/statuses", StatusController, :index) get("/config", AdminAPIController, :config_show) post("/config", AdminAPIController, :config_update) -- cgit v1.2.3 From 45d2c4157fc264dacdca0f17268d3a33f364801f Mon Sep 17 00:00:00 2001 From: Egor Kislitsyn Date: Thu, 21 May 2020 14:03:38 +0400 Subject: Add OpenAPI spec for AdminAPI.StatusController --- .../admin_api/controllers/fallback_controller.ex | 6 +- .../web/admin_api/controllers/status_controller.ex | 59 ++------ .../api_spec/operations/admin/status_operation.ex | 165 +++++++++++++++++++++ .../web/api_spec/operations/status_operation.ex | 2 +- 4 files changed, 182 insertions(+), 50 deletions(-) create mode 100644 lib/pleroma/web/api_spec/operations/admin/status_operation.ex (limited to 'lib') diff --git a/lib/pleroma/web/admin_api/controllers/fallback_controller.ex b/lib/pleroma/web/admin_api/controllers/fallback_controller.ex index 9f7bb92ce..82965936d 100644 --- a/lib/pleroma/web/admin_api/controllers/fallback_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/fallback_controller.ex @@ -8,13 +8,13 @@ defmodule Pleroma.Web.AdminAPI.FallbackController do def call(conn, {:error, :not_found}) do conn |> put_status(:not_found) - |> json(dgettext("errors", "Not found")) + |> json(%{error: dgettext("errors", "Not found")}) end def call(conn, {:error, reason}) do conn |> put_status(:bad_request) - |> json(reason) + |> json(%{error: reason}) end def call(conn, {:param_cast, _}) do @@ -26,6 +26,6 @@ defmodule Pleroma.Web.AdminAPI.FallbackController do def call(conn, _) do conn |> put_status(:internal_server_error) - |> json(dgettext("errors", "Something went wrong")) + |> json(%{error: dgettext("errors", "Something went wrong")}) end end diff --git a/lib/pleroma/web/admin_api/controllers/status_controller.ex b/lib/pleroma/web/admin_api/controllers/status_controller.ex index 1e9763979..08cb9c10b 100644 --- a/lib/pleroma/web/admin_api/controllers/status_controller.ex +++ b/lib/pleroma/web/admin_api/controllers/status_controller.ex @@ -14,8 +14,7 @@ defmodule Pleroma.Web.AdminAPI.StatusController do require Logger - @users_page_size 50 - + plug(Pleroma.Web.ApiSpec.CastAndValidate) plug(OAuthScopesPlug, %{scopes: ["read:statuses"], admin: true} when action in [:index, :show]) plug( @@ -25,25 +24,22 @@ defmodule Pleroma.Web.AdminAPI.StatusController do action_fallback(Pleroma.Web.AdminAPI.FallbackController) - def index(%{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) + defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.StatusOperation + def index(%{assigns: %{user: _admin}} = conn, params) do activities = ActivityPub.fetch_statuses(nil, %{ - "godmode" => godmode, - "local_only" => local_only, - "limit" => page_size, - "offset" => (page - 1) * page_size, - "exclude_reblogs" => !with_reblogs && "true" + "godmode" => params.godmode, + "local_only" => params.local_only, + "limit" => params.page_size, + "offset" => (params.page - 1) * params.page_size, + "exclude_reblogs" => not params.with_reblogs }) - render(conn, "index.json", %{activities: activities, as: :activity}) + render(conn, "index.json", activities: activities, as: :activity) end - def show(conn, %{"id" => id}) do + def show(conn, %{id: id}) do with %Activity{} = activity <- Activity.get_by_id(id) do conn |> put_view(MastodonAPI.StatusView) @@ -53,20 +49,13 @@ defmodule Pleroma.Web.AdminAPI.StatusController do end end - def update(%{assigns: %{user: admin}} = conn, %{"id" => id} = params) do - params = - params - |> Map.take(["sensitive", "visibility"]) - |> Map.new(fn {key, value} -> {String.to_existing_atom(key), value} end) - + def update(%{assigns: %{user: admin}, body_params: params} = conn, %{id: id}) do with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do - {:ok, sensitive} = Ecto.Type.cast(:boolean, params[:sensitive]) - ModerationLog.insert_log(%{ action: "status_update", actor: admin, subject: activity, - sensitive: sensitive, + sensitive: params[:sensitive], visibility: params[:visibility] }) @@ -76,7 +65,7 @@ defmodule Pleroma.Web.AdminAPI.StatusController do end end - def delete(%{assigns: %{user: user}} = conn, %{"id" => id}) do + def delete(%{assigns: %{user: user}} = conn, %{id: id}) do with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do ModerationLog.insert_log(%{ action: "status_delete", @@ -87,26 +76,4 @@ defmodule Pleroma.Web.AdminAPI.StatusController do json(conn, %{}) end end - - defp page_params(params) do - {get_page(params["page"]), get_page_size(params["page_size"])} - end - - defp get_page(page_string) when is_nil(page_string), do: 1 - - defp get_page(page_string) do - case Integer.parse(page_string) do - {page, _} -> page - :error -> 1 - end - end - - defp get_page_size(page_size_string) when is_nil(page_size_string), do: @users_page_size - - defp get_page_size(page_size_string) do - case Integer.parse(page_size_string) do - {page_size, _} -> page_size - :error -> @users_page_size - end - end end diff --git a/lib/pleroma/web/api_spec/operations/admin/status_operation.ex b/lib/pleroma/web/api_spec/operations/admin/status_operation.ex new file mode 100644 index 000000000..0b138dc79 --- /dev/null +++ b/lib/pleroma/web/api_spec/operations/admin/status_operation.ex @@ -0,0 +1,165 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2020 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.ApiSpec.Admin.StatusOperation do + alias OpenApiSpex.Operation + alias OpenApiSpex.Schema + alias Pleroma.Web.ApiSpec.Schemas.Account + alias Pleroma.Web.ApiSpec.Schemas.ApiError + alias Pleroma.Web.ApiSpec.Schemas.FlakeID + alias Pleroma.Web.ApiSpec.Schemas.Status + alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope + + import Pleroma.Web.ApiSpec.Helpers + import Pleroma.Web.ApiSpec.StatusOperation, only: [id_param: 0] + + def open_api_operation(action) do + operation = String.to_existing_atom("#{action}_operation") + apply(__MODULE__, operation, []) + end + + def index_operation do + %Operation{ + tags: ["Admin", "Statuses"], + operationId: "AdminAPI.StatusController.index", + security: [%{"oAuth" => ["read:statuses"]}], + parameters: [ + Operation.parameter( + :godmode, + :query, + %Schema{type: :boolean, default: false}, + "Allows to see private statuses" + ), + Operation.parameter( + :local_only, + :query, + %Schema{type: :boolean, default: false}, + "Excludes remote statuses" + ), + Operation.parameter( + :with_reblogs, + :query, + %Schema{type: :boolean, default: false}, + "Allows to see reblogs" + ), + Operation.parameter( + :page, + :query, + %Schema{type: :integer, default: 1}, + "Page" + ), + Operation.parameter( + :page_size, + :query, + %Schema{type: :integer, default: 50}, + "Number of statuses to return" + ) + ], + responses: %{ + 200 => + Operation.response("Array of statuses", "application/json", %Schema{ + type: :array, + items: status() + }) + } + } + end + + def show_operation do + %Operation{ + tags: ["Admin", "Statuses"], + summary: "Show Status", + operationId: "AdminAPI.StatusController.show", + parameters: [id_param()], + security: [%{"oAuth" => ["read:statuses"]}], + responses: %{ + 200 => Operation.response("Status", "application/json", Status), + 404 => Operation.response("Not Found", "application/json", ApiError) + } + } + end + + def update_operation do + %Operation{ + tags: ["Admin", "Statuses"], + summary: "Change the scope of an individual reported status", + operationId: "AdminAPI.StatusController.update", + parameters: [id_param()], + security: [%{"oAuth" => ["write:statuses"]}], + requestBody: request_body("Parameters", update_request(), required: true), + responses: %{ + 200 => Operation.response("Status", "application/json", Status), + 400 => Operation.response("Error", "application/json", ApiError) + } + } + end + + def delete_operation do + %Operation{ + tags: ["Admin", "Statuses"], + summary: "Delete an individual reported status", + operationId: "AdminAPI.StatusController.delete", + parameters: [id_param()], + security: [%{"oAuth" => ["write:statuses"]}], + responses: %{ + 200 => empty_object_response(), + 404 => Operation.response("Not Found", "application/json", ApiError) + } + } + end + + defp status do + %Schema{ + anyOf: [ + Status, + %Schema{ + type: :object, + properties: %{ + account: %Schema{allOf: [Account, admin_account()]} + } + } + ] + } + end + + defp admin_account do + %Schema{ + type: :object, + properties: %{ + id: FlakeID, + avatar: %Schema{type: :string}, + nickname: %Schema{type: :string}, + display_name: %Schema{type: :string}, + deactivated: %Schema{type: :boolean}, + local: %Schema{type: :boolean}, + roles: %Schema{ + type: :object, + properties: %{ + admin: %Schema{type: :boolean}, + moderator: %Schema{type: :boolean} + } + }, + tags: %Schema{type: :string}, + confirmation_pending: %Schema{type: :string} + } + } + end + + defp update_request do + %Schema{ + type: :object, + properties: %{ + sensitive: %Schema{ + type: :boolean, + description: "Mark status and attached media as sensitive?" + }, + visibility: VisibilityScope + }, + example: %{ + "visibility" => "private", + "sensitive" => "false" + } + } + end +end diff --git a/lib/pleroma/web/api_spec/operations/status_operation.ex b/lib/pleroma/web/api_spec/operations/status_operation.ex index 0682ca6e5..ca9db01e5 100644 --- a/lib/pleroma/web/api_spec/operations/status_operation.ex +++ b/lib/pleroma/web/api_spec/operations/status_operation.ex @@ -487,7 +487,7 @@ defmodule Pleroma.Web.ApiSpec.StatusOperation do } end - defp id_param do + def id_param do Operation.parameter(:id, :path, FlakeID, "Status ID", example: "9umDrYheeY451cQnEe", required: true -- cgit v1.2.3