Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ This piece of middleware validates the parameters of incoming requests to make s
|optimistic_json| false | false | Will attempt to parse JSON in the request body even without a `Content-Type: application/json` before falling back to other options. |
|raise| false | false | Raise an exception on error instead of responding with a generic error body. |
|strict| false | false | Puts the middleware into strict mode, meaning that paths which are not defined in the schema will be responded to with a 404 instead of being run. |
|strict_query_params| not supported | false | Rejects requests with query parameters not defined in the schema. When enabled, any query parameter not explicitly defined in the OpenAPI specification will result in a 400 error. |
|strict_reference_validation| always false | false | Raises an exception (`OpenAPIParser::MissingReferenceError`) on middleware load if the provided schema file contains unresolvable references (`$ref:"#/something/not/here"`). Not supported on Hyper-schema parser. Defaults to `false` on OpenAPI3 but will default to `true` in next major version. |
|ignore_error| false | false | Validate and ignore result even if validation is error. So always return original data. |

Expand Down
20 changes: 20 additions & 0 deletions lib/committee/schema_validator/open_api_3/operation_wrapper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,13 @@ def request_content_types
# @return [OpenAPIParser::RequestOperation]
attr_reader :request_operation

# @return [Array<String>] names of query parameters defined in the schema
def query_parameter_names
return [] unless request_operation.operation_object&.parameters

request_operation.operation_object.parameters.select { |p| p.in == 'query' }.map(&:name)
end

private

# @return [OpenAPIParser::SchemaValidator::Options]
Expand Down Expand Up @@ -132,6 +139,19 @@ def validate_path_and_query_params(path_params, query_params, headers, validator
path_params[k] = v if path_keys.include?(k)
query_params[k] = v if query_keys.include?(k)
end

validate_no_unknown_query_params(query_params) if validator_option.strict_query_params
end

def validate_no_unknown_query_params(query_params)
return if query_params.nil? || query_params.empty?

defined_params = query_parameter_names
unknown_params = query_params.keys - defined_params

return if unknown_params.empty?

raise Committee::InvalidRequest.new("Unknown query parameter(s): #{unknown_params.join(', ')}")
end

def response_validate_options(strict, check_header, validator_options: {})
Expand Down
3 changes: 2 additions & 1 deletion lib/committee/schema_validator/option.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ module Committee
module SchemaValidator
class Option
# Boolean Options
attr_reader :allow_blank_structures, :allow_empty_date_and_datetime, :allow_form_params, :allow_get_body, :allow_query_params, :allow_non_get_query_params, :check_content_type, :check_header, :coerce_date_times, :coerce_form_params, :coerce_path_params, :coerce_query_params, :coerce_recursive, :coerce_response_values, :deserialize_parameters, :optimistic_json, :validate_success_only, :parse_response_by_content_type, :parameter_overwrite_by_rails_rule
attr_reader :allow_blank_structures, :allow_empty_date_and_datetime, :allow_form_params, :allow_get_body, :allow_query_params, :allow_non_get_query_params, :check_content_type, :check_header, :coerce_date_times, :coerce_form_params, :coerce_path_params, :coerce_query_params, :coerce_recursive, :coerce_response_values, :deserialize_parameters, :optimistic_json, :validate_success_only, :parse_response_by_content_type, :parameter_overwrite_by_rails_rule, :strict_query_params

# Non-boolean options:
attr_reader :headers_key, :params_key, :query_hash_key, :request_body_hash_key, :path_hash_key, :prefix
Expand All @@ -31,6 +31,7 @@ def initialize(options, schema, schema_type)
@coerce_response_values = options.fetch(:coerce_response_values, false)
@optimistic_json = options.fetch(:optimistic_json, false)
@parse_response_by_content_type = options.fetch(:parse_response_by_content_type, true)
@strict_query_params = options.fetch(:strict_query_params, false)

@parameter_overwrite_by_rails_rule =
if options.key?(:parameter_overwite_by_rails_rule)
Expand Down
81 changes: 81 additions & 0 deletions test/middleware/request_validation_open_api_3_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -551,6 +551,87 @@ def app
end
end

describe ':strict_query_params option' do
it 'allows unknown query params when strict_query_params is false' do
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: false)
get "/characters", { limit: 10, unknown_param: "value" }
assert_equal 200, last_response.status
end

it 'rejects unknown query params when strict_query_params is true' do
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
get "/characters", { limit: 10, unknown_param: "value" }
assert_equal 400, last_response.status
assert_match(/unknown_param/, last_response.body)
end

it 'allows defined query params when strict_query_params is true' do
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
get "/characters", { limit: 10, school_name: "test" }
assert_equal 200, last_response.status
end

it 'handles empty query params when strict_query_params is true' do
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
get "/characters"
assert_equal 200, last_response.status
end

it 'rejects multiple unknown query params' do
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
get "/characters", { limit: 10, unknown1: "a", unknown2: "b" }
assert_equal 400, last_response.status
assert_match(/unknown1/, last_response.body)
assert_match(/unknown2/, last_response.body)
end

it 'allows partial query params (only some defined params)' do
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
get "/characters", { limit: 10 }
assert_equal 200, last_response.status
end

it 'works with POST requests that have query params' do
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
header "Content-Type", "application/json"
post "/additional_properties?first_name=test&unknown=value", JSON.generate(last_name: "test")
assert_equal 400, last_response.status
assert_match(/unknown/, last_response.body)
end

it 'allows defined query params in POST requests' do
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
header "Content-Type", "application/json"
post "/additional_properties?first_name=test", JSON.generate(last_name: "test")
assert_equal 200, last_response.status
end

it 'works with DELETE requests' do
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
delete "/characters?limit=10"
assert_equal 200, last_response.status
end

it 'rejects unknown query params in DELETE requests' do
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
delete "/characters?limit=10&unknown=value"
assert_equal 400, last_response.status
assert_match(/unknown/, last_response.body)
end

it 'works with HEAD requests' do
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
head "/characters?limit=10"
assert_equal 200, last_response.status
end

it 'rejects unknown query params in HEAD requests' do
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
head "/characters?limit=10&unknown=value"
assert_equal 400, last_response.status
end
end

private

def new_rack_app(options = {})
Expand Down