Skip to content

Commit

Permalink
Merge pull request #166 from ipepe-oss/issue-160/cleanup-parameters-f…
Browse files Browse the repository at this point in the history
…rom-securityschemes

Add RSpec::OpenAPI::SchemaCleaner.cleanup_conflicting_security_parameters!
  • Loading branch information
exoego authored Jan 9, 2024
2 parents 71764c0 + 618e5e4 commit 8b60147
Show file tree
Hide file tree
Showing 9 changed files with 185 additions and 5 deletions.
3 changes: 2 additions & 1 deletion lib/rspec/openapi/result_recorder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def record_results!
config_file = File.join(File.dirname(path), RSpec::OpenAPI.config_filename)
begin
require config_file if File.exist?(config_file)
rescue => e
rescue StandardError => e
puts "WARNING: Unable to load #{config_file}: #{e}"
end

Expand All @@ -29,6 +29,7 @@ def record_results!
rescue StandardError, NotImplementedError => e # e.g. SchemaBuilder raises a NotImplementedError
@error_records[e] = record # Avoid failing the build
end
RSpec::OpenAPI::SchemaCleaner.cleanup_conflicting_security_parameters!(spec)
RSpec::OpenAPI::SchemaCleaner.cleanup!(spec, new_from_zero)
RSpec::OpenAPI::ComponentsUpdater.update!(spec, new_from_zero)
RSpec::OpenAPI::SchemaCleaner.cleanup_empty_required_array!(spec)
Expand Down
30 changes: 30 additions & 0 deletions lib/rspec/openapi/schema_cleaner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@ def cleanup!(base, spec)
base
end

def cleanup_conflicting_security_parameters!(base)
security_schemes = base.dig('components', 'securitySchemes') || {}

return if security_schemes.empty?

paths_to_security_definitions = RSpec::OpenAPI::HashHelper.matched_paths_deeply_nested(base, 'paths', 'security')

paths_to_security_definitions.each do |path|
parent_path_definition = base.dig(*path.take(path.length - 1))

security_schemes.each do |security_scheme_name, security_scheme|
remove_parameters_conflicting_with_security_sceheme!(
parent_path_definition, security_scheme, security_scheme_name,
)
end
end
end

def cleanup_empty_required_array!(base)
paths_to_objects = [
*RSpec::OpenAPI::HashHelper.matched_paths_deeply_nested(base, 'components.schemas', 'properties'),
Expand All @@ -53,6 +71,18 @@ def cleanup_empty_required_array!(base)

private

def remove_parameters_conflicting_with_security_sceheme!(path_definition, security_scheme, security_scheme_name)
return unless path_definition['security']
return unless path_definition['parameters']
return unless path_definition.dig('security', 0).keys.include?(security_scheme_name)

path_definition['parameters'].reject! do |parameter|
parameter['in'] == security_scheme['in'] && # same location (ie. header)
parameter['name'] == security_scheme['name'] # same name (ie. AUTHORIZATION)
end
path_definition.delete('parameters') if path_definition['parameters'].empty?
end

def cleanup_array!(base, spec, selector, fields_for_identity = [])
marshal = lambda do |obj|
Marshal.dump(slice(obj, fields_for_identity))
Expand Down
20 changes: 20 additions & 0 deletions spec/integration_tests/rails_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@
url: 'https://www.apache.org/licenses/LICENSE-2.0.html',
},
}
RSpec::OpenAPI.security_schemes = {
SecretApiKeyAuth: {
type: 'apiKey',
in: 'header',
name: 'Secret-Key',
},
}

class TablesIndexTest < ActionDispatch::IntegrationTest
i_suck_and_my_tests_are_order_dependent!
Expand Down Expand Up @@ -196,6 +203,19 @@ class ImageTest < ActionDispatch::IntegrationTest
end
end

class SecretKeyTest < ActionDispatch::IntegrationTest
i_suck_and_my_tests_are_order_dependent!
openapi!

test 'authorizes with secret key' do
get '/secret_items',
headers: {
'Secret-Key' => '42',
}
assert_response 200
end
end

class ExtraRoutesTest < ActionDispatch::IntegrationTest
i_suck_and_my_tests_are_order_dependent!
openapi!
Expand Down
5 changes: 5 additions & 0 deletions spec/rails/app/controllers/secret_items_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class SecretItemsController < ApplicationController
def index
render json: { items: ['secrets'] }
end
end
2 changes: 2 additions & 0 deletions spec/rails/config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,7 @@
end

get '/test_block' => ->(_env) { [200, { 'Content-Type' => 'text/plain' }, ['A TEST']] }

get '/secret_items' => 'secret_items#index'
end
end
63 changes: 63 additions & 0 deletions spec/rails/doc/openapi.json
Original file line number Diff line number Diff line change
Expand Up @@ -341,6 +341,60 @@
}
}
},
"/secret_items": {
"get": {
"summary": "index",
"tags": [
"SecretItem"
],
"security": [
{
"SecretApiKeyAuth": [

]
}
],
"responses": {
"200": {
"description": "authorizes with secret key",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "string"
}
}
},
"required": [
"items"
]
},
"example": {
"items": [
"secrets"
]
}
}
}
},
"401": {
"description": "authorizes with secret key",
"content": {
"text/html": {
"schema": {
"type": "string"
},
"example": ""
}
}
}
}
}
},
"/tables": {
"get": {
"summary": "index",
Expand Down Expand Up @@ -1035,5 +1089,14 @@
}
}
}
},
"components": {
"securitySchemes": {
"SecretApiKeyAuth": {
"type": "apiKey",
"in": "header",
"name": "Secret-Key"
}
}
}
}
37 changes: 37 additions & 0 deletions spec/rails/doc/openapi.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,37 @@ paths:
schema:
type: string
example: ANOTHER TEST
"/secret_items":
get:
summary: index
tags:
- SecretItem
security:
- SecretApiKeyAuth: []
responses:
'200':
description: authorizes with secret key
content:
application/json:
schema:
type: object
properties:
items:
type: array
items:
type: string
required:
- items
example:
items:
- secrets
'401':
description: authorizes with secret key
content:
text/html:
schema:
type: string
example: ''
"/tables":
get:
summary: index
Expand Down Expand Up @@ -678,3 +709,9 @@ paths:
schema:
type: string
example: A TEST
components:
securitySchemes:
SecretApiKeyAuth:
type: apiKey
in: header
name: Secret-Key
24 changes: 23 additions & 1 deletion spec/requests/rails_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
require 'rspec/rails'

RSpec::OpenAPI.title = 'OpenAPI Documentation'
RSpec::OpenAPI.request_headers = %w[X-Authorization-Token]
RSpec::OpenAPI.request_headers = %w[X-Authorization-Token Secret-Key]
RSpec::OpenAPI.response_headers = %w[X-Cursor]
RSpec::OpenAPI.path = File.expand_path("../rails/doc/openapi.#{ENV.fetch('OPENAPI_OUTPUT', nil)}", __dir__)
RSpec::OpenAPI.comment = <<~COMMENT
Expand All @@ -26,6 +26,14 @@
},
}

RSpec::OpenAPI.security_schemes = {
SecretApiKeyAuth: {
type: 'apiKey',
in: 'header',
name: 'Secret-Key',
},
}

RSpec.describe 'Tables', type: :request do
describe '#index' do
context it 'returns a list of tables' do
Expand Down Expand Up @@ -198,6 +206,20 @@
end
end

RSpec.describe 'SecretKey securityScheme',
type: :request,
openapi: { security: [{ 'SecretApiKeyAuth' => [] }] } do
describe '#secret_items' do
it 'authorizes with secret key' do
get '/secret_items',
headers: {
'Secret-Key' => '42',
}
expect(response.status).to eq(200)
end
end
end

RSpec.describe 'Extra routes', type: :request do
describe '#test_block' do
it 'returns the block content' do
Expand Down
6 changes: 3 additions & 3 deletions spec/rspec/rails_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,10 @@
end

it 'generates the same spec/rails/doc/openapi.json' do
org_yaml = JSON.parse(File.read(openapi_path))
org_json = JSON.parse(File.read(openapi_path))
rspec 'spec/requests/rails_spec.rb', openapi: true, output: :json
new_yaml = JSON.parse(File.read(openapi_path))
expect(new_yaml).to eq org_yaml
new_json = JSON.parse(File.read(openapi_path))
expect(new_json).to eq org_json
end
end

Expand Down

0 comments on commit 8b60147

Please sign in to comment.