fix(api_spec): drop every unexpected query param in permissive mode

Ported from Akkoma 7e6efb235.

Co-authored-by: Oneric <oneric@oneric.stub>
This commit is contained in:
Lain Soykaf 2026-10-31 13:44:00 +04:00
commit d601a94c69
No known key found for this signature in database
3 changed files with 66 additions and 23 deletions

View file

@ -0,0 +1 @@
Drop all unexpected query params, not just the first one, when `Pleroma.Web.ApiSpec.CastAndValidate` `strict` is off

View file

@ -20,6 +20,9 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do
require Logger
# How often to drop offending query params and retry in permissive mode
@max_retries 20
@impl Plug
def init(opts) do
opts
@ -114,34 +117,49 @@ defmodule Pleroma.Web.ApiSpec.CastAndValidate do
false = _strict,
cast_opts
) do
case OpenApiSpex.cast_and_validate(spec, operation, conn, content_type, cast_opts) do
{:ok, conn} ->
{:ok, conn}
permissive_cast_and_validate(spec, operation, conn, content_type, cast_opts, @max_retries)
end
# Remove unexpected query params and cast/validate again
{:error, errors} ->
query_params =
Enum.reduce(errors, conn.query_params, fn
%{reason: :unexpected_field, name: name, path: [name]}, params ->
Map.delete(params, name)
# OpenApiSpex reports one offending param at a time, so drop it and cast/validate again
defp permissive_cast_and_validate(
spec,
operation,
%Conn{} = conn,
content_type,
cast_opts,
retries
) do
with {:error, errors} = error <-
OpenApiSpex.cast_and_validate(spec, operation, conn, content_type, cast_opts) do
query_params = drop_invalid_query_params(errors, conn.query_params)
# Filter out empty params
%{reason: :invalid_type, path: [name_atom], value: ""}, params ->
Map.delete(params, to_string(name_atom))
%{reason: :invalid_enum, name: nil, path: path, value: value}, params ->
path = path |> Enum.reverse() |> tl() |> Enum.reverse() |> list_items_to_string()
update_in(params, path, &List.delete(&1, value))
_, params ->
params
end)
conn = %Conn{conn | query_params: query_params}
OpenApiSpex.cast_and_validate(spec, operation, conn, content_type, cast_opts)
if retries > 0 and query_params != conn.query_params do
conn = %{conn | query_params: query_params}
permissive_cast_and_validate(spec, operation, conn, content_type, cast_opts, retries - 1)
else
error
end
end
end
defp drop_invalid_query_params(errors, query_params) do
Enum.reduce(errors, query_params, fn
%{reason: :unexpected_field, name: name, path: [name]}, params ->
Map.delete(params, name)
# Filter out empty params
%{reason: :invalid_type, path: [name_atom], value: ""}, params ->
Map.delete(params, to_string(name_atom))
%{reason: :invalid_enum, name: nil, path: path, value: value}, params ->
path = path |> Enum.reverse() |> tl() |> Enum.reverse() |> list_items_to_string()
update_in(params, path, &List.delete(&1, value))
_, params ->
params
end)
end
defp list_items_to_string(list) do
Enum.map(list, fn
i when is_atom(i) -> to_string(i)

View file

@ -0,0 +1,24 @@
# 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.CastAndValidateTest do
use Pleroma.Web.ConnCase, async: false
describe "permissive mode" do
setup do: clear_config([Pleroma.Web.ApiSpec.CastAndValidate, :strict], false)
test "drops several unexpected query params", %{conn: conn} do
assert conn
|> get("/api/v1/timelines/public?foo=1&bar=2&baz=3&local=true")
|> json_response_and_validate_schema(200)
end
test "still rejects an invalid known param", %{conn: conn} do
assert %{"error" => _} =
conn
|> get("/api/v1/timelines/public?foo=1&bar=2&limit=lots")
|> json_response(400)
end
end
end