logo

pleroma

My custom branche(s) on git.pleroma.social/pleroma/pleroma git clone https://anongit.hacktivis.me/git/pleroma.git/

cast_and_validate.ex (4577B)


  1. # Pleroma: A lightweight social networking server
  2. # Copyright © 2019-2020 Moxley Stratton, Mike Buhot <https://github.com/open-api-spex/open_api_spex>, MPL-2.0
  3. # Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
  4. # SPDX-License-Identifier: AGPL-3.0-only
  5. defmodule Pleroma.Web.ApiSpec.CastAndValidate do
  6. @moduledoc """
  7. This plug is based on [`OpenApiSpex.Plug.CastAndValidate`]
  8. (https://github.com/open-api-spex/open_api_spex/blob/master/lib/open_api_spex/plug/cast_and_validate.ex).
  9. The main difference is ignoring unexpected query params instead of throwing
  10. an error and a config option (`[Pleroma.Web.ApiSpec.CastAndValidate, :strict]`)
  11. to disable this behavior. Also, the default rendering error module
  12. is `Pleroma.Web.ApiSpec.RenderError`.
  13. """
  14. @behaviour Plug
  15. alias OpenApiSpex.Plug.PutApiSpec
  16. alias Plug.Conn
  17. require Logger
  18. @impl Plug
  19. def init(opts) do
  20. opts
  21. |> Map.new()
  22. |> Map.put_new(:render_error, Pleroma.Web.ApiSpec.RenderError)
  23. end
  24. @impl Plug
  25. def call(conn, %{operation_id: operation_id, render_error: render_error} = opts) do
  26. {spec, operation_lookup} = PutApiSpec.get_spec_and_operation_lookup(conn)
  27. operation = operation_lookup[operation_id]
  28. cast_opts = opts |> Map.take([:replace_params]) |> Map.to_list()
  29. content_type =
  30. case Conn.get_req_header(conn, "content-type") do
  31. [header_value | _] ->
  32. header_value
  33. |> String.split(";")
  34. |> List.first()
  35. _ ->
  36. "application/json"
  37. end
  38. conn = Conn.put_private(conn, :operation_id, operation_id)
  39. case cast_and_validate(spec, operation, conn, content_type, strict?(), cast_opts) do
  40. {:ok, conn} ->
  41. conn
  42. {:error, reason} ->
  43. Logger.error(
  44. "Strict ApiSpec: request denied to #{conn.request_path} with params #{inspect(conn.params)}"
  45. )
  46. opts = render_error.init(reason)
  47. conn
  48. |> render_error.call(opts)
  49. |> Plug.Conn.halt()
  50. end
  51. end
  52. def call(
  53. %{
  54. private: %{
  55. phoenix_controller: controller,
  56. phoenix_action: action,
  57. open_api_spex: %{spec_module: spec_module}
  58. }
  59. } = conn,
  60. opts
  61. ) do
  62. {spec, operation_lookup} = PutApiSpec.get_spec_and_operation_lookup(conn)
  63. operation =
  64. case operation_lookup[{controller, action}] do
  65. nil ->
  66. operation_id = controller.open_api_operation(action).operationId
  67. operation = operation_lookup[operation_id]
  68. operation_lookup = Map.put(operation_lookup, {controller, action}, operation)
  69. OpenApiSpex.Plug.Cache.adapter().put(spec_module, {spec, operation_lookup})
  70. operation
  71. operation ->
  72. operation
  73. end
  74. if operation.operationId do
  75. call(conn, Map.put(opts, :operation_id, operation.operationId))
  76. else
  77. raise "operationId was not found in action API spec"
  78. end
  79. end
  80. def call(conn, opts), do: OpenApiSpex.Plug.CastAndValidate.call(conn, opts)
  81. defp cast_and_validate(spec, operation, conn, content_type, true = _strict, cast_opts) do
  82. OpenApiSpex.cast_and_validate(spec, operation, conn, content_type, cast_opts)
  83. end
  84. defp cast_and_validate(spec, operation, conn, content_type, false = _strict, cast_opts) do
  85. case OpenApiSpex.cast_and_validate(spec, operation, conn, content_type) do
  86. {:ok, conn} ->
  87. {:ok, conn}
  88. # Remove unexpected query params and cast/validate again
  89. {:error, errors} ->
  90. query_params =
  91. Enum.reduce(errors, conn.query_params, fn
  92. %{reason: :unexpected_field, name: name, path: [name]}, params ->
  93. Map.delete(params, name)
  94. # Filter out empty params
  95. %{reason: :invalid_type, path: [name_atom], value: ""}, params ->
  96. Map.delete(params, to_string(name_atom))
  97. %{reason: :invalid_enum, name: nil, path: path, value: value}, params ->
  98. path = path |> Enum.reverse() |> tl() |> Enum.reverse() |> list_items_to_string()
  99. update_in(params, path, &List.delete(&1, value))
  100. _, params ->
  101. params
  102. end)
  103. conn = %Conn{conn | query_params: query_params}
  104. OpenApiSpex.cast_and_validate(spec, operation, conn, content_type, cast_opts)
  105. end
  106. end
  107. defp list_items_to_string(list) do
  108. Enum.map(list, fn
  109. i when is_atom(i) -> to_string(i)
  110. i -> i
  111. end)
  112. end
  113. defp strict?, do: Pleroma.Config.get([__MODULE__, :strict], false)
  114. end