commit: ec470c4c7717dc9479df9e7b70f9805dcf2f5e08
parent: 7bc2ec0aa26037337b6d562a36f24aa131d025d6
Author: lain <lain@soykaf.club>
Date: Mon, 25 May 2020 11:40:54 +0000
Merge branch 'openapi/admin/status' into 'develop'
Add OpenAPI spec for AdminAPI.StatusController
See merge request pleroma/pleroma!2566
Diffstat:
10 files changed, 5294 insertions(+), 5078 deletions(-)
diff --git a/lib/pleroma/web/admin_api/admin_api_controller.ex b/lib/pleroma/web/admin_api/admin_api_controller.ex
@@ -1,1207 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# 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
@@ -0,0 +1,1103 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# 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
@@ -0,0 +1,31 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# 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(%{error: dgettext("errors", "Not found")})
+ end
+
+ def call(conn, {:error, reason}) do
+ conn
+ |> put_status(:bad_request)
+ |> json(%{error: 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(%{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
@@ -0,0 +1,79 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# 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
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+ 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)
+
+ 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" => 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)
+ 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}, body_params: params} = conn, %{id: id}) do
+ with {:ok, activity} <- CommonAPI.update_activity_scope(id, params) do
+ ModerationLog.insert_log(%{
+ action: "status_update",
+ actor: admin,
+ subject: activity,
+ sensitive: params[: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
+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
@@ -0,0 +1,165 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# 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
@@ -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
diff --git 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)
diff --git a/test/web/admin_api/admin_api_controller_test.exs b/test/web/admin_api/admin_api_controller_test.exs
@@ -1,3866 +0,0 @@
-# Pleroma: A lightweight social networking server
-# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
-# SPDX-License-Identifier: AGPL-3.0-only
-
-defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
- use Pleroma.Web.ConnCase
- use Oban.Testing, repo: Pleroma.Repo
-
- import ExUnit.CaptureLog
- import Mock
- import Pleroma.Factory
-
- alias Pleroma.Activity
- alias Pleroma.Config
- alias Pleroma.ConfigDB
- alias Pleroma.HTML
- alias Pleroma.MFA
- alias Pleroma.ModerationLog
- alias Pleroma.Repo
- alias Pleroma.ReportNote
- alias Pleroma.Tests.ObanHelpers
- alias Pleroma.User
- alias Pleroma.UserInviteToken
- alias Pleroma.Web
- alias Pleroma.Web.ActivityPub.Relay
- alias Pleroma.Web.CommonAPI
- alias Pleroma.Web.MediaProxy
-
- setup_all do
- Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
-
- :ok
- end
-
- setup do
- admin = insert(:user, is_admin: true)
- token = insert(:oauth_admin_token, user: admin)
-
- conn =
- build_conn()
- |> assign(:user, admin)
- |> assign(:token, token)
-
- {:ok, %{admin: admin, token: token, conn: conn}}
- end
-
- describe "with [:auth, :enforce_oauth_admin_scope_usage]," do
- setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], true)
-
- test "GET /api/pleroma/admin/users/:nickname requires admin:read:accounts or broader scope",
- %{admin: admin} do
- user = insert(:user)
- url = "/api/pleroma/admin/users/#{user.nickname}"
-
- good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"])
- good_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read"])
- good_token3 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts"])
-
- bad_token1 = insert(:oauth_token, user: admin, scopes: ["read:accounts"])
- bad_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts:partial"])
- bad_token3 = nil
-
- for good_token <- [good_token1, good_token2, good_token3] do
- conn =
- build_conn()
- |> assign(:user, admin)
- |> assign(:token, good_token)
- |> get(url)
-
- assert json_response(conn, 200)
- end
-
- for good_token <- [good_token1, good_token2, good_token3] do
- conn =
- build_conn()
- |> assign(:user, nil)
- |> assign(:token, good_token)
- |> get(url)
-
- assert json_response(conn, :forbidden)
- end
-
- for bad_token <- [bad_token1, bad_token2, bad_token3] do
- conn =
- build_conn()
- |> assign(:user, admin)
- |> assign(:token, bad_token)
- |> get(url)
-
- assert json_response(conn, :forbidden)
- end
- end
- end
-
- describe "unless [:auth, :enforce_oauth_admin_scope_usage]," do
- setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], false)
-
- test "GET /api/pleroma/admin/users/:nickname requires " <>
- "read:accounts or admin:read:accounts or broader scope",
- %{admin: admin} do
- user = insert(:user)
- url = "/api/pleroma/admin/users/#{user.nickname}"
-
- good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"])
- good_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read"])
- good_token3 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts"])
- good_token4 = insert(:oauth_token, user: admin, scopes: ["read:accounts"])
- good_token5 = insert(:oauth_token, user: admin, scopes: ["read"])
-
- good_tokens = [good_token1, good_token2, good_token3, good_token4, good_token5]
-
- bad_token1 = insert(:oauth_token, user: admin, scopes: ["read:accounts:partial"])
- bad_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts:partial"])
- bad_token3 = nil
-
- for good_token <- good_tokens do
- conn =
- build_conn()
- |> assign(:user, admin)
- |> assign(:token, good_token)
- |> get(url)
-
- assert json_response(conn, 200)
- end
-
- for good_token <- good_tokens do
- conn =
- build_conn()
- |> assign(:user, nil)
- |> assign(:token, good_token)
- |> get(url)
-
- assert json_response(conn, :forbidden)
- end
-
- for bad_token <- [bad_token1, bad_token2, bad_token3] do
- conn =
- build_conn()
- |> assign(:user, admin)
- |> assign(:token, bad_token)
- |> get(url)
-
- assert json_response(conn, :forbidden)
- end
- end
- end
-
- describe "DELETE /api/pleroma/admin/users" do
- test "single user", %{admin: admin, conn: conn} do
- user = insert(:user)
- clear_config([:instance, :federating], true)
-
- with_mock Pleroma.Web.Federator,
- publish: fn _ -> nil end do
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> delete("/api/pleroma/admin/users?nickname=#{user.nickname}")
-
- ObanHelpers.perform_all()
-
- assert User.get_by_nickname(user.nickname).deactivated
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} deleted users: @#{user.nickname}"
-
- assert json_response(conn, 200) == [user.nickname]
-
- assert called(Pleroma.Web.Federator.publish(:_))
- end
- end
-
- test "multiple users", %{admin: admin, conn: conn} do
- user_one = insert(:user)
- user_two = insert(:user)
-
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> delete("/api/pleroma/admin/users", %{
- nicknames: [user_one.nickname, user_two.nickname]
- })
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} deleted users: @#{user_one.nickname}, @#{user_two.nickname}"
-
- response = json_response(conn, 200)
- assert response -- [user_one.nickname, user_two.nickname] == []
- end
- end
-
- describe "/api/pleroma/admin/users" do
- test "Create", %{conn: conn} do
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> post("/api/pleroma/admin/users", %{
- "users" => [
- %{
- "nickname" => "lain",
- "email" => "lain@example.org",
- "password" => "test"
- },
- %{
- "nickname" => "lain2",
- "email" => "lain2@example.org",
- "password" => "test"
- }
- ]
- })
-
- response = json_response(conn, 200) |> Enum.map(&Map.get(&1, "type"))
- assert response == ["success", "success"]
-
- log_entry = Repo.one(ModerationLog)
-
- assert ["lain", "lain2"] -- Enum.map(log_entry.data["subjects"], & &1["nickname"]) == []
- end
-
- test "Cannot create user with existing email", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> post("/api/pleroma/admin/users", %{
- "users" => [
- %{
- "nickname" => "lain",
- "email" => user.email,
- "password" => "test"
- }
- ]
- })
-
- assert json_response(conn, 409) == [
- %{
- "code" => 409,
- "data" => %{
- "email" => user.email,
- "nickname" => "lain"
- },
- "error" => "email has already been taken",
- "type" => "error"
- }
- ]
- end
-
- test "Cannot create user with existing nickname", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> post("/api/pleroma/admin/users", %{
- "users" => [
- %{
- "nickname" => user.nickname,
- "email" => "someuser@plerama.social",
- "password" => "test"
- }
- ]
- })
-
- assert json_response(conn, 409) == [
- %{
- "code" => 409,
- "data" => %{
- "email" => "someuser@plerama.social",
- "nickname" => user.nickname
- },
- "error" => "nickname has already been taken",
- "type" => "error"
- }
- ]
- end
-
- test "Multiple user creation works in transaction", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> post("/api/pleroma/admin/users", %{
- "users" => [
- %{
- "nickname" => "newuser",
- "email" => "newuser@pleroma.social",
- "password" => "test"
- },
- %{
- "nickname" => "lain",
- "email" => user.email,
- "password" => "test"
- }
- ]
- })
-
- assert json_response(conn, 409) == [
- %{
- "code" => 409,
- "data" => %{
- "email" => user.email,
- "nickname" => "lain"
- },
- "error" => "email has already been taken",
- "type" => "error"
- },
- %{
- "code" => 409,
- "data" => %{
- "email" => "newuser@pleroma.social",
- "nickname" => "newuser"
- },
- "error" => "",
- "type" => "error"
- }
- ]
-
- assert User.get_by_nickname("newuser") === nil
- end
- end
-
- describe "/api/pleroma/admin/users/:nickname" do
- test "Show", %{conn: conn} do
- user = insert(:user)
-
- conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")
-
- expected = %{
- "deactivated" => false,
- "id" => to_string(user.id),
- "local" => true,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
- }
-
- assert expected == json_response(conn, 200)
- end
-
- test "when the user doesn't exist", %{conn: conn} do
- user = build(:user)
-
- conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")
-
- assert "Not found" == json_response(conn, 404)
- end
- end
-
- describe "/api/pleroma/admin/users/follow" do
- test "allows to force-follow another user", %{admin: admin, conn: conn} do
- user = insert(:user)
- follower = insert(:user)
-
- conn
- |> put_req_header("accept", "application/json")
- |> post("/api/pleroma/admin/users/follow", %{
- "follower" => follower.nickname,
- "followed" => user.nickname
- })
-
- user = User.get_cached_by_id(user.id)
- follower = User.get_cached_by_id(follower.id)
-
- assert User.following?(follower, user)
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} made @#{follower.nickname} follow @#{user.nickname}"
- end
- end
-
- describe "/api/pleroma/admin/users/unfollow" do
- test "allows to force-unfollow another user", %{admin: admin, conn: conn} do
- user = insert(:user)
- follower = insert(:user)
-
- User.follow(follower, user)
-
- conn
- |> put_req_header("accept", "application/json")
- |> post("/api/pleroma/admin/users/unfollow", %{
- "follower" => follower.nickname,
- "followed" => user.nickname
- })
-
- user = User.get_cached_by_id(user.id)
- follower = User.get_cached_by_id(follower.id)
-
- refute User.following?(follower, user)
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} made @#{follower.nickname} unfollow @#{user.nickname}"
- end
- end
-
- describe "PUT /api/pleroma/admin/users/tag" do
- setup %{conn: conn} do
- user1 = insert(:user, %{tags: ["x"]})
- user2 = insert(:user, %{tags: ["y"]})
- user3 = insert(:user, %{tags: ["unchanged"]})
-
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> put(
- "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <>
- "#{user2.nickname}&tags[]=foo&tags[]=bar"
- )
-
- %{conn: conn, user1: user1, user2: user2, user3: user3}
- end
-
- test "it appends specified tags to users with specified nicknames", %{
- conn: conn,
- admin: admin,
- user1: user1,
- user2: user2
- } do
- assert json_response(conn, :no_content)
- assert User.get_cached_by_id(user1.id).tags == ["x", "foo", "bar"]
- assert User.get_cached_by_id(user2.id).tags == ["y", "foo", "bar"]
-
- log_entry = Repo.one(ModerationLog)
-
- users =
- [user1.nickname, user2.nickname]
- |> Enum.map(&"@#{&1}")
- |> Enum.join(", ")
-
- tags = ["foo", "bar"] |> Enum.join(", ")
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} added tags: #{tags} to users: #{users}"
- end
-
- test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
- assert json_response(conn, :no_content)
- assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
- end
- end
-
- describe "DELETE /api/pleroma/admin/users/tag" do
- setup %{conn: conn} do
- user1 = insert(:user, %{tags: ["x"]})
- user2 = insert(:user, %{tags: ["y", "z"]})
- user3 = insert(:user, %{tags: ["unchanged"]})
-
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> delete(
- "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <>
- "#{user2.nickname}&tags[]=x&tags[]=z"
- )
-
- %{conn: conn, user1: user1, user2: user2, user3: user3}
- end
-
- test "it removes specified tags from users with specified nicknames", %{
- conn: conn,
- admin: admin,
- user1: user1,
- user2: user2
- } do
- assert json_response(conn, :no_content)
- assert User.get_cached_by_id(user1.id).tags == []
- assert User.get_cached_by_id(user2.id).tags == ["y"]
-
- log_entry = Repo.one(ModerationLog)
-
- users =
- [user1.nickname, user2.nickname]
- |> Enum.map(&"@#{&1}")
- |> Enum.join(", ")
-
- tags = ["x", "z"] |> Enum.join(", ")
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} removed tags: #{tags} from users: #{users}"
- end
-
- test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
- assert json_response(conn, :no_content)
- assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
- end
- end
-
- describe "/api/pleroma/admin/users/:nickname/permission_group" do
- test "GET is giving user_info", %{admin: admin, conn: conn} do
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> get("/api/pleroma/admin/users/#{admin.nickname}/permission_group/")
-
- assert json_response(conn, 200) == %{
- "is_admin" => true,
- "is_moderator" => false
- }
- end
-
- test "/:right POST, can add to a permission group", %{admin: admin, conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> post("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin")
-
- assert json_response(conn, 200) == %{
- "is_admin" => true
- }
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} made @#{user.nickname} admin"
- end
-
- test "/:right POST, can add to a permission group (multiple)", %{admin: admin, conn: conn} do
- user_one = insert(:user)
- user_two = insert(:user)
-
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> post("/api/pleroma/admin/users/permission_group/admin", %{
- nicknames: [user_one.nickname, user_two.nickname]
- })
-
- assert json_response(conn, 200) == %{"is_admin" => true}
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} made @#{user_one.nickname}, @#{user_two.nickname} admin"
- end
-
- test "/:right DELETE, can remove from a permission group", %{admin: admin, conn: conn} do
- user = insert(:user, is_admin: true)
-
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> delete("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin")
-
- assert json_response(conn, 200) == %{"is_admin" => false}
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} revoked admin role from @#{user.nickname}"
- end
-
- test "/:right DELETE, can remove from a permission group (multiple)", %{
- admin: admin,
- conn: conn
- } do
- user_one = insert(:user, is_admin: true)
- user_two = insert(:user, is_admin: true)
-
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> delete("/api/pleroma/admin/users/permission_group/admin", %{
- nicknames: [user_one.nickname, user_two.nickname]
- })
-
- assert json_response(conn, 200) == %{"is_admin" => false}
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} revoked admin role from @#{user_one.nickname}, @#{
- user_two.nickname
- }"
- end
- end
-
- describe "POST /api/pleroma/admin/email_invite, with valid config" do
- setup do: clear_config([:instance, :registrations_open], false)
- setup do: clear_config([:instance, :invites_enabled], true)
-
- test "sends invitation and returns 204", %{admin: admin, conn: conn} do
- recipient_email = "foo@bar.com"
- recipient_name = "J. D."
-
- conn =
- post(
- conn,
- "/api/pleroma/admin/users/email_invite?email=#{recipient_email}&name=#{recipient_name}"
- )
-
- assert json_response(conn, :no_content)
-
- token_record = List.last(Repo.all(Pleroma.UserInviteToken))
- assert token_record
- refute token_record.used
-
- notify_email = Config.get([:instance, :notify_email])
- instance_name = Config.get([:instance, :name])
-
- email =
- Pleroma.Emails.UserEmail.user_invitation_email(
- admin,
- token_record,
- recipient_email,
- recipient_name
- )
-
- Swoosh.TestAssertions.assert_email_sent(
- from: {instance_name, notify_email},
- to: {recipient_name, recipient_email},
- html_body: email.html_body
- )
- end
-
- test "it returns 403 if requested by a non-admin" do
- non_admin_user = insert(:user)
- token = insert(:oauth_token, user: non_admin_user)
-
- conn =
- build_conn()
- |> assign(:user, non_admin_user)
- |> assign(:token, token)
- |> post("/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
-
- assert json_response(conn, :forbidden)
- end
-
- test "email with +", %{conn: conn, admin: admin} do
- recipient_email = "foo+bar@baz.com"
-
- conn
- |> put_req_header("content-type", "application/json;charset=utf-8")
- |> post("/api/pleroma/admin/users/email_invite", %{email: recipient_email})
- |> json_response(:no_content)
-
- token_record =
- Pleroma.UserInviteToken
- |> Repo.all()
- |> List.last()
-
- assert token_record
- refute token_record.used
-
- notify_email = Config.get([:instance, :notify_email])
- instance_name = Config.get([:instance, :name])
-
- email =
- Pleroma.Emails.UserEmail.user_invitation_email(
- admin,
- token_record,
- recipient_email
- )
-
- Swoosh.TestAssertions.assert_email_sent(
- from: {instance_name, notify_email},
- to: recipient_email,
- html_body: email.html_body
- )
- end
- end
-
- describe "POST /api/pleroma/admin/users/email_invite, with invalid config" do
- setup do: clear_config([:instance, :registrations_open])
- setup do: clear_config([:instance, :invites_enabled])
-
- test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn} do
- Config.put([:instance, :registrations_open], false)
- Config.put([:instance, :invites_enabled], false)
-
- conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
-
- assert json_response(conn, :bad_request) ==
- "To send invites you need to set the `invites_enabled` option to true."
- end
-
- test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do
- Config.put([:instance, :registrations_open], true)
- Config.put([:instance, :invites_enabled], true)
-
- conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
-
- assert json_response(conn, :bad_request) ==
- "To send invites you need to set the `registrations_open` option to false."
- end
- end
-
- test "/api/pleroma/admin/users/:nickname/password_reset", %{conn: conn} do
- user = insert(:user)
-
- conn =
- conn
- |> put_req_header("accept", "application/json")
- |> get("/api/pleroma/admin/users/#{user.nickname}/password_reset")
-
- resp = json_response(conn, 200)
-
- assert Regex.match?(~r/(http:\/\/|https:\/\/)/, resp["link"])
- end
-
- describe "GET /api/pleroma/admin/users" do
- test "renders users array for the first page", %{conn: conn, admin: admin} do
- user = insert(:user, local: false, tags: ["foo", "bar"])
- conn = get(conn, "/api/pleroma/admin/users?page=1")
-
- users =
- [
- %{
- "deactivated" => admin.deactivated,
- "id" => admin.id,
- "nickname" => admin.nickname,
- "roles" => %{"admin" => true, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(admin.name || admin.nickname),
- "confirmation_pending" => false
- },
- %{
- "deactivated" => user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => false,
- "tags" => ["foo", "bar"],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
- }
- ]
- |> Enum.sort_by(& &1["nickname"])
-
- assert json_response(conn, 200) == %{
- "count" => 2,
- "page_size" => 50,
- "users" => users
- }
- end
-
- test "pagination works correctly with service users", %{conn: conn} do
- service1 = insert(:user, ap_id: Web.base_url() <> "/relay")
- service2 = insert(:user, ap_id: Web.base_url() <> "/internal/fetch")
- insert_list(25, :user)
-
- assert %{"count" => 26, "page_size" => 10, "users" => users1} =
- conn
- |> get("/api/pleroma/admin/users?page=1&filters=", %{page_size: "10"})
- |> json_response(200)
-
- assert Enum.count(users1) == 10
- assert service1 not in [users1]
- assert service2 not in [users1]
-
- assert %{"count" => 26, "page_size" => 10, "users" => users2} =
- conn
- |> get("/api/pleroma/admin/users?page=2&filters=", %{page_size: "10"})
- |> json_response(200)
-
- assert Enum.count(users2) == 10
- assert service1 not in [users2]
- assert service2 not in [users2]
-
- assert %{"count" => 26, "page_size" => 10, "users" => users3} =
- conn
- |> get("/api/pleroma/admin/users?page=3&filters=", %{page_size: "10"})
- |> json_response(200)
-
- assert Enum.count(users3) == 6
- assert service1 not in [users3]
- assert service2 not in [users3]
- end
-
- test "renders empty array for the second page", %{conn: conn} do
- insert(:user)
-
- conn = get(conn, "/api/pleroma/admin/users?page=2")
-
- assert json_response(conn, 200) == %{
- "count" => 2,
- "page_size" => 50,
- "users" => []
- }
- end
-
- test "regular search", %{conn: conn} do
- user = insert(:user, nickname: "bob")
-
- conn = get(conn, "/api/pleroma/admin/users?query=bo")
-
- assert json_response(conn, 200) == %{
- "count" => 1,
- "page_size" => 50,
- "users" => [
- %{
- "deactivated" => user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
- }
- ]
- }
- end
-
- test "search by domain", %{conn: conn} do
- user = insert(:user, nickname: "nickname@domain.com")
- insert(:user)
-
- conn = get(conn, "/api/pleroma/admin/users?query=domain.com")
-
- assert json_response(conn, 200) == %{
- "count" => 1,
- "page_size" => 50,
- "users" => [
- %{
- "deactivated" => user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
- }
- ]
- }
- end
-
- test "search by full nickname", %{conn: conn} do
- user = insert(:user, nickname: "nickname@domain.com")
- insert(:user)
-
- conn = get(conn, "/api/pleroma/admin/users?query=nickname@domain.com")
-
- assert json_response(conn, 200) == %{
- "count" => 1,
- "page_size" => 50,
- "users" => [
- %{
- "deactivated" => user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
- }
- ]
- }
- end
-
- test "search by display name", %{conn: conn} do
- user = insert(:user, name: "Display name")
- insert(:user)
-
- conn = get(conn, "/api/pleroma/admin/users?name=display")
-
- assert json_response(conn, 200) == %{
- "count" => 1,
- "page_size" => 50,
- "users" => [
- %{
- "deactivated" => user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
- }
- ]
- }
- end
-
- test "search by email", %{conn: conn} do
- user = insert(:user, email: "email@example.com")
- insert(:user)
-
- conn = get(conn, "/api/pleroma/admin/users?email=email@example.com")
-
- assert json_response(conn, 200) == %{
- "count" => 1,
- "page_size" => 50,
- "users" => [
- %{
- "deactivated" => user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
- }
- ]
- }
- end
-
- test "regular search with page size", %{conn: conn} do
- user = insert(:user, nickname: "aalice")
- user2 = insert(:user, nickname: "alice")
-
- conn1 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=1")
-
- assert json_response(conn1, 200) == %{
- "count" => 2,
- "page_size" => 1,
- "users" => [
- %{
- "deactivated" => user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
- }
- ]
- }
-
- conn2 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=2")
-
- assert json_response(conn2, 200) == %{
- "count" => 2,
- "page_size" => 1,
- "users" => [
- %{
- "deactivated" => user2.deactivated,
- "id" => user2.id,
- "nickname" => user2.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user2) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user2.name || user2.nickname),
- "confirmation_pending" => false
- }
- ]
- }
- end
-
- test "only local users" do
- admin = insert(:user, is_admin: true, nickname: "john")
- token = insert(:oauth_admin_token, user: admin)
- user = insert(:user, nickname: "bob")
-
- insert(:user, nickname: "bobb", local: false)
-
- conn =
- build_conn()
- |> assign(:user, admin)
- |> assign(:token, token)
- |> get("/api/pleroma/admin/users?query=bo&filters=local")
-
- assert json_response(conn, 200) == %{
- "count" => 1,
- "page_size" => 50,
- "users" => [
- %{
- "deactivated" => user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
- }
- ]
- }
- end
-
- test "only local users with no query", %{conn: conn, admin: old_admin} do
- admin = insert(:user, is_admin: true, nickname: "john")
- user = insert(:user, nickname: "bob")
-
- insert(:user, nickname: "bobb", local: false)
-
- conn = get(conn, "/api/pleroma/admin/users?filters=local")
-
- users =
- [
- %{
- "deactivated" => user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
- },
- %{
- "deactivated" => admin.deactivated,
- "id" => admin.id,
- "nickname" => admin.nickname,
- "roles" => %{"admin" => true, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(admin.name || admin.nickname),
- "confirmation_pending" => false
- },
- %{
- "deactivated" => false,
- "id" => old_admin.id,
- "local" => true,
- "nickname" => old_admin.nickname,
- "roles" => %{"admin" => true, "moderator" => false},
- "tags" => [],
- "avatar" => User.avatar_url(old_admin) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname),
- "confirmation_pending" => false
- }
- ]
- |> Enum.sort_by(& &1["nickname"])
-
- assert json_response(conn, 200) == %{
- "count" => 3,
- "page_size" => 50,
- "users" => users
- }
- end
-
- test "load only admins", %{conn: conn, admin: admin} do
- second_admin = insert(:user, is_admin: true)
- insert(:user)
- insert(:user)
-
- conn = get(conn, "/api/pleroma/admin/users?filters=is_admin")
-
- users =
- [
- %{
- "deactivated" => false,
- "id" => admin.id,
- "nickname" => admin.nickname,
- "roles" => %{"admin" => true, "moderator" => false},
- "local" => admin.local,
- "tags" => [],
- "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(admin.name || admin.nickname),
- "confirmation_pending" => false
- },
- %{
- "deactivated" => false,
- "id" => second_admin.id,
- "nickname" => second_admin.nickname,
- "roles" => %{"admin" => true, "moderator" => false},
- "local" => second_admin.local,
- "tags" => [],
- "avatar" => User.avatar_url(second_admin) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname),
- "confirmation_pending" => false
- }
- ]
- |> Enum.sort_by(& &1["nickname"])
-
- assert json_response(conn, 200) == %{
- "count" => 2,
- "page_size" => 50,
- "users" => users
- }
- end
-
- test "load only moderators", %{conn: conn} do
- moderator = insert(:user, is_moderator: true)
- insert(:user)
- insert(:user)
-
- conn = get(conn, "/api/pleroma/admin/users?filters=is_moderator")
-
- assert json_response(conn, 200) == %{
- "count" => 1,
- "page_size" => 50,
- "users" => [
- %{
- "deactivated" => false,
- "id" => moderator.id,
- "nickname" => moderator.nickname,
- "roles" => %{"admin" => false, "moderator" => true},
- "local" => moderator.local,
- "tags" => [],
- "avatar" => User.avatar_url(moderator) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(moderator.name || moderator.nickname),
- "confirmation_pending" => false
- }
- ]
- }
- end
-
- test "load users with tags list", %{conn: conn} do
- user1 = insert(:user, tags: ["first"])
- user2 = insert(:user, tags: ["second"])
- insert(:user)
- insert(:user)
-
- conn = get(conn, "/api/pleroma/admin/users?tags[]=first&tags[]=second")
-
- users =
- [
- %{
- "deactivated" => false,
- "id" => user1.id,
- "nickname" => user1.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => user1.local,
- "tags" => ["first"],
- "avatar" => User.avatar_url(user1) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user1.name || user1.nickname),
- "confirmation_pending" => false
- },
- %{
- "deactivated" => false,
- "id" => user2.id,
- "nickname" => user2.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => user2.local,
- "tags" => ["second"],
- "avatar" => User.avatar_url(user2) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user2.name || user2.nickname),
- "confirmation_pending" => false
- }
- ]
- |> Enum.sort_by(& &1["nickname"])
-
- assert json_response(conn, 200) == %{
- "count" => 2,
- "page_size" => 50,
- "users" => users
- }
- end
-
- test "it works with multiple filters" do
- admin = insert(:user, nickname: "john", is_admin: true)
- token = insert(:oauth_admin_token, user: admin)
- user = insert(:user, nickname: "bob", local: false, deactivated: true)
-
- insert(:user, nickname: "ken", local: true, deactivated: true)
- insert(:user, nickname: "bobb", local: false, deactivated: false)
-
- conn =
- build_conn()
- |> assign(:user, admin)
- |> assign(:token, token)
- |> get("/api/pleroma/admin/users?filters=deactivated,external")
-
- assert json_response(conn, 200) == %{
- "count" => 1,
- "page_size" => 50,
- "users" => [
- %{
- "deactivated" => user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => user.local,
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
- }
- ]
- }
- end
-
- test "it omits relay user", %{admin: admin, conn: conn} do
- assert %User{} = Relay.get_actor()
-
- conn = get(conn, "/api/pleroma/admin/users")
-
- assert json_response(conn, 200) == %{
- "count" => 1,
- "page_size" => 50,
- "users" => [
- %{
- "deactivated" => admin.deactivated,
- "id" => admin.id,
- "nickname" => admin.nickname,
- "roles" => %{"admin" => true, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(admin.name || admin.nickname),
- "confirmation_pending" => false
- }
- ]
- }
- end
- end
-
- test "PATCH /api/pleroma/admin/users/activate", %{admin: admin, conn: conn} do
- user_one = insert(:user, deactivated: true)
- user_two = insert(:user, deactivated: true)
-
- conn =
- patch(
- conn,
- "/api/pleroma/admin/users/activate",
- %{nicknames: [user_one.nickname, user_two.nickname]}
- )
-
- response = json_response(conn, 200)
- assert Enum.map(response["users"], & &1["deactivated"]) == [false, false]
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} activated users: @#{user_one.nickname}, @#{user_two.nickname}"
- end
-
- test "PATCH /api/pleroma/admin/users/deactivate", %{admin: admin, conn: conn} do
- user_one = insert(:user, deactivated: false)
- user_two = insert(:user, deactivated: false)
-
- conn =
- patch(
- conn,
- "/api/pleroma/admin/users/deactivate",
- %{nicknames: [user_one.nickname, user_two.nickname]}
- )
-
- response = json_response(conn, 200)
- assert Enum.map(response["users"], & &1["deactivated"]) == [true, true]
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} deactivated users: @#{user_one.nickname}, @#{user_two.nickname}"
- end
-
- test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do
- user = insert(:user)
-
- conn = patch(conn, "/api/pleroma/admin/users/#{user.nickname}/toggle_activation")
-
- assert json_response(conn, 200) ==
- %{
- "deactivated" => !user.deactivated,
- "id" => user.id,
- "nickname" => user.nickname,
- "roles" => %{"admin" => false, "moderator" => false},
- "local" => true,
- "tags" => [],
- "avatar" => User.avatar_url(user) |> MediaProxy.url(),
- "display_name" => HTML.strip_tags(user.name || user.nickname),
- "confirmation_pending" => false
- }
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} deactivated users: @#{user.nickname}"
- end
-
- describe "PUT disable_mfa" do
- test "returns 200 and disable 2fa", %{conn: conn} do
- user =
- insert(:user,
- multi_factor_authentication_settings: %MFA.Settings{
- enabled: true,
- totp: %MFA.Settings.TOTP{secret: "otp_secret", confirmed: true}
- }
- )
-
- response =
- conn
- |> put("/api/pleroma/admin/users/disable_mfa", %{nickname: user.nickname})
- |> json_response(200)
-
- assert response == user.nickname
- mfa_settings = refresh_record(user).multi_factor_authentication_settings
-
- refute mfa_settings.enabled
- refute mfa_settings.totp.confirmed
- end
-
- test "returns 404 if user not found", %{conn: conn} do
- response =
- conn
- |> put("/api/pleroma/admin/users/disable_mfa", %{nickname: "nickname"})
- |> json_response(404)
-
- assert response == "Not found"
- end
- end
-
- describe "POST /api/pleroma/admin/users/invite_token" do
- test "without options", %{conn: conn} do
- conn = post(conn, "/api/pleroma/admin/users/invite_token")
-
- invite_json = json_response(conn, 200)
- invite = UserInviteToken.find_by_token!(invite_json["token"])
- refute invite.used
- refute invite.expires_at
- refute invite.max_use
- assert invite.invite_type == "one_time"
- end
-
- test "with expires_at", %{conn: conn} do
- conn =
- post(conn, "/api/pleroma/admin/users/invite_token", %{
- "expires_at" => Date.to_string(Date.utc_today())
- })
-
- invite_json = json_response(conn, 200)
- invite = UserInviteToken.find_by_token!(invite_json["token"])
-
- refute invite.used
- assert invite.expires_at == Date.utc_today()
- refute invite.max_use
- assert invite.invite_type == "date_limited"
- end
-
- test "with max_use", %{conn: conn} do
- conn = post(conn, "/api/pleroma/admin/users/invite_token", %{"max_use" => 150})
-
- invite_json = json_response(conn, 200)
- invite = UserInviteToken.find_by_token!(invite_json["token"])
- refute invite.used
- refute invite.expires_at
- assert invite.max_use == 150
- assert invite.invite_type == "reusable"
- end
-
- test "with max use and expires_at", %{conn: conn} do
- conn =
- post(conn, "/api/pleroma/admin/users/invite_token", %{
- "max_use" => 150,
- "expires_at" => Date.to_string(Date.utc_today())
- })
-
- invite_json = json_response(conn, 200)
- invite = UserInviteToken.find_by_token!(invite_json["token"])
- refute invite.used
- assert invite.expires_at == Date.utc_today()
- assert invite.max_use == 150
- assert invite.invite_type == "reusable_date_limited"
- end
- end
-
- describe "GET /api/pleroma/admin/users/invites" do
- test "no invites", %{conn: conn} do
- conn = get(conn, "/api/pleroma/admin/users/invites")
-
- assert json_response(conn, 200) == %{"invites" => []}
- end
-
- test "with invite", %{conn: conn} do
- {:ok, invite} = UserInviteToken.create_invite()
-
- conn = get(conn, "/api/pleroma/admin/users/invites")
-
- assert json_response(conn, 200) == %{
- "invites" => [
- %{
- "expires_at" => nil,
- "id" => invite.id,
- "invite_type" => "one_time",
- "max_use" => nil,
- "token" => invite.token,
- "used" => false,
- "uses" => 0
- }
- ]
- }
- end
- end
-
- describe "POST /api/pleroma/admin/users/revoke_invite" do
- test "with token", %{conn: conn} do
- {:ok, invite} = UserInviteToken.create_invite()
-
- conn = post(conn, "/api/pleroma/admin/users/revoke_invite", %{"token" => invite.token})
-
- assert json_response(conn, 200) == %{
- "expires_at" => nil,
- "id" => invite.id,
- "invite_type" => "one_time",
- "max_use" => nil,
- "token" => invite.token,
- "used" => true,
- "uses" => 0
- }
- end
-
- test "with invalid token", %{conn: conn} do
- conn = post(conn, "/api/pleroma/admin/users/revoke_invite", %{"token" => "foo"})
-
- assert json_response(conn, :not_found) == "Not found"
- end
- end
-
- describe "GET /api/pleroma/admin/reports/:id" do
- test "returns report by its id", %{conn: conn} do
- [reporter, target_user] = insert_pair(:user)
- activity = insert(:note_activity, user: target_user)
-
- {:ok, %{id: report_id}} =
- CommonAPI.report(reporter, %{
- account_id: target_user.id,
- comment: "I feel offended",
- status_ids: [activity.id]
- })
-
- response =
- conn
- |> get("/api/pleroma/admin/reports/#{report_id}")
- |> json_response(:ok)
-
- assert response["id"] == report_id
- end
-
- test "returns 404 when report id is invalid", %{conn: conn} do
- conn = get(conn, "/api/pleroma/admin/reports/test")
-
- assert json_response(conn, :not_found) == "Not found"
- end
- end
-
- describe "PATCH /api/pleroma/admin/reports" do
- setup do
- [reporter, target_user] = insert_pair(:user)
- activity = insert(:note_activity, user: target_user)
-
- {:ok, %{id: report_id}} =
- CommonAPI.report(reporter, %{
- account_id: target_user.id,
- comment: "I feel offended",
- status_ids: [activity.id]
- })
-
- {:ok, %{id: second_report_id}} =
- CommonAPI.report(reporter, %{
- account_id: target_user.id,
- comment: "I feel very offended",
- status_ids: [activity.id]
- })
-
- %{
- id: report_id,
- second_report_id: second_report_id
- }
- end
-
- test "requires admin:write:reports scope", %{conn: conn, id: id, admin: admin} do
- read_token = insert(:oauth_token, user: admin, scopes: ["admin:read"])
- write_token = insert(:oauth_token, user: admin, scopes: ["admin:write:reports"])
-
- response =
- conn
- |> assign(:token, read_token)
- |> patch("/api/pleroma/admin/reports", %{
- "reports" => [%{"state" => "resolved", "id" => id}]
- })
- |> json_response(403)
-
- assert response == %{
- "error" => "Insufficient permissions: admin:write:reports."
- }
-
- conn
- |> assign(:token, write_token)
- |> patch("/api/pleroma/admin/reports", %{
- "reports" => [%{"state" => "resolved", "id" => id}]
- })
- |> json_response(:no_content)
- end
-
- test "mark report as resolved", %{conn: conn, id: id, admin: admin} do
- conn
- |> patch("/api/pleroma/admin/reports", %{
- "reports" => [
- %{"state" => "resolved", "id" => id}
- ]
- })
- |> json_response(:no_content)
-
- activity = Activity.get_by_id(id)
- assert activity.data["state"] == "resolved"
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} updated report ##{id} with 'resolved' state"
- end
-
- test "closes report", %{conn: conn, id: id, admin: admin} do
- conn
- |> patch("/api/pleroma/admin/reports", %{
- "reports" => [
- %{"state" => "closed", "id" => id}
- ]
- })
- |> json_response(:no_content)
-
- activity = Activity.get_by_id(id)
- assert activity.data["state"] == "closed"
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} updated report ##{id} with 'closed' state"
- end
-
- test "returns 400 when state is unknown", %{conn: conn, id: id} do
- conn =
- conn
- |> patch("/api/pleroma/admin/reports", %{
- "reports" => [
- %{"state" => "test", "id" => id}
- ]
- })
-
- assert hd(json_response(conn, :bad_request))["error"] == "Unsupported state"
- end
-
- test "returns 404 when report is not exist", %{conn: conn} do
- conn =
- conn
- |> patch("/api/pleroma/admin/reports", %{
- "reports" => [
- %{"state" => "closed", "id" => "test"}
- ]
- })
-
- assert hd(json_response(conn, :bad_request))["error"] == "not_found"
- end
-
- test "updates state of multiple reports", %{
- conn: conn,
- id: id,
- admin: admin,
- second_report_id: second_report_id
- } do
- conn
- |> patch("/api/pleroma/admin/reports", %{
- "reports" => [
- %{"state" => "resolved", "id" => id},
- %{"state" => "closed", "id" => second_report_id}
- ]
- })
- |> json_response(:no_content)
-
- activity = Activity.get_by_id(id)
- second_activity = Activity.get_by_id(second_report_id)
- assert activity.data["state"] == "resolved"
- assert second_activity.data["state"] == "closed"
-
- [first_log_entry, second_log_entry] = Repo.all(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(first_log_entry) ==
- "@#{admin.nickname} updated report ##{id} with 'resolved' state"
-
- assert ModerationLog.get_log_entry_message(second_log_entry) ==
- "@#{admin.nickname} updated report ##{second_report_id} with 'closed' state"
- end
- end
-
- describe "GET /api/pleroma/admin/reports" do
- test "returns empty response when no reports created", %{conn: conn} do
- response =
- conn
- |> get("/api/pleroma/admin/reports")
- |> json_response(:ok)
-
- assert Enum.empty?(response["reports"])
- assert response["total"] == 0
- end
-
- test "returns reports", %{conn: conn} do
- [reporter, target_user] = insert_pair(:user)
- activity = insert(:note_activity, user: target_user)
-
- {:ok, %{id: report_id}} =
- CommonAPI.report(reporter, %{
- account_id: target_user.id,
- comment: "I feel offended",
- status_ids: [activity.id]
- })
-
- response =
- conn
- |> get("/api/pleroma/admin/reports")
- |> json_response(:ok)
-
- [report] = response["reports"]
-
- assert length(response["reports"]) == 1
- assert report["id"] == report_id
-
- assert response["total"] == 1
- end
-
- test "returns reports with specified state", %{conn: conn} do
- [reporter, target_user] = insert_pair(:user)
- activity = insert(:note_activity, user: target_user)
-
- {:ok, %{id: first_report_id}} =
- CommonAPI.report(reporter, %{
- account_id: target_user.id,
- comment: "I feel offended",
- status_ids: [activity.id]
- })
-
- {:ok, %{id: second_report_id}} =
- CommonAPI.report(reporter, %{
- account_id: target_user.id,
- comment: "I don't like this user"
- })
-
- CommonAPI.update_report_state(second_report_id, "closed")
-
- response =
- conn
- |> get("/api/pleroma/admin/reports", %{
- "state" => "open"
- })
- |> json_response(:ok)
-
- [open_report] = response["reports"]
-
- assert length(response["reports"]) == 1
- assert open_report["id"] == first_report_id
-
- assert response["total"] == 1
-
- response =
- conn
- |> get("/api/pleroma/admin/reports", %{
- "state" => "closed"
- })
- |> json_response(:ok)
-
- [closed_report] = response["reports"]
-
- assert length(response["reports"]) == 1
- assert closed_report["id"] == second_report_id
-
- assert response["total"] == 1
-
- response =
- conn
- |> get("/api/pleroma/admin/reports", %{
- "state" => "resolved"
- })
- |> json_response(:ok)
-
- assert Enum.empty?(response["reports"])
- assert response["total"] == 0
- end
-
- test "returns 403 when requested by a non-admin" do
- user = insert(:user)
- token = insert(:oauth_token, user: user)
-
- conn =
- build_conn()
- |> assign(:user, user)
- |> assign(:token, token)
- |> get("/api/pleroma/admin/reports")
-
- assert json_response(conn, :forbidden) ==
- %{"error" => "User is not an admin or OAuth admin scope is not granted."}
- end
-
- test "returns 403 when requested by anonymous" do
- conn = get(build_conn(), "/api/pleroma/admin/reports")
-
- assert json_response(conn, :forbidden) == %{"error" => "Invalid credentials."}
- end
- end
-
- describe "GET /api/pleroma/admin/statuses/:id" do
- test "not found", %{conn: conn} do
- assert conn
- |> get("/api/pleroma/admin/statuses/not_found")
- |> json_response(:not_found)
- end
-
- test "shows activity", %{conn: conn} do
- activity = insert(:note_activity)
-
- response =
- conn
- |> get("/api/pleroma/admin/statuses/#{activity.id}")
- |> json_response(200)
-
- assert response["id"] == activity.id
- end
- end
-
- describe "PUT /api/pleroma/admin/statuses/:id" do
- setup do
- activity = insert(:note_activity)
-
- %{id: activity.id}
- end
-
- test "toggle sensitive flag", %{conn: conn, id: id, admin: admin} do
- response =
- conn
- |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "true"})
- |> json_response(:ok)
-
- assert response["sensitive"]
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} updated status ##{id}, set sensitive: 'true'"
-
- response =
- conn
- |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "false"})
- |> json_response(:ok)
-
- refute response["sensitive"]
- end
-
- test "change visibility flag", %{conn: conn, id: id, admin: admin} do
- response =
- conn
- |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "public"})
- |> json_response(:ok)
-
- assert response["visibility"] == "public"
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} updated status ##{id}, set visibility: 'public'"
-
- response =
- conn
- |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "private"})
- |> json_response(:ok)
-
- assert response["visibility"] == "private"
-
- response =
- conn
- |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "unlisted"})
- |> json_response(:ok)
-
- assert response["visibility"] == "unlisted"
- end
-
- test "returns 400 when visibility is unknown", %{conn: conn, id: id} do
- conn = put(conn, "/api/pleroma/admin/statuses/#{id}", %{visibility: "test"})
-
- assert json_response(conn, :bad_request) == "Unsupported visibility"
- end
- end
-
- describe "DELETE /api/pleroma/admin/statuses/:id" do
- setup do
- activity = insert(:note_activity)
-
- %{id: activity.id}
- end
-
- test "deletes status", %{conn: conn, id: id, admin: admin} do
- conn
- |> delete("/api/pleroma/admin/statuses/#{id}")
- |> json_response(:ok)
-
- refute Activity.get_by_id(id)
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} deleted status ##{id}"
- end
-
- test "returns 404 when the status does not exist", %{conn: conn} do
- conn = delete(conn, "/api/pleroma/admin/statuses/test")
-
- assert json_response(conn, :not_found) == "Not found"
- end
- end
-
- describe "GET /api/pleroma/admin/config" do
- setup do: clear_config(:configurable_from_database, true)
-
- test "when configuration from database is off", %{conn: conn} do
- Config.put(:configurable_from_database, false)
- conn = get(conn, "/api/pleroma/admin/config")
-
- assert json_response(conn, 400) ==
- "To use this endpoint you need to enable configuration from database."
- end
-
- test "with settings only in db", %{conn: conn} do
- config1 = insert(:config)
- config2 = insert(:config)
-
- conn = get(conn, "/api/pleroma/admin/config", %{"only_db" => true})
-
- %{
- "configs" => [
- %{
- "group" => ":pleroma",
- "key" => key1,
- "value" => _
- },
- %{
- "group" => ":pleroma",
- "key" => key2,
- "value" => _
- }
- ]
- } = json_response(conn, 200)
-
- assert key1 == config1.key
- assert key2 == config2.key
- end
-
- test "db is added to settings that are in db", %{conn: conn} do
- _config = insert(:config, key: ":instance", value: ConfigDB.to_binary(name: "Some name"))
-
- %{"configs" => configs} =
- conn
- |> get("/api/pleroma/admin/config")
- |> json_response(200)
-
- [instance_config] =
- Enum.filter(configs, fn %{"group" => group, "key" => key} ->
- group == ":pleroma" and key == ":instance"
- end)
-
- assert instance_config["db"] == [":name"]
- end
-
- test "merged default setting with db settings", %{conn: conn} do
- config1 = insert(:config)
- config2 = insert(:config)
-
- config3 =
- insert(:config,
- value: ConfigDB.to_binary(k1: :v1, k2: :v2)
- )
-
- %{"configs" => configs} =
- conn
- |> get("/api/pleroma/admin/config")
- |> json_response(200)
-
- assert length(configs) > 3
-
- received_configs =
- Enum.filter(configs, fn %{"group" => group, "key" => key} ->
- group == ":pleroma" and key in [config1.key, config2.key, config3.key]
- end)
-
- assert length(received_configs) == 3
-
- db_keys =
- config3.value
- |> ConfigDB.from_binary()
- |> Keyword.keys()
- |> ConfigDB.convert()
-
- Enum.each(received_configs, fn %{"value" => value, "db" => db} ->
- assert db in [[config1.key], [config2.key], db_keys]
-
- assert value in [
- ConfigDB.from_binary_with_convert(config1.value),
- ConfigDB.from_binary_with_convert(config2.value),
- ConfigDB.from_binary_with_convert(config3.value)
- ]
- end)
- end
-
- test "subkeys with full update right merge", %{conn: conn} do
- config1 =
- insert(:config,
- key: ":emoji",
- value: ConfigDB.to_binary(groups: [a: 1, b: 2], key: [a: 1])
- )
-
- config2 =
- insert(:config,
- key: ":assets",
- value: ConfigDB.to_binary(mascots: [a: 1, b: 2], key: [a: 1])
- )
-
- %{"configs" => configs} =
- conn
- |> get("/api/pleroma/admin/config")
- |> json_response(200)
-
- vals =
- Enum.filter(configs, fn %{"group" => group, "key" => key} ->
- group == ":pleroma" and key in [config1.key, config2.key]
- end)
-
- emoji = Enum.find(vals, fn %{"key" => key} -> key == ":emoji" end)
- assets = Enum.find(vals, fn %{"key" => key} -> key == ":assets" end)
-
- emoji_val = ConfigDB.transform_with_out_binary(emoji["value"])
- assets_val = ConfigDB.transform_with_out_binary(assets["value"])
-
- assert emoji_val[:groups] == [a: 1, b: 2]
- assert assets_val[:mascots] == [a: 1, b: 2]
- end
- end
-
- test "POST /api/pleroma/admin/config error", %{conn: conn} do
- conn = post(conn, "/api/pleroma/admin/config", %{"configs" => []})
-
- assert json_response(conn, 400) ==
- "To use this endpoint you need to enable configuration from database."
- end
-
- describe "POST /api/pleroma/admin/config" do
- setup do
- http = Application.get_env(:pleroma, :http)
-
- on_exit(fn ->
- Application.delete_env(:pleroma, :key1)
- Application.delete_env(:pleroma, :key2)
- Application.delete_env(:pleroma, :key3)
- Application.delete_env(:pleroma, :key4)
- Application.delete_env(:pleroma, :keyaa1)
- Application.delete_env(:pleroma, :keyaa2)
- Application.delete_env(:pleroma, Pleroma.Web.Endpoint.NotReal)
- Application.delete_env(:pleroma, Pleroma.Captcha.NotReal)
- Application.put_env(:pleroma, :http, http)
- Application.put_env(:tesla, :adapter, Tesla.Mock)
- Restarter.Pleroma.refresh()
- end)
- end
-
- setup do: clear_config(:configurable_from_database, true)
-
- @tag capture_log: true
- test "create new config setting in db", %{conn: conn} do
- ueberauth = Application.get_env(:ueberauth, Ueberauth)
- on_exit(fn -> Application.put_env(:ueberauth, Ueberauth, ueberauth) end)
-
- conn =
- post(conn, "/api/pleroma/admin/config", %{
- configs: [
- %{group: ":pleroma", key: ":key1", value: "value1"},
- %{
- group: ":ueberauth",
- key: "Ueberauth",
- value: [%{"tuple" => [":consumer_secret", "aaaa"]}]
- },
- %{
- group: ":pleroma",
- key: ":key2",
- value: %{
- ":nested_1" => "nested_value1",
- ":nested_2" => [
- %{":nested_22" => "nested_value222"},
- %{":nested_33" => %{":nested_44" => "nested_444"}}
- ]
- }
- },
- %{
- group: ":pleroma",
- key: ":key3",
- value: [
- %{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
- %{"nested_4" => true}
- ]
- },
- %{
- group: ":pleroma",
- key: ":key4",
- value: %{":nested_5" => ":upload", "endpoint" => "https://example.com"}
- },
- %{
- group: ":idna",
- key: ":key5",
- value: %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]}
- }
- ]
- })
-
- assert json_response(conn, 200) == %{
- "configs" => [
- %{
- "group" => ":pleroma",
- "key" => ":key1",
- "value" => "value1",
- "db" => [":key1"]
- },
- %{
- "group" => ":ueberauth",
- "key" => "Ueberauth",
- "value" => [%{"tuple" => [":consumer_secret", "aaaa"]}],
- "db" => [":consumer_secret"]
- },
- %{
- "group" => ":pleroma",
- "key" => ":key2",
- "value" => %{
- ":nested_1" => "nested_value1",
- ":nested_2" => [
- %{":nested_22" => "nested_value222"},
- %{":nested_33" => %{":nested_44" => "nested_444"}}
- ]
- },
- "db" => [":key2"]
- },
- %{
- "group" => ":pleroma",
- "key" => ":key3",
- "value" => [
- %{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
- %{"nested_4" => true}
- ],
- "db" => [":key3"]
- },
- %{
- "group" => ":pleroma",
- "key" => ":key4",
- "value" => %{"endpoint" => "https://example.com", ":nested_5" => ":upload"},
- "db" => [":key4"]
- },
- %{
- "group" => ":idna",
- "key" => ":key5",
- "value" => %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]},
- "db" => [":key5"]
- }
- ]
- }
-
- assert Application.get_env(:pleroma, :key1) == "value1"
-
- assert Application.get_env(:pleroma, :key2) == %{
- nested_1: "nested_value1",
- nested_2: [
- %{nested_22: "nested_value222"},
- %{nested_33: %{nested_44: "nested_444"}}
- ]
- }
-
- assert Application.get_env(:pleroma, :key3) == [
- %{"nested_3" => :nested_3, "nested_33" => "nested_33"},
- %{"nested_4" => true}
- ]
-
- assert Application.get_env(:pleroma, :key4) == %{
- "endpoint" => "https://example.com",
- nested_5: :upload
- }
-
- assert Application.get_env(:idna, :key5) == {"string", Pleroma.Captcha.NotReal, []}
- end
-
- test "save configs setting without explicit key", %{conn: conn} do
- level = Application.get_env(:quack, :level)
- meta = Application.get_env(:quack, :meta)
- webhook_url = Application.get_env(:quack, :webhook_url)
-
- on_exit(fn ->
- Application.put_env(:quack, :level, level)
- Application.put_env(:quack, :meta, meta)
- Application.put_env(:quack, :webhook_url, webhook_url)
- end)
-
- conn =
- post(conn, "/api/pleroma/admin/config", %{
- configs: [
- %{
- group: ":quack",
- key: ":level",
- value: ":info"
- },
- %{
- group: ":quack",
- key: ":meta",
- value: [":none"]
- },
- %{
- group: ":quack",
- key: ":webhook_url",
- value: "https://hooks.slack.com/services/KEY"
- }
- ]
- })
-
- assert json_response(conn, 200) == %{
- "configs" => [
- %{
- "group" => ":quack",
- "key" => ":level",
- "value" => ":info",
- "db" => [":level"]
- },
- %{
- "group" => ":quack",
- "key" => ":meta",
- "value" => [":none"],
- "db" => [":meta"]
- },
- %{
- "group" => ":quack",
- "key" => ":webhook_url",
- "value" => "https://hooks.slack.com/services/KEY",
- "db" => [":webhook_url"]
- }
- ]
- }
-
- assert Application.get_env(:quack, :level) == :info
- assert Application.get_env(:quack, :meta) == [:none]
- assert Application.get_env(:quack, :webhook_url) == "https://hooks.slack.com/services/KEY"
- end
-
- test "saving config with partial update", %{conn: conn} do
- config = insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: 2))
-
- conn =
- post(conn, "/api/pleroma/admin/config", %{
- configs: [
- %{group: config.group, key: config.key, value: [%{"tuple" => [":key3", 3]}]}
- ]
- })
-
- assert json_response(conn, 200) == %{
- "configs" => [
- %{
- "group" => ":pleroma",
- "key" => ":key1",
- "value" => [
- %{"tuple" => [":key1", 1]},
- %{"tuple" => [":key2", 2]},
- %{"tuple" => [":key3", 3]}
- ],
- "db" => [":key1", ":key2", ":key3"]
- }
- ]
- }
- end
-
- test "saving config which need pleroma reboot", %{conn: conn} do
- chat = Config.get(:chat)
- on_exit(fn -> Config.put(:chat, chat) end)
-
- assert post(
- conn,
- "/api/pleroma/admin/config",
- %{
- configs: [
- %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
- ]
- }
- )
- |> json_response(200) == %{
- "configs" => [
- %{
- "db" => [":enabled"],
- "group" => ":pleroma",
- "key" => ":chat",
- "value" => [%{"tuple" => [":enabled", true]}]
- }
- ],
- "need_reboot" => true
- }
-
- configs =
- conn
- |> get("/api/pleroma/admin/config")
- |> json_response(200)
-
- assert configs["need_reboot"]
-
- capture_log(fn ->
- assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
- end) =~ "pleroma restarted"
-
- configs =
- conn
- |> get("/api/pleroma/admin/config")
- |> json_response(200)
-
- assert configs["need_reboot"] == false
- end
-
- test "update setting which need reboot, don't change reboot flag until reboot", %{conn: conn} do
- chat = Config.get(:chat)
- on_exit(fn -> Config.put(:chat, chat) end)
-
- assert post(
- conn,
- "/api/pleroma/admin/config",
- %{
- configs: [
- %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
- ]
- }
- )
- |> json_response(200) == %{
- "configs" => [
- %{
- "db" => [":enabled"],
- "group" => ":pleroma",
- "key" => ":chat",
- "value" => [%{"tuple" => [":enabled", true]}]
- }
- ],
- "need_reboot" => true
- }
-
- assert post(conn, "/api/pleroma/admin/config", %{
- configs: [
- %{group: ":pleroma", key: ":key1", value: [%{"tuple" => [":key3", 3]}]}
- ]
- })
- |> json_response(200) == %{
- "configs" => [
- %{
- "group" => ":pleroma",
- "key" => ":key1",
- "value" => [
- %{"tuple" => [":key3", 3]}
- ],
- "db" => [":key3"]
- }
- ],
- "need_reboot" => true
- }
-
- capture_log(fn ->
- assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
- end) =~ "pleroma restarted"
-
- configs =
- conn
- |> get("/api/pleroma/admin/config")
- |> json_response(200)
-
- assert configs["need_reboot"] == false
- end
-
- test "saving config with nested merge", %{conn: conn} do
- config =
- insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: [k1: 1, k2: 2]))
-
- conn =
- post(conn, "/api/pleroma/admin/config", %{
- configs: [
- %{
- group: config.group,
- key: config.key,
- value: [
- %{"tuple" => [":key3", 3]},
- %{
- "tuple" => [
- ":key2",
- [
- %{"tuple" => [":k2", 1]},
- %{"tuple" => [":k3", 3]}
- ]
- ]
- }
- ]
- }
- ]
- })
-
- assert json_response(conn, 200) == %{
- "configs" => [
- %{
- "group" => ":pleroma",
- "key" => ":key1",
- "value" => [
- %{"tuple" => [":key1", 1]},
- %{"tuple" => [":key3", 3]},
- %{
- "tuple" => [
- ":key2",
- [
- %{"tuple" => [":k1", 1]},
- %{"tuple" => [":k2", 1]},
- %{"tuple" => [":k3", 3]}
- ]
- ]
- }
- ],
- "db" => [":key1", ":key3", ":key2"]
- }
- ]
- }
- end
-
- test "saving special atoms", %{conn: conn} do
- conn =
- post(conn, "/api/pleroma/admin/config", %{
- "configs" => [
- %{
- "group" => ":pleroma",
- "key" => ":key1",
- "value" => [
- %{
- "tuple" => [
- ":ssl_options",
- [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}]
- ]
- }
- ]
- }
- ]
- })
-
- assert json_response(conn, 200) == %{
- "configs" => [
- %{
- "group" => ":pleroma",
- "key" => ":key1",
- "value" => [
- %{
- "tuple" => [
- ":ssl_options",
- [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}]
- ]
- }
- ],
- "db" => [":ssl_options"]
- }
- ]
- }
-
- assert Application.get_env(:pleroma, :key1) == [
- ssl_options: [versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"]]
- ]
- end
-
- test "saving full setting if value is in full_key_update list", %{conn: conn} do
- backends = Application.get_env(:logger, :backends)
- on_exit(fn -> Application.put_env(:logger, :backends, backends) end)
-
- config =
- insert(:config,
- group: ":logger",
- key: ":backends",
- value: :erlang.term_to_binary([])
- )
-
- Pleroma.Config.TransferTask.load_and_update_env([], false)
-
- assert Application.get_env(:logger, :backends) == []
-
- conn =
- post(conn, "/api/pleroma/admin/config", %{
- configs: [
- %{
- group: config.group,
- key: config.key,
- value: [":console"]
- }
- ]
- })
-
- assert json_response(conn, 200) == %{
- "configs" => [
- %{
- "group" => ":logger",
- "key" => ":backends",
- "value" => [
- ":console"
- ],
- "db" => [":backends"]
- }
- ]
- }
-
- assert Application.get_env(:logger, :backends) == [
- :console
- ]
- end
-
- test "saving full setting if value is not keyword", %{conn: conn} do
- config =
- insert(:config,
- group: ":tesla",
- key: ":adapter",
- value: :erlang.term_to_binary(Tesla.Adapter.Hackey)
- )
-
- conn =
- post(conn, "/api/pleroma/admin/config", %{
- configs: [
- %{group: config.group, key: config.key, value: "Tesla.Adapter.Httpc"}
- ]
- })
-
- assert json_response(conn, 200) == %{
- "configs" => [
- %{
- "group" => ":tesla",
- "key" => ":adapter",
- "value" => "Tesla.Adapter.Httpc",
- "db" => [":adapter"]
- }
- ]
- }
- end
-
- test "update config setting & delete with fallback to default value", %{
- conn: conn,
- admin: admin,
- token: token
- } do
- ueberauth = Application.get_env(:ueberauth, Ueberauth)
- config1 = insert(:config, key: ":keyaa1")
- config2 = insert(:config, key: ":keyaa2")
-
- config3 =
- insert(:config,
- group: ":ueberauth",
- key: "Ueberauth"
- )
-
- conn =
- post(conn, "/api/pleroma/admin/config", %{
- configs: [
- %{group: config1.group, key: config1.key, value: "another_value"},
- %{group: config2.group, key: config2.key, value: "another_value"}
- ]
- })
-
- assert json_response(conn, 200) == %{
- "configs" => [
- %{
- "group" => ":pleroma",
- "key" => config1.key,
- "value" => "another_value",
- "db" => [":keyaa1"]
- },
- %{
- "group" => ":pleroma",
- "key" => config2.key,
- "value" => "another_value",
- "db" => [":keyaa2"]
- }
- ]
- }
-
- assert Application.get_env(:pleroma, :keyaa1) == "another_value"
- assert Application.get_env(:pleroma, :keyaa2) == "another_value"
- assert Application.get_env(:ueberauth, Ueberauth) == ConfigDB.from_binary(config3.value)
-
- conn =
- build_conn()
- |> assign(:user, admin)
- |> assign(:token, token)
- |> post("/api/pleroma/admin/config", %{
- configs: [
- %{group: config2.group, key: config2.key, delete: true},
- %{
- group: ":ueberauth",
- key: "Ueberauth",
- delete: true
- }
- ]
- })
-
- assert json_response(conn, 200) == %{
- "configs" => []
- }
-
- assert Application.get_env(:ueberauth, Ueberauth) == ueberauth
- refute Keyword.has_key?(Application.get_all_env(:pleroma), :keyaa2)
- end
-
- test "common config example", %{conn: conn} do
- conn =
- post(conn, "/api/pleroma/admin/config", %{
- configs: [
- %{
- "group" => ":pleroma",
- "key" => "Pleroma.Captcha.NotReal",
- "value" => [
- %{"tuple" => [":enabled", false]},
- %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
- %{"tuple" => [":seconds_valid", 60]},
- %{"tuple" => [":path", ""]},
- %{"tuple" => [":key1", nil]},
- %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
- %{"tuple" => [":regex1", "~r/https:\/\/example.com/"]},
- %{"tuple" => [":regex2", "~r/https:\/\/example.com/u"]},
- %{"tuple" => [":regex3", "~r/https:\/\/example.com/i"]},
- %{"tuple" => [":regex4", "~r/https:\/\/example.com/s"]},
- %{"tuple" => [":name", "Pleroma"]}
- ]
- }
- ]
- })
-
- assert Config.get([Pleroma.Captcha.NotReal, :name]) == "Pleroma"
-
- assert json_response(conn, 200) == %{
- "configs" => [
- %{
- "group" => ":pleroma",
- "key" => "Pleroma.Captcha.NotReal",
- "value" => [
- %{"tuple" => [":enabled", false]},
- %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
- %{"tuple" => [":seconds_valid", 60]},
- %{"tuple" => [":path", ""]},
- %{"tuple" => [":key1", nil]},
- %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
- %{"tuple" => [":regex1", "~r/https:\\/\\/example.com/"]},
- %{"tuple" => [":regex2", "~r/https:\\/\\/example.com/u"]},
- %{"tuple" => [":regex3", "~r/https:\\/\\/example.com/i"]},
- %{"tuple" => [":regex4", "~r/https:\\/\\/example.com/s"]},
- %{"tuple" => [":name", "Pleroma"]}
- ],
- "db" => [
- ":enabled",
- ":method",
- ":seconds_valid",
- ":path",
- ":key1",
- ":partial_chain",
- ":regex1",
- ":regex2",
- ":regex3",
- ":regex4",
- ":name"
- ]
- }
- ]
- }
- end
-
- test "tuples with more than two values", %{conn: conn} do
- conn =
- post(conn, "/api/pleroma/admin/config", %{
- configs: [
- %{
- "group" => ":pleroma",
- "key" => "Pleroma.Web.Endpoint.NotReal",
- "value" => [
- %{
- "tuple" => [
- ":http",
- [
- %{
- "tuple" => [
- ":key2",
- [
- %{
- "tuple" => [
- ":_",
- [
- %{
- "tuple" => [
- "/api/v1/streaming",
- "Pleroma.Web.MastodonAPI.WebsocketHandler",
- []
- ]
- },
- %{
- "tuple" => [
- "/websocket",
- "Phoenix.Endpoint.CowboyWebSocket",
- %{
- "tuple" => [
- "Phoenix.Transports.WebSocket",
- %{
- "tuple" => [
- "Pleroma.Web.Endpoint",
- "Pleroma.Web.UserSocket",
- []
- ]
- }
- ]
- }
- ]
- },
- %{
- "tuple" => [
- ":_",
- "Phoenix.Endpoint.Cowboy2Handler",
- %{"tuple" => ["Pleroma.Web.Endpoint", []]}
- ]
- }
- ]
- ]
- }
- ]
- ]
- }
- ]
- ]
- }
- ]
- }
- ]
- })
-
- assert json_response(conn, 200) == %{
- "configs" => [
- %{
- "group" => ":pleroma",
- "key" => "Pleroma.Web.Endpoint.NotReal",
- "value" => [
- %{
- "tuple" => [
- ":http",
- [
- %{
- "tuple" => [
- ":key2",
- [
- %{
- "tuple" => [
- ":_",
- [
- %{
- "tuple" => [
- "/api/v1/streaming",
- "Pleroma.Web.MastodonAPI.WebsocketHandler",
- []
- ]
- },
- %{
- "tuple" => [
- "/websocket",
- "Phoenix.Endpoint.CowboyWebSocket",
- %{
- "tuple" => [
- "Phoenix.Transports.WebSocket",
- %{
- "tuple" => [
- "Pleroma.Web.Endpoint",
- "Pleroma.Web.UserSocket",
- []
- ]
- }
- ]
- }
- ]
- },
- %{
- "tuple" => [
- ":_",
- "Phoenix.Endpoint.Cowboy2Handler",
- %{"tuple" => ["Pleroma.Web.Endpoint", []]}
- ]
- }
- ]
- ]
- }
- ]
- ]
- }
- ]
- ]
- }
- ],
- "db" => [":http"]
- }
- ]
- }
- end
-
- test "settings with nesting map", %{conn: conn} do
- conn =
- post(conn, "/api/pleroma/admin/config", %{
- configs: [
- %{
- "group" => ":pleroma",
- "key" => ":key1",
- "value" => [
- %{"tuple" => [":key2", "some_val"]},
- %{
- "tuple" => [
- ":key3",
- %{
- ":max_options" => 20,
- ":max_option_chars" => 200,
- ":min_expiration" => 0,
- ":max_expiration" => 31_536_000,
- "nested" => %{
- ":max_options" => 20,
- ":max_option_chars" => 200,
- ":min_expiration" => 0,
- ":max_expiration" => 31_536_000
- }
- }
- ]
- }
- ]
- }
- ]
- })
-
- assert json_response(conn, 200) ==
- %{
- "configs" => [
- %{
- "group" => ":pleroma",
- "key" => ":key1",
- "value" => [
- %{"tuple" => [":key2", "some_val"]},
- %{
- "tuple" => [
- ":key3",
- %{
- ":max_expiration" => 31_536_000,
- ":max_option_chars" => 200,
- ":max_options" => 20,
- ":min_expiration" => 0,
- "nested" => %{
- ":max_expiration" => 31_536_000,
- ":max_option_chars" => 200,
- ":max_options" => 20,
- ":min_expiration" => 0
- }
- }
- ]
- }
- ],
- "db" => [":key2", ":key3"]
- }
- ]
- }
- end
-
- test "value as map", %{conn: conn} do
- conn =
- post(conn, "/api/pleroma/admin/config", %{
- configs: [
- %{
- "group" => ":pleroma",
- "key" => ":key1",
- "value" => %{"key" => "some_val"}
- }
- ]
- })
-
- assert json_response(conn, 200) ==
- %{
- "configs" => [
- %{
- "group" => ":pleroma",
- "key" => ":key1",
- "value" => %{"key" => "some_val"},
- "db" => [":key1"]
- }
- ]
- }
- end
-
- test "queues key as atom", %{conn: conn} do
- conn =
- post(conn, "/api/pleroma/admin/config", %{
- configs: [
- %{
- "group" => ":oban",
- "key" => ":queues",
- "value" => [
- %{"tuple" => [":federator_incoming", 50]},
- %{"tuple" => [":federator_outgoing", 50]},
- %{"tuple" => [":web_push", 50]},
- %{"tuple" => [":mailer", 10]},
- %{"tuple" => [":transmogrifier", 20]},
- %{"tuple" => [":scheduled_activities", 10]},
- %{"tuple" => [":background", 5]}
- ]
- }
- ]
- })
-
- assert json_response(conn, 200) == %{
- "configs" => [
- %{
- "group" => ":oban",
- "key" => ":queues",
- "value" => [
- %{"tuple" => [":federator_incoming", 50]},
- %{"tuple" => [":federator_outgoing", 50]},
- %{"tuple" => [":web_push", 50]},
- %{"tuple" => [":mailer", 10]},
- %{"tuple" => [":transmogrifier", 20]},
- %{"tuple" => [":scheduled_activities", 10]},
- %{"tuple" => [":background", 5]}
- ],
- "db" => [
- ":federator_incoming",
- ":federator_outgoing",
- ":web_push",
- ":mailer",
- ":transmogrifier",
- ":scheduled_activities",
- ":background"
- ]
- }
- ]
- }
- end
-
- test "delete part of settings by atom subkeys", %{conn: conn} do
- config =
- insert(:config,
- key: ":keyaa1",
- value: :erlang.term_to_binary(subkey1: "val1", subkey2: "val2", subkey3: "val3")
- )
-
- conn =
- post(conn, "/api/pleroma/admin/config", %{
- configs: [
- %{
- group: config.group,
- key: config.key,
- subkeys: [":subkey1", ":subkey3"],
- delete: true
- }
- ]
- })
-
- assert json_response(conn, 200) == %{
- "configs" => [
- %{
- "group" => ":pleroma",
- "key" => ":keyaa1",
- "value" => [%{"tuple" => [":subkey2", "val2"]}],
- "db" => [":subkey2"]
- }
- ]
- }
- end
-
- test "proxy tuple localhost", %{conn: conn} do
- conn =
- post(conn, "/api/pleroma/admin/config", %{
- configs: [
- %{
- group: ":pleroma",
- key: ":http",
- value: [
- %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]}
- ]
- }
- ]
- })
-
- assert %{
- "configs" => [
- %{
- "group" => ":pleroma",
- "key" => ":http",
- "value" => value,
- "db" => db
- }
- ]
- } = json_response(conn, 200)
-
- assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]} in value
- assert ":proxy_url" in db
- end
-
- test "proxy tuple domain", %{conn: conn} do
- conn =
- post(conn, "/api/pleroma/admin/config", %{
- configs: [
- %{
- group: ":pleroma",
- key: ":http",
- value: [
- %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]}
- ]
- }
- ]
- })
-
- assert %{
- "configs" => [
- %{
- "group" => ":pleroma",
- "key" => ":http",
- "value" => value,
- "db" => db
- }
- ]
- } = json_response(conn, 200)
-
- assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]} in value
- assert ":proxy_url" in db
- end
-
- test "proxy tuple ip", %{conn: conn} do
- conn =
- post(conn, "/api/pleroma/admin/config", %{
- configs: [
- %{
- group: ":pleroma",
- key: ":http",
- value: [
- %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]}
- ]
- }
- ]
- })
-
- assert %{
- "configs" => [
- %{
- "group" => ":pleroma",
- "key" => ":http",
- "value" => value,
- "db" => db
- }
- ]
- } = json_response(conn, 200)
-
- assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]} in value
- assert ":proxy_url" in db
- end
-
- @tag capture_log: true
- test "doesn't set keys not in the whitelist", %{conn: conn} do
- clear_config(:database_config_whitelist, [
- {:pleroma, :key1},
- {:pleroma, :key2},
- {:pleroma, Pleroma.Captcha.NotReal},
- {:not_real}
- ])
-
- post(conn, "/api/pleroma/admin/config", %{
- configs: [
- %{group: ":pleroma", key: ":key1", value: "value1"},
- %{group: ":pleroma", key: ":key2", value: "value2"},
- %{group: ":pleroma", key: ":key3", value: "value3"},
- %{group: ":pleroma", key: "Pleroma.Web.Endpoint.NotReal", value: "value4"},
- %{group: ":pleroma", key: "Pleroma.Captcha.NotReal", value: "value5"},
- %{group: ":not_real", key: ":anything", value: "value6"}
- ]
- })
-
- assert Application.get_env(:pleroma, :key1) == "value1"
- assert Application.get_env(:pleroma, :key2) == "value2"
- assert Application.get_env(:pleroma, :key3) == nil
- assert Application.get_env(:pleroma, Pleroma.Web.Endpoint.NotReal) == nil
- assert Application.get_env(:pleroma, Pleroma.Captcha.NotReal) == "value5"
- assert Application.get_env(:not_real, :anything) == "value6"
- end
- end
-
- describe "GET /api/pleroma/admin/restart" do
- setup do: clear_config(:configurable_from_database, true)
-
- test "pleroma restarts", %{conn: conn} do
- capture_log(fn ->
- assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
- end) =~ "pleroma restarted"
-
- refute Restarter.Pleroma.need_reboot?()
- end
- end
-
- test "need_reboot flag", %{conn: conn} do
- assert conn
- |> get("/api/pleroma/admin/need_reboot")
- |> json_response(200) == %{"need_reboot" => false}
-
- Restarter.Pleroma.need_reboot()
-
- assert conn
- |> get("/api/pleroma/admin/need_reboot")
- |> json_response(200) == %{"need_reboot" => true}
-
- on_exit(fn -> Restarter.Pleroma.refresh() end)
- end
-
- describe "GET /api/pleroma/admin/statuses" do
- test "returns all public and unlisted statuses", %{conn: conn, admin: admin} do
- blocked = insert(:user)
- user = insert(:user)
- User.block(admin, blocked)
-
- {:ok, _} = CommonAPI.post(user, %{status: "@#{admin.nickname}", visibility: "direct"})
-
- {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"})
- {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "private"})
- {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "public"})
- {:ok, _} = CommonAPI.post(blocked, %{status: ".", visibility: "public"})
-
- response =
- conn
- |> get("/api/pleroma/admin/statuses")
- |> json_response(200)
-
- refute "private" in Enum.map(response, & &1["visibility"])
- assert length(response) == 3
- end
-
- test "returns only local statuses with local_only on", %{conn: conn} do
- user = insert(:user)
- remote_user = insert(:user, local: false, nickname: "archaeme@archae.me")
- insert(:note_activity, user: user, local: true)
- insert(:note_activity, user: remote_user, local: false)
-
- response =
- conn
- |> get("/api/pleroma/admin/statuses?local_only=true")
- |> json_response(200)
-
- assert length(response) == 1
- end
-
- test "returns private and direct statuses with godmode on", %{conn: conn, admin: admin} do
- user = insert(:user)
-
- {:ok, _} = CommonAPI.post(user, %{status: "@#{admin.nickname}", visibility: "direct"})
-
- {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "private"})
- {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "public"})
- conn = get(conn, "/api/pleroma/admin/statuses?godmode=true")
- assert json_response(conn, 200) |> length() == 3
- end
- end
-
- describe "GET /api/pleroma/admin/users/:nickname/statuses" do
- setup do
- user = insert(:user)
-
- date1 = (DateTime.to_unix(DateTime.utc_now()) + 2000) |> DateTime.from_unix!()
- date2 = (DateTime.to_unix(DateTime.utc_now()) + 1000) |> DateTime.from_unix!()
- date3 = (DateTime.to_unix(DateTime.utc_now()) + 3000) |> DateTime.from_unix!()
-
- insert(:note_activity, user: user, published: date1)
- insert(:note_activity, user: user, published: date2)
- insert(:note_activity, user: user, published: date3)
-
- %{user: user}
- end
-
- test "renders user's statuses", %{conn: conn, user: user} do
- conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses")
-
- assert json_response(conn, 200) |> length() == 3
- end
-
- test "renders user's statuses with a limit", %{conn: conn, user: user} do
- conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=2")
-
- assert json_response(conn, 200) |> length() == 2
- end
-
- test "doesn't return private statuses by default", %{conn: conn, user: user} do
- {:ok, _private_status} = CommonAPI.post(user, %{status: "private", visibility: "private"})
-
- {:ok, _public_status} = CommonAPI.post(user, %{status: "public", visibility: "public"})
-
- conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses")
-
- assert json_response(conn, 200) |> length() == 4
- end
-
- test "returns private statuses with godmode on", %{conn: conn, user: user} do
- {:ok, _private_status} = CommonAPI.post(user, %{status: "private", visibility: "private"})
-
- {:ok, _public_status} = CommonAPI.post(user, %{status: "public", visibility: "public"})
-
- conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?godmode=true")
-
- assert json_response(conn, 200) |> length() == 5
- end
-
- test "excludes reblogs by default", %{conn: conn, user: user} do
- other_user = insert(:user)
- {:ok, activity} = CommonAPI.post(user, %{status: "."})
- {:ok, %Activity{}} = CommonAPI.repeat(activity.id, other_user)
-
- conn_res = get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses")
- assert json_response(conn_res, 200) |> length() == 0
-
- conn_res =
- get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses?with_reblogs=true")
-
- assert json_response(conn_res, 200) |> length() == 1
- end
- end
-
- describe "GET /api/pleroma/admin/moderation_log" do
- setup do
- moderator = insert(:user, is_moderator: true)
-
- %{moderator: moderator}
- end
-
- test "returns the log", %{conn: conn, admin: admin} do
- Repo.insert(%ModerationLog{
- data: %{
- actor: %{
- "id" => admin.id,
- "nickname" => admin.nickname,
- "type" => "user"
- },
- action: "relay_follow",
- target: "https://example.org/relay"
- },
- inserted_at: NaiveDateTime.truncate(~N[2017-08-15 15:47:06.597036], :second)
- })
-
- Repo.insert(%ModerationLog{
- data: %{
- actor: %{
- "id" => admin.id,
- "nickname" => admin.nickname,
- "type" => "user"
- },
- action: "relay_unfollow",
- target: "https://example.org/relay"
- },
- inserted_at: NaiveDateTime.truncate(~N[2017-08-16 15:47:06.597036], :second)
- })
-
- conn = get(conn, "/api/pleroma/admin/moderation_log")
-
- response = json_response(conn, 200)
- [first_entry, second_entry] = response["items"]
-
- assert response["total"] == 2
- assert first_entry["data"]["action"] == "relay_unfollow"
-
- assert first_entry["message"] ==
- "@#{admin.nickname} unfollowed relay: https://example.org/relay"
-
- assert second_entry["data"]["action"] == "relay_follow"
-
- assert second_entry["message"] ==
- "@#{admin.nickname} followed relay: https://example.org/relay"
- end
-
- test "returns the log with pagination", %{conn: conn, admin: admin} do
- Repo.insert(%ModerationLog{
- data: %{
- actor: %{
- "id" => admin.id,
- "nickname" => admin.nickname,
- "type" => "user"
- },
- action: "relay_follow",
- target: "https://example.org/relay"
- },
- inserted_at: NaiveDateTime.truncate(~N[2017-08-15 15:47:06.597036], :second)
- })
-
- Repo.insert(%ModerationLog{
- data: %{
- actor: %{
- "id" => admin.id,
- "nickname" => admin.nickname,
- "type" => "user"
- },
- action: "relay_unfollow",
- target: "https://example.org/relay"
- },
- inserted_at: NaiveDateTime.truncate(~N[2017-08-16 15:47:06.597036], :second)
- })
-
- conn1 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=1")
-
- response1 = json_response(conn1, 200)
- [first_entry] = response1["items"]
-
- assert response1["total"] == 2
- assert response1["items"] |> length() == 1
- assert first_entry["data"]["action"] == "relay_unfollow"
-
- assert first_entry["message"] ==
- "@#{admin.nickname} unfollowed relay: https://example.org/relay"
-
- conn2 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=2")
-
- response2 = json_response(conn2, 200)
- [second_entry] = response2["items"]
-
- assert response2["total"] == 2
- assert response2["items"] |> length() == 1
- assert second_entry["data"]["action"] == "relay_follow"
-
- assert second_entry["message"] ==
- "@#{admin.nickname} followed relay: https://example.org/relay"
- end
-
- test "filters log by date", %{conn: conn, admin: admin} do
- first_date = "2017-08-15T15:47:06Z"
- second_date = "2017-08-20T15:47:06Z"
-
- Repo.insert(%ModerationLog{
- data: %{
- actor: %{
- "id" => admin.id,
- "nickname" => admin.nickname,
- "type" => "user"
- },
- action: "relay_follow",
- target: "https://example.org/relay"
- },
- inserted_at: NaiveDateTime.from_iso8601!(first_date)
- })
-
- Repo.insert(%ModerationLog{
- data: %{
- actor: %{
- "id" => admin.id,
- "nickname" => admin.nickname,
- "type" => "user"
- },
- action: "relay_unfollow",
- target: "https://example.org/relay"
- },
- inserted_at: NaiveDateTime.from_iso8601!(second_date)
- })
-
- conn1 =
- get(
- conn,
- "/api/pleroma/admin/moderation_log?start_date=#{second_date}"
- )
-
- response1 = json_response(conn1, 200)
- [first_entry] = response1["items"]
-
- assert response1["total"] == 1
- assert first_entry["data"]["action"] == "relay_unfollow"
-
- assert first_entry["message"] ==
- "@#{admin.nickname} unfollowed relay: https://example.org/relay"
- end
-
- test "returns log filtered by user", %{conn: conn, admin: admin, moderator: moderator} do
- Repo.insert(%ModerationLog{
- data: %{
- actor: %{
- "id" => admin.id,
- "nickname" => admin.nickname,
- "type" => "user"
- },
- action: "relay_follow",
- target: "https://example.org/relay"
- }
- })
-
- Repo.insert(%ModerationLog{
- data: %{
- actor: %{
- "id" => moderator.id,
- "nickname" => moderator.nickname,
- "type" => "user"
- },
- action: "relay_unfollow",
- target: "https://example.org/relay"
- }
- })
-
- conn1 = get(conn, "/api/pleroma/admin/moderation_log?user_id=#{moderator.id}")
-
- response1 = json_response(conn1, 200)
- [first_entry] = response1["items"]
-
- assert response1["total"] == 1
- assert get_in(first_entry, ["data", "actor", "id"]) == moderator.id
- end
-
- test "returns log filtered by search", %{conn: conn, moderator: moderator} do
- ModerationLog.insert_log(%{
- actor: moderator,
- action: "relay_follow",
- target: "https://example.org/relay"
- })
-
- ModerationLog.insert_log(%{
- actor: moderator,
- action: "relay_unfollow",
- target: "https://example.org/relay"
- })
-
- conn1 = get(conn, "/api/pleroma/admin/moderation_log?search=unfo")
-
- response1 = json_response(conn1, 200)
- [first_entry] = response1["items"]
-
- assert response1["total"] == 1
-
- assert get_in(first_entry, ["data", "message"]) ==
- "@#{moderator.nickname} unfollowed relay: https://example.org/relay"
- end
- end
-
- describe "GET /users/:nickname/credentials" do
- test "gets the user credentials", %{conn: conn} do
- user = insert(:user)
- conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials")
-
- response = assert json_response(conn, 200)
- assert response["email"] == user.email
- end
-
- test "returns 403 if requested by a non-admin" do
- user = insert(:user)
-
- conn =
- build_conn()
- |> assign(:user, user)
- |> get("/api/pleroma/admin/users/#{user.nickname}/credentials")
-
- assert json_response(conn, :forbidden)
- end
- end
-
- describe "PATCH /users/:nickname/credentials" do
- test "changes password and email", %{conn: conn, admin: admin} do
- user = insert(:user)
- assert user.password_reset_pending == false
-
- conn =
- patch(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials", %{
- "password" => "new_password",
- "email" => "new_email@example.com",
- "name" => "new_name"
- })
-
- assert json_response(conn, 200) == %{"status" => "success"}
-
- ObanHelpers.perform_all()
-
- updated_user = User.get_by_id(user.id)
-
- assert updated_user.email == "new_email@example.com"
- assert updated_user.name == "new_name"
- assert updated_user.password_hash != user.password_hash
- assert updated_user.password_reset_pending == true
-
- [log_entry2, log_entry1] = ModerationLog |> Repo.all() |> Enum.sort()
-
- assert ModerationLog.get_log_entry_message(log_entry1) ==
- "@#{admin.nickname} updated users: @#{user.nickname}"
-
- assert ModerationLog.get_log_entry_message(log_entry2) ==
- "@#{admin.nickname} forced password reset for users: @#{user.nickname}"
- end
-
- test "returns 403 if requested by a non-admin" do
- user = insert(:user)
-
- conn =
- build_conn()
- |> assign(:user, user)
- |> patch("/api/pleroma/admin/users/#{user.nickname}/credentials", %{
- "password" => "new_password",
- "email" => "new_email@example.com",
- "name" => "new_name"
- })
-
- assert json_response(conn, :forbidden)
- end
- end
-
- describe "PATCH /users/:nickname/force_password_reset" do
- test "sets password_reset_pending to true", %{conn: conn} do
- user = insert(:user)
- assert user.password_reset_pending == false
-
- conn =
- patch(conn, "/api/pleroma/admin/users/force_password_reset", %{nicknames: [user.nickname]})
-
- assert json_response(conn, 204) == ""
-
- ObanHelpers.perform_all()
-
- assert User.get_by_id(user.id).password_reset_pending == true
- end
- end
-
- describe "relays" do
- test "POST /relay", %{conn: conn, admin: admin} do
- conn =
- post(conn, "/api/pleroma/admin/relay", %{
- relay_url: "http://mastodon.example.org/users/admin"
- })
-
- assert json_response(conn, 200) == "http://mastodon.example.org/users/admin"
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin"
- end
-
- test "GET /relay", %{conn: conn} do
- relay_user = Pleroma.Web.ActivityPub.Relay.get_actor()
-
- ["http://mastodon.example.org/users/admin", "https://mstdn.io/users/mayuutann"]
- |> Enum.each(fn ap_id ->
- {:ok, user} = User.get_or_fetch_by_ap_id(ap_id)
- User.follow(relay_user, user)
- end)
-
- conn = get(conn, "/api/pleroma/admin/relay")
-
- assert json_response(conn, 200)["relays"] -- ["mastodon.example.org", "mstdn.io"] == []
- end
-
- test "DELETE /relay", %{conn: conn, admin: admin} do
- post(conn, "/api/pleroma/admin/relay", %{
- relay_url: "http://mastodon.example.org/users/admin"
- })
-
- conn =
- delete(conn, "/api/pleroma/admin/relay", %{
- relay_url: "http://mastodon.example.org/users/admin"
- })
-
- assert json_response(conn, 200) == "http://mastodon.example.org/users/admin"
-
- [log_entry_one, log_entry_two] = Repo.all(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry_one) ==
- "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin"
-
- assert ModerationLog.get_log_entry_message(log_entry_two) ==
- "@#{admin.nickname} unfollowed relay: http://mastodon.example.org/users/admin"
- end
- end
-
- describe "instances" do
- test "GET /instances/:instance/statuses", %{conn: conn} do
- user = insert(:user, local: false, nickname: "archaeme@archae.me")
- user2 = insert(:user, local: false, nickname: "test@test.com")
- insert_pair(:note_activity, user: user)
- activity = insert(:note_activity, user: user2)
-
- ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses")
-
- response = json_response(ret_conn, 200)
-
- assert length(response) == 2
-
- ret_conn = get(conn, "/api/pleroma/admin/instances/test.com/statuses")
-
- response = json_response(ret_conn, 200)
-
- assert length(response) == 1
-
- ret_conn = get(conn, "/api/pleroma/admin/instances/nonexistent.com/statuses")
-
- response = json_response(ret_conn, 200)
-
- assert Enum.empty?(response)
-
- CommonAPI.repeat(activity.id, user)
-
- ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses")
- response = json_response(ret_conn, 200)
- assert length(response) == 2
-
- ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses?with_reblogs=true")
- response = json_response(ret_conn, 200)
- assert length(response) == 3
- end
- end
-
- describe "PATCH /confirm_email" do
- test "it confirms emails of two users", %{conn: conn, admin: admin} do
- [first_user, second_user] = insert_pair(:user, confirmation_pending: true)
-
- assert first_user.confirmation_pending == true
- assert second_user.confirmation_pending == true
-
- ret_conn =
- patch(conn, "/api/pleroma/admin/users/confirm_email", %{
- nicknames: [
- first_user.nickname,
- second_user.nickname
- ]
- })
-
- assert ret_conn.status == 200
-
- assert first_user.confirmation_pending == true
- assert second_user.confirmation_pending == true
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} confirmed email for users: @#{first_user.nickname}, @#{
- second_user.nickname
- }"
- end
- end
-
- describe "PATCH /resend_confirmation_email" do
- test "it resend emails for two users", %{conn: conn, admin: admin} do
- [first_user, second_user] = insert_pair(:user, confirmation_pending: true)
-
- ret_conn =
- patch(conn, "/api/pleroma/admin/users/resend_confirmation_email", %{
- nicknames: [
- first_user.nickname,
- second_user.nickname
- ]
- })
-
- assert ret_conn.status == 200
-
- log_entry = Repo.one(ModerationLog)
-
- assert ModerationLog.get_log_entry_message(log_entry) ==
- "@#{admin.nickname} re-sent confirmation email for users: @#{first_user.nickname}, @#{
- second_user.nickname
- }"
- end
- end
-
- describe "POST /reports/:id/notes" do
- setup %{conn: conn, admin: admin} do
- [reporter, target_user] = insert_pair(:user)
- activity = insert(:note_activity, user: target_user)
-
- {:ok, %{id: report_id}} =
- CommonAPI.report(reporter, %{
- account_id: target_user.id,
- comment: "I feel offended",
- status_ids: [activity.id]
- })
-
- post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{
- content: "this is disgusting!"
- })
-
- post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{
- content: "this is disgusting2!"
- })
-
- %{
- admin_id: admin.id,
- report_id: report_id
- }
- end
-
- test "it creates report note", %{admin_id: admin_id, report_id: report_id} do
- [note, _] = Repo.all(ReportNote)
-
- assert %{
- activity_id: ^report_id,
- content: "this is disgusting!",
- user_id: ^admin_id
- } = note
- end
-
- test "it returns reports with notes", %{conn: conn, admin: admin} do
- conn = get(conn, "/api/pleroma/admin/reports")
-
- response = json_response(conn, 200)
- notes = hd(response["reports"])["notes"]
- [note, _] = notes
-
- assert note["user"]["nickname"] == admin.nickname
- assert note["content"] == "this is disgusting!"
- assert note["created_at"]
- assert response["total"] == 1
- end
-
- test "it deletes the note", %{conn: conn, report_id: report_id} do
- assert ReportNote |> Repo.all() |> length() == 2
-
- [note, _] = Repo.all(ReportNote)
-
- delete(conn, "/api/pleroma/admin/reports/#{report_id}/notes/#{note.id}")
-
- assert ReportNote |> Repo.all() |> length() == 1
- end
- end
-
- describe "GET /api/pleroma/admin/config/descriptions" do
- test "structure", %{conn: conn} do
- admin = insert(:user, is_admin: true)
-
- conn =
- assign(conn, :user, admin)
- |> get("/api/pleroma/admin/config/descriptions")
-
- assert [child | _others] = json_response(conn, 200)
-
- assert child["children"]
- assert child["key"]
- assert String.starts_with?(child["group"], ":")
- assert child["description"]
- end
-
- test "filters by database configuration whitelist", %{conn: conn} do
- clear_config(:database_config_whitelist, [
- {:pleroma, :instance},
- {:pleroma, :activitypub},
- {:pleroma, Pleroma.Upload},
- {:esshd}
- ])
-
- admin = insert(:user, is_admin: true)
-
- conn =
- assign(conn, :user, admin)
- |> get("/api/pleroma/admin/config/descriptions")
-
- children = json_response(conn, 200)
-
- assert length(children) == 4
-
- assert Enum.count(children, fn c -> c["group"] == ":pleroma" end) == 3
-
- instance = Enum.find(children, fn c -> c["key"] == ":instance" end)
- assert instance["children"]
-
- activitypub = Enum.find(children, fn c -> c["key"] == ":activitypub" end)
- assert activitypub["children"]
-
- web_endpoint = Enum.find(children, fn c -> c["key"] == "Pleroma.Upload" end)
- assert web_endpoint["children"]
-
- esshd = Enum.find(children, fn c -> c["group"] == ":esshd" end)
- assert esshd["children"]
- end
- end
-
- describe "/api/pleroma/admin/stats" do
- test "status visibility count", %{conn: conn} do
- admin = insert(:user, is_admin: true)
- user = insert(:user)
- CommonAPI.post(user, %{visibility: "public", status: "hey"})
- CommonAPI.post(user, %{visibility: "unlisted", status: "hey"})
- CommonAPI.post(user, %{visibility: "unlisted", status: "hey"})
-
- response =
- conn
- |> assign(:user, admin)
- |> get("/api/pleroma/admin/stats")
- |> json_response(200)
-
- assert %{"direct" => 0, "private" => 0, "public" => 1, "unlisted" => 2} =
- response["status_visibility"]
- end
- end
-
- describe "POST /api/pleroma/admin/oauth_app" do
- test "errors", %{conn: conn} do
- response = conn |> post("/api/pleroma/admin/oauth_app", %{}) |> json_response(200)
-
- assert response == %{"name" => "can't be blank", "redirect_uris" => "can't be blank"}
- end
-
- test "success", %{conn: conn} do
- base_url = Web.base_url()
- app_name = "Trusted app"
-
- response =
- conn
- |> post("/api/pleroma/admin/oauth_app", %{
- name: app_name,
- redirect_uris: base_url
- })
- |> json_response(200)
-
- assert %{
- "client_id" => _,
- "client_secret" => _,
- "name" => ^app_name,
- "redirect_uri" => ^base_url,
- "trusted" => false
- } = response
- end
-
- test "with trusted", %{conn: conn} do
- base_url = Web.base_url()
- app_name = "Trusted app"
-
- response =
- conn
- |> post("/api/pleroma/admin/oauth_app", %{
- name: app_name,
- redirect_uris: base_url,
- trusted: true
- })
- |> json_response(200)
-
- assert %{
- "client_id" => _,
- "client_secret" => _,
- "name" => ^app_name,
- "redirect_uri" => ^base_url,
- "trusted" => true
- } = response
- end
- end
-
- describe "GET /api/pleroma/admin/oauth_app" do
- setup do
- app = insert(:oauth_app)
- {:ok, app: app}
- end
-
- test "list", %{conn: conn} do
- response =
- conn
- |> get("/api/pleroma/admin/oauth_app")
- |> json_response(200)
-
- assert %{"apps" => apps, "count" => count, "page_size" => _} = response
-
- assert length(apps) == count
- end
-
- test "with page size", %{conn: conn} do
- insert(:oauth_app)
- page_size = 1
-
- response =
- conn
- |> get("/api/pleroma/admin/oauth_app", %{page_size: to_string(page_size)})
- |> json_response(200)
-
- assert %{"apps" => apps, "count" => _, "page_size" => ^page_size} = response
-
- assert length(apps) == page_size
- end
-
- test "search by client name", %{conn: conn, app: app} do
- response =
- conn
- |> get("/api/pleroma/admin/oauth_app", %{name: app.client_name})
- |> json_response(200)
-
- assert %{"apps" => [returned], "count" => _, "page_size" => _} = response
-
- assert returned["client_id"] == app.client_id
- assert returned["name"] == app.client_name
- end
-
- test "search by client id", %{conn: conn, app: app} do
- response =
- conn
- |> get("/api/pleroma/admin/oauth_app", %{client_id: app.client_id})
- |> json_response(200)
-
- assert %{"apps" => [returned], "count" => _, "page_size" => _} = response
-
- assert returned["client_id"] == app.client_id
- assert returned["name"] == app.client_name
- end
-
- test "only trusted", %{conn: conn} do
- app = insert(:oauth_app, trusted: true)
-
- response =
- conn
- |> get("/api/pleroma/admin/oauth_app", %{trusted: true})
- |> json_response(200)
-
- assert %{"apps" => [returned], "count" => _, "page_size" => _} = response
-
- assert returned["client_id"] == app.client_id
- assert returned["name"] == app.client_name
- end
- end
-
- describe "DELETE /api/pleroma/admin/oauth_app/:id" do
- test "with id", %{conn: conn} do
- app = insert(:oauth_app)
-
- response =
- conn
- |> delete("/api/pleroma/admin/oauth_app/" <> to_string(app.id))
- |> json_response(:no_content)
-
- assert response == ""
- end
-
- test "with non existance id", %{conn: conn} do
- response =
- conn
- |> delete("/api/pleroma/admin/oauth_app/0")
- |> json_response(:bad_request)
-
- assert response == ""
- end
- end
-
- describe "PATCH /api/pleroma/admin/oauth_app/:id" do
- test "with id", %{conn: conn} do
- app = insert(:oauth_app)
-
- name = "another name"
- url = "https://example.com"
- scopes = ["admin"]
- id = app.id
- website = "http://website.com"
-
- response =
- conn
- |> patch("/api/pleroma/admin/oauth_app/" <> to_string(app.id), %{
- name: name,
- trusted: true,
- redirect_uris: url,
- scopes: scopes,
- website: website
- })
- |> json_response(200)
-
- assert %{
- "client_id" => _,
- "client_secret" => _,
- "id" => ^id,
- "name" => ^name,
- "redirect_uri" => ^url,
- "trusted" => true,
- "website" => ^website
- } = response
- end
-
- test "without id", %{conn: conn} do
- response =
- conn
- |> patch("/api/pleroma/admin/oauth_app/0")
- |> json_response(:bad_request)
-
- assert response == ""
- end
- end
-end
-
-# Needed for testing
-defmodule Pleroma.Web.Endpoint.NotReal do
-end
-
-defmodule Pleroma.Captcha.NotReal do
-end
diff --git a/test/web/admin_api/controllers/admin_api_controller_test.exs b/test/web/admin_api/controllers/admin_api_controller_test.exs
@@ -0,0 +1,3717 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.AdminAPIControllerTest do
+ use Pleroma.Web.ConnCase
+ use Oban.Testing, repo: Pleroma.Repo
+
+ import ExUnit.CaptureLog
+ import Mock
+ import Pleroma.Factory
+
+ alias Pleroma.Activity
+ alias Pleroma.Config
+ alias Pleroma.ConfigDB
+ alias Pleroma.HTML
+ alias Pleroma.MFA
+ alias Pleroma.ModerationLog
+ alias Pleroma.Repo
+ alias Pleroma.ReportNote
+ alias Pleroma.Tests.ObanHelpers
+ alias Pleroma.User
+ alias Pleroma.UserInviteToken
+ alias Pleroma.Web
+ alias Pleroma.Web.ActivityPub.Relay
+ alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.MediaProxy
+
+ setup_all do
+ Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
+
+ :ok
+ end
+
+ setup do
+ admin = insert(:user, is_admin: true)
+ token = insert(:oauth_admin_token, user: admin)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, token)
+
+ {:ok, %{admin: admin, token: token, conn: conn}}
+ end
+
+ describe "with [:auth, :enforce_oauth_admin_scope_usage]," do
+ setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], true)
+
+ test "GET /api/pleroma/admin/users/:nickname requires admin:read:accounts or broader scope",
+ %{admin: admin} do
+ user = insert(:user)
+ url = "/api/pleroma/admin/users/#{user.nickname}"
+
+ good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"])
+ good_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read"])
+ good_token3 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts"])
+
+ bad_token1 = insert(:oauth_token, user: admin, scopes: ["read:accounts"])
+ bad_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts:partial"])
+ bad_token3 = nil
+
+ for good_token <- [good_token1, good_token2, good_token3] do
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, good_token)
+ |> get(url)
+
+ assert json_response(conn, 200)
+ end
+
+ for good_token <- [good_token1, good_token2, good_token3] do
+ conn =
+ build_conn()
+ |> assign(:user, nil)
+ |> assign(:token, good_token)
+ |> get(url)
+
+ assert json_response(conn, :forbidden)
+ end
+
+ for bad_token <- [bad_token1, bad_token2, bad_token3] do
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, bad_token)
+ |> get(url)
+
+ assert json_response(conn, :forbidden)
+ end
+ end
+ end
+
+ describe "unless [:auth, :enforce_oauth_admin_scope_usage]," do
+ setup do: clear_config([:auth, :enforce_oauth_admin_scope_usage], false)
+
+ test "GET /api/pleroma/admin/users/:nickname requires " <>
+ "read:accounts or admin:read:accounts or broader scope",
+ %{admin: admin} do
+ user = insert(:user)
+ url = "/api/pleroma/admin/users/#{user.nickname}"
+
+ good_token1 = insert(:oauth_token, user: admin, scopes: ["admin"])
+ good_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read"])
+ good_token3 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts"])
+ good_token4 = insert(:oauth_token, user: admin, scopes: ["read:accounts"])
+ good_token5 = insert(:oauth_token, user: admin, scopes: ["read"])
+
+ good_tokens = [good_token1, good_token2, good_token3, good_token4, good_token5]
+
+ bad_token1 = insert(:oauth_token, user: admin, scopes: ["read:accounts:partial"])
+ bad_token2 = insert(:oauth_token, user: admin, scopes: ["admin:read:accounts:partial"])
+ bad_token3 = nil
+
+ for good_token <- good_tokens do
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, good_token)
+ |> get(url)
+
+ assert json_response(conn, 200)
+ end
+
+ for good_token <- good_tokens do
+ conn =
+ build_conn()
+ |> assign(:user, nil)
+ |> assign(:token, good_token)
+ |> get(url)
+
+ assert json_response(conn, :forbidden)
+ end
+
+ for bad_token <- [bad_token1, bad_token2, bad_token3] do
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, bad_token)
+ |> get(url)
+
+ assert json_response(conn, :forbidden)
+ end
+ end
+ end
+
+ describe "DELETE /api/pleroma/admin/users" do
+ test "single user", %{admin: admin, conn: conn} do
+ user = insert(:user)
+ clear_config([:instance, :federating], true)
+
+ with_mock Pleroma.Web.Federator,
+ publish: fn _ -> nil end do
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> delete("/api/pleroma/admin/users?nickname=#{user.nickname}")
+
+ ObanHelpers.perform_all()
+
+ assert User.get_by_nickname(user.nickname).deactivated
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} deleted users: @#{user.nickname}"
+
+ assert json_response(conn, 200) == [user.nickname]
+
+ assert called(Pleroma.Web.Federator.publish(:_))
+ end
+ end
+
+ test "multiple users", %{admin: admin, conn: conn} do
+ user_one = insert(:user)
+ user_two = insert(:user)
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> delete("/api/pleroma/admin/users", %{
+ nicknames: [user_one.nickname, user_two.nickname]
+ })
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} deleted users: @#{user_one.nickname}, @#{user_two.nickname}"
+
+ response = json_response(conn, 200)
+ assert response -- [user_one.nickname, user_two.nickname] == []
+ end
+ end
+
+ describe "/api/pleroma/admin/users" do
+ test "Create", %{conn: conn} do
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> post("/api/pleroma/admin/users", %{
+ "users" => [
+ %{
+ "nickname" => "lain",
+ "email" => "lain@example.org",
+ "password" => "test"
+ },
+ %{
+ "nickname" => "lain2",
+ "email" => "lain2@example.org",
+ "password" => "test"
+ }
+ ]
+ })
+
+ response = json_response(conn, 200) |> Enum.map(&Map.get(&1, "type"))
+ assert response == ["success", "success"]
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ["lain", "lain2"] -- Enum.map(log_entry.data["subjects"], & &1["nickname"]) == []
+ end
+
+ test "Cannot create user with existing email", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> post("/api/pleroma/admin/users", %{
+ "users" => [
+ %{
+ "nickname" => "lain",
+ "email" => user.email,
+ "password" => "test"
+ }
+ ]
+ })
+
+ assert json_response(conn, 409) == [
+ %{
+ "code" => 409,
+ "data" => %{
+ "email" => user.email,
+ "nickname" => "lain"
+ },
+ "error" => "email has already been taken",
+ "type" => "error"
+ }
+ ]
+ end
+
+ test "Cannot create user with existing nickname", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> post("/api/pleroma/admin/users", %{
+ "users" => [
+ %{
+ "nickname" => user.nickname,
+ "email" => "someuser@plerama.social",
+ "password" => "test"
+ }
+ ]
+ })
+
+ assert json_response(conn, 409) == [
+ %{
+ "code" => 409,
+ "data" => %{
+ "email" => "someuser@plerama.social",
+ "nickname" => user.nickname
+ },
+ "error" => "nickname has already been taken",
+ "type" => "error"
+ }
+ ]
+ end
+
+ test "Multiple user creation works in transaction", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> post("/api/pleroma/admin/users", %{
+ "users" => [
+ %{
+ "nickname" => "newuser",
+ "email" => "newuser@pleroma.social",
+ "password" => "test"
+ },
+ %{
+ "nickname" => "lain",
+ "email" => user.email,
+ "password" => "test"
+ }
+ ]
+ })
+
+ assert json_response(conn, 409) == [
+ %{
+ "code" => 409,
+ "data" => %{
+ "email" => user.email,
+ "nickname" => "lain"
+ },
+ "error" => "email has already been taken",
+ "type" => "error"
+ },
+ %{
+ "code" => 409,
+ "data" => %{
+ "email" => "newuser@pleroma.social",
+ "nickname" => "newuser"
+ },
+ "error" => "",
+ "type" => "error"
+ }
+ ]
+
+ assert User.get_by_nickname("newuser") === nil
+ end
+ end
+
+ describe "/api/pleroma/admin/users/:nickname" do
+ test "Show", %{conn: conn} do
+ user = insert(:user)
+
+ conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")
+
+ expected = %{
+ "deactivated" => false,
+ "id" => to_string(user.id),
+ "local" => true,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "tags" => [],
+ "avatar" => User.avatar_url(user) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(user.name || user.nickname),
+ "confirmation_pending" => false
+ }
+
+ assert expected == json_response(conn, 200)
+ end
+
+ test "when the user doesn't exist", %{conn: conn} do
+ user = build(:user)
+
+ conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")
+
+ assert %{"error" => "Not found"} == json_response(conn, 404)
+ end
+ end
+
+ describe "/api/pleroma/admin/users/follow" do
+ test "allows to force-follow another user", %{admin: admin, conn: conn} do
+ user = insert(:user)
+ follower = insert(:user)
+
+ conn
+ |> put_req_header("accept", "application/json")
+ |> post("/api/pleroma/admin/users/follow", %{
+ "follower" => follower.nickname,
+ "followed" => user.nickname
+ })
+
+ user = User.get_cached_by_id(user.id)
+ follower = User.get_cached_by_id(follower.id)
+
+ assert User.following?(follower, user)
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} made @#{follower.nickname} follow @#{user.nickname}"
+ end
+ end
+
+ describe "/api/pleroma/admin/users/unfollow" do
+ test "allows to force-unfollow another user", %{admin: admin, conn: conn} do
+ user = insert(:user)
+ follower = insert(:user)
+
+ User.follow(follower, user)
+
+ conn
+ |> put_req_header("accept", "application/json")
+ |> post("/api/pleroma/admin/users/unfollow", %{
+ "follower" => follower.nickname,
+ "followed" => user.nickname
+ })
+
+ user = User.get_cached_by_id(user.id)
+ follower = User.get_cached_by_id(follower.id)
+
+ refute User.following?(follower, user)
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} made @#{follower.nickname} unfollow @#{user.nickname}"
+ end
+ end
+
+ describe "PUT /api/pleroma/admin/users/tag" do
+ setup %{conn: conn} do
+ user1 = insert(:user, %{tags: ["x"]})
+ user2 = insert(:user, %{tags: ["y"]})
+ user3 = insert(:user, %{tags: ["unchanged"]})
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> put(
+ "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <>
+ "#{user2.nickname}&tags[]=foo&tags[]=bar"
+ )
+
+ %{conn: conn, user1: user1, user2: user2, user3: user3}
+ end
+
+ test "it appends specified tags to users with specified nicknames", %{
+ conn: conn,
+ admin: admin,
+ user1: user1,
+ user2: user2
+ } do
+ assert json_response(conn, :no_content)
+ assert User.get_cached_by_id(user1.id).tags == ["x", "foo", "bar"]
+ assert User.get_cached_by_id(user2.id).tags == ["y", "foo", "bar"]
+
+ log_entry = Repo.one(ModerationLog)
+
+ users =
+ [user1.nickname, user2.nickname]
+ |> Enum.map(&"@#{&1}")
+ |> Enum.join(", ")
+
+ tags = ["foo", "bar"] |> Enum.join(", ")
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} added tags: #{tags} to users: #{users}"
+ end
+
+ test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
+ assert json_response(conn, :no_content)
+ assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
+ end
+ end
+
+ describe "DELETE /api/pleroma/admin/users/tag" do
+ setup %{conn: conn} do
+ user1 = insert(:user, %{tags: ["x"]})
+ user2 = insert(:user, %{tags: ["y", "z"]})
+ user3 = insert(:user, %{tags: ["unchanged"]})
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> delete(
+ "/api/pleroma/admin/users/tag?nicknames[]=#{user1.nickname}&nicknames[]=" <>
+ "#{user2.nickname}&tags[]=x&tags[]=z"
+ )
+
+ %{conn: conn, user1: user1, user2: user2, user3: user3}
+ end
+
+ test "it removes specified tags from users with specified nicknames", %{
+ conn: conn,
+ admin: admin,
+ user1: user1,
+ user2: user2
+ } do
+ assert json_response(conn, :no_content)
+ assert User.get_cached_by_id(user1.id).tags == []
+ assert User.get_cached_by_id(user2.id).tags == ["y"]
+
+ log_entry = Repo.one(ModerationLog)
+
+ users =
+ [user1.nickname, user2.nickname]
+ |> Enum.map(&"@#{&1}")
+ |> Enum.join(", ")
+
+ tags = ["x", "z"] |> Enum.join(", ")
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} removed tags: #{tags} from users: #{users}"
+ end
+
+ test "it does not modify tags of not specified users", %{conn: conn, user3: user3} do
+ assert json_response(conn, :no_content)
+ assert User.get_cached_by_id(user3.id).tags == ["unchanged"]
+ end
+ end
+
+ describe "/api/pleroma/admin/users/:nickname/permission_group" do
+ test "GET is giving user_info", %{admin: admin, conn: conn} do
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> get("/api/pleroma/admin/users/#{admin.nickname}/permission_group/")
+
+ assert json_response(conn, 200) == %{
+ "is_admin" => true,
+ "is_moderator" => false
+ }
+ end
+
+ test "/:right POST, can add to a permission group", %{admin: admin, conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> post("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin")
+
+ assert json_response(conn, 200) == %{
+ "is_admin" => true
+ }
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} made @#{user.nickname} admin"
+ end
+
+ test "/:right POST, can add to a permission group (multiple)", %{admin: admin, conn: conn} do
+ user_one = insert(:user)
+ user_two = insert(:user)
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> post("/api/pleroma/admin/users/permission_group/admin", %{
+ nicknames: [user_one.nickname, user_two.nickname]
+ })
+
+ assert json_response(conn, 200) == %{"is_admin" => true}
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} made @#{user_one.nickname}, @#{user_two.nickname} admin"
+ end
+
+ test "/:right DELETE, can remove from a permission group", %{admin: admin, conn: conn} do
+ user = insert(:user, is_admin: true)
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> delete("/api/pleroma/admin/users/#{user.nickname}/permission_group/admin")
+
+ assert json_response(conn, 200) == %{"is_admin" => false}
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} revoked admin role from @#{user.nickname}"
+ end
+
+ test "/:right DELETE, can remove from a permission group (multiple)", %{
+ admin: admin,
+ conn: conn
+ } do
+ user_one = insert(:user, is_admin: true)
+ user_two = insert(:user, is_admin: true)
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> delete("/api/pleroma/admin/users/permission_group/admin", %{
+ nicknames: [user_one.nickname, user_two.nickname]
+ })
+
+ assert json_response(conn, 200) == %{"is_admin" => false}
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} revoked admin role from @#{user_one.nickname}, @#{
+ user_two.nickname
+ }"
+ end
+ end
+
+ describe "POST /api/pleroma/admin/email_invite, with valid config" do
+ setup do: clear_config([:instance, :registrations_open], false)
+ setup do: clear_config([:instance, :invites_enabled], true)
+
+ test "sends invitation and returns 204", %{admin: admin, conn: conn} do
+ recipient_email = "foo@bar.com"
+ recipient_name = "J. D."
+
+ conn =
+ post(
+ conn,
+ "/api/pleroma/admin/users/email_invite?email=#{recipient_email}&name=#{recipient_name}"
+ )
+
+ assert json_response(conn, :no_content)
+
+ token_record = List.last(Repo.all(Pleroma.UserInviteToken))
+ assert token_record
+ refute token_record.used
+
+ notify_email = Config.get([:instance, :notify_email])
+ instance_name = Config.get([:instance, :name])
+
+ email =
+ Pleroma.Emails.UserEmail.user_invitation_email(
+ admin,
+ token_record,
+ recipient_email,
+ recipient_name
+ )
+
+ Swoosh.TestAssertions.assert_email_sent(
+ from: {instance_name, notify_email},
+ to: {recipient_name, recipient_email},
+ html_body: email.html_body
+ )
+ end
+
+ test "it returns 403 if requested by a non-admin" do
+ non_admin_user = insert(:user)
+ token = insert(:oauth_token, user: non_admin_user)
+
+ conn =
+ build_conn()
+ |> assign(:user, non_admin_user)
+ |> assign(:token, token)
+ |> post("/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
+
+ assert json_response(conn, :forbidden)
+ end
+
+ test "email with +", %{conn: conn, admin: admin} do
+ recipient_email = "foo+bar@baz.com"
+
+ conn
+ |> put_req_header("content-type", "application/json;charset=utf-8")
+ |> post("/api/pleroma/admin/users/email_invite", %{email: recipient_email})
+ |> json_response(:no_content)
+
+ token_record =
+ Pleroma.UserInviteToken
+ |> Repo.all()
+ |> List.last()
+
+ assert token_record
+ refute token_record.used
+
+ notify_email = Config.get([:instance, :notify_email])
+ instance_name = Config.get([:instance, :name])
+
+ email =
+ Pleroma.Emails.UserEmail.user_invitation_email(
+ admin,
+ token_record,
+ recipient_email
+ )
+
+ Swoosh.TestAssertions.assert_email_sent(
+ from: {instance_name, notify_email},
+ to: recipient_email,
+ html_body: email.html_body
+ )
+ end
+ end
+
+ describe "POST /api/pleroma/admin/users/email_invite, with invalid config" do
+ setup do: clear_config([:instance, :registrations_open])
+ setup do: clear_config([:instance, :invites_enabled])
+
+ test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn} do
+ Config.put([:instance, :registrations_open], false)
+ Config.put([:instance, :invites_enabled], false)
+
+ conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
+
+ assert json_response(conn, :bad_request) ==
+ %{
+ "error" =>
+ "To send invites you need to set the `invites_enabled` option to true."
+ }
+ end
+
+ test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do
+ Config.put([:instance, :registrations_open], true)
+ Config.put([:instance, :invites_enabled], true)
+
+ conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
+
+ assert json_response(conn, :bad_request) ==
+ %{
+ "error" =>
+ "To send invites you need to set the `registrations_open` option to false."
+ }
+ end
+ end
+
+ test "/api/pleroma/admin/users/:nickname/password_reset", %{conn: conn} do
+ user = insert(:user)
+
+ conn =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> get("/api/pleroma/admin/users/#{user.nickname}/password_reset")
+
+ resp = json_response(conn, 200)
+
+ assert Regex.match?(~r/(http:\/\/|https:\/\/)/, resp["link"])
+ end
+
+ describe "GET /api/pleroma/admin/users" do
+ test "renders users array for the first page", %{conn: conn, admin: admin} do
+ user = insert(:user, local: false, tags: ["foo", "bar"])
+ conn = get(conn, "/api/pleroma/admin/users?page=1")
+
+ users =
+ [
+ %{
+ "deactivated" => admin.deactivated,
+ "id" => admin.id,
+ "nickname" => admin.nickname,
+ "roles" => %{"admin" => true, "moderator" => false},
+ "local" => true,
+ "tags" => [],
+ "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(admin.name || admin.nickname),
+ "confirmation_pending" => false
+ },
+ %{
+ "deactivated" => user.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => false,
+ "tags" => ["foo", "bar"],
+ "avatar" => User.avatar_url(user) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(user.name || user.nickname),
+ "confirmation_pending" => false
+ }
+ ]
+ |> Enum.sort_by(& &1["nickname"])
+
+ assert json_response(conn, 200) == %{
+ "count" => 2,
+ "page_size" => 50,
+ "users" => users
+ }
+ end
+
+ test "pagination works correctly with service users", %{conn: conn} do
+ service1 = insert(:user, ap_id: Web.base_url() <> "/relay")
+ service2 = insert(:user, ap_id: Web.base_url() <> "/internal/fetch")
+ insert_list(25, :user)
+
+ assert %{"count" => 26, "page_size" => 10, "users" => users1} =
+ conn
+ |> get("/api/pleroma/admin/users?page=1&filters=", %{page_size: "10"})
+ |> json_response(200)
+
+ assert Enum.count(users1) == 10
+ assert service1 not in [users1]
+ assert service2 not in [users1]
+
+ assert %{"count" => 26, "page_size" => 10, "users" => users2} =
+ conn
+ |> get("/api/pleroma/admin/users?page=2&filters=", %{page_size: "10"})
+ |> json_response(200)
+
+ assert Enum.count(users2) == 10
+ assert service1 not in [users2]
+ assert service2 not in [users2]
+
+ assert %{"count" => 26, "page_size" => 10, "users" => users3} =
+ conn
+ |> get("/api/pleroma/admin/users?page=3&filters=", %{page_size: "10"})
+ |> json_response(200)
+
+ assert Enum.count(users3) == 6
+ assert service1 not in [users3]
+ assert service2 not in [users3]
+ end
+
+ test "renders empty array for the second page", %{conn: conn} do
+ insert(:user)
+
+ conn = get(conn, "/api/pleroma/admin/users?page=2")
+
+ assert json_response(conn, 200) == %{
+ "count" => 2,
+ "page_size" => 50,
+ "users" => []
+ }
+ end
+
+ test "regular search", %{conn: conn} do
+ user = insert(:user, nickname: "bob")
+
+ conn = get(conn, "/api/pleroma/admin/users?query=bo")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [
+ %{
+ "deactivated" => user.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => true,
+ "tags" => [],
+ "avatar" => User.avatar_url(user) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(user.name || user.nickname),
+ "confirmation_pending" => false
+ }
+ ]
+ }
+ end
+
+ test "search by domain", %{conn: conn} do
+ user = insert(:user, nickname: "nickname@domain.com")
+ insert(:user)
+
+ conn = get(conn, "/api/pleroma/admin/users?query=domain.com")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [
+ %{
+ "deactivated" => user.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => true,
+ "tags" => [],
+ "avatar" => User.avatar_url(user) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(user.name || user.nickname),
+ "confirmation_pending" => false
+ }
+ ]
+ }
+ end
+
+ test "search by full nickname", %{conn: conn} do
+ user = insert(:user, nickname: "nickname@domain.com")
+ insert(:user)
+
+ conn = get(conn, "/api/pleroma/admin/users?query=nickname@domain.com")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [
+ %{
+ "deactivated" => user.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => true,
+ "tags" => [],
+ "avatar" => User.avatar_url(user) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(user.name || user.nickname),
+ "confirmation_pending" => false
+ }
+ ]
+ }
+ end
+
+ test "search by display name", %{conn: conn} do
+ user = insert(:user, name: "Display name")
+ insert(:user)
+
+ conn = get(conn, "/api/pleroma/admin/users?name=display")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [
+ %{
+ "deactivated" => user.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => true,
+ "tags" => [],
+ "avatar" => User.avatar_url(user) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(user.name || user.nickname),
+ "confirmation_pending" => false
+ }
+ ]
+ }
+ end
+
+ test "search by email", %{conn: conn} do
+ user = insert(:user, email: "email@example.com")
+ insert(:user)
+
+ conn = get(conn, "/api/pleroma/admin/users?email=email@example.com")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [
+ %{
+ "deactivated" => user.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => true,
+ "tags" => [],
+ "avatar" => User.avatar_url(user) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(user.name || user.nickname),
+ "confirmation_pending" => false
+ }
+ ]
+ }
+ end
+
+ test "regular search with page size", %{conn: conn} do
+ user = insert(:user, nickname: "aalice")
+ user2 = insert(:user, nickname: "alice")
+
+ conn1 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=1")
+
+ assert json_response(conn1, 200) == %{
+ "count" => 2,
+ "page_size" => 1,
+ "users" => [
+ %{
+ "deactivated" => user.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => true,
+ "tags" => [],
+ "avatar" => User.avatar_url(user) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(user.name || user.nickname),
+ "confirmation_pending" => false
+ }
+ ]
+ }
+
+ conn2 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=2")
+
+ assert json_response(conn2, 200) == %{
+ "count" => 2,
+ "page_size" => 1,
+ "users" => [
+ %{
+ "deactivated" => user2.deactivated,
+ "id" => user2.id,
+ "nickname" => user2.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => true,
+ "tags" => [],
+ "avatar" => User.avatar_url(user2) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(user2.name || user2.nickname),
+ "confirmation_pending" => false
+ }
+ ]
+ }
+ end
+
+ test "only local users" do
+ admin = insert(:user, is_admin: true, nickname: "john")
+ token = insert(:oauth_admin_token, user: admin)
+ user = insert(:user, nickname: "bob")
+
+ insert(:user, nickname: "bobb", local: false)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, token)
+ |> get("/api/pleroma/admin/users?query=bo&filters=local")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [
+ %{
+ "deactivated" => user.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => true,
+ "tags" => [],
+ "avatar" => User.avatar_url(user) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(user.name || user.nickname),
+ "confirmation_pending" => false
+ }
+ ]
+ }
+ end
+
+ test "only local users with no query", %{conn: conn, admin: old_admin} do
+ admin = insert(:user, is_admin: true, nickname: "john")
+ user = insert(:user, nickname: "bob")
+
+ insert(:user, nickname: "bobb", local: false)
+
+ conn = get(conn, "/api/pleroma/admin/users?filters=local")
+
+ users =
+ [
+ %{
+ "deactivated" => user.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => true,
+ "tags" => [],
+ "avatar" => User.avatar_url(user) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(user.name || user.nickname),
+ "confirmation_pending" => false
+ },
+ %{
+ "deactivated" => admin.deactivated,
+ "id" => admin.id,
+ "nickname" => admin.nickname,
+ "roles" => %{"admin" => true, "moderator" => false},
+ "local" => true,
+ "tags" => [],
+ "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(admin.name || admin.nickname),
+ "confirmation_pending" => false
+ },
+ %{
+ "deactivated" => false,
+ "id" => old_admin.id,
+ "local" => true,
+ "nickname" => old_admin.nickname,
+ "roles" => %{"admin" => true, "moderator" => false},
+ "tags" => [],
+ "avatar" => User.avatar_url(old_admin) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(old_admin.name || old_admin.nickname),
+ "confirmation_pending" => false
+ }
+ ]
+ |> Enum.sort_by(& &1["nickname"])
+
+ assert json_response(conn, 200) == %{
+ "count" => 3,
+ "page_size" => 50,
+ "users" => users
+ }
+ end
+
+ test "load only admins", %{conn: conn, admin: admin} do
+ second_admin = insert(:user, is_admin: true)
+ insert(:user)
+ insert(:user)
+
+ conn = get(conn, "/api/pleroma/admin/users?filters=is_admin")
+
+ users =
+ [
+ %{
+ "deactivated" => false,
+ "id" => admin.id,
+ "nickname" => admin.nickname,
+ "roles" => %{"admin" => true, "moderator" => false},
+ "local" => admin.local,
+ "tags" => [],
+ "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(admin.name || admin.nickname),
+ "confirmation_pending" => false
+ },
+ %{
+ "deactivated" => false,
+ "id" => second_admin.id,
+ "nickname" => second_admin.nickname,
+ "roles" => %{"admin" => true, "moderator" => false},
+ "local" => second_admin.local,
+ "tags" => [],
+ "avatar" => User.avatar_url(second_admin) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(second_admin.name || second_admin.nickname),
+ "confirmation_pending" => false
+ }
+ ]
+ |> Enum.sort_by(& &1["nickname"])
+
+ assert json_response(conn, 200) == %{
+ "count" => 2,
+ "page_size" => 50,
+ "users" => users
+ }
+ end
+
+ test "load only moderators", %{conn: conn} do
+ moderator = insert(:user, is_moderator: true)
+ insert(:user)
+ insert(:user)
+
+ conn = get(conn, "/api/pleroma/admin/users?filters=is_moderator")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [
+ %{
+ "deactivated" => false,
+ "id" => moderator.id,
+ "nickname" => moderator.nickname,
+ "roles" => %{"admin" => false, "moderator" => true},
+ "local" => moderator.local,
+ "tags" => [],
+ "avatar" => User.avatar_url(moderator) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(moderator.name || moderator.nickname),
+ "confirmation_pending" => false
+ }
+ ]
+ }
+ end
+
+ test "load users with tags list", %{conn: conn} do
+ user1 = insert(:user, tags: ["first"])
+ user2 = insert(:user, tags: ["second"])
+ insert(:user)
+ insert(:user)
+
+ conn = get(conn, "/api/pleroma/admin/users?tags[]=first&tags[]=second")
+
+ users =
+ [
+ %{
+ "deactivated" => false,
+ "id" => user1.id,
+ "nickname" => user1.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => user1.local,
+ "tags" => ["first"],
+ "avatar" => User.avatar_url(user1) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(user1.name || user1.nickname),
+ "confirmation_pending" => false
+ },
+ %{
+ "deactivated" => false,
+ "id" => user2.id,
+ "nickname" => user2.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => user2.local,
+ "tags" => ["second"],
+ "avatar" => User.avatar_url(user2) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(user2.name || user2.nickname),
+ "confirmation_pending" => false
+ }
+ ]
+ |> Enum.sort_by(& &1["nickname"])
+
+ assert json_response(conn, 200) == %{
+ "count" => 2,
+ "page_size" => 50,
+ "users" => users
+ }
+ end
+
+ test "it works with multiple filters" do
+ admin = insert(:user, nickname: "john", is_admin: true)
+ token = insert(:oauth_admin_token, user: admin)
+ user = insert(:user, nickname: "bob", local: false, deactivated: true)
+
+ insert(:user, nickname: "ken", local: true, deactivated: true)
+ insert(:user, nickname: "bobb", local: false, deactivated: false)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, token)
+ |> get("/api/pleroma/admin/users?filters=deactivated,external")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [
+ %{
+ "deactivated" => user.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => user.local,
+ "tags" => [],
+ "avatar" => User.avatar_url(user) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(user.name || user.nickname),
+ "confirmation_pending" => false
+ }
+ ]
+ }
+ end
+
+ test "it omits relay user", %{admin: admin, conn: conn} do
+ assert %User{} = Relay.get_actor()
+
+ conn = get(conn, "/api/pleroma/admin/users")
+
+ assert json_response(conn, 200) == %{
+ "count" => 1,
+ "page_size" => 50,
+ "users" => [
+ %{
+ "deactivated" => admin.deactivated,
+ "id" => admin.id,
+ "nickname" => admin.nickname,
+ "roles" => %{"admin" => true, "moderator" => false},
+ "local" => true,
+ "tags" => [],
+ "avatar" => User.avatar_url(admin) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(admin.name || admin.nickname),
+ "confirmation_pending" => false
+ }
+ ]
+ }
+ end
+ end
+
+ test "PATCH /api/pleroma/admin/users/activate", %{admin: admin, conn: conn} do
+ user_one = insert(:user, deactivated: true)
+ user_two = insert(:user, deactivated: true)
+
+ conn =
+ patch(
+ conn,
+ "/api/pleroma/admin/users/activate",
+ %{nicknames: [user_one.nickname, user_two.nickname]}
+ )
+
+ response = json_response(conn, 200)
+ assert Enum.map(response["users"], & &1["deactivated"]) == [false, false]
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} activated users: @#{user_one.nickname}, @#{user_two.nickname}"
+ end
+
+ test "PATCH /api/pleroma/admin/users/deactivate", %{admin: admin, conn: conn} do
+ user_one = insert(:user, deactivated: false)
+ user_two = insert(:user, deactivated: false)
+
+ conn =
+ patch(
+ conn,
+ "/api/pleroma/admin/users/deactivate",
+ %{nicknames: [user_one.nickname, user_two.nickname]}
+ )
+
+ response = json_response(conn, 200)
+ assert Enum.map(response["users"], & &1["deactivated"]) == [true, true]
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} deactivated users: @#{user_one.nickname}, @#{user_two.nickname}"
+ end
+
+ test "PATCH /api/pleroma/admin/users/:nickname/toggle_activation", %{admin: admin, conn: conn} do
+ user = insert(:user)
+
+ conn = patch(conn, "/api/pleroma/admin/users/#{user.nickname}/toggle_activation")
+
+ assert json_response(conn, 200) ==
+ %{
+ "deactivated" => !user.deactivated,
+ "id" => user.id,
+ "nickname" => user.nickname,
+ "roles" => %{"admin" => false, "moderator" => false},
+ "local" => true,
+ "tags" => [],
+ "avatar" => User.avatar_url(user) |> MediaProxy.url(),
+ "display_name" => HTML.strip_tags(user.name || user.nickname),
+ "confirmation_pending" => false
+ }
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} deactivated users: @#{user.nickname}"
+ end
+
+ describe "PUT disable_mfa" do
+ test "returns 200 and disable 2fa", %{conn: conn} do
+ user =
+ insert(:user,
+ multi_factor_authentication_settings: %MFA.Settings{
+ enabled: true,
+ totp: %MFA.Settings.TOTP{secret: "otp_secret", confirmed: true}
+ }
+ )
+
+ response =
+ conn
+ |> put("/api/pleroma/admin/users/disable_mfa", %{nickname: user.nickname})
+ |> json_response(200)
+
+ assert response == user.nickname
+ mfa_settings = refresh_record(user).multi_factor_authentication_settings
+
+ refute mfa_settings.enabled
+ refute mfa_settings.totp.confirmed
+ end
+
+ test "returns 404 if user not found", %{conn: conn} do
+ response =
+ conn
+ |> put("/api/pleroma/admin/users/disable_mfa", %{nickname: "nickname"})
+ |> json_response(404)
+
+ assert response == %{"error" => "Not found"}
+ end
+ end
+
+ describe "POST /api/pleroma/admin/users/invite_token" do
+ test "without options", %{conn: conn} do
+ conn = post(conn, "/api/pleroma/admin/users/invite_token")
+
+ invite_json = json_response(conn, 200)
+ invite = UserInviteToken.find_by_token!(invite_json["token"])
+ refute invite.used
+ refute invite.expires_at
+ refute invite.max_use
+ assert invite.invite_type == "one_time"
+ end
+
+ test "with expires_at", %{conn: conn} do
+ conn =
+ post(conn, "/api/pleroma/admin/users/invite_token", %{
+ "expires_at" => Date.to_string(Date.utc_today())
+ })
+
+ invite_json = json_response(conn, 200)
+ invite = UserInviteToken.find_by_token!(invite_json["token"])
+
+ refute invite.used
+ assert invite.expires_at == Date.utc_today()
+ refute invite.max_use
+ assert invite.invite_type == "date_limited"
+ end
+
+ test "with max_use", %{conn: conn} do
+ conn = post(conn, "/api/pleroma/admin/users/invite_token", %{"max_use" => 150})
+
+ invite_json = json_response(conn, 200)
+ invite = UserInviteToken.find_by_token!(invite_json["token"])
+ refute invite.used
+ refute invite.expires_at
+ assert invite.max_use == 150
+ assert invite.invite_type == "reusable"
+ end
+
+ test "with max use and expires_at", %{conn: conn} do
+ conn =
+ post(conn, "/api/pleroma/admin/users/invite_token", %{
+ "max_use" => 150,
+ "expires_at" => Date.to_string(Date.utc_today())
+ })
+
+ invite_json = json_response(conn, 200)
+ invite = UserInviteToken.find_by_token!(invite_json["token"])
+ refute invite.used
+ assert invite.expires_at == Date.utc_today()
+ assert invite.max_use == 150
+ assert invite.invite_type == "reusable_date_limited"
+ end
+ end
+
+ describe "GET /api/pleroma/admin/users/invites" do
+ test "no invites", %{conn: conn} do
+ conn = get(conn, "/api/pleroma/admin/users/invites")
+
+ assert json_response(conn, 200) == %{"invites" => []}
+ end
+
+ test "with invite", %{conn: conn} do
+ {:ok, invite} = UserInviteToken.create_invite()
+
+ conn = get(conn, "/api/pleroma/admin/users/invites")
+
+ assert json_response(conn, 200) == %{
+ "invites" => [
+ %{
+ "expires_at" => nil,
+ "id" => invite.id,
+ "invite_type" => "one_time",
+ "max_use" => nil,
+ "token" => invite.token,
+ "used" => false,
+ "uses" => 0
+ }
+ ]
+ }
+ end
+ end
+
+ describe "POST /api/pleroma/admin/users/revoke_invite" do
+ test "with token", %{conn: conn} do
+ {:ok, invite} = UserInviteToken.create_invite()
+
+ conn = post(conn, "/api/pleroma/admin/users/revoke_invite", %{"token" => invite.token})
+
+ assert json_response(conn, 200) == %{
+ "expires_at" => nil,
+ "id" => invite.id,
+ "invite_type" => "one_time",
+ "max_use" => nil,
+ "token" => invite.token,
+ "used" => true,
+ "uses" => 0
+ }
+ end
+
+ test "with invalid token", %{conn: conn} do
+ conn = post(conn, "/api/pleroma/admin/users/revoke_invite", %{"token" => "foo"})
+
+ assert json_response(conn, :not_found) == %{"error" => "Not found"}
+ end
+ end
+
+ describe "GET /api/pleroma/admin/reports/:id" do
+ test "returns report by its id", %{conn: conn} do
+ [reporter, target_user] = insert_pair(:user)
+ activity = insert(:note_activity, user: target_user)
+
+ {:ok, %{id: report_id}} =
+ CommonAPI.report(reporter, %{
+ account_id: target_user.id,
+ comment: "I feel offended",
+ status_ids: [activity.id]
+ })
+
+ response =
+ conn
+ |> get("/api/pleroma/admin/reports/#{report_id}")
+ |> json_response(:ok)
+
+ assert response["id"] == report_id
+ end
+
+ test "returns 404 when report id is invalid", %{conn: conn} do
+ conn = get(conn, "/api/pleroma/admin/reports/test")
+
+ assert json_response(conn, :not_found) == %{"error" => "Not found"}
+ end
+ end
+
+ describe "PATCH /api/pleroma/admin/reports" do
+ setup do
+ [reporter, target_user] = insert_pair(:user)
+ activity = insert(:note_activity, user: target_user)
+
+ {:ok, %{id: report_id}} =
+ CommonAPI.report(reporter, %{
+ account_id: target_user.id,
+ comment: "I feel offended",
+ status_ids: [activity.id]
+ })
+
+ {:ok, %{id: second_report_id}} =
+ CommonAPI.report(reporter, %{
+ account_id: target_user.id,
+ comment: "I feel very offended",
+ status_ids: [activity.id]
+ })
+
+ %{
+ id: report_id,
+ second_report_id: second_report_id
+ }
+ end
+
+ test "requires admin:write:reports scope", %{conn: conn, id: id, admin: admin} do
+ read_token = insert(:oauth_token, user: admin, scopes: ["admin:read"])
+ write_token = insert(:oauth_token, user: admin, scopes: ["admin:write:reports"])
+
+ response =
+ conn
+ |> assign(:token, read_token)
+ |> patch("/api/pleroma/admin/reports", %{
+ "reports" => [%{"state" => "resolved", "id" => id}]
+ })
+ |> json_response(403)
+
+ assert response == %{
+ "error" => "Insufficient permissions: admin:write:reports."
+ }
+
+ conn
+ |> assign(:token, write_token)
+ |> patch("/api/pleroma/admin/reports", %{
+ "reports" => [%{"state" => "resolved", "id" => id}]
+ })
+ |> json_response(:no_content)
+ end
+
+ test "mark report as resolved", %{conn: conn, id: id, admin: admin} do
+ conn
+ |> patch("/api/pleroma/admin/reports", %{
+ "reports" => [
+ %{"state" => "resolved", "id" => id}
+ ]
+ })
+ |> json_response(:no_content)
+
+ activity = Activity.get_by_id(id)
+ assert activity.data["state"] == "resolved"
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} updated report ##{id} with 'resolved' state"
+ end
+
+ test "closes report", %{conn: conn, id: id, admin: admin} do
+ conn
+ |> patch("/api/pleroma/admin/reports", %{
+ "reports" => [
+ %{"state" => "closed", "id" => id}
+ ]
+ })
+ |> json_response(:no_content)
+
+ activity = Activity.get_by_id(id)
+ assert activity.data["state"] == "closed"
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} updated report ##{id} with 'closed' state"
+ end
+
+ test "returns 400 when state is unknown", %{conn: conn, id: id} do
+ conn =
+ conn
+ |> patch("/api/pleroma/admin/reports", %{
+ "reports" => [
+ %{"state" => "test", "id" => id}
+ ]
+ })
+
+ assert hd(json_response(conn, :bad_request))["error"] == "Unsupported state"
+ end
+
+ test "returns 404 when report is not exist", %{conn: conn} do
+ conn =
+ conn
+ |> patch("/api/pleroma/admin/reports", %{
+ "reports" => [
+ %{"state" => "closed", "id" => "test"}
+ ]
+ })
+
+ assert hd(json_response(conn, :bad_request))["error"] == "not_found"
+ end
+
+ test "updates state of multiple reports", %{
+ conn: conn,
+ id: id,
+ admin: admin,
+ second_report_id: second_report_id
+ } do
+ conn
+ |> patch("/api/pleroma/admin/reports", %{
+ "reports" => [
+ %{"state" => "resolved", "id" => id},
+ %{"state" => "closed", "id" => second_report_id}
+ ]
+ })
+ |> json_response(:no_content)
+
+ activity = Activity.get_by_id(id)
+ second_activity = Activity.get_by_id(second_report_id)
+ assert activity.data["state"] == "resolved"
+ assert second_activity.data["state"] == "closed"
+
+ [first_log_entry, second_log_entry] = Repo.all(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(first_log_entry) ==
+ "@#{admin.nickname} updated report ##{id} with 'resolved' state"
+
+ assert ModerationLog.get_log_entry_message(second_log_entry) ==
+ "@#{admin.nickname} updated report ##{second_report_id} with 'closed' state"
+ end
+ end
+
+ describe "GET /api/pleroma/admin/reports" do
+ test "returns empty response when no reports created", %{conn: conn} do
+ response =
+ conn
+ |> get("/api/pleroma/admin/reports")
+ |> json_response(:ok)
+
+ assert Enum.empty?(response["reports"])
+ assert response["total"] == 0
+ end
+
+ test "returns reports", %{conn: conn} do
+ [reporter, target_user] = insert_pair(:user)
+ activity = insert(:note_activity, user: target_user)
+
+ {:ok, %{id: report_id}} =
+ CommonAPI.report(reporter, %{
+ account_id: target_user.id,
+ comment: "I feel offended",
+ status_ids: [activity.id]
+ })
+
+ response =
+ conn
+ |> get("/api/pleroma/admin/reports")
+ |> json_response(:ok)
+
+ [report] = response["reports"]
+
+ assert length(response["reports"]) == 1
+ assert report["id"] == report_id
+
+ assert response["total"] == 1
+ end
+
+ test "returns reports with specified state", %{conn: conn} do
+ [reporter, target_user] = insert_pair(:user)
+ activity = insert(:note_activity, user: target_user)
+
+ {:ok, %{id: first_report_id}} =
+ CommonAPI.report(reporter, %{
+ account_id: target_user.id,
+ comment: "I feel offended",
+ status_ids: [activity.id]
+ })
+
+ {:ok, %{id: second_report_id}} =
+ CommonAPI.report(reporter, %{
+ account_id: target_user.id,
+ comment: "I don't like this user"
+ })
+
+ CommonAPI.update_report_state(second_report_id, "closed")
+
+ response =
+ conn
+ |> get("/api/pleroma/admin/reports", %{
+ "state" => "open"
+ })
+ |> json_response(:ok)
+
+ [open_report] = response["reports"]
+
+ assert length(response["reports"]) == 1
+ assert open_report["id"] == first_report_id
+
+ assert response["total"] == 1
+
+ response =
+ conn
+ |> get("/api/pleroma/admin/reports", %{
+ "state" => "closed"
+ })
+ |> json_response(:ok)
+
+ [closed_report] = response["reports"]
+
+ assert length(response["reports"]) == 1
+ assert closed_report["id"] == second_report_id
+
+ assert response["total"] == 1
+
+ response =
+ conn
+ |> get("/api/pleroma/admin/reports", %{
+ "state" => "resolved"
+ })
+ |> json_response(:ok)
+
+ assert Enum.empty?(response["reports"])
+ assert response["total"] == 0
+ end
+
+ test "returns 403 when requested by a non-admin" do
+ user = insert(:user)
+ token = insert(:oauth_token, user: user)
+
+ conn =
+ build_conn()
+ |> assign(:user, user)
+ |> assign(:token, token)
+ |> get("/api/pleroma/admin/reports")
+
+ assert json_response(conn, :forbidden) ==
+ %{"error" => "User is not an admin or OAuth admin scope is not granted."}
+ end
+
+ test "returns 403 when requested by anonymous" do
+ conn = get(build_conn(), "/api/pleroma/admin/reports")
+
+ assert json_response(conn, :forbidden) == %{"error" => "Invalid credentials."}
+ end
+ end
+
+ describe "GET /api/pleroma/admin/config" do
+ setup do: clear_config(:configurable_from_database, true)
+
+ test "when configuration from database is off", %{conn: conn} do
+ Config.put(:configurable_from_database, false)
+ conn = get(conn, "/api/pleroma/admin/config")
+
+ assert json_response(conn, 400) ==
+ %{
+ "error" => "To use this endpoint you need to enable configuration from database."
+ }
+ end
+
+ test "with settings only in db", %{conn: conn} do
+ config1 = insert(:config)
+ config2 = insert(:config)
+
+ conn = get(conn, "/api/pleroma/admin/config", %{"only_db" => true})
+
+ %{
+ "configs" => [
+ %{
+ "group" => ":pleroma",
+ "key" => key1,
+ "value" => _
+ },
+ %{
+ "group" => ":pleroma",
+ "key" => key2,
+ "value" => _
+ }
+ ]
+ } = json_response(conn, 200)
+
+ assert key1 == config1.key
+ assert key2 == config2.key
+ end
+
+ test "db is added to settings that are in db", %{conn: conn} do
+ _config = insert(:config, key: ":instance", value: ConfigDB.to_binary(name: "Some name"))
+
+ %{"configs" => configs} =
+ conn
+ |> get("/api/pleroma/admin/config")
+ |> json_response(200)
+
+ [instance_config] =
+ Enum.filter(configs, fn %{"group" => group, "key" => key} ->
+ group == ":pleroma" and key == ":instance"
+ end)
+
+ assert instance_config["db"] == [":name"]
+ end
+
+ test "merged default setting with db settings", %{conn: conn} do
+ config1 = insert(:config)
+ config2 = insert(:config)
+
+ config3 =
+ insert(:config,
+ value: ConfigDB.to_binary(k1: :v1, k2: :v2)
+ )
+
+ %{"configs" => configs} =
+ conn
+ |> get("/api/pleroma/admin/config")
+ |> json_response(200)
+
+ assert length(configs) > 3
+
+ received_configs =
+ Enum.filter(configs, fn %{"group" => group, "key" => key} ->
+ group == ":pleroma" and key in [config1.key, config2.key, config3.key]
+ end)
+
+ assert length(received_configs) == 3
+
+ db_keys =
+ config3.value
+ |> ConfigDB.from_binary()
+ |> Keyword.keys()
+ |> ConfigDB.convert()
+
+ Enum.each(received_configs, fn %{"value" => value, "db" => db} ->
+ assert db in [[config1.key], [config2.key], db_keys]
+
+ assert value in [
+ ConfigDB.from_binary_with_convert(config1.value),
+ ConfigDB.from_binary_with_convert(config2.value),
+ ConfigDB.from_binary_with_convert(config3.value)
+ ]
+ end)
+ end
+
+ test "subkeys with full update right merge", %{conn: conn} do
+ config1 =
+ insert(:config,
+ key: ":emoji",
+ value: ConfigDB.to_binary(groups: [a: 1, b: 2], key: [a: 1])
+ )
+
+ config2 =
+ insert(:config,
+ key: ":assets",
+ value: ConfigDB.to_binary(mascots: [a: 1, b: 2], key: [a: 1])
+ )
+
+ %{"configs" => configs} =
+ conn
+ |> get("/api/pleroma/admin/config")
+ |> json_response(200)
+
+ vals =
+ Enum.filter(configs, fn %{"group" => group, "key" => key} ->
+ group == ":pleroma" and key in [config1.key, config2.key]
+ end)
+
+ emoji = Enum.find(vals, fn %{"key" => key} -> key == ":emoji" end)
+ assets = Enum.find(vals, fn %{"key" => key} -> key == ":assets" end)
+
+ emoji_val = ConfigDB.transform_with_out_binary(emoji["value"])
+ assets_val = ConfigDB.transform_with_out_binary(assets["value"])
+
+ assert emoji_val[:groups] == [a: 1, b: 2]
+ assert assets_val[:mascots] == [a: 1, b: 2]
+ end
+ end
+
+ test "POST /api/pleroma/admin/config error", %{conn: conn} do
+ conn = post(conn, "/api/pleroma/admin/config", %{"configs" => []})
+
+ assert json_response(conn, 400) ==
+ %{"error" => "To use this endpoint you need to enable configuration from database."}
+ end
+
+ describe "POST /api/pleroma/admin/config" do
+ setup do
+ http = Application.get_env(:pleroma, :http)
+
+ on_exit(fn ->
+ Application.delete_env(:pleroma, :key1)
+ Application.delete_env(:pleroma, :key2)
+ Application.delete_env(:pleroma, :key3)
+ Application.delete_env(:pleroma, :key4)
+ Application.delete_env(:pleroma, :keyaa1)
+ Application.delete_env(:pleroma, :keyaa2)
+ Application.delete_env(:pleroma, Pleroma.Web.Endpoint.NotReal)
+ Application.delete_env(:pleroma, Pleroma.Captcha.NotReal)
+ Application.put_env(:pleroma, :http, http)
+ Application.put_env(:tesla, :adapter, Tesla.Mock)
+ Restarter.Pleroma.refresh()
+ end)
+ end
+
+ setup do: clear_config(:configurable_from_database, true)
+
+ @tag capture_log: true
+ test "create new config setting in db", %{conn: conn} do
+ ueberauth = Application.get_env(:ueberauth, Ueberauth)
+ on_exit(fn -> Application.put_env(:ueberauth, Ueberauth, ueberauth) end)
+
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{group: ":pleroma", key: ":key1", value: "value1"},
+ %{
+ group: ":ueberauth",
+ key: "Ueberauth",
+ value: [%{"tuple" => [":consumer_secret", "aaaa"]}]
+ },
+ %{
+ group: ":pleroma",
+ key: ":key2",
+ value: %{
+ ":nested_1" => "nested_value1",
+ ":nested_2" => [
+ %{":nested_22" => "nested_value222"},
+ %{":nested_33" => %{":nested_44" => "nested_444"}}
+ ]
+ }
+ },
+ %{
+ group: ":pleroma",
+ key: ":key3",
+ value: [
+ %{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
+ %{"nested_4" => true}
+ ]
+ },
+ %{
+ group: ":pleroma",
+ key: ":key4",
+ value: %{":nested_5" => ":upload", "endpoint" => "https://example.com"}
+ },
+ %{
+ group: ":idna",
+ key: ":key5",
+ value: %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]}
+ }
+ ]
+ })
+
+ assert json_response(conn, 200) == %{
+ "configs" => [
+ %{
+ "group" => ":pleroma",
+ "key" => ":key1",
+ "value" => "value1",
+ "db" => [":key1"]
+ },
+ %{
+ "group" => ":ueberauth",
+ "key" => "Ueberauth",
+ "value" => [%{"tuple" => [":consumer_secret", "aaaa"]}],
+ "db" => [":consumer_secret"]
+ },
+ %{
+ "group" => ":pleroma",
+ "key" => ":key2",
+ "value" => %{
+ ":nested_1" => "nested_value1",
+ ":nested_2" => [
+ %{":nested_22" => "nested_value222"},
+ %{":nested_33" => %{":nested_44" => "nested_444"}}
+ ]
+ },
+ "db" => [":key2"]
+ },
+ %{
+ "group" => ":pleroma",
+ "key" => ":key3",
+ "value" => [
+ %{"nested_3" => ":nested_3", "nested_33" => "nested_33"},
+ %{"nested_4" => true}
+ ],
+ "db" => [":key3"]
+ },
+ %{
+ "group" => ":pleroma",
+ "key" => ":key4",
+ "value" => %{"endpoint" => "https://example.com", ":nested_5" => ":upload"},
+ "db" => [":key4"]
+ },
+ %{
+ "group" => ":idna",
+ "key" => ":key5",
+ "value" => %{"tuple" => ["string", "Pleroma.Captcha.NotReal", []]},
+ "db" => [":key5"]
+ }
+ ]
+ }
+
+ assert Application.get_env(:pleroma, :key1) == "value1"
+
+ assert Application.get_env(:pleroma, :key2) == %{
+ nested_1: "nested_value1",
+ nested_2: [
+ %{nested_22: "nested_value222"},
+ %{nested_33: %{nested_44: "nested_444"}}
+ ]
+ }
+
+ assert Application.get_env(:pleroma, :key3) == [
+ %{"nested_3" => :nested_3, "nested_33" => "nested_33"},
+ %{"nested_4" => true}
+ ]
+
+ assert Application.get_env(:pleroma, :key4) == %{
+ "endpoint" => "https://example.com",
+ nested_5: :upload
+ }
+
+ assert Application.get_env(:idna, :key5) == {"string", Pleroma.Captcha.NotReal, []}
+ end
+
+ test "save configs setting without explicit key", %{conn: conn} do
+ level = Application.get_env(:quack, :level)
+ meta = Application.get_env(:quack, :meta)
+ webhook_url = Application.get_env(:quack, :webhook_url)
+
+ on_exit(fn ->
+ Application.put_env(:quack, :level, level)
+ Application.put_env(:quack, :meta, meta)
+ Application.put_env(:quack, :webhook_url, webhook_url)
+ end)
+
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{
+ group: ":quack",
+ key: ":level",
+ value: ":info"
+ },
+ %{
+ group: ":quack",
+ key: ":meta",
+ value: [":none"]
+ },
+ %{
+ group: ":quack",
+ key: ":webhook_url",
+ value: "https://hooks.slack.com/services/KEY"
+ }
+ ]
+ })
+
+ assert json_response(conn, 200) == %{
+ "configs" => [
+ %{
+ "group" => ":quack",
+ "key" => ":level",
+ "value" => ":info",
+ "db" => [":level"]
+ },
+ %{
+ "group" => ":quack",
+ "key" => ":meta",
+ "value" => [":none"],
+ "db" => [":meta"]
+ },
+ %{
+ "group" => ":quack",
+ "key" => ":webhook_url",
+ "value" => "https://hooks.slack.com/services/KEY",
+ "db" => [":webhook_url"]
+ }
+ ]
+ }
+
+ assert Application.get_env(:quack, :level) == :info
+ assert Application.get_env(:quack, :meta) == [:none]
+ assert Application.get_env(:quack, :webhook_url) == "https://hooks.slack.com/services/KEY"
+ end
+
+ test "saving config with partial update", %{conn: conn} do
+ config = insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: 2))
+
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{group: config.group, key: config.key, value: [%{"tuple" => [":key3", 3]}]}
+ ]
+ })
+
+ assert json_response(conn, 200) == %{
+ "configs" => [
+ %{
+ "group" => ":pleroma",
+ "key" => ":key1",
+ "value" => [
+ %{"tuple" => [":key1", 1]},
+ %{"tuple" => [":key2", 2]},
+ %{"tuple" => [":key3", 3]}
+ ],
+ "db" => [":key1", ":key2", ":key3"]
+ }
+ ]
+ }
+ end
+
+ test "saving config which need pleroma reboot", %{conn: conn} do
+ chat = Config.get(:chat)
+ on_exit(fn -> Config.put(:chat, chat) end)
+
+ assert post(
+ conn,
+ "/api/pleroma/admin/config",
+ %{
+ configs: [
+ %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
+ ]
+ }
+ )
+ |> json_response(200) == %{
+ "configs" => [
+ %{
+ "db" => [":enabled"],
+ "group" => ":pleroma",
+ "key" => ":chat",
+ "value" => [%{"tuple" => [":enabled", true]}]
+ }
+ ],
+ "need_reboot" => true
+ }
+
+ configs =
+ conn
+ |> get("/api/pleroma/admin/config")
+ |> json_response(200)
+
+ assert configs["need_reboot"]
+
+ capture_log(fn ->
+ assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
+ end) =~ "pleroma restarted"
+
+ configs =
+ conn
+ |> get("/api/pleroma/admin/config")
+ |> json_response(200)
+
+ assert configs["need_reboot"] == false
+ end
+
+ test "update setting which need reboot, don't change reboot flag until reboot", %{conn: conn} do
+ chat = Config.get(:chat)
+ on_exit(fn -> Config.put(:chat, chat) end)
+
+ assert post(
+ conn,
+ "/api/pleroma/admin/config",
+ %{
+ configs: [
+ %{group: ":pleroma", key: ":chat", value: [%{"tuple" => [":enabled", true]}]}
+ ]
+ }
+ )
+ |> json_response(200) == %{
+ "configs" => [
+ %{
+ "db" => [":enabled"],
+ "group" => ":pleroma",
+ "key" => ":chat",
+ "value" => [%{"tuple" => [":enabled", true]}]
+ }
+ ],
+ "need_reboot" => true
+ }
+
+ assert post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{group: ":pleroma", key: ":key1", value: [%{"tuple" => [":key3", 3]}]}
+ ]
+ })
+ |> json_response(200) == %{
+ "configs" => [
+ %{
+ "group" => ":pleroma",
+ "key" => ":key1",
+ "value" => [
+ %{"tuple" => [":key3", 3]}
+ ],
+ "db" => [":key3"]
+ }
+ ],
+ "need_reboot" => true
+ }
+
+ capture_log(fn ->
+ assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
+ end) =~ "pleroma restarted"
+
+ configs =
+ conn
+ |> get("/api/pleroma/admin/config")
+ |> json_response(200)
+
+ assert configs["need_reboot"] == false
+ end
+
+ test "saving config with nested merge", %{conn: conn} do
+ config =
+ insert(:config, key: ":key1", value: :erlang.term_to_binary(key1: 1, key2: [k1: 1, k2: 2]))
+
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{
+ group: config.group,
+ key: config.key,
+ value: [
+ %{"tuple" => [":key3", 3]},
+ %{
+ "tuple" => [
+ ":key2",
+ [
+ %{"tuple" => [":k2", 1]},
+ %{"tuple" => [":k3", 3]}
+ ]
+ ]
+ }
+ ]
+ }
+ ]
+ })
+
+ assert json_response(conn, 200) == %{
+ "configs" => [
+ %{
+ "group" => ":pleroma",
+ "key" => ":key1",
+ "value" => [
+ %{"tuple" => [":key1", 1]},
+ %{"tuple" => [":key3", 3]},
+ %{
+ "tuple" => [
+ ":key2",
+ [
+ %{"tuple" => [":k1", 1]},
+ %{"tuple" => [":k2", 1]},
+ %{"tuple" => [":k3", 3]}
+ ]
+ ]
+ }
+ ],
+ "db" => [":key1", ":key3", ":key2"]
+ }
+ ]
+ }
+ end
+
+ test "saving special atoms", %{conn: conn} do
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ "configs" => [
+ %{
+ "group" => ":pleroma",
+ "key" => ":key1",
+ "value" => [
+ %{
+ "tuple" => [
+ ":ssl_options",
+ [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}]
+ ]
+ }
+ ]
+ }
+ ]
+ })
+
+ assert json_response(conn, 200) == %{
+ "configs" => [
+ %{
+ "group" => ":pleroma",
+ "key" => ":key1",
+ "value" => [
+ %{
+ "tuple" => [
+ ":ssl_options",
+ [%{"tuple" => [":versions", [":tlsv1", ":tlsv1.1", ":tlsv1.2"]]}]
+ ]
+ }
+ ],
+ "db" => [":ssl_options"]
+ }
+ ]
+ }
+
+ assert Application.get_env(:pleroma, :key1) == [
+ ssl_options: [versions: [:tlsv1, :"tlsv1.1", :"tlsv1.2"]]
+ ]
+ end
+
+ test "saving full setting if value is in full_key_update list", %{conn: conn} do
+ backends = Application.get_env(:logger, :backends)
+ on_exit(fn -> Application.put_env(:logger, :backends, backends) end)
+
+ config =
+ insert(:config,
+ group: ":logger",
+ key: ":backends",
+ value: :erlang.term_to_binary([])
+ )
+
+ Pleroma.Config.TransferTask.load_and_update_env([], false)
+
+ assert Application.get_env(:logger, :backends) == []
+
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{
+ group: config.group,
+ key: config.key,
+ value: [":console"]
+ }
+ ]
+ })
+
+ assert json_response(conn, 200) == %{
+ "configs" => [
+ %{
+ "group" => ":logger",
+ "key" => ":backends",
+ "value" => [
+ ":console"
+ ],
+ "db" => [":backends"]
+ }
+ ]
+ }
+
+ assert Application.get_env(:logger, :backends) == [
+ :console
+ ]
+ end
+
+ test "saving full setting if value is not keyword", %{conn: conn} do
+ config =
+ insert(:config,
+ group: ":tesla",
+ key: ":adapter",
+ value: :erlang.term_to_binary(Tesla.Adapter.Hackey)
+ )
+
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{group: config.group, key: config.key, value: "Tesla.Adapter.Httpc"}
+ ]
+ })
+
+ assert json_response(conn, 200) == %{
+ "configs" => [
+ %{
+ "group" => ":tesla",
+ "key" => ":adapter",
+ "value" => "Tesla.Adapter.Httpc",
+ "db" => [":adapter"]
+ }
+ ]
+ }
+ end
+
+ test "update config setting & delete with fallback to default value", %{
+ conn: conn,
+ admin: admin,
+ token: token
+ } do
+ ueberauth = Application.get_env(:ueberauth, Ueberauth)
+ config1 = insert(:config, key: ":keyaa1")
+ config2 = insert(:config, key: ":keyaa2")
+
+ config3 =
+ insert(:config,
+ group: ":ueberauth",
+ key: "Ueberauth"
+ )
+
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{group: config1.group, key: config1.key, value: "another_value"},
+ %{group: config2.group, key: config2.key, value: "another_value"}
+ ]
+ })
+
+ assert json_response(conn, 200) == %{
+ "configs" => [
+ %{
+ "group" => ":pleroma",
+ "key" => config1.key,
+ "value" => "another_value",
+ "db" => [":keyaa1"]
+ },
+ %{
+ "group" => ":pleroma",
+ "key" => config2.key,
+ "value" => "another_value",
+ "db" => [":keyaa2"]
+ }
+ ]
+ }
+
+ assert Application.get_env(:pleroma, :keyaa1) == "another_value"
+ assert Application.get_env(:pleroma, :keyaa2) == "another_value"
+ assert Application.get_env(:ueberauth, Ueberauth) == ConfigDB.from_binary(config3.value)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, token)
+ |> post("/api/pleroma/admin/config", %{
+ configs: [
+ %{group: config2.group, key: config2.key, delete: true},
+ %{
+ group: ":ueberauth",
+ key: "Ueberauth",
+ delete: true
+ }
+ ]
+ })
+
+ assert json_response(conn, 200) == %{
+ "configs" => []
+ }
+
+ assert Application.get_env(:ueberauth, Ueberauth) == ueberauth
+ refute Keyword.has_key?(Application.get_all_env(:pleroma), :keyaa2)
+ end
+
+ test "common config example", %{conn: conn} do
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{
+ "group" => ":pleroma",
+ "key" => "Pleroma.Captcha.NotReal",
+ "value" => [
+ %{"tuple" => [":enabled", false]},
+ %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
+ %{"tuple" => [":seconds_valid", 60]},
+ %{"tuple" => [":path", ""]},
+ %{"tuple" => [":key1", nil]},
+ %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
+ %{"tuple" => [":regex1", "~r/https:\/\/example.com/"]},
+ %{"tuple" => [":regex2", "~r/https:\/\/example.com/u"]},
+ %{"tuple" => [":regex3", "~r/https:\/\/example.com/i"]},
+ %{"tuple" => [":regex4", "~r/https:\/\/example.com/s"]},
+ %{"tuple" => [":name", "Pleroma"]}
+ ]
+ }
+ ]
+ })
+
+ assert Config.get([Pleroma.Captcha.NotReal, :name]) == "Pleroma"
+
+ assert json_response(conn, 200) == %{
+ "configs" => [
+ %{
+ "group" => ":pleroma",
+ "key" => "Pleroma.Captcha.NotReal",
+ "value" => [
+ %{"tuple" => [":enabled", false]},
+ %{"tuple" => [":method", "Pleroma.Captcha.Kocaptcha"]},
+ %{"tuple" => [":seconds_valid", 60]},
+ %{"tuple" => [":path", ""]},
+ %{"tuple" => [":key1", nil]},
+ %{"tuple" => [":partial_chain", "&:hackney_connect.partial_chain/1"]},
+ %{"tuple" => [":regex1", "~r/https:\\/\\/example.com/"]},
+ %{"tuple" => [":regex2", "~r/https:\\/\\/example.com/u"]},
+ %{"tuple" => [":regex3", "~r/https:\\/\\/example.com/i"]},
+ %{"tuple" => [":regex4", "~r/https:\\/\\/example.com/s"]},
+ %{"tuple" => [":name", "Pleroma"]}
+ ],
+ "db" => [
+ ":enabled",
+ ":method",
+ ":seconds_valid",
+ ":path",
+ ":key1",
+ ":partial_chain",
+ ":regex1",
+ ":regex2",
+ ":regex3",
+ ":regex4",
+ ":name"
+ ]
+ }
+ ]
+ }
+ end
+
+ test "tuples with more than two values", %{conn: conn} do
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{
+ "group" => ":pleroma",
+ "key" => "Pleroma.Web.Endpoint.NotReal",
+ "value" => [
+ %{
+ "tuple" => [
+ ":http",
+ [
+ %{
+ "tuple" => [
+ ":key2",
+ [
+ %{
+ "tuple" => [
+ ":_",
+ [
+ %{
+ "tuple" => [
+ "/api/v1/streaming",
+ "Pleroma.Web.MastodonAPI.WebsocketHandler",
+ []
+ ]
+ },
+ %{
+ "tuple" => [
+ "/websocket",
+ "Phoenix.Endpoint.CowboyWebSocket",
+ %{
+ "tuple" => [
+ "Phoenix.Transports.WebSocket",
+ %{
+ "tuple" => [
+ "Pleroma.Web.Endpoint",
+ "Pleroma.Web.UserSocket",
+ []
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ %{
+ "tuple" => [
+ ":_",
+ "Phoenix.Endpoint.Cowboy2Handler",
+ %{"tuple" => ["Pleroma.Web.Endpoint", []]}
+ ]
+ }
+ ]
+ ]
+ }
+ ]
+ ]
+ }
+ ]
+ ]
+ }
+ ]
+ }
+ ]
+ })
+
+ assert json_response(conn, 200) == %{
+ "configs" => [
+ %{
+ "group" => ":pleroma",
+ "key" => "Pleroma.Web.Endpoint.NotReal",
+ "value" => [
+ %{
+ "tuple" => [
+ ":http",
+ [
+ %{
+ "tuple" => [
+ ":key2",
+ [
+ %{
+ "tuple" => [
+ ":_",
+ [
+ %{
+ "tuple" => [
+ "/api/v1/streaming",
+ "Pleroma.Web.MastodonAPI.WebsocketHandler",
+ []
+ ]
+ },
+ %{
+ "tuple" => [
+ "/websocket",
+ "Phoenix.Endpoint.CowboyWebSocket",
+ %{
+ "tuple" => [
+ "Phoenix.Transports.WebSocket",
+ %{
+ "tuple" => [
+ "Pleroma.Web.Endpoint",
+ "Pleroma.Web.UserSocket",
+ []
+ ]
+ }
+ ]
+ }
+ ]
+ },
+ %{
+ "tuple" => [
+ ":_",
+ "Phoenix.Endpoint.Cowboy2Handler",
+ %{"tuple" => ["Pleroma.Web.Endpoint", []]}
+ ]
+ }
+ ]
+ ]
+ }
+ ]
+ ]
+ }
+ ]
+ ]
+ }
+ ],
+ "db" => [":http"]
+ }
+ ]
+ }
+ end
+
+ test "settings with nesting map", %{conn: conn} do
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{
+ "group" => ":pleroma",
+ "key" => ":key1",
+ "value" => [
+ %{"tuple" => [":key2", "some_val"]},
+ %{
+ "tuple" => [
+ ":key3",
+ %{
+ ":max_options" => 20,
+ ":max_option_chars" => 200,
+ ":min_expiration" => 0,
+ ":max_expiration" => 31_536_000,
+ "nested" => %{
+ ":max_options" => 20,
+ ":max_option_chars" => 200,
+ ":min_expiration" => 0,
+ ":max_expiration" => 31_536_000
+ }
+ }
+ ]
+ }
+ ]
+ }
+ ]
+ })
+
+ assert json_response(conn, 200) ==
+ %{
+ "configs" => [
+ %{
+ "group" => ":pleroma",
+ "key" => ":key1",
+ "value" => [
+ %{"tuple" => [":key2", "some_val"]},
+ %{
+ "tuple" => [
+ ":key3",
+ %{
+ ":max_expiration" => 31_536_000,
+ ":max_option_chars" => 200,
+ ":max_options" => 20,
+ ":min_expiration" => 0,
+ "nested" => %{
+ ":max_expiration" => 31_536_000,
+ ":max_option_chars" => 200,
+ ":max_options" => 20,
+ ":min_expiration" => 0
+ }
+ }
+ ]
+ }
+ ],
+ "db" => [":key2", ":key3"]
+ }
+ ]
+ }
+ end
+
+ test "value as map", %{conn: conn} do
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{
+ "group" => ":pleroma",
+ "key" => ":key1",
+ "value" => %{"key" => "some_val"}
+ }
+ ]
+ })
+
+ assert json_response(conn, 200) ==
+ %{
+ "configs" => [
+ %{
+ "group" => ":pleroma",
+ "key" => ":key1",
+ "value" => %{"key" => "some_val"},
+ "db" => [":key1"]
+ }
+ ]
+ }
+ end
+
+ test "queues key as atom", %{conn: conn} do
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{
+ "group" => ":oban",
+ "key" => ":queues",
+ "value" => [
+ %{"tuple" => [":federator_incoming", 50]},
+ %{"tuple" => [":federator_outgoing", 50]},
+ %{"tuple" => [":web_push", 50]},
+ %{"tuple" => [":mailer", 10]},
+ %{"tuple" => [":transmogrifier", 20]},
+ %{"tuple" => [":scheduled_activities", 10]},
+ %{"tuple" => [":background", 5]}
+ ]
+ }
+ ]
+ })
+
+ assert json_response(conn, 200) == %{
+ "configs" => [
+ %{
+ "group" => ":oban",
+ "key" => ":queues",
+ "value" => [
+ %{"tuple" => [":federator_incoming", 50]},
+ %{"tuple" => [":federator_outgoing", 50]},
+ %{"tuple" => [":web_push", 50]},
+ %{"tuple" => [":mailer", 10]},
+ %{"tuple" => [":transmogrifier", 20]},
+ %{"tuple" => [":scheduled_activities", 10]},
+ %{"tuple" => [":background", 5]}
+ ],
+ "db" => [
+ ":federator_incoming",
+ ":federator_outgoing",
+ ":web_push",
+ ":mailer",
+ ":transmogrifier",
+ ":scheduled_activities",
+ ":background"
+ ]
+ }
+ ]
+ }
+ end
+
+ test "delete part of settings by atom subkeys", %{conn: conn} do
+ config =
+ insert(:config,
+ key: ":keyaa1",
+ value: :erlang.term_to_binary(subkey1: "val1", subkey2: "val2", subkey3: "val3")
+ )
+
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{
+ group: config.group,
+ key: config.key,
+ subkeys: [":subkey1", ":subkey3"],
+ delete: true
+ }
+ ]
+ })
+
+ assert json_response(conn, 200) == %{
+ "configs" => [
+ %{
+ "group" => ":pleroma",
+ "key" => ":keyaa1",
+ "value" => [%{"tuple" => [":subkey2", "val2"]}],
+ "db" => [":subkey2"]
+ }
+ ]
+ }
+ end
+
+ test "proxy tuple localhost", %{conn: conn} do
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{
+ group: ":pleroma",
+ key: ":http",
+ value: [
+ %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]}
+ ]
+ }
+ ]
+ })
+
+ assert %{
+ "configs" => [
+ %{
+ "group" => ":pleroma",
+ "key" => ":http",
+ "value" => value,
+ "db" => db
+ }
+ ]
+ } = json_response(conn, 200)
+
+ assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "localhost", 1234]}]} in value
+ assert ":proxy_url" in db
+ end
+
+ test "proxy tuple domain", %{conn: conn} do
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{
+ group: ":pleroma",
+ key: ":http",
+ value: [
+ %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]}
+ ]
+ }
+ ]
+ })
+
+ assert %{
+ "configs" => [
+ %{
+ "group" => ":pleroma",
+ "key" => ":http",
+ "value" => value,
+ "db" => db
+ }
+ ]
+ } = json_response(conn, 200)
+
+ assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "domain.com", 1234]}]} in value
+ assert ":proxy_url" in db
+ end
+
+ test "proxy tuple ip", %{conn: conn} do
+ conn =
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{
+ group: ":pleroma",
+ key: ":http",
+ value: [
+ %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]}
+ ]
+ }
+ ]
+ })
+
+ assert %{
+ "configs" => [
+ %{
+ "group" => ":pleroma",
+ "key" => ":http",
+ "value" => value,
+ "db" => db
+ }
+ ]
+ } = json_response(conn, 200)
+
+ assert %{"tuple" => [":proxy_url", %{"tuple" => [":socks5", "127.0.0.1", 1234]}]} in value
+ assert ":proxy_url" in db
+ end
+
+ @tag capture_log: true
+ test "doesn't set keys not in the whitelist", %{conn: conn} do
+ clear_config(:database_config_whitelist, [
+ {:pleroma, :key1},
+ {:pleroma, :key2},
+ {:pleroma, Pleroma.Captcha.NotReal},
+ {:not_real}
+ ])
+
+ post(conn, "/api/pleroma/admin/config", %{
+ configs: [
+ %{group: ":pleroma", key: ":key1", value: "value1"},
+ %{group: ":pleroma", key: ":key2", value: "value2"},
+ %{group: ":pleroma", key: ":key3", value: "value3"},
+ %{group: ":pleroma", key: "Pleroma.Web.Endpoint.NotReal", value: "value4"},
+ %{group: ":pleroma", key: "Pleroma.Captcha.NotReal", value: "value5"},
+ %{group: ":not_real", key: ":anything", value: "value6"}
+ ]
+ })
+
+ assert Application.get_env(:pleroma, :key1) == "value1"
+ assert Application.get_env(:pleroma, :key2) == "value2"
+ assert Application.get_env(:pleroma, :key3) == nil
+ assert Application.get_env(:pleroma, Pleroma.Web.Endpoint.NotReal) == nil
+ assert Application.get_env(:pleroma, Pleroma.Captcha.NotReal) == "value5"
+ assert Application.get_env(:not_real, :anything) == "value6"
+ end
+ end
+
+ describe "GET /api/pleroma/admin/restart" do
+ setup do: clear_config(:configurable_from_database, true)
+
+ test "pleroma restarts", %{conn: conn} do
+ capture_log(fn ->
+ assert conn |> get("/api/pleroma/admin/restart") |> json_response(200) == %{}
+ end) =~ "pleroma restarted"
+
+ refute Restarter.Pleroma.need_reboot?()
+ end
+ end
+
+ test "need_reboot flag", %{conn: conn} do
+ assert conn
+ |> get("/api/pleroma/admin/need_reboot")
+ |> json_response(200) == %{"need_reboot" => false}
+
+ Restarter.Pleroma.need_reboot()
+
+ assert conn
+ |> get("/api/pleroma/admin/need_reboot")
+ |> json_response(200) == %{"need_reboot" => true}
+
+ on_exit(fn -> Restarter.Pleroma.refresh() end)
+ end
+
+ describe "GET /api/pleroma/admin/users/:nickname/statuses" do
+ setup do
+ user = insert(:user)
+
+ date1 = (DateTime.to_unix(DateTime.utc_now()) + 2000) |> DateTime.from_unix!()
+ date2 = (DateTime.to_unix(DateTime.utc_now()) + 1000) |> DateTime.from_unix!()
+ date3 = (DateTime.to_unix(DateTime.utc_now()) + 3000) |> DateTime.from_unix!()
+
+ insert(:note_activity, user: user, published: date1)
+ insert(:note_activity, user: user, published: date2)
+ insert(:note_activity, user: user, published: date3)
+
+ %{user: user}
+ end
+
+ test "renders user's statuses", %{conn: conn, user: user} do
+ conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses")
+
+ assert json_response(conn, 200) |> length() == 3
+ end
+
+ test "renders user's statuses with a limit", %{conn: conn, user: user} do
+ conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?page_size=2")
+
+ assert json_response(conn, 200) |> length() == 2
+ end
+
+ test "doesn't return private statuses by default", %{conn: conn, user: user} do
+ {:ok, _private_status} = CommonAPI.post(user, %{status: "private", visibility: "private"})
+
+ {:ok, _public_status} = CommonAPI.post(user, %{status: "public", visibility: "public"})
+
+ conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses")
+
+ assert json_response(conn, 200) |> length() == 4
+ end
+
+ test "returns private statuses with godmode on", %{conn: conn, user: user} do
+ {:ok, _private_status} = CommonAPI.post(user, %{status: "private", visibility: "private"})
+
+ {:ok, _public_status} = CommonAPI.post(user, %{status: "public", visibility: "public"})
+
+ conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/statuses?godmode=true")
+
+ assert json_response(conn, 200) |> length() == 5
+ end
+
+ test "excludes reblogs by default", %{conn: conn, user: user} do
+ other_user = insert(:user)
+ {:ok, activity} = CommonAPI.post(user, %{status: "."})
+ {:ok, %Activity{}} = CommonAPI.repeat(activity.id, other_user)
+
+ conn_res = get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses")
+ assert json_response(conn_res, 200) |> length() == 0
+
+ conn_res =
+ get(conn, "/api/pleroma/admin/users/#{other_user.nickname}/statuses?with_reblogs=true")
+
+ assert json_response(conn_res, 200) |> length() == 1
+ end
+ end
+
+ describe "GET /api/pleroma/admin/moderation_log" do
+ setup do
+ moderator = insert(:user, is_moderator: true)
+
+ %{moderator: moderator}
+ end
+
+ test "returns the log", %{conn: conn, admin: admin} do
+ Repo.insert(%ModerationLog{
+ data: %{
+ actor: %{
+ "id" => admin.id,
+ "nickname" => admin.nickname,
+ "type" => "user"
+ },
+ action: "relay_follow",
+ target: "https://example.org/relay"
+ },
+ inserted_at: NaiveDateTime.truncate(~N[2017-08-15 15:47:06.597036], :second)
+ })
+
+ Repo.insert(%ModerationLog{
+ data: %{
+ actor: %{
+ "id" => admin.id,
+ "nickname" => admin.nickname,
+ "type" => "user"
+ },
+ action: "relay_unfollow",
+ target: "https://example.org/relay"
+ },
+ inserted_at: NaiveDateTime.truncate(~N[2017-08-16 15:47:06.597036], :second)
+ })
+
+ conn = get(conn, "/api/pleroma/admin/moderation_log")
+
+ response = json_response(conn, 200)
+ [first_entry, second_entry] = response["items"]
+
+ assert response["total"] == 2
+ assert first_entry["data"]["action"] == "relay_unfollow"
+
+ assert first_entry["message"] ==
+ "@#{admin.nickname} unfollowed relay: https://example.org/relay"
+
+ assert second_entry["data"]["action"] == "relay_follow"
+
+ assert second_entry["message"] ==
+ "@#{admin.nickname} followed relay: https://example.org/relay"
+ end
+
+ test "returns the log with pagination", %{conn: conn, admin: admin} do
+ Repo.insert(%ModerationLog{
+ data: %{
+ actor: %{
+ "id" => admin.id,
+ "nickname" => admin.nickname,
+ "type" => "user"
+ },
+ action: "relay_follow",
+ target: "https://example.org/relay"
+ },
+ inserted_at: NaiveDateTime.truncate(~N[2017-08-15 15:47:06.597036], :second)
+ })
+
+ Repo.insert(%ModerationLog{
+ data: %{
+ actor: %{
+ "id" => admin.id,
+ "nickname" => admin.nickname,
+ "type" => "user"
+ },
+ action: "relay_unfollow",
+ target: "https://example.org/relay"
+ },
+ inserted_at: NaiveDateTime.truncate(~N[2017-08-16 15:47:06.597036], :second)
+ })
+
+ conn1 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=1")
+
+ response1 = json_response(conn1, 200)
+ [first_entry] = response1["items"]
+
+ assert response1["total"] == 2
+ assert response1["items"] |> length() == 1
+ assert first_entry["data"]["action"] == "relay_unfollow"
+
+ assert first_entry["message"] ==
+ "@#{admin.nickname} unfollowed relay: https://example.org/relay"
+
+ conn2 = get(conn, "/api/pleroma/admin/moderation_log?page_size=1&page=2")
+
+ response2 = json_response(conn2, 200)
+ [second_entry] = response2["items"]
+
+ assert response2["total"] == 2
+ assert response2["items"] |> length() == 1
+ assert second_entry["data"]["action"] == "relay_follow"
+
+ assert second_entry["message"] ==
+ "@#{admin.nickname} followed relay: https://example.org/relay"
+ end
+
+ test "filters log by date", %{conn: conn, admin: admin} do
+ first_date = "2017-08-15T15:47:06Z"
+ second_date = "2017-08-20T15:47:06Z"
+
+ Repo.insert(%ModerationLog{
+ data: %{
+ actor: %{
+ "id" => admin.id,
+ "nickname" => admin.nickname,
+ "type" => "user"
+ },
+ action: "relay_follow",
+ target: "https://example.org/relay"
+ },
+ inserted_at: NaiveDateTime.from_iso8601!(first_date)
+ })
+
+ Repo.insert(%ModerationLog{
+ data: %{
+ actor: %{
+ "id" => admin.id,
+ "nickname" => admin.nickname,
+ "type" => "user"
+ },
+ action: "relay_unfollow",
+ target: "https://example.org/relay"
+ },
+ inserted_at: NaiveDateTime.from_iso8601!(second_date)
+ })
+
+ conn1 =
+ get(
+ conn,
+ "/api/pleroma/admin/moderation_log?start_date=#{second_date}"
+ )
+
+ response1 = json_response(conn1, 200)
+ [first_entry] = response1["items"]
+
+ assert response1["total"] == 1
+ assert first_entry["data"]["action"] == "relay_unfollow"
+
+ assert first_entry["message"] ==
+ "@#{admin.nickname} unfollowed relay: https://example.org/relay"
+ end
+
+ test "returns log filtered by user", %{conn: conn, admin: admin, moderator: moderator} do
+ Repo.insert(%ModerationLog{
+ data: %{
+ actor: %{
+ "id" => admin.id,
+ "nickname" => admin.nickname,
+ "type" => "user"
+ },
+ action: "relay_follow",
+ target: "https://example.org/relay"
+ }
+ })
+
+ Repo.insert(%ModerationLog{
+ data: %{
+ actor: %{
+ "id" => moderator.id,
+ "nickname" => moderator.nickname,
+ "type" => "user"
+ },
+ action: "relay_unfollow",
+ target: "https://example.org/relay"
+ }
+ })
+
+ conn1 = get(conn, "/api/pleroma/admin/moderation_log?user_id=#{moderator.id}")
+
+ response1 = json_response(conn1, 200)
+ [first_entry] = response1["items"]
+
+ assert response1["total"] == 1
+ assert get_in(first_entry, ["data", "actor", "id"]) == moderator.id
+ end
+
+ test "returns log filtered by search", %{conn: conn, moderator: moderator} do
+ ModerationLog.insert_log(%{
+ actor: moderator,
+ action: "relay_follow",
+ target: "https://example.org/relay"
+ })
+
+ ModerationLog.insert_log(%{
+ actor: moderator,
+ action: "relay_unfollow",
+ target: "https://example.org/relay"
+ })
+
+ conn1 = get(conn, "/api/pleroma/admin/moderation_log?search=unfo")
+
+ response1 = json_response(conn1, 200)
+ [first_entry] = response1["items"]
+
+ assert response1["total"] == 1
+
+ assert get_in(first_entry, ["data", "message"]) ==
+ "@#{moderator.nickname} unfollowed relay: https://example.org/relay"
+ end
+ end
+
+ describe "GET /users/:nickname/credentials" do
+ test "gets the user credentials", %{conn: conn} do
+ user = insert(:user)
+ conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials")
+
+ response = assert json_response(conn, 200)
+ assert response["email"] == user.email
+ end
+
+ test "returns 403 if requested by a non-admin" do
+ user = insert(:user)
+
+ conn =
+ build_conn()
+ |> assign(:user, user)
+ |> get("/api/pleroma/admin/users/#{user.nickname}/credentials")
+
+ assert json_response(conn, :forbidden)
+ end
+ end
+
+ describe "PATCH /users/:nickname/credentials" do
+ test "changes password and email", %{conn: conn, admin: admin} do
+ user = insert(:user)
+ assert user.password_reset_pending == false
+
+ conn =
+ patch(conn, "/api/pleroma/admin/users/#{user.nickname}/credentials", %{
+ "password" => "new_password",
+ "email" => "new_email@example.com",
+ "name" => "new_name"
+ })
+
+ assert json_response(conn, 200) == %{"status" => "success"}
+
+ ObanHelpers.perform_all()
+
+ updated_user = User.get_by_id(user.id)
+
+ assert updated_user.email == "new_email@example.com"
+ assert updated_user.name == "new_name"
+ assert updated_user.password_hash != user.password_hash
+ assert updated_user.password_reset_pending == true
+
+ [log_entry2, log_entry1] = ModerationLog |> Repo.all() |> Enum.sort()
+
+ assert ModerationLog.get_log_entry_message(log_entry1) ==
+ "@#{admin.nickname} updated users: @#{user.nickname}"
+
+ assert ModerationLog.get_log_entry_message(log_entry2) ==
+ "@#{admin.nickname} forced password reset for users: @#{user.nickname}"
+ end
+
+ test "returns 403 if requested by a non-admin" do
+ user = insert(:user)
+
+ conn =
+ build_conn()
+ |> assign(:user, user)
+ |> patch("/api/pleroma/admin/users/#{user.nickname}/credentials", %{
+ "password" => "new_password",
+ "email" => "new_email@example.com",
+ "name" => "new_name"
+ })
+
+ assert json_response(conn, :forbidden)
+ end
+ end
+
+ describe "PATCH /users/:nickname/force_password_reset" do
+ test "sets password_reset_pending to true", %{conn: conn} do
+ user = insert(:user)
+ assert user.password_reset_pending == false
+
+ conn =
+ patch(conn, "/api/pleroma/admin/users/force_password_reset", %{nicknames: [user.nickname]})
+
+ assert json_response(conn, 204) == ""
+
+ ObanHelpers.perform_all()
+
+ assert User.get_by_id(user.id).password_reset_pending == true
+ end
+ end
+
+ describe "relays" do
+ test "POST /relay", %{conn: conn, admin: admin} do
+ conn =
+ post(conn, "/api/pleroma/admin/relay", %{
+ relay_url: "http://mastodon.example.org/users/admin"
+ })
+
+ assert json_response(conn, 200) == "http://mastodon.example.org/users/admin"
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin"
+ end
+
+ test "GET /relay", %{conn: conn} do
+ relay_user = Pleroma.Web.ActivityPub.Relay.get_actor()
+
+ ["http://mastodon.example.org/users/admin", "https://mstdn.io/users/mayuutann"]
+ |> Enum.each(fn ap_id ->
+ {:ok, user} = User.get_or_fetch_by_ap_id(ap_id)
+ User.follow(relay_user, user)
+ end)
+
+ conn = get(conn, "/api/pleroma/admin/relay")
+
+ assert json_response(conn, 200)["relays"] -- ["mastodon.example.org", "mstdn.io"] == []
+ end
+
+ test "DELETE /relay", %{conn: conn, admin: admin} do
+ post(conn, "/api/pleroma/admin/relay", %{
+ relay_url: "http://mastodon.example.org/users/admin"
+ })
+
+ conn =
+ delete(conn, "/api/pleroma/admin/relay", %{
+ relay_url: "http://mastodon.example.org/users/admin"
+ })
+
+ assert json_response(conn, 200) == "http://mastodon.example.org/users/admin"
+
+ [log_entry_one, log_entry_two] = Repo.all(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry_one) ==
+ "@#{admin.nickname} followed relay: http://mastodon.example.org/users/admin"
+
+ assert ModerationLog.get_log_entry_message(log_entry_two) ==
+ "@#{admin.nickname} unfollowed relay: http://mastodon.example.org/users/admin"
+ end
+ end
+
+ describe "instances" do
+ test "GET /instances/:instance/statuses", %{conn: conn} do
+ user = insert(:user, local: false, nickname: "archaeme@archae.me")
+ user2 = insert(:user, local: false, nickname: "test@test.com")
+ insert_pair(:note_activity, user: user)
+ activity = insert(:note_activity, user: user2)
+
+ ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses")
+
+ response = json_response(ret_conn, 200)
+
+ assert length(response) == 2
+
+ ret_conn = get(conn, "/api/pleroma/admin/instances/test.com/statuses")
+
+ response = json_response(ret_conn, 200)
+
+ assert length(response) == 1
+
+ ret_conn = get(conn, "/api/pleroma/admin/instances/nonexistent.com/statuses")
+
+ response = json_response(ret_conn, 200)
+
+ assert Enum.empty?(response)
+
+ CommonAPI.repeat(activity.id, user)
+
+ ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses")
+ response = json_response(ret_conn, 200)
+ assert length(response) == 2
+
+ ret_conn = get(conn, "/api/pleroma/admin/instances/archae.me/statuses?with_reblogs=true")
+ response = json_response(ret_conn, 200)
+ assert length(response) == 3
+ end
+ end
+
+ describe "PATCH /confirm_email" do
+ test "it confirms emails of two users", %{conn: conn, admin: admin} do
+ [first_user, second_user] = insert_pair(:user, confirmation_pending: true)
+
+ assert first_user.confirmation_pending == true
+ assert second_user.confirmation_pending == true
+
+ ret_conn =
+ patch(conn, "/api/pleroma/admin/users/confirm_email", %{
+ nicknames: [
+ first_user.nickname,
+ second_user.nickname
+ ]
+ })
+
+ assert ret_conn.status == 200
+
+ assert first_user.confirmation_pending == true
+ assert second_user.confirmation_pending == true
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} confirmed email for users: @#{first_user.nickname}, @#{
+ second_user.nickname
+ }"
+ end
+ end
+
+ describe "PATCH /resend_confirmation_email" do
+ test "it resend emails for two users", %{conn: conn, admin: admin} do
+ [first_user, second_user] = insert_pair(:user, confirmation_pending: true)
+
+ ret_conn =
+ patch(conn, "/api/pleroma/admin/users/resend_confirmation_email", %{
+ nicknames: [
+ first_user.nickname,
+ second_user.nickname
+ ]
+ })
+
+ assert ret_conn.status == 200
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} re-sent confirmation email for users: @#{first_user.nickname}, @#{
+ second_user.nickname
+ }"
+ end
+ end
+
+ describe "POST /reports/:id/notes" do
+ setup %{conn: conn, admin: admin} do
+ [reporter, target_user] = insert_pair(:user)
+ activity = insert(:note_activity, user: target_user)
+
+ {:ok, %{id: report_id}} =
+ CommonAPI.report(reporter, %{
+ account_id: target_user.id,
+ comment: "I feel offended",
+ status_ids: [activity.id]
+ })
+
+ post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{
+ content: "this is disgusting!"
+ })
+
+ post(conn, "/api/pleroma/admin/reports/#{report_id}/notes", %{
+ content: "this is disgusting2!"
+ })
+
+ %{
+ admin_id: admin.id,
+ report_id: report_id
+ }
+ end
+
+ test "it creates report note", %{admin_id: admin_id, report_id: report_id} do
+ [note, _] = Repo.all(ReportNote)
+
+ assert %{
+ activity_id: ^report_id,
+ content: "this is disgusting!",
+ user_id: ^admin_id
+ } = note
+ end
+
+ test "it returns reports with notes", %{conn: conn, admin: admin} do
+ conn = get(conn, "/api/pleroma/admin/reports")
+
+ response = json_response(conn, 200)
+ notes = hd(response["reports"])["notes"]
+ [note, _] = notes
+
+ assert note["user"]["nickname"] == admin.nickname
+ assert note["content"] == "this is disgusting!"
+ assert note["created_at"]
+ assert response["total"] == 1
+ end
+
+ test "it deletes the note", %{conn: conn, report_id: report_id} do
+ assert ReportNote |> Repo.all() |> length() == 2
+
+ [note, _] = Repo.all(ReportNote)
+
+ delete(conn, "/api/pleroma/admin/reports/#{report_id}/notes/#{note.id}")
+
+ assert ReportNote |> Repo.all() |> length() == 1
+ end
+ end
+
+ describe "GET /api/pleroma/admin/config/descriptions" do
+ test "structure", %{conn: conn} do
+ admin = insert(:user, is_admin: true)
+
+ conn =
+ assign(conn, :user, admin)
+ |> get("/api/pleroma/admin/config/descriptions")
+
+ assert [child | _others] = json_response(conn, 200)
+
+ assert child["children"]
+ assert child["key"]
+ assert String.starts_with?(child["group"], ":")
+ assert child["description"]
+ end
+
+ test "filters by database configuration whitelist", %{conn: conn} do
+ clear_config(:database_config_whitelist, [
+ {:pleroma, :instance},
+ {:pleroma, :activitypub},
+ {:pleroma, Pleroma.Upload},
+ {:esshd}
+ ])
+
+ admin = insert(:user, is_admin: true)
+
+ conn =
+ assign(conn, :user, admin)
+ |> get("/api/pleroma/admin/config/descriptions")
+
+ children = json_response(conn, 200)
+
+ assert length(children) == 4
+
+ assert Enum.count(children, fn c -> c["group"] == ":pleroma" end) == 3
+
+ instance = Enum.find(children, fn c -> c["key"] == ":instance" end)
+ assert instance["children"]
+
+ activitypub = Enum.find(children, fn c -> c["key"] == ":activitypub" end)
+ assert activitypub["children"]
+
+ web_endpoint = Enum.find(children, fn c -> c["key"] == "Pleroma.Upload" end)
+ assert web_endpoint["children"]
+
+ esshd = Enum.find(children, fn c -> c["group"] == ":esshd" end)
+ assert esshd["children"]
+ end
+ end
+
+ describe "/api/pleroma/admin/stats" do
+ test "status visibility count", %{conn: conn} do
+ admin = insert(:user, is_admin: true)
+ user = insert(:user)
+ CommonAPI.post(user, %{visibility: "public", status: "hey"})
+ CommonAPI.post(user, %{visibility: "unlisted", status: "hey"})
+ CommonAPI.post(user, %{visibility: "unlisted", status: "hey"})
+
+ response =
+ conn
+ |> assign(:user, admin)
+ |> get("/api/pleroma/admin/stats")
+ |> json_response(200)
+
+ assert %{"direct" => 0, "private" => 0, "public" => 1, "unlisted" => 2} =
+ response["status_visibility"]
+ end
+ end
+
+ describe "POST /api/pleroma/admin/oauth_app" do
+ test "errors", %{conn: conn} do
+ response = conn |> post("/api/pleroma/admin/oauth_app", %{}) |> json_response(200)
+
+ assert response == %{"name" => "can't be blank", "redirect_uris" => "can't be blank"}
+ end
+
+ test "success", %{conn: conn} do
+ base_url = Web.base_url()
+ app_name = "Trusted app"
+
+ response =
+ conn
+ |> post("/api/pleroma/admin/oauth_app", %{
+ name: app_name,
+ redirect_uris: base_url
+ })
+ |> json_response(200)
+
+ assert %{
+ "client_id" => _,
+ "client_secret" => _,
+ "name" => ^app_name,
+ "redirect_uri" => ^base_url,
+ "trusted" => false
+ } = response
+ end
+
+ test "with trusted", %{conn: conn} do
+ base_url = Web.base_url()
+ app_name = "Trusted app"
+
+ response =
+ conn
+ |> post("/api/pleroma/admin/oauth_app", %{
+ name: app_name,
+ redirect_uris: base_url,
+ trusted: true
+ })
+ |> json_response(200)
+
+ assert %{
+ "client_id" => _,
+ "client_secret" => _,
+ "name" => ^app_name,
+ "redirect_uri" => ^base_url,
+ "trusted" => true
+ } = response
+ end
+ end
+
+ describe "GET /api/pleroma/admin/oauth_app" do
+ setup do
+ app = insert(:oauth_app)
+ {:ok, app: app}
+ end
+
+ test "list", %{conn: conn} do
+ response =
+ conn
+ |> get("/api/pleroma/admin/oauth_app")
+ |> json_response(200)
+
+ assert %{"apps" => apps, "count" => count, "page_size" => _} = response
+
+ assert length(apps) == count
+ end
+
+ test "with page size", %{conn: conn} do
+ insert(:oauth_app)
+ page_size = 1
+
+ response =
+ conn
+ |> get("/api/pleroma/admin/oauth_app", %{page_size: to_string(page_size)})
+ |> json_response(200)
+
+ assert %{"apps" => apps, "count" => _, "page_size" => ^page_size} = response
+
+ assert length(apps) == page_size
+ end
+
+ test "search by client name", %{conn: conn, app: app} do
+ response =
+ conn
+ |> get("/api/pleroma/admin/oauth_app", %{name: app.client_name})
+ |> json_response(200)
+
+ assert %{"apps" => [returned], "count" => _, "page_size" => _} = response
+
+ assert returned["client_id"] == app.client_id
+ assert returned["name"] == app.client_name
+ end
+
+ test "search by client id", %{conn: conn, app: app} do
+ response =
+ conn
+ |> get("/api/pleroma/admin/oauth_app", %{client_id: app.client_id})
+ |> json_response(200)
+
+ assert %{"apps" => [returned], "count" => _, "page_size" => _} = response
+
+ assert returned["client_id"] == app.client_id
+ assert returned["name"] == app.client_name
+ end
+
+ test "only trusted", %{conn: conn} do
+ app = insert(:oauth_app, trusted: true)
+
+ response =
+ conn
+ |> get("/api/pleroma/admin/oauth_app", %{trusted: true})
+ |> json_response(200)
+
+ assert %{"apps" => [returned], "count" => _, "page_size" => _} = response
+
+ assert returned["client_id"] == app.client_id
+ assert returned["name"] == app.client_name
+ end
+ end
+
+ describe "DELETE /api/pleroma/admin/oauth_app/:id" do
+ test "with id", %{conn: conn} do
+ app = insert(:oauth_app)
+
+ response =
+ conn
+ |> delete("/api/pleroma/admin/oauth_app/" <> to_string(app.id))
+ |> json_response(:no_content)
+
+ assert response == ""
+ end
+
+ test "with non existance id", %{conn: conn} do
+ response =
+ conn
+ |> delete("/api/pleroma/admin/oauth_app/0")
+ |> json_response(:bad_request)
+
+ assert response == ""
+ end
+ end
+
+ describe "PATCH /api/pleroma/admin/oauth_app/:id" do
+ test "with id", %{conn: conn} do
+ app = insert(:oauth_app)
+
+ name = "another name"
+ url = "https://example.com"
+ scopes = ["admin"]
+ id = app.id
+ website = "http://website.com"
+
+ response =
+ conn
+ |> patch("/api/pleroma/admin/oauth_app/" <> to_string(app.id), %{
+ name: name,
+ trusted: true,
+ redirect_uris: url,
+ scopes: scopes,
+ website: website
+ })
+ |> json_response(200)
+
+ assert %{
+ "client_id" => _,
+ "client_secret" => _,
+ "id" => ^id,
+ "name" => ^name,
+ "redirect_uri" => ^url,
+ "trusted" => true,
+ "website" => ^website
+ } = response
+ end
+
+ test "without id", %{conn: conn} do
+ response =
+ conn
+ |> patch("/api/pleroma/admin/oauth_app/0")
+ |> json_response(:bad_request)
+
+ assert response == ""
+ end
+ end
+end
+
+# Needed for testing
+defmodule Pleroma.Web.Endpoint.NotReal do
+end
+
+defmodule Pleroma.Captcha.NotReal do
+end
diff --git a/test/web/admin_api/controllers/status_controller_test.exs b/test/web/admin_api/controllers/status_controller_test.exs
@@ -0,0 +1,194 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.StatusControllerTest do
+ use Pleroma.Web.ConnCase
+
+ import Pleroma.Factory
+
+ alias Pleroma.Activity
+ alias Pleroma.Config
+ alias Pleroma.ModerationLog
+ alias Pleroma.Repo
+ alias Pleroma.User
+ alias Pleroma.Web.CommonAPI
+
+ setup do
+ admin = insert(:user, is_admin: true)
+ token = insert(:oauth_admin_token, user: admin)
+
+ conn =
+ build_conn()
+ |> assign(:user, admin)
+ |> assign(:token, token)
+
+ {:ok, %{admin: admin, token: token, conn: conn}}
+ end
+
+ describe "GET /api/pleroma/admin/statuses/:id" do
+ test "not found", %{conn: conn} do
+ assert conn
+ |> get("/api/pleroma/admin/statuses/not_found")
+ |> json_response_and_validate_schema(:not_found)
+ end
+
+ test "shows activity", %{conn: conn} do
+ activity = insert(:note_activity)
+
+ response =
+ conn
+ |> get("/api/pleroma/admin/statuses/#{activity.id}")
+ |> json_response_and_validate_schema(200)
+
+ assert response["id"] == activity.id
+ end
+ end
+
+ describe "PUT /api/pleroma/admin/statuses/:id" do
+ setup do
+ activity = insert(:note_activity)
+
+ %{id: activity.id}
+ end
+
+ test "toggle sensitive flag", %{conn: conn, id: id, admin: admin} do
+ response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "true"})
+ |> json_response_and_validate_schema(:ok)
+
+ assert response["sensitive"]
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} updated status ##{id}, set sensitive: 'true'"
+
+ response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> put("/api/pleroma/admin/statuses/#{id}", %{"sensitive" => "false"})
+ |> json_response_and_validate_schema(:ok)
+
+ refute response["sensitive"]
+ end
+
+ test "change visibility flag", %{conn: conn, id: id, admin: admin} do
+ response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "public"})
+ |> json_response_and_validate_schema(:ok)
+
+ assert response["visibility"] == "public"
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} updated status ##{id}, set visibility: 'public'"
+
+ response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "private"})
+ |> json_response_and_validate_schema(:ok)
+
+ assert response["visibility"] == "private"
+
+ response =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "unlisted"})
+ |> json_response_and_validate_schema(:ok)
+
+ assert response["visibility"] == "unlisted"
+ end
+
+ test "returns 400 when visibility is unknown", %{conn: conn, id: id} do
+ conn =
+ conn
+ |> put_req_header("content-type", "application/json")
+ |> put("/api/pleroma/admin/statuses/#{id}", %{visibility: "test"})
+
+ assert %{"error" => "test - Invalid value for enum."} =
+ json_response_and_validate_schema(conn, :bad_request)
+ end
+ end
+
+ describe "DELETE /api/pleroma/admin/statuses/:id" do
+ setup do
+ activity = insert(:note_activity)
+
+ %{id: activity.id}
+ end
+
+ test "deletes status", %{conn: conn, id: id, admin: admin} do
+ conn
+ |> delete("/api/pleroma/admin/statuses/#{id}")
+ |> json_response_and_validate_schema(:ok)
+
+ refute Activity.get_by_id(id)
+
+ log_entry = Repo.one(ModerationLog)
+
+ assert ModerationLog.get_log_entry_message(log_entry) ==
+ "@#{admin.nickname} deleted status ##{id}"
+ end
+
+ test "returns 404 when the status does not exist", %{conn: conn} do
+ conn = delete(conn, "/api/pleroma/admin/statuses/test")
+
+ assert json_response_and_validate_schema(conn, :not_found) == %{"error" => "Not found"}
+ end
+ end
+
+ describe "GET /api/pleroma/admin/statuses" do
+ test "returns all public and unlisted statuses", %{conn: conn, admin: admin} do
+ blocked = insert(:user)
+ user = insert(:user)
+ User.block(admin, blocked)
+
+ {:ok, _} = CommonAPI.post(user, %{status: "@#{admin.nickname}", visibility: "direct"})
+
+ {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "unlisted"})
+ {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "private"})
+ {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "public"})
+ {:ok, _} = CommonAPI.post(blocked, %{status: ".", visibility: "public"})
+
+ response =
+ conn
+ |> get("/api/pleroma/admin/statuses")
+ |> json_response_and_validate_schema(200)
+
+ refute "private" in Enum.map(response, & &1["visibility"])
+ assert length(response) == 3
+ end
+
+ test "returns only local statuses with local_only on", %{conn: conn} do
+ user = insert(:user)
+ remote_user = insert(:user, local: false, nickname: "archaeme@archae.me")
+ insert(:note_activity, user: user, local: true)
+ insert(:note_activity, user: remote_user, local: false)
+
+ response =
+ conn
+ |> get("/api/pleroma/admin/statuses?local_only=true")
+ |> json_response_and_validate_schema(200)
+
+ assert length(response) == 1
+ end
+
+ test "returns private and direct statuses with godmode on", %{conn: conn, admin: admin} do
+ user = insert(:user)
+
+ {:ok, _} = CommonAPI.post(user, %{status: "@#{admin.nickname}", visibility: "direct"})
+
+ {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "private"})
+ {:ok, _} = CommonAPI.post(user, %{status: ".", visibility: "public"})
+ conn = get(conn, "/api/pleroma/admin/statuses?godmode=true")
+ assert json_response_and_validate_schema(conn, 200) |> length() == 3
+ end
+ end
+end