diff --git a/lib/openapi_parser/errors.rb b/lib/openapi_parser/errors.rb index 8ffddd8..f7bb585 100644 --- a/lib/openapi_parser/errors.rb +++ b/lib/openapi_parser/errors.rb @@ -272,4 +272,10 @@ def message "#{@reference} #{@value.inspect} contains fewer than min items" end end + + class ValidateSecurityError < OpenAPIError + def message + "access denied" + end + end end diff --git a/lib/openapi_parser/request_operation.rb b/lib/openapi_parser/request_operation.rb index d0312b9..ffd6fb2 100644 --- a/lib/openapi_parser/request_operation.rb +++ b/lib/openapi_parser/request_operation.rb @@ -5,11 +5,11 @@ class << self # @param [OpenAPIParser::Config] config # @param [OpenAPIParser::PathItemFinder] path_item_finder # @return [OpenAPIParser::RequestOperation, nil] - def create(http_method, request_path, path_item_finder, config) + def create(http_method, request_path, path_item_finder, config, security_schemes = {}) result = path_item_finder.operation_object(http_method, request_path) return nil unless result - self.new(http_method, result, config) + self.new(http_method, result, config, security_schemes) end end @@ -25,18 +25,23 @@ def create(http_method, request_path, path_item_finder, config) # @return [String] # @!attribute [r] path_item # @return [OpenAPIParser::Schemas::PathItem] - attr_reader :operation_object, :path_params, :config, :http_method, :original_path, :path_item + attr_reader :operation_object, :path_params, :config, :http_method, :original_path, :path_item, :security_schemes # @param [String] http_method # @param [OpenAPIParser::PathItemFinder::Result] result # @param [OpenAPIParser::Config] config - def initialize(http_method, result, config) + def initialize(http_method, result, config, security_schemes) @http_method = http_method.to_s @original_path = result.original_path @operation_object = result.operation_object @path_params = result.path_params || {} @path_item = result.path_item_object @config = config + @security_schemes = security_schemes + end + + def validate_security(security_schemes, headers) + operation_object.validate_security_schemes(security_schemes, headers) end def validate_path_params(options = nil) @@ -65,6 +70,7 @@ def validate_response_body(response_body, response_validate_options = nil) # @param [OpenAPIParser::SchemaValidator::Options] options request validator options def validate_request_parameter(params, headers, options = nil) options ||= config.request_validator_options + validate_security(security_schemes, headers) operation_object&.validate_request_parameter(params, headers, options) end diff --git a/lib/openapi_parser/schemas.rb b/lib/openapi_parser/schemas.rb index b489b1a..c3bc824 100644 --- a/lib/openapi_parser/schemas.rb +++ b/lib/openapi_parser/schemas.rb @@ -15,4 +15,6 @@ require_relative 'schemas/media_type' require_relative 'schemas/schema' require_relative 'schemas/header' +require_relative 'schemas/security_schemes' require_relative 'schemas/info' +require_relative 'schemas/security' diff --git a/lib/openapi_parser/schemas/classes.rb b/lib/openapi_parser/schemas/classes.rb index a1b2ad7..a82dd9d 100644 --- a/lib/openapi_parser/schemas/classes.rb +++ b/lib/openapi_parser/schemas/classes.rb @@ -16,5 +16,7 @@ class MediaType < Base; end class Schema < Base; end class Components < Base; end class Header < Base; end + class SecuritySchemes < Base; end class Info < Base; end + class Security < Base; end end diff --git a/lib/openapi_parser/schemas/components.rb b/lib/openapi_parser/schemas/components.rb index 77f890d..1d0a30f 100644 --- a/lib/openapi_parser/schemas/components.rb +++ b/lib/openapi_parser/schemas/components.rb @@ -24,5 +24,9 @@ class Components < Base # @!attribute [r] headers # @return [Hash{String => Header}, nil] header objects openapi_attr_hash_object :headers, Header, reference: true + + # @!attribute [r] security_schemes + # @return [Hash{String => Header}, nil] + openapi_attr_hash_object :security_schemes, SecuritySchemes, reference: true, schema_key: :securitySchemes end end diff --git a/lib/openapi_parser/schemas/openapi.rb b/lib/openapi_parser/schemas/openapi.rb index 9ab7e63..c394edc 100644 --- a/lib/openapi_parser/schemas/openapi.rb +++ b/lib/openapi_parser/schemas/openapi.rb @@ -35,7 +35,7 @@ def initialize(raw_schema, config, uri: nil, schema_registry: {}) # @return [OpenAPIParser::RequestOperation, nil] def request_operation(http_method, request_path) - OpenAPIParser::RequestOperation.create(http_method, request_path, @path_item_finder, @config) + OpenAPIParser::RequestOperation.create(http_method, request_path, @path_item_finder, @config, components&.security_schemes) end # load another schema with shared config and schema_registry diff --git a/lib/openapi_parser/schemas/operation.rb b/lib/openapi_parser/schemas/operation.rb index 96b729c..99db3a4 100644 --- a/lib/openapi_parser/schemas/operation.rb +++ b/lib/openapi_parser/schemas/operation.rb @@ -10,9 +10,11 @@ class Operation < Base openapi_attr_values :tags, :summary, :description, :deprecated openapi_attr_value :operation_id, schema_key: :operationId - + openapi_attr_list_object :parameters, Parameter, reference: true + openapi_attr_list_object :security, Security, reference: false + # @!attribute [r] request_body # @return [OpenAPIParser::Schemas::RequestBody, nil] return OpenAPI3 object openapi_attr_object :request_body, RequestBody, reference: true, schema_key: :requestBody @@ -30,5 +32,30 @@ def validate_request_body(content_type, params, options) def validate_response(response_body, response_validate_options) responses&.validate(response_body, response_validate_options) end + + def validate_security_schemes(securitySchemes, headers) + validate_results = security.map do |s| # s is security requirement object + # check all security + s.validate_security_requirements(securitySchemes, headers) + end + if validate_results.count(true) == 1 + return true # accept + end + return OpenAPIParser::ValidateSecurityError + + # securitySchemes&.each do |securityScheme| + # # check if the endpoint has security in schema + # if security + # # if security exists, check what securitySchemas used for enforcing + # security.each do |s| + # # securityScheme[0] is the securitySchema name + # if s == securityScheme[0] + # # securitySchema[1] is the values like "type", "scheme" and bearerFormat + # securityScheme[1].validate_security_schemes(securityScheme[1], headers) + # end + # end + # end + # end + end end end diff --git a/lib/openapi_parser/schemas/security.rb b/lib/openapi_parser/schemas/security.rb new file mode 100644 index 0000000..acbf88c --- /dev/null +++ b/lib/openapi_parser/schemas/security.rb @@ -0,0 +1,7 @@ +module OpenAPIParser::Schemas + class Security < Base + + def validate_security_requirements(securityScheme, headers) + end + end +end diff --git a/lib/openapi_parser/schemas/security_schemes.rb b/lib/openapi_parser/schemas/security_schemes.rb new file mode 100644 index 0000000..f834058 --- /dev/null +++ b/lib/openapi_parser/schemas/security_schemes.rb @@ -0,0 +1,20 @@ +require 'jwt' +module OpenAPIParser::Schemas + class SecuritySchemes < Base + + openapi_attr_values :type, :description, :scheme + openapi_attr_value :bearer_format, schema_key: :bearerFormat + + def validate_security_schemes(securityScheme, headers) + if self.type == "http" && self.scheme == "bearer" && self.bearer_format == "JWT" + raise "need authorization" unless headers["AUTHORIZATION"] + raise "not bearer" unless headers["AUTHORIZATION"].split[0] == "Bearer" + + # check if the JWT token is being sent and try to decode. + # if JWT token does not exist or token cannot decode, then deny access + token = headers["AUTHORIZATION"].split[1] + JWT.decode token, nil, false + end + end + end +end diff --git a/openapi_parser.gemspec b/openapi_parser.gemspec index 19e739f..7572b7c 100644 --- a/openapi_parser.gemspec +++ b/openapi_parser.gemspec @@ -23,6 +23,8 @@ Gem::Specification.new do |spec| spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) } spec.require_paths = ['lib'] + # production + spec.add_dependency 'jwt', '~> 2.5' spec.add_development_dependency 'bundler', '>= 1.16' spec.add_development_dependency 'fincop' diff --git a/spec/data/petstore-expanded.yaml b/spec/data/petstore-expanded.yaml index eb17b83..3979b06 100644 --- a/spec/data/petstore-expanded.yaml +++ b/spec/data/petstore-expanded.yaml @@ -306,6 +306,11 @@ paths: $ref: '#/components/schemas/Pet' components: + securitySchemes: + jwt: + type: "http" + scheme: "bearer" + bearerFormat: "JWT" parameters: test: name: limit diff --git a/spec/openapi_parser/schemas/components_spec.rb b/spec/openapi_parser/schemas/components_spec.rb index bb8c16c..f8abb29 100644 --- a/spec/openapi_parser/schemas/components_spec.rb +++ b/spec/openapi_parser/schemas/components_spec.rb @@ -21,6 +21,8 @@ expect(subject.request_bodies['test_body'].class).to eq OpenAPIParser::Schemas::RequestBody expect(subject.request_bodies['test_body'].object_reference).to eq '#/components/requestBodies/test_body' + expect(subject.security_schemes['jwt'].class).to eq OpenAPIParser::Schemas::SecuritySchemes + expect(subject.headers['X-Rate-Limit-Limit'].class).to eq OpenAPIParser::Schemas::Header end end