Merge pull request 'Allow creating accounts without a password in the AdminAPI, update docs' (#7937) from mkljczk/pleroma:admin-api-passwordless-user-creation into develop
Reviewed-on: https://git.pleroma.social/pleroma/pleroma/pulls/7937
This commit is contained in:
commit
8ab0e046b1
8 changed files with 478 additions and 39 deletions
|
|
@ -176,6 +176,183 @@ defmodule Pleroma.Web.AdminAPI.UserControllerTest do
|
|||
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)
|
||||
|
||||
|
|
|
|||
|
|
@ -70,6 +70,139 @@ defmodule Pleroma.Web.Plugs.IdempotencyPlugTest do
|
|||
assert [^key] = Conn.get_resp_header(conn, "idempotency-key")
|
||||
end
|
||||
|
||||
test "does not cache responses marked as sensitive" do
|
||||
key = Ecto.UUID.generate()
|
||||
|
||||
:post
|
||||
|> conn("/cofe")
|
||||
|> put_req_header("idempotency-key", key)
|
||||
|> Conn.put_resp_header("x-request-id", "sensitive-request")
|
||||
|> Conn.put_resp_content_type("application/json")
|
||||
|> IdempotencyPlug.call([])
|
||||
|> Conn.put_private(:skip_idempotency_cache, true)
|
||||
|> Conn.send_resp(200, "secret")
|
||||
|
||||
conn =
|
||||
:post
|
||||
|> conn("/cofe")
|
||||
|> put_req_header("idempotency-key", key)
|
||||
|> Conn.put_resp_header("x-request-id", "second-request")
|
||||
|> Conn.put_resp_content_type("application/json")
|
||||
|> IdempotencyPlug.call([])
|
||||
|
||||
refute conn.halted
|
||||
refute conn.resp_body
|
||||
end
|
||||
|
||||
test "does not replay legacy unscoped cache entries" do
|
||||
key = Ecto.UUID.generate()
|
||||
record = {"legacy-request", "application/json", 200, "secret"}
|
||||
assert {:ok, true} = Cachex.put(:idempotency_cache, key, record)
|
||||
|
||||
conn =
|
||||
:post
|
||||
|> conn("/cofe")
|
||||
|> put_req_header("idempotency-key", key)
|
||||
|> Conn.put_resp_header("x-request-id", "new-request")
|
||||
|> Conn.put_resp_content_type("application/json")
|
||||
|> IdempotencyPlug.call([])
|
||||
|
||||
refute conn.halted
|
||||
refute conn.resp_body
|
||||
end
|
||||
|
||||
test "scopes cached responses to the route and authenticated actor" do
|
||||
key = Ecto.UUID.generate()
|
||||
|
||||
:post
|
||||
|> conn("/cofe")
|
||||
|> Conn.assign(:user, %{id: "user-one", is_admin: false, is_moderator: false})
|
||||
|> put_req_header("idempotency-key", key)
|
||||
|> Conn.put_resp_header("x-request-id", "original-request")
|
||||
|> Conn.put_resp_content_type("application/json")
|
||||
|> IdempotencyPlug.call([])
|
||||
|> Conn.send_resp(200, "one")
|
||||
|
||||
for {path, user} <- [
|
||||
{"/tea", %{id: "user-one", is_admin: false, is_moderator: false}},
|
||||
{"/cofe", %{id: "user-two", is_admin: false, is_moderator: false}},
|
||||
{"/cofe", %{id: "user-one", is_admin: true, is_moderator: false}}
|
||||
] do
|
||||
conn =
|
||||
:post
|
||||
|> conn(path)
|
||||
|> Conn.assign(:user, user)
|
||||
|> put_req_header("idempotency-key", key)
|
||||
|> Conn.put_resp_header("x-request-id", Ecto.UUID.generate())
|
||||
|> Conn.put_resp_content_type("application/json")
|
||||
|> IdempotencyPlug.call([])
|
||||
|
||||
refute conn.halted
|
||||
refute conn.resp_body
|
||||
end
|
||||
end
|
||||
|
||||
test "scopes cached responses to the OAuth application and token" do
|
||||
key = Ecto.UUID.generate()
|
||||
|
||||
:post
|
||||
|> conn("/cofe")
|
||||
|> Conn.assign(:app, %{id: "app-one"})
|
||||
|> Conn.assign(:token, %{id: "token-one", scopes: ["write"]})
|
||||
|> put_req_header("idempotency-key", key)
|
||||
|> Conn.put_resp_header("x-request-id", "original-request")
|
||||
|> Conn.put_resp_content_type("application/json")
|
||||
|> IdempotencyPlug.call([])
|
||||
|> Conn.send_resp(200, "one")
|
||||
|
||||
for {app, token} <- [
|
||||
{%{id: "app-two"}, %{id: "token-two", scopes: ["write"]}},
|
||||
{%{id: "app-one"}, %{id: "token-two", scopes: ["write"]}},
|
||||
{%{id: "app-one"}, %{id: "token-one", scopes: ["read"]}}
|
||||
] do
|
||||
conn =
|
||||
:post
|
||||
|> conn("/cofe")
|
||||
|> Conn.assign(:app, app)
|
||||
|> Conn.assign(:token, token)
|
||||
|> put_req_header("idempotency-key", key)
|
||||
|> Conn.put_resp_header("x-request-id", Ecto.UUID.generate())
|
||||
|> Conn.put_resp_content_type("application/json")
|
||||
|> IdempotencyPlug.call([])
|
||||
|
||||
refute conn.halted
|
||||
refute conn.resp_body
|
||||
end
|
||||
end
|
||||
|
||||
test "scopes cached responses to configured staff privileges" do
|
||||
key = Ecto.UUID.generate()
|
||||
clear_config([:instance, :admin_privileges], [:users_delete])
|
||||
|
||||
:post
|
||||
|> conn("/cofe")
|
||||
|> Conn.assign(:user, %{id: "admin", is_admin: true, is_moderator: false})
|
||||
|> put_req_header("idempotency-key", key)
|
||||
|> Conn.put_resp_header("x-request-id", "original-request")
|
||||
|> Conn.put_resp_content_type("application/json")
|
||||
|> IdempotencyPlug.call([])
|
||||
|> Conn.send_resp(200, "one")
|
||||
|
||||
clear_config([:instance, :admin_privileges], [])
|
||||
|
||||
conn =
|
||||
:post
|
||||
|> conn("/cofe")
|
||||
|> Conn.assign(:user, %{id: "admin", is_admin: true, is_moderator: false})
|
||||
|> put_req_header("idempotency-key", key)
|
||||
|> Conn.put_resp_header("x-request-id", "second-request")
|
||||
|> Conn.put_resp_content_type("application/json")
|
||||
|> IdempotencyPlug.call([])
|
||||
|
||||
refute conn.halted
|
||||
refute conn.resp_body
|
||||
end
|
||||
|
||||
test "passes conn downstream if idempotency is not present in headers" do
|
||||
orig_request_id = "test4"
|
||||
body = "testing"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue