From d601a94c693161bb8c371f4187a8f4a96f093bb5 Mon Sep 17 00:00:00 2001 From: Lain Soykaf Date: Sat, 31 Oct 2026 13:44:00 +0400 Subject: [PATCH] fix(api_spec): drop every unexpected query param in permissive mode Ported from Akkoma 7e6efb235. Co-authored-by: Oneric --- changelog.d/cast-and-validate-many-params.fix | 1 + lib/pleroma/web/api_spec/cast_and_validate.ex | 64 ++++++++++++------- .../web/api_spec/cast_and_validate_test.exs | 24 +++++++ 3 files changed, 66 insertions(+), 23 deletions(-) create mode 100644 changelog.d/cast-and-validate-many-params.fix create mode 100644 test/pleroma/web/api_spec/cast_and_validate_test.exs diff --git a/changelog.d/cast-and-validate-many-params.fix b/changelog.d/cast-and-validate-many-params.fix new file mode 100644 index 000000000..c44396618 --- /dev/null +++ b/changelog.d/cast-and-validate-many-params.fix @@ -0,0 +1 @@ +Drop all unexpected query params, not just the first one, when `Pleroma.Web.ApiSpec.CastAndValidate` `strict` is off diff --git a/lib/pleroma/web/api_spec/cast_and_validate.ex b/lib/pleroma/web/api_spec/cast_and_validate.ex index 92f1c5376..41e69727e 100644 --- a/lib/pleroma/web/api_spec/cast_and_validate.ex +++ b/lib/pleroma/web/api_spec/cast_and_validate.ex @@ -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) diff --git a/test/pleroma/web/api_spec/cast_and_validate_test.exs b/test/pleroma/web/api_spec/cast_and_validate_test.exs new file mode 100644 index 000000000..b7ee673e1 --- /dev/null +++ b/test/pleroma/web/api_spec/cast_and_validate_test.exs @@ -0,0 +1,24 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2022 Pleroma Authors +# 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