Skip to content

Commit 568c77e

Browse files
authored
Add support strict mode for query parameter (#464)
Fixes: #320
1 parent 63a2a14 commit 568c77e

File tree

4 files changed

+104
-1
lines changed

4 files changed

+104
-1
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ This piece of middleware validates the parameters of incoming requests to make s
4343
|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. |
4444
|raise| false | false | Raise an exception on error instead of responding with a generic error body. |
4545
|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. |
46+
|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. |
4647
|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. |
4748
|ignore_error| false | false | Validate and ignore result even if validation is error. So always return original data. |
4849

lib/committee/schema_validator/open_api_3/operation_wrapper.rb

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,13 @@ def request_content_types
7474
# @return [OpenAPIParser::RequestOperation]
7575
attr_reader :request_operation
7676

77+
# @return [Array<String>] names of query parameters defined in the schema
78+
def query_parameter_names
79+
return [] unless request_operation.operation_object&.parameters
80+
81+
request_operation.operation_object.parameters.select { |p| p.in == 'query' }.map(&:name)
82+
end
83+
7784
private
7885

7986
# @return [OpenAPIParser::SchemaValidator::Options]
@@ -132,6 +139,19 @@ def validate_path_and_query_params(path_params, query_params, headers, validator
132139
path_params[k] = v if path_keys.include?(k)
133140
query_params[k] = v if query_keys.include?(k)
134141
end
142+
143+
validate_no_unknown_query_params(query_params) if validator_option.strict_query_params
144+
end
145+
146+
def validate_no_unknown_query_params(query_params)
147+
return if query_params.nil? || query_params.empty?
148+
149+
defined_params = query_parameter_names
150+
unknown_params = query_params.keys - defined_params
151+
152+
return if unknown_params.empty?
153+
154+
raise Committee::InvalidRequest.new("Unknown query parameter(s): #{unknown_params.join(', ')}")
135155
end
136156

137157
def response_validate_options(strict, check_header, validator_options: {})

lib/committee/schema_validator/option.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ module Committee
44
module SchemaValidator
55
class Option
66
# Boolean Options
7-
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
7+
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
88

99
# Non-boolean options:
1010
attr_reader :headers_key, :params_key, :query_hash_key, :request_body_hash_key, :path_hash_key, :prefix
@@ -31,6 +31,7 @@ def initialize(options, schema, schema_type)
3131
@coerce_response_values = options.fetch(:coerce_response_values, false)
3232
@optimistic_json = options.fetch(:optimistic_json, false)
3333
@parse_response_by_content_type = options.fetch(:parse_response_by_content_type, true)
34+
@strict_query_params = options.fetch(:strict_query_params, false)
3435

3536
@parameter_overwrite_by_rails_rule =
3637
if options.key?(:parameter_overwite_by_rails_rule)

test/middleware/request_validation_open_api_3_test.rb

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -551,6 +551,87 @@ def app
551551
end
552552
end
553553

554+
describe ':strict_query_params option' do
555+
it 'allows unknown query params when strict_query_params is false' do
556+
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: false)
557+
get "/characters", { limit: 10, unknown_param: "value" }
558+
assert_equal 200, last_response.status
559+
end
560+
561+
it 'rejects unknown query params when strict_query_params is true' do
562+
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
563+
get "/characters", { limit: 10, unknown_param: "value" }
564+
assert_equal 400, last_response.status
565+
assert_match(/unknown_param/, last_response.body)
566+
end
567+
568+
it 'allows defined query params when strict_query_params is true' do
569+
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
570+
get "/characters", { limit: 10, school_name: "test" }
571+
assert_equal 200, last_response.status
572+
end
573+
574+
it 'handles empty query params when strict_query_params is true' do
575+
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
576+
get "/characters"
577+
assert_equal 200, last_response.status
578+
end
579+
580+
it 'rejects multiple unknown query params' do
581+
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
582+
get "/characters", { limit: 10, unknown1: "a", unknown2: "b" }
583+
assert_equal 400, last_response.status
584+
assert_match(/unknown1/, last_response.body)
585+
assert_match(/unknown2/, last_response.body)
586+
end
587+
588+
it 'allows partial query params (only some defined params)' do
589+
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
590+
get "/characters", { limit: 10 }
591+
assert_equal 200, last_response.status
592+
end
593+
594+
it 'works with POST requests that have query params' do
595+
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
596+
header "Content-Type", "application/json"
597+
post "/additional_properties?first_name=test&unknown=value", JSON.generate(last_name: "test")
598+
assert_equal 400, last_response.status
599+
assert_match(/unknown/, last_response.body)
600+
end
601+
602+
it 'allows defined query params in POST requests' do
603+
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
604+
header "Content-Type", "application/json"
605+
post "/additional_properties?first_name=test", JSON.generate(last_name: "test")
606+
assert_equal 200, last_response.status
607+
end
608+
609+
it 'works with DELETE requests' do
610+
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
611+
delete "/characters?limit=10"
612+
assert_equal 200, last_response.status
613+
end
614+
615+
it 'rejects unknown query params in DELETE requests' do
616+
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
617+
delete "/characters?limit=10&unknown=value"
618+
assert_equal 400, last_response.status
619+
assert_match(/unknown/, last_response.body)
620+
end
621+
622+
it 'works with HEAD requests' do
623+
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
624+
head "/characters?limit=10"
625+
assert_equal 200, last_response.status
626+
end
627+
628+
it 'rejects unknown query params in HEAD requests' do
629+
@app = new_rack_app(schema: open_api_3_schema, strict_query_params: true)
630+
head "/characters?limit=10&unknown=value"
631+
assert_equal 400, last_response.status
632+
end
633+
end
634+
554635
private
555636

556637
def new_rack_app(options = {})

0 commit comments

Comments
 (0)