Merge pull request 'Return HTTP code 413 when uploading an avatar/header that's too large' (#367) from norm/akkoma:return-413-max-size into develop

Reviewed-on: https://akkoma.dev/AkkomaGang/akkoma/pulls/367
This commit is contained in:
floatingghost 2022-12-14 10:07:24 +00:00
commit 7f4d218cff
5 changed files with 106 additions and 6 deletions

View file

@ -6,11 +6,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## Unreleased ## Unreleased
## Removed ### Removed
- Non-finch HTTP adapters - Non-finch HTTP adapters
### Upgrade notes
## Upgrade notes
- Ensure `config :tesla, :adapter` is either unset, or set to `{Tesla.Adapter.Finch, name: MyFinch}` in your .exs config - Ensure `config :tesla, :adapter` is either unset, or set to `{Tesla.Adapter.Finch, name: MyFinch}` in your .exs config
### Changed
- Return HTTP error 413 when uploading an avatar or banner that's above the configured upload limit instead of a 500.
## 2022.12 ## 2022.12

View file

@ -599,7 +599,13 @@ defp put_change_if_present(changeset, map_field, value_function) do
{:ok, new_value} <- value_function.(value) do {:ok, new_value} <- value_function.(value) do
put_change(changeset, map_field, new_value) put_change(changeset, map_field, new_value)
else else
_ -> changeset {:error, :file_too_large} ->
Ecto.Changeset.validate_change(changeset, map_field, fn map_field, _value ->
[{map_field, "file is too large"}]
end)
_ ->
changeset
end end
end end

View file

@ -64,7 +64,8 @@ def update_credentials_operation do
requestBody: request_body("Parameters", update_credentials_request(), required: true), requestBody: request_body("Parameters", update_credentials_request(), required: true),
responses: %{ responses: %{
200 => Operation.response("Account", "application/json", Account), 200 => Operation.response("Account", "application/json", Account),
403 => Operation.response("Error", "application/json", ApiError) 403 => Operation.response("Error", "application/json", ApiError),
413 => Operation.response("Error", "application/json", ApiError)
} }
} }
end end

View file

@ -251,7 +251,17 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
with_pleroma_settings: true with_pleroma_settings: true
) )
else else
_e -> render_error(conn, :forbidden, "Invalid request") {:error, %Ecto.Changeset{errors: [avatar: {"file is too large", _}]}} ->
render_error(conn, :request_entity_too_large, "File is too large")
{:error, %Ecto.Changeset{errors: [banner: {"file is too large", _}]}} ->
render_error(conn, :request_entity_too_large, "File is too large")
{:error, %Ecto.Changeset{errors: [background: {"file is too large", _}]}} ->
render_error(conn, :request_entity_too_large, "File is too large")
_e ->
render_error(conn, :forbidden, "Invalid request")
end end
end end

View file

@ -272,6 +272,34 @@ test "updates the user's avatar", %{user: user, conn: conn} do
assert user.avatar == nil assert user.avatar == nil
end end
test "updates the user's avatar, upload_limit, returns a HTTP 413", %{conn: conn, user: user} do
upload_limit = Config.get([:instance, :upload_limit]) * 8 + 8
assert :ok ==
File.write(Path.absname("test/tmp/large_binary.data"), <<0::size(upload_limit)>>)
new_avatar_oversized = %Plug.Upload{
content_type: nil,
path: Path.absname("test/tmp/large_binary.data"),
filename: "large_binary.data"
}
assert user.avatar == %{}
res =
patch(conn, "/api/v1/accounts/update_credentials", %{"avatar" => new_avatar_oversized})
assert user_response = json_response_and_validate_schema(res, 413)
assert user_response["avatar"] != User.avatar_url(user)
user = User.get_by_id(user.id)
assert user.avatar == %{}
clear_config([:instance, :upload_limit], upload_limit)
assert :ok == File.rm(Path.absname("test/tmp/large_binary.data"))
end
test "updates the user's banner", %{user: user, conn: conn} do test "updates the user's banner", %{user: user, conn: conn} do
new_header = %Plug.Upload{ new_header = %Plug.Upload{
content_type: "image/jpeg", content_type: "image/jpeg",
@ -291,6 +319,32 @@ test "updates the user's banner", %{user: user, conn: conn} do
assert user.banner == nil assert user.banner == nil
end end
test "updates the user's banner, upload_limit, returns a HTTP 413", %{conn: conn, user: user} do
upload_limit = Config.get([:instance, :upload_limit]) * 8 + 8
assert :ok ==
File.write(Path.absname("test/tmp/large_binary.data"), <<0::size(upload_limit)>>)
new_header_oversized = %Plug.Upload{
content_type: nil,
path: Path.absname("test/tmp/large_binary.data"),
filename: "large_binary.data"
}
res =
patch(conn, "/api/v1/accounts/update_credentials", %{"header" => new_header_oversized})
assert user_response = json_response_and_validate_schema(res, 413)
assert user_response["header"] != User.banner_url(user)
user = User.get_by_id(user.id)
assert user.banner == %{}
clear_config([:instance, :upload_limit], upload_limit)
assert :ok == File.rm(Path.absname("test/tmp/large_binary.data"))
end
test "updates the user's background", %{conn: conn, user: user} do test "updates the user's background", %{conn: conn, user: user} do
new_header = %Plug.Upload{ new_header = %Plug.Upload{
content_type: "image/jpeg", content_type: "image/jpeg",
@ -314,6 +368,34 @@ test "updates the user's background", %{conn: conn, user: user} do
assert user.background == nil assert user.background == nil
end end
test "updates the user's background, upload_limit, returns a HTTP 413", %{
conn: conn,
user: user
} do
upload_limit = Config.get([:instance, :upload_limit]) * 8 + 8
assert :ok ==
File.write(Path.absname("test/tmp/large_binary.data"), <<0::size(upload_limit)>>)
new_background_oversized = %Plug.Upload{
content_type: nil,
path: Path.absname("test/tmp/large_binary.data"),
filename: "large_binary.data"
}
res =
patch(conn, "/api/v1/accounts/update_credentials", %{
"pleroma_background_image" => new_background_oversized
})
assert user_response = json_response_and_validate_schema(res, 413)
assert user.background == %{}
clear_config([:instance, :upload_limit], upload_limit)
assert :ok == File.rm(Path.absname("test/tmp/large_binary.data"))
end
test "requires 'write:accounts' permission" do test "requires 'write:accounts' permission" do
token1 = insert(:oauth_token, scopes: ["read"]) token1 = insert(:oauth_token, scopes: ["read"])
token2 = insert(:oauth_token, scopes: ["write", "follow"]) token2 = insert(:oauth_token, scopes: ["write", "follow"])