Page MenuHomePhorge

No OneTemporary

Authored By
Unknown
Size
68 KB
Referenced Files
None
Subscribers
None
diff --git a/changelog.d/admin-delete-unknown-nickname.fix b/changelog.d/admin-delete-unknown-nickname.fix
new file mode 100644
index 000000000..3c13e6389
--- /dev/null
+++ b/changelog.d/admin-delete-unknown-nickname.fix
@@ -0,0 +1 @@
+Fix 500 when deleting users by a nickname that no longer resolves
diff --git a/lib/pleroma/web/admin_api/controllers/user_controller.ex b/lib/pleroma/web/admin_api/controllers/user_controller.ex
index ab8bf73ee..4c8e8fee0 100644
--- a/lib/pleroma/web/admin_api/controllers/user_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/user_controller.ex
@@ -1,428 +1,437 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.UserController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper,
only: [fetch_integer_param: 3]
alias Ecto.Multi
alias Pleroma.ModerationLog
alias Pleroma.PasswordResetToken
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.AdminAPI
alias Pleroma.Web.AdminAPI.Search
alias Pleroma.Web.Endpoint
alias Pleroma.Web.Plugs.OAuthScopesPlug
alias Pleroma.Web.Router
@users_page_size 50
plug(Pleroma.Web.ApiSpec.CastAndValidate, replace_params: false)
plug(
OAuthScopesPlug,
%{scopes: ["admin:read:accounts"]}
when action in [:index, :show]
)
plug(
OAuthScopesPlug,
%{scopes: ["admin:write:accounts"]}
when action in [
:delete,
:create,
:toggle_activation,
:activate,
:deactivate,
:approve,
:suggest,
:unsuggest
]
)
plug(
OAuthScopesPlug,
%{scopes: ["admin:write:follows"]}
when action in [:follow, :unfollow]
)
action_fallback(AdminAPI.FallbackController)
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.UserOperation
def delete(%{private: %{open_api_spex: %{params: %{nickname: nickname}}}} = conn, _) do
conn
|> do_deletes([nickname])
end
def delete(
%{
private: %{open_api_spex: %{body_params: %{nicknames: nicknames}}}
} = conn,
_
) do
conn
|> do_deletes(nicknames)
end
defp do_deletes(%{assigns: %{user: admin}} = conn, nicknames) when is_list(nicknames) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
- Enum.each(users, fn user ->
- {:ok, delete_data, _} = Builder.delete(admin, user.ap_id)
- Pipeline.common_pipeline(delete_data, local: true)
- end)
+ # A nickname that no longer resolves (the account was already deleted, or the
+ # admin frontend sent a stale entry) maps to nil here. `Builder.delete/2` has
+ # no nil clause, so without this the whole request dies with
+ # UndefinedFunctionError (`nil.ap_id/0`) and returns 500. Fail closed with a
+ # 404 instead of deleting an arbitrary subset of the requested users.
+ if Enum.all?(users, &is_struct(&1, User)) do
+ Enum.each(users, 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"
- })
+ ModerationLog.insert_log(%{
+ actor: admin,
+ subject: users,
+ action: "delete"
+ })
- json(conn, nicknames)
+ json(conn, nicknames)
+ else
+ {:error, :not_found}
+ end
end
def follow(
%{
assigns: %{user: admin},
private: %{
open_api_spex: %{
body_params: %{
follower: follower_nick,
followed: followed_nick
}
}
}
} = conn,
_
) 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
json(conn, "ok")
end
def unfollow(
%{
assigns: %{user: admin},
private: %{
open_api_spex: %{
body_params: %{
follower: follower_nick,
followed: followed_nick
}
}
}
} = conn,
_
) 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
json(conn, "ok")
end
def create(
%{
assigns: %{user: admin},
private: %{open_api_spex: %{body_params: %{users: users}}}
} = conn,
_
) do
conn =
if Enum.any?(users, fn user -> not Map.has_key?(user, :password) end) do
put_private(conn, :skip_idempotency_cache, true)
else
conn
end
multi =
users
|> Enum.map(fn %{nickname: nickname, email: email} = attrs ->
{password, passwordless?} =
case Map.fetch(attrs, :password) do
{:ok, password} ->
{password, false}
:error ->
{:crypto.strong_rand_bytes(16) |> Base.encode64(), true}
end
user_data = %{
nickname: nickname,
name: nickname,
email: email,
password: password,
password_confirmation: password,
bio: "."
}
{User.register_changeset(%User{}, user_data, confirmed: true), passwordless?}
end)
|> Enum.reduce(Multi.new(), fn {changeset, passwordless?}, multi ->
user_operation = {:user, Ecto.UUID.generate()}
multi = Multi.insert(multi, user_operation, changeset)
if passwordless? do
Multi.run(
multi,
{:password_reset_token, Ecto.UUID.generate()},
fn _repo, changes ->
changes
|> Map.fetch!(user_operation)
|> PasswordResetToken.create_token()
end
)
else
multi
end
end)
case Pleroma.Repo.transaction(multi) do
{:ok, results} ->
{users, password_reset_tokens} =
results
|> Map.values()
|> Enum.split_with(&match?(%User{}, &1))
users =
users
|> Enum.map(fn user ->
{:ok, user} = User.post_register_action(user)
user
end)
password_reset_links =
Map.new(password_reset_tokens, fn token ->
{token.user_id, Router.Helpers.reset_password_url(Endpoint, :reset, token.token)}
end)
ModerationLog.insert_log(%{
actor: admin,
subjects: users,
action: "create"
})
render(conn, "created_many.json",
users: users,
password_reset_links: password_reset_links
)
{:error, {:user, _} = id, %Ecto.Changeset{} = changeset, _} ->
changesets =
Enum.flat_map(multi.operations, fn
{^id, {:changeset, _current_changeset, _}} ->
[changeset]
{{:user, _}, {:changeset, current_changeset, _}} ->
[current_changeset]
_ ->
[]
end)
conn
|> put_status(:conflict)
|> render("create_errors.json", changesets: changesets)
{:error, {:password_reset_token, _}, _reason, _} ->
Pleroma.Web.AdminAPI.FallbackController.call(
conn,
{:password_reset_token_error, :creation_failed}
)
end
end
def show(
%{
assigns: %{user: admin},
private: %{open_api_spex: %{params: %{nickname: nickname}}}
} = conn,
_
) do
with %User{} = user <- User.get_cached_by_nickname_or_id(nickname, for: admin) do
render(conn, "show.json", %{user: user})
else
_ -> {:error, :not_found}
end
end
def toggle_activation(
%{assigns: %{user: admin}, private: %{open_api_spex: %{params: %{nickname: nickname}}}} =
conn,
_
) do
user = User.get_cached_by_nickname(nickname)
{:ok, updated_user} = User.set_activation(user, !user.is_active)
action = if !user.is_active, do: "activate", else: "deactivate"
ModerationLog.insert_log(%{
actor: admin,
subject: [user],
action: action
})
render(conn, "show.json", user: updated_user)
end
def activate(
%{
assigns: %{user: admin},
private: %{open_api_spex: %{body_params: %{nicknames: nicknames}}}
} = conn,
_
) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
{:ok, updated_users} = User.set_activation(users, true)
ModerationLog.insert_log(%{
actor: admin,
subject: users,
action: "activate"
})
render(conn, "index.json", users: updated_users)
end
def deactivate(
%{
assigns: %{user: admin},
private: %{open_api_spex: %{body_params: %{nicknames: nicknames}}}
} = conn,
_
) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
{:ok, updated_users} = User.set_activation(users, false)
ModerationLog.insert_log(%{
actor: admin,
subject: users,
action: "deactivate"
})
render(conn, "index.json", users: updated_users)
end
def approve(
%{
assigns: %{user: admin},
private: %{open_api_spex: %{body_params: %{nicknames: nicknames}}}
} = conn,
_
) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
{:ok, updated_users} = User.approve(users)
ModerationLog.insert_log(%{
actor: admin,
subject: users,
action: "approve"
})
render(conn, "index.json", users: updated_users)
end
def suggest(
%{
assigns: %{user: admin},
private: %{open_api_spex: %{body_params: %{nicknames: nicknames}}}
} = conn,
_
) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
{:ok, updated_users} = User.set_suggestion(users, true)
ModerationLog.insert_log(%{
actor: admin,
subject: users,
action: "add_suggestion"
})
render(conn, "index.json", users: updated_users)
end
def unsuggest(
%{
assigns: %{user: admin},
private: %{open_api_spex: %{body_params: %{nicknames: nicknames}}}
} = conn,
_
) do
users = Enum.map(nicknames, &User.get_cached_by_nickname/1)
{:ok, updated_users} = User.set_suggestion(users, false)
ModerationLog.insert_log(%{
actor: admin,
subject: users,
action: "remove_suggestion"
})
render(conn, "index.json", users: updated_users)
end
def index(%{private: %{open_api_spex: %{params: params}}} = conn, _) 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],
actor_types: params[:actor_types]
}
|> Map.merge(filters)
with {:ok, users, count} <- Search.user(search_params) do
render(conn, "index.json", users: users, count: count, page_size: page_size)
end
end
@filters ~w(local external active deactivated need_approval unconfirmed 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))
|> Map.new(&{String.to_existing_atom(&1), true})
end
defp page_params(params) do
{
fetch_integer_param(params, :page, 1),
fetch_integer_param(params, :page_size, @users_page_size)
}
end
end
diff --git a/lib/pleroma/web/api_spec/operations/admin/user_operation.ex b/lib/pleroma/web/api_spec/operations/admin/user_operation.ex
index 7c4725f56..d798b90cb 100644
--- a/lib/pleroma/web/api_spec/operations/admin/user_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/admin/user_operation.ex
@@ -1,461 +1,462 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ApiSpec.Admin.UserOperation do
alias OpenApiSpex.Operation
alias OpenApiSpex.Schema
alias Pleroma.Web.ApiSpec.Schemas.ActorType
alias Pleroma.Web.ApiSpec.Schemas.ApiError
import Pleroma.Web.ApiSpec.Helpers
def open_api_operation(action) do
operation = String.to_existing_atom("#{action}_operation")
apply(__MODULE__, operation, [])
end
def index_operation do
%Operation{
tags: ["User administration"],
summary: "List users",
operationId: "AdminAPI.UserController.index",
security: [%{"oAuth" => ["admin:read:accounts"]}],
parameters: [
Operation.parameter(:filters, :query, :string, "Comma separated list of filters"),
Operation.parameter(:query, :query, :string, "Search users query"),
Operation.parameter(:name, :query, :string, "Search by display name"),
Operation.parameter(:email, :query, :string, "Search by email"),
Operation.parameter(:page, :query, :integer, "Page Number"),
Operation.parameter(:page_size, :query, :integer, "Number of users to return per page"),
Operation.parameter(
:actor_types,
:query,
%Schema{type: :array, items: ActorType},
"Filter by actor type"
),
Operation.parameter(
:tags,
:query,
%Schema{type: :array, items: %Schema{type: :string}},
"Filter by tags"
)
| admin_api_params()
],
responses: %{
200 =>
Operation.response(
"Response",
"application/json",
%Schema{
type: :object,
properties: %{
users: %Schema{type: :array, items: user()},
count: %Schema{type: :integer},
page_size: %Schema{type: :integer}
}
}
),
403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
def create_operation do
%Operation{
tags: ["User administration"],
summary: "Create a single or multiple users",
operationId: "AdminAPI.UserController.create",
security: [%{"oAuth" => ["admin:write:accounts"]}],
parameters: admin_api_params(),
requestBody:
request_body(
"Parameters",
%Schema{
description: "POST body for creating users",
type: :object,
properties: %{
users: %Schema{
type: :array,
items: %Schema{
type: :object,
properties: %{
nickname: %Schema{type: :string},
email: %Schema{type: :string},
password: %Schema{
type: :string,
description:
"Optional. When omitted, a `password_reset_link` is returned, " <>
"allowing the user to set their own password. When provided, " <>
"it must not be empty."
}
},
required: [:nickname, :email]
}
}
}
}
),
responses: %{
200 =>
Operation.response("Response", "application/json", %Schema{
type: :array,
items: %Schema{
type: :object,
properties: %{
code: %Schema{type: :integer},
type: %Schema{type: :string},
data: %Schema{
type: :object,
properties: %{
email: %Schema{type: :string, format: :email},
nickname: %Schema{type: :string},
password_reset_link: %Schema{type: :string}
}
}
}
}
}),
403 => Operation.response("Forbidden", "application/json", ApiError),
409 =>
Operation.response("Conflict", "application/json", %Schema{
type: :array,
items: %Schema{
type: :object,
properties: %{
code: %Schema{type: :integer},
error: %Schema{type: :string},
type: %Schema{type: :string},
data: %Schema{
type: :object,
properties: %{
email: %Schema{type: :string, format: :email},
nickname: %Schema{type: :string}
}
}
}
}
})
}
}
end
def show_operation do
%Operation{
tags: ["User administration"],
summary: "Show user",
operationId: "AdminAPI.UserController.show",
security: [%{"oAuth" => ["admin:read:accounts"]}],
parameters: [
Operation.parameter(
:nickname,
:path,
:string,
"User nickname or ID"
)
| admin_api_params()
],
responses: %{
200 => Operation.response("Response", "application/json", user()),
403 => Operation.response("Forbidden", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
def follow_operation do
%Operation{
tags: ["User administration"],
summary: "Follow",
operationId: "AdminAPI.UserController.follow",
security: [%{"oAuth" => ["admin:write:follows"]}],
parameters: admin_api_params(),
requestBody:
request_body(
"Parameters",
%Schema{
type: :object,
properties: %{
follower: %Schema{type: :string, description: "Follower nickname"},
followed: %Schema{type: :string, description: "Followed nickname"}
}
}
),
responses: %{
200 => Operation.response("Response", "application/json", %Schema{type: :string}),
403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
def unfollow_operation do
%Operation{
tags: ["User administration"],
summary: "Unfollow",
operationId: "AdminAPI.UserController.unfollow",
security: [%{"oAuth" => ["admin:write:follows"]}],
parameters: admin_api_params(),
requestBody:
request_body(
"Parameters",
%Schema{
type: :object,
properties: %{
follower: %Schema{type: :string, description: "Follower nickname"},
followed: %Schema{type: :string, description: "Followed nickname"}
}
}
),
responses: %{
200 => Operation.response("Response", "application/json", %Schema{type: :string}),
403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
def approve_operation do
%Operation{
tags: ["User administration"],
summary: "Approve multiple users",
operationId: "AdminAPI.UserController.approve",
security: [%{"oAuth" => ["admin:write:accounts"]}],
parameters: admin_api_params(),
requestBody:
request_body(
"Parameters",
%Schema{
description: "POST body for approving multiple users",
type: :object,
properties: %{
nicknames: %Schema{
type: :array,
items: %Schema{type: :string}
}
}
}
),
responses: %{
200 =>
Operation.response("Response", "application/json", %Schema{
type: :object,
properties: %{user: %Schema{type: :array, items: user()}}
}),
403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
def suggest_operation do
%Operation{
tags: ["User administration"],
summary: "Suggest multiple users",
operationId: "AdminAPI.UserController.suggest",
security: [%{"oAuth" => ["admin:write:accounts"]}],
parameters: admin_api_params(),
requestBody:
request_body(
"Parameters",
%Schema{
description: "POST body for adding multiple suggested users",
type: :object,
properties: %{
nicknames: %Schema{
type: :array,
items: %Schema{type: :string}
}
}
}
),
responses: %{
200 =>
Operation.response("Response", "application/json", %Schema{
type: :object,
properties: %{user: %Schema{type: :array, items: user()}}
}),
403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
def unsuggest_operation do
%Operation{
tags: ["User administration"],
summary: "Unsuggest multiple users",
operationId: "AdminAPI.UserController.unsuggest",
security: [%{"oAuth" => ["admin:write:accounts"]}],
parameters: admin_api_params(),
requestBody:
request_body(
"Parameters",
%Schema{
description: "POST body for removing multiple suggested users",
type: :object,
properties: %{
nicknames: %Schema{
type: :array,
items: %Schema{type: :string}
}
}
}
),
responses: %{
200 =>
Operation.response("Response", "application/json", %Schema{
type: :object,
properties: %{user: %Schema{type: :array, items: user()}}
}),
403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
def toggle_activation_operation do
%Operation{
tags: ["User administration"],
summary: "Toggle user activation",
operationId: "AdminAPI.UserController.toggle_activation",
security: [%{"oAuth" => ["admin:write:accounts"]}],
parameters: [
Operation.parameter(:nickname, :path, :string, "User nickname")
| admin_api_params()
],
responses: %{
200 => Operation.response("Response", "application/json", user()),
403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
def activate_operation do
%Operation{
tags: ["User administration"],
summary: "Activate multiple users",
operationId: "AdminAPI.UserController.activate",
security: [%{"oAuth" => ["admin:write:accounts"]}],
parameters: admin_api_params(),
requestBody:
request_body(
"Parameters",
%Schema{
description: "POST body for deleting multiple users",
type: :object,
properties: %{
nicknames: %Schema{
type: :array,
items: %Schema{type: :string}
}
}
}
),
responses: %{
200 =>
Operation.response("Response", "application/json", %Schema{
type: :object,
properties: %{user: %Schema{type: :array, items: user()}}
}),
403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
def deactivate_operation do
%Operation{
tags: ["User administration"],
summary: "Deactivates multiple users",
operationId: "AdminAPI.UserController.deactivate",
security: [%{"oAuth" => ["admin:write:accounts"]}],
parameters: admin_api_params(),
requestBody:
request_body(
"Parameters",
%Schema{
description: "POST body for deleting multiple users",
type: :object,
properties: %{
nicknames: %Schema{
type: :array,
items: %Schema{type: :string}
}
}
}
),
responses: %{
200 =>
Operation.response("Response", "application/json", %Schema{
type: :object,
properties: %{user: %Schema{type: :array, items: user()}}
}),
403 => Operation.response("Forbidden", "application/json", ApiError)
}
}
end
def delete_operation do
%Operation{
tags: ["User administration"],
summary: "Removes a single or multiple users",
operationId: "AdminAPI.UserController.delete",
security: [%{"oAuth" => ["admin:write:accounts"]}],
parameters: [
Operation.parameter(
:nickname,
:query,
:string,
"User nickname"
)
| admin_api_params()
],
requestBody:
request_body(
"Parameters",
%Schema{
description: "POST body for deleting multiple users",
type: :object,
properties: %{
nicknames: %Schema{
type: :array,
items: %Schema{type: :string}
}
}
}
),
responses: %{
200 =>
Operation.response("Response", "application/json", %Schema{
description: "Array of nicknames",
type: :array,
items: %Schema{type: :string}
}),
- 403 => Operation.response("Forbidden", "application/json", ApiError)
+ 403 => Operation.response("Forbidden", "application/json", ApiError),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
}
}
end
defp user do
%Schema{
type: :object,
properties: %{
id: %Schema{type: :string},
email: %Schema{type: :string, format: :email},
avatar: %Schema{type: :string, format: :uri},
nickname: %Schema{type: :string},
display_name: %Schema{type: :string},
is_active: %Schema{type: :boolean},
local: %Schema{type: :boolean},
roles: %Schema{
type: :object,
properties: %{
admin: %Schema{type: :boolean},
moderator: %Schema{type: :boolean}
}
},
tags: %Schema{type: :array, items: %Schema{type: :string}},
is_confirmed: %Schema{type: :boolean},
is_approved: %Schema{type: :boolean},
url: %Schema{type: :string, format: :uri},
registration_reason: %Schema{type: :string, nullable: true},
actor_type: %Schema{type: :string}
}
}
end
end
diff --git a/test/pleroma/web/admin_api/controllers/user_controller_test.exs b/test/pleroma/web/admin_api/controllers/user_controller_test.exs
index 764f5e01f..4128558e1 100644
--- a/test/pleroma/web/admin_api/controllers/user_controller_test.exs
+++ b/test/pleroma/web/admin_api/controllers/user_controller_test.exs
@@ -1,1252 +1,1274 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.AdminAPI.UserControllerTest do
use Pleroma.Web.ConnCase, async: false
use Oban.Testing, repo: Pleroma.Repo
import Mock
import Pleroma.Factory
alias Pleroma.HTML
alias Pleroma.ModerationLog
alias Pleroma.Repo
alias Pleroma.Tests.ObanHelpers
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Relay
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Endpoint
alias Pleroma.Web.MediaProxy
setup do
Mox.stub_with(Pleroma.UnstubbedConfigMock, Pleroma.Test.StaticConfig)
:ok
end
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
test "with valid `admin_token` query parameter, skips OAuth scopes check" do
clear_config([:instance, :admin_privileges], [:users_read])
clear_config([:admin_token], "password123")
user = insert(:user)
conn = get(build_conn(), "/api/pleroma/admin/users/#{user.nickname}?admin_token=password123")
assert json_response_and_validate_schema(conn, 200)
end
describe "DELETE /api/pleroma/admin/users" do
test "single user", %{admin: admin, conn: conn} do
clear_config([:instance, :federating], true)
clear_config([:instance, :admin_privileges], [:users_delete])
user =
insert(:user,
avatar: %{"url" => [%{"href" => "https://someurl"}]},
banner: %{"url" => [%{"href" => "https://somebanner"}]},
bio: "Hello world!",
name: "A guy"
)
# Create some activities to check they got deleted later
follower = insert(:user)
{:ok, _} = CommonAPI.post(user, %{status: "test"})
{:ok, _, _, _} = CommonAPI.follow(follower, user)
{:ok, _, _, _} = CommonAPI.follow(user, follower)
user = Repo.get(User, user.id)
assert user.note_count == 1
assert user.follower_count == 1
assert user.following_count == 1
assert user.is_active
with_mock Pleroma.Web.Federator,
publish: fn _ -> nil end,
perform: fn _, _ -> nil end do
conn =
conn
|> put_req_header("accept", "application/json")
|> delete("/api/pleroma/admin/users?nickname=#{user.nickname}")
ObanHelpers.perform_all()
refute User.get_by_nickname(user.nickname).is_active
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} deleted users: @#{user.nickname}"
assert json_response_and_validate_schema(conn, 200) == [user.nickname]
user = Repo.get(User, user.id)
refute user.is_active
assert user.avatar == %{}
assert user.banner == %{}
assert user.note_count == 0
assert user.follower_count == 0
assert user.following_count == 0
assert user.bio == ""
assert user.name == nil
assert called(Pleroma.Web.Federator.publish(:_))
end
end
test "multiple users", %{admin: admin, conn: conn} do
clear_config([:instance, :admin_privileges], [:users_delete])
user_one = insert(:user)
user_two = insert(:user)
response =
conn
|> put_req_header("accept", "application/json")
|> put_req_header("content-type", "application/json")
|> delete("/api/pleroma/admin/users", %{
nicknames: [user_one.nickname, user_two.nickname]
})
|> json_response_and_validate_schema(200)
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} deleted users: @#{user_one.nickname}, @#{user_two.nickname}"
assert response -- [user_one.nickname, user_two.nickname] == []
end
+ test "returns 404 for a nickname that does not resolve", %{conn: conn} do
+ clear_config([:instance, :admin_privileges], [:users_delete])
+
+ user = insert(:user)
+
+ response =
+ conn
+ |> put_req_header("accept", "application/json")
+ |> put_req_header("content-type", "application/json")
+ |> delete("/api/pleroma/admin/users", %{
+ nicknames: [user.nickname, "this-user-does-not-exist"]
+ })
+ |> json_response_and_validate_schema(404)
+
+ assert response == %{"error" => "Not found"}
+
+ # Fail closed: the resolvable user that was part of the request is left
+ # untouched and no moderation log entry is written.
+ assert Repo.get(User, user.id).is_active
+ refute Repo.one(ModerationLog)
+ end
+
test "Needs privileged role", %{conn: conn} do
clear_config([:instance, :admin_privileges], [])
response =
conn
|> put_req_header("accept", "application/json")
|> delete("/api/pleroma/admin/users?nickname=nickname")
assert json_response(response, :forbidden)
end
end
describe "/api/pleroma/admin/users" do
test "Create", %{conn: conn} do
response =
conn
|> put_req_header("accept", "application/json")
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/users", %{
"users" => [
%{
"nickname" => "lain",
"email" => "lain@example.org",
"password" => "test"
},
%{
"nickname" => "lain2",
"email" => "lain2@example.org",
"password" => "test"
}
]
})
|> json_response_and_validate_schema(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 "Admin-created users bypass account confirmation", %{conn: conn} do
clear_config([:instance, :account_activation_required], true)
conn
|> put_req_header("accept", "application/json")
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/users", %{
"users" => [
%{"nickname" => "lain", "email" => "lain@example.org", "password" => "test"}
]
})
|> json_response_and_validate_schema(200)
assert User.get_by_nickname("lain").is_confirmed
end
test "Creating without a password returns a password reset link", %{conn: conn} do
accounts =
conn
|> put_req_header("accept", "application/json")
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/users", %{
"users" => [
%{
"nickname" => "lain",
"email" => "lain@example.org"
},
%{
"nickname" => "lain2",
"email" => "lain2@example.org",
"password" => "dupa.8"
}
]
})
|> json_response_and_validate_schema(200)
|> Map.new(fn account -> {account["data"]["nickname"], account} end)
account1 = accounts["lain"]
account2 = accounts["lain2"]
assert account1["type"] == "success"
assert %{"password_reset_link" => link} = account1["data"]
assert link =~ "/api/v1/pleroma/password_reset/"
assert account2["type"] == "success"
refute Map.has_key?(account2["data"], "password_reset_link")
end
test "Explicit empty passwords return a conflict", %{conn: conn} do
conn =
conn
|> put_req_header("accept", "application/json")
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/users", %{
"users" => [
%{"nickname" => "lain", "email" => "lain@example.org", "password" => ""}
]
})
assert [%{"error" => "password can't be blank"}] = json_response(conn, 409)
refute User.get_by_nickname("lain")
end
test "Password reset token failures roll back the user batch", %{conn: conn} do
idempotency_key = Ecto.UUID.generate()
request = fn conn ->
conn
|> put_req_header("accept", "application/json")
|> put_req_header("content-type", "application/json")
|> put_req_header("idempotency-key", idempotency_key)
|> post("/api/pleroma/admin/users", %{
"users" => [
%{"nickname" => "lain", "email" => "lain@example.org"},
%{"nickname" => "lain2", "email" => "lain2@example.org"}
]
})
end
with_mock Pleroma.PasswordResetToken, [:passthrough],
create_token: fn user ->
if user.nickname == "lain2" do
{:error, :forced}
else
result = passthrough([user])
send(self(), {:token_created, user.nickname})
result
end
end do
conn = request.(conn)
assert json_response(conn, 500) == %{"error" => "Something went wrong"}
end
assert_received {:token_created, "lain"}
refute User.get_by_nickname("lain")
refute User.get_by_nickname("lain2")
assert Repo.aggregate(Pleroma.PasswordResetToken, :count) == 0
assert [_account1, _account2] = conn |> request.() |> json_response(200)
end
test "Password reset links are not cached by idempotency key", %{conn: conn} do
idempotency_key = Ecto.UUID.generate()
request = fn conn ->
conn
|> put_req_header("accept", "application/json")
|> put_req_header("content-type", "application/json")
|> put_req_header("idempotency-key", idempotency_key)
|> post("/api/pleroma/admin/users", %{
"users" => [%{"nickname" => "lain", "email" => "lain@example.org"}]
})
end
assert [%{"data" => %{"password_reset_link" => _link}}] =
conn |> request.() |> json_response(200)
second_conn = request.(conn)
assert [_error] = json_response(second_conn, 409)
refute get_resp_header(second_conn, "idempotent-replayed") == ["true"]
end
test "Explicit-password responses remain idempotent", %{conn: conn} do
idempotency_key = Ecto.UUID.generate()
request = fn conn ->
conn
|> put_req_header("accept", "application/json")
|> put_req_header("content-type", "application/json")
|> put_req_header("idempotency-key", idempotency_key)
|> post("/api/pleroma/admin/users", %{
"users" => [
%{"nickname" => "lain", "email" => "lain@example.org", "password" => "test"}
]
})
end
assert [_account] = conn |> request.() |> json_response(200)
second_conn = request.(conn)
assert [_account] = json_response(second_conn, 200)
assert get_resp_header(second_conn, "idempotent-replayed") == ["true"]
end
test "Duplicate nicknames in a batch return a conflict", %{conn: conn} do
conn =
conn
|> put_req_header("accept", "application/json")
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/users", %{
"users" => [
%{"nickname" => "lain", "email" => "lain1@example.org", "password" => "test"},
%{"nickname" => "lain", "email" => "lain2@example.org", "password" => "test"}
]
})
assert [_first, _second] = json_response(conn, 409)
refute User.get_by_nickname("lain")
end
test "Blank nicknames return a conflict", %{conn: conn} do
conn =
conn
|> put_req_header("accept", "application/json")
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/users", %{
"users" => [
%{"nickname" => "", "email" => "lain@example.org", "password" => "test"}
]
})
assert [%{"error" => "nickname can't be blank"}] = json_response(conn, 409)
end
test "Cannot create user with existing email", %{conn: conn} do
user = insert(:user)
conn =
conn
|> put_req_header("accept", "application/json")
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/users", %{
"users" => [
%{
"nickname" => "lain",
"email" => user.email,
"password" => "test"
}
]
})
assert json_response_and_validate_schema(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")
|> put_req_header("content-type", "application/json")
|> post("/api/pleroma/admin/users", %{
"users" => [
%{
"nickname" => user.nickname,
"email" => "someuser@plerama.social",
"password" => "test"
}
]
})
assert json_response_and_validate_schema(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")
|> put_req_header("content-type", "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_and_validate_schema(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 "GET /api/pleroma/admin/users/:nickname" do
setup do
clear_config([:instance, :admin_privileges], [:users_read])
end
test "returns 403 if not privileged with :users_read", %{conn: conn} do
clear_config([:instance, :admin_privileges], [])
conn = get(conn, "/api/pleroma/admin/users/user.nickname")
assert json_response(conn, :forbidden)
end
test "Show", %{conn: conn} do
user = insert(:user)
conn = get(conn, "/api/pleroma/admin/users/#{user.nickname}")
assert user_response(user) == json_response_and_validate_schema(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_and_validate_schema(conn, 404)
end
test "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_and_validate_schema(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_and_validate_schema(conn, :forbidden)
end
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")
|> put_req_header("content-type", "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")
|> put_req_header("content-type", "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 "GET /api/pleroma/admin/users" do
setup do
clear_config([:instance, :admin_privileges], [:users_read])
end
test "returns 403 if not privileged with :users_read", %{conn: conn} do
clear_config([:instance, :admin_privileges], [])
conn = get(conn, "/api/pleroma/admin/users?page=1")
assert json_response(conn, :forbidden)
end
test "renders users array for the first page", %{conn: conn, admin: admin} do
user = insert(:user, local: false, tags: ["foo", "bar"])
user2 = insert(:user, is_approved: false, registration_reason: "I'm a chill dude")
conn = get(conn, "/api/pleroma/admin/users?page=1")
users = [
user_response(
user2,
%{
"local" => true,
"is_approved" => false,
"registration_reason" => "I'm a chill dude",
"actor_type" => "Person"
}
),
user_response(user, %{"local" => false, "tags" => ["foo", "bar"]}),
user_response(
admin,
%{"roles" => %{"admin" => true, "moderator" => false}}
)
]
assert json_response_and_validate_schema(conn, 200) == %{
"count" => 3,
"page_size" => 50,
"users" => users
}
end
test "pagination works correctly with service users", %{conn: conn} do
service1 = User.get_or_create_service_actor_by_ap_id(Endpoint.url() <> "/meido", "meido")
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_and_validate_schema(200)
assert Enum.count(users1) == 10
assert service1 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_and_validate_schema(200)
assert Enum.count(users2) == 10
assert service1 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_and_validate_schema(200)
assert Enum.count(users3) == 6
assert service1 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_and_validate_schema(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_and_validate_schema(conn, 200) == %{
"count" => 1,
"page_size" => 50,
"users" => [user_response(user, %{"local" => true})]
}
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_and_validate_schema(conn, 200) == %{
"count" => 1,
"page_size" => 50,
"users" => [user_response(user)]
}
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_and_validate_schema(conn, 200) == %{
"count" => 1,
"page_size" => 50,
"users" => [user_response(user)]
}
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_and_validate_schema(conn, 200) == %{
"count" => 1,
"page_size" => 50,
"users" => [user_response(user)]
}
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_and_validate_schema(conn, 200) == %{
"count" => 1,
"page_size" => 50,
"users" => [user_response(user)]
}
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_and_validate_schema(conn1, 200) == %{
"count" => 2,
"page_size" => 1,
"users" => [user_response(user2)]
}
conn2 = get(conn, "/api/pleroma/admin/users?query=a&page_size=1&page=2")
assert json_response_and_validate_schema(conn2, 200) == %{
"count" => 2,
"page_size" => 1,
"users" => [user_response(user)]
}
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_and_validate_schema(conn, 200) == %{
"count" => 1,
"page_size" => 50,
"users" => [user_response(user)]
}
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 = [
user_response(user),
user_response(admin, %{
"roles" => %{"admin" => true, "moderator" => false}
}),
user_response(old_admin, %{
"is_active" => true,
"roles" => %{"admin" => true, "moderator" => false}
})
]
assert json_response_and_validate_schema(conn, 200) == %{
"count" => 3,
"page_size" => 50,
"users" => users
}
end
test "only unconfirmed users", %{conn: conn} do
sad_user = insert(:user, nickname: "sadboy", is_confirmed: false)
old_user = insert(:user, nickname: "oldboy", is_confirmed: false)
insert(:user, nickname: "happyboy", is_approved: true)
insert(:user, is_confirmed: true)
result =
conn
|> get("/api/pleroma/admin/users?filters=unconfirmed")
|> json_response_and_validate_schema(200)
users =
Enum.map([old_user, sad_user], fn user ->
user_response(user, %{
"is_confirmed" => false,
"is_approved" => true
})
end)
assert result == %{"count" => 2, "page_size" => 50, "users" => users}
end
test "only unapproved users", %{conn: conn} do
user =
insert(:user,
nickname: "sadboy",
is_approved: false,
registration_reason: "Plz let me in!"
)
insert(:user, nickname: "happyboy", is_approved: true)
conn = get(conn, "/api/pleroma/admin/users?filters=need_approval")
users = [
user_response(
user,
%{"is_approved" => false, "registration_reason" => "Plz let me in!"}
)
]
assert json_response_and_validate_schema(conn, 200) == %{
"count" => 1,
"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 = [
user_response(second_admin, %{
"is_active" => true,
"roles" => %{"admin" => true, "moderator" => false}
}),
user_response(admin, %{
"is_active" => true,
"roles" => %{"admin" => true, "moderator" => false}
})
]
assert json_response_and_validate_schema(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_and_validate_schema(conn, 200) == %{
"count" => 1,
"page_size" => 50,
"users" => [
user_response(moderator, %{
"is_active" => true,
"roles" => %{"admin" => false, "moderator" => true}
})
]
}
end
test "load users with actor_type is Person", %{admin: admin, conn: conn} do
insert(:user, actor_type: "Service")
insert(:user, actor_type: "Application")
user1 = insert(:user)
user2 = insert(:user)
response =
conn
|> get(user_path(conn, :index), %{actor_types: ["Person"]})
|> json_response_and_validate_schema(200)
users = [
user_response(user2),
user_response(user1),
user_response(admin, %{"roles" => %{"admin" => true, "moderator" => false}})
]
assert response == %{"count" => 3, "page_size" => 50, "users" => users}
end
test "load users with actor_type is Person and Service", %{admin: admin, conn: conn} do
user_service = insert(:user, actor_type: "Service")
insert(:user, actor_type: "Application")
user1 = insert(:user)
user2 = insert(:user)
response =
conn
|> get(user_path(conn, :index), %{actor_types: ["Person", "Service"]})
|> json_response_and_validate_schema(200)
users = [
user_response(user2),
user_response(user1),
user_response(user_service, %{"actor_type" => "Service"}),
user_response(admin, %{"roles" => %{"admin" => true, "moderator" => false}})
]
assert response == %{"count" => 4, "page_size" => 50, "users" => users}
end
test "load users with actor_type is Service", %{conn: conn} do
user_service = insert(:user, actor_type: "Service")
insert(:user, actor_type: "Application")
insert(:user)
insert(:user)
response =
conn
|> get(user_path(conn, :index), %{actor_types: ["Service"]})
|> json_response_and_validate_schema(200)
users = [user_response(user_service, %{"actor_type" => "Service"})]
assert response == %{"count" => 1, "page_size" => 50, "users" => users}
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 = [
user_response(user2, %{"tags" => ["second"]}),
user_response(user1, %{"tags" => ["first"]})
]
assert json_response_and_validate_schema(conn, 200) == %{
"count" => 2,
"page_size" => 50,
"users" => users
}
end
test "`active` filters out users pending approval", %{token: token} do
insert(:user, is_approved: false)
%{id: user_id} = insert(:user, is_approved: true)
%{id: admin_id} = token.user
conn =
build_conn()
|> assign(:user, token.user)
|> assign(:token, token)
|> get("/api/pleroma/admin/users?filters=active")
assert %{
"count" => 2,
"page_size" => 50,
"users" => [
%{"id" => ^user_id},
%{"id" => ^admin_id}
]
} = json_response_and_validate_schema(conn, 200)
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, is_active: false)
insert(:user, nickname: "ken", local: true, is_active: false)
insert(:user, nickname: "bobb", local: false, is_active: true)
conn =
build_conn()
|> assign(:user, admin)
|> assign(:token, token)
|> get("/api/pleroma/admin/users?filters=deactivated,external")
assert json_response_and_validate_schema(conn, 200) == %{
"count" => 1,
"page_size" => 50,
"users" => [user_response(user)]
}
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_and_validate_schema(conn, 200) == %{
"count" => 1,
"page_size" => 50,
"users" => [
user_response(admin, %{"roles" => %{"admin" => true, "moderator" => false}})
]
}
end
end
test "PATCH /api/pleroma/admin/users/approve", %{admin: admin, conn: conn} do
clear_config([:instance, :admin_privileges], [:users_manage_invites])
user_one = insert(:user, is_approved: false)
user_two = insert(:user, is_approved: false)
conn =
conn
|> put_req_header("content-type", "application/json")
|> patch(
"/api/pleroma/admin/users/approve",
%{nicknames: [user_one.nickname, user_two.nickname]}
)
response = json_response_and_validate_schema(conn, 200)
assert Enum.map(response["users"], & &1["is_approved"]) == [true, true]
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} approved users: @#{user_one.nickname}, @#{user_two.nickname}"
end
test "PATCH /api/pleroma/admin/users/approve returns 403 if not privileged with :users_manage_invites",
%{conn: conn} do
clear_config([:instance, :admin_privileges], [])
conn =
conn
|> put_req_header("content-type", "application/json")
|> patch(
"/api/pleroma/admin/users/approve",
%{nicknames: ["user_one.nickname", "user_two.nickname"]}
)
assert json_response(conn, :forbidden)
end
test "PATCH /api/pleroma/admin/users/suggest", %{admin: admin, conn: conn} do
user1 = insert(:user, is_suggested: false)
user2 = insert(:user, is_suggested: false)
response =
conn
|> put_req_header("content-type", "application/json")
|> patch(
"/api/pleroma/admin/users/suggest",
%{nicknames: [user1.nickname, user2.nickname]}
)
|> json_response_and_validate_schema(200)
assert Enum.map(response["users"], & &1["is_suggested"]) == [true, true]
[user1, user2] = Repo.reload!([user1, user2])
assert user1.is_suggested
assert user2.is_suggested
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} added suggested users: @#{user1.nickname}, @#{user2.nickname}"
end
test "PATCH /api/pleroma/admin/users/unsuggest", %{admin: admin, conn: conn} do
user1 = insert(:user, is_suggested: true)
user2 = insert(:user, is_suggested: true)
response =
conn
|> put_req_header("content-type", "application/json")
|> patch(
"/api/pleroma/admin/users/unsuggest",
%{nicknames: [user1.nickname, user2.nickname]}
)
|> json_response_and_validate_schema(200)
assert Enum.map(response["users"], & &1["is_suggested"]) == [false, false]
[user1, user2] = Repo.reload!([user1, user2])
refute user1.is_suggested
refute user2.is_suggested
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} removed suggested users: @#{user1.nickname}, @#{user2.nickname}"
end
describe "user activation" do
test "PATCH /api/pleroma/admin/users/activate", %{admin: admin, conn: conn} do
clear_config([:instance, :admin_privileges], [:users_manage_activation_state])
user_one = insert(:user, is_active: false)
user_two = insert(:user, is_active: false)
conn =
conn
|> put_req_header("content-type", "application/json")
|> patch(
"/api/pleroma/admin/users/activate",
%{nicknames: [user_one.nickname, user_two.nickname]}
)
response = json_response_and_validate_schema(conn, 200)
assert Enum.map(response["users"], & &1["is_active"]) == [true, true]
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
clear_config([:instance, :admin_privileges], [:users_manage_activation_state])
user_one = insert(:user, is_active: true)
user_two = insert(:user, is_active: true)
conn =
conn
|> put_req_header("content-type", "application/json")
|> patch(
"/api/pleroma/admin/users/deactivate",
%{nicknames: [user_one.nickname, user_two.nickname]}
)
response = json_response_and_validate_schema(conn, 200)
assert Enum.map(response["users"], & &1["is_active"]) == [false, false]
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
clear_config([:instance, :admin_privileges], [:users_manage_activation_state])
user = insert(:user)
conn =
conn
|> put_req_header("content-type", "application/json")
|> patch("/api/pleroma/admin/users/#{user.nickname}/toggle_activation")
assert json_response_and_validate_schema(conn, 200) ==
user_response(
user,
%{"is_active" => !user.is_active}
)
log_entry = Repo.one(ModerationLog)
assert ModerationLog.get_log_entry_message(log_entry) ==
"@#{admin.nickname} deactivated users: @#{user.nickname}"
end
test "it requires privileged role :statuses_activation to activate", %{conn: conn} do
clear_config([:instance, :admin_privileges], [])
conn =
conn
|> put_req_header("content-type", "application/json")
|> patch(
"/api/pleroma/admin/users/activate",
%{nicknames: ["user_one.nickname", "user_two.nickname"]}
)
assert json_response(conn, :forbidden)
end
test "it requires privileged role :statuses_activation to deactivate", %{conn: conn} do
clear_config([:instance, :admin_privileges], [])
conn =
conn
|> put_req_header("content-type", "application/json")
|> patch(
"/api/pleroma/admin/users/deactivate",
%{nicknames: ["user_one.nickname", "user_two.nickname"]}
)
assert json_response(conn, :forbidden)
end
test "it requires privileged role :statuses_activation to toggle activation", %{conn: conn} do
clear_config([:instance, :admin_privileges], [])
conn =
conn
|> put_req_header("content-type", "application/json")
|> patch("/api/pleroma/admin/users/user.nickname/toggle_activation")
assert json_response(conn, :forbidden)
end
end
defp user_response(user, attrs \\ %{}) do
%{
"is_active" => user.is_active,
"id" => user.id,
"email" => user.email,
"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),
"is_confirmed" => true,
"is_approved" => true,
"is_suggested" => false,
"url" => user.ap_id,
"registration_reason" => nil,
"actor_type" => "Person",
"created_at" => CommonAPI.Utils.to_masto_date(user.inserted_at)
}
|> Map.merge(attrs)
end
end

File Metadata

Mime Type
text/x-diff
Expires
Sun, Nov 1, 11:06 AM (11 h, 15 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1802965
Default Alt Text
(68 KB)

Event Timeline