From ad9cd5de663967031d13e43e6297476363e3fc6a Mon Sep 17 00:00:00 2001 From: domaindrivendev Date: Mon, 21 Aug 2017 01:07:47 -0700 Subject: [PATCH] Support paired security requirements - e.g. basic and apiKey --- .../lib/rswag/specs/request_factory.rb | 10 +-- .../spec/rswag/specs/request_factory_spec.rb | 41 ++++++++---- .../app/controllers/auth_tests_controller.rb | 29 +++++++-- test-app/config/routes.rb | 2 + test-app/spec/integration/auth_tests_spec.rb | 40 +++++++++++- test-app/spec/integration/blogs_spec.rb | 2 +- test-app/spec/swagger_helper.rb | 5 +- test-app/swagger/v1/swagger.json | 64 ++++++++++++++++--- 8 files changed, 155 insertions(+), 38 deletions(-) diff --git a/rswag-specs/lib/rswag/specs/request_factory.rb b/rswag-specs/lib/rswag/specs/request_factory.rb index 81aeb54..0a880e8 100644 --- a/rswag-specs/lib/rswag/specs/request_factory.rb +++ b/rswag-specs/lib/rswag/specs/request_factory.rb @@ -38,13 +38,13 @@ module Rswag end def derive_security_params(metadata, swagger_doc) - requirements = metadata[:operation][:security] || swagger_doc[:security] - scheme_names = requirements ? requirements.map { |r| r.keys.first } : [] - applicable_schemes = (swagger_doc[:securityDefinitions] || {}).slice(*scheme_names).values + requirements = metadata[:operation][:security] || swagger_doc[:security] || [] + scheme_names = requirements.flat_map { |r| r.keys } + schemes = (swagger_doc[:securityDefinitions] || {}).slice(*scheme_names).values - applicable_schemes.map do |scheme| + schemes.map do |scheme| param = (scheme[:type] == :apiKey) ? scheme.slice(:name, :in) : { name: 'Authorization', in: :header } - param.merge(type: :string) + param.merge(type: :string, required: requirements.one?) end end diff --git a/rswag-specs/spec/rswag/specs/request_factory_spec.rb b/rswag-specs/spec/rswag/specs/request_factory_spec.rb index d906644..3ed4736 100644 --- a/rswag-specs/spec/rswag/specs/request_factory_spec.rb +++ b/rswag-specs/spec/rswag/specs/request_factory_spec.rb @@ -201,6 +201,18 @@ module Rswag end end + context 'basic auth' do + before do + swagger_doc[:securityDefinitions] = { basic: { type: :basic } } + metadata[:operation][:security] = [ basic: [] ] + allow(example).to receive(:Authorization).and_return('Basic foobar') + end + + it "sets 'HTTP_AUTHORIZATION' header to example value" do + expect(request[:headers]).to eq('HTTP_AUTHORIZATION' => 'Basic foobar') + end + end + context 'apiKey' do before do swagger_doc[:securityDefinitions] = { apiKey: { type: :apiKey, name: 'api_key', in: key_location } } @@ -225,18 +237,6 @@ module Rswag end end - context 'basic auth' do - before do - swagger_doc[:securityDefinitions] = { basic: { type: :basic } } - metadata[:operation][:security] = [ basic: [] ] - allow(example).to receive(:Authorization).and_return('Basic foobar') - end - - it "sets 'HTTP_AUTHORIZATION' header to example value" do - expect(request[:headers]).to eq('HTTP_AUTHORIZATION' => 'Basic foobar') - end - end - context 'oauth2' do before do swagger_doc[:securityDefinitions] = { oauth2: { type: :oauth2, scopes: [ 'read:blogs' ] } } @@ -249,6 +249,23 @@ module Rswag end end + context 'paired security requirements' do + before do + swagger_doc[:securityDefinitions] = { + basic: { type: :basic }, + api_key: { type: :apiKey, name: 'api_key', in: :query } + } + metadata[:operation][:security] = [ { basic: [], api_key: [] } ] + allow(example).to receive(:Authorization).and_return('Basic foobar') + allow(example).to receive(:api_key).and_return('foobar') + end + + it "sets both params to example values" do + expect(request[:headers]).to eq('HTTP_AUTHORIZATION' => 'Basic foobar') + expect(request[:path]).to eq('/blogs?api_key=foobar') + end + end + context "path-level parameters" do before do metadata[:operation][:parameters] = [ { name: 'q1', in: :query, type: :string } ] diff --git a/test-app/app/controllers/auth_tests_controller.rb b/test-app/app/controllers/auth_tests_controller.rb index 162ef2f..bd502fe 100644 --- a/test-app/app/controllers/auth_tests_controller.rb +++ b/test-app/app/controllers/auth_tests_controller.rb @@ -2,10 +2,29 @@ class AuthTestsController < ApplicationController # POST /auth-tests/basic def basic - if authenticate_with_http_basic { |u, p| u == 'jsmith' && p == 'jspass' } - head :no_content - else - request_http_basic_authentication - end + return head :unauthorized unless authenticate_basic + head :no_content + end + + # POST /auth-tests/api-key + def api_key + return head :unauthorized unless authenticate_api_key + head :no_content + end + + # POST /auth-tests/basic-and-api-key + def basic_and_api_key + return head :unauthorized unless authenticate_basic and authenticate_api_key + head :no_content + end + + private + + def authenticate_basic + authenticate_with_http_basic { |u, p| u == 'jsmith' && p == 'jspass' } + end + + def authenticate_api_key + params['api_key'] == 'foobar' end end diff --git a/test-app/config/routes.rb b/test-app/config/routes.rb index dff0779..be02215 100644 --- a/test-app/config/routes.rb +++ b/test-app/config/routes.rb @@ -3,6 +3,8 @@ TestApp::Application.routes.draw do put '/blogs/:id/upload', to: 'blogs#upload' post 'auth-tests/basic', to: 'auth_tests#basic' + post 'auth-tests/api-key', to: 'auth_tests#api_key' + post 'auth-tests/basic-and-api-key', to: 'auth_tests#basic_and_api_key' mount Rswag::Api::Engine => 'api-docs' mount Rswag::Ui::Engine => 'api-docs' diff --git a/test-app/spec/integration/auth_tests_spec.rb b/test-app/spec/integration/auth_tests_spec.rb index 9a6a4f4..8e47d2e 100644 --- a/test-app/spec/integration/auth_tests_spec.rb +++ b/test-app/spec/integration/auth_tests_spec.rb @@ -4,7 +4,7 @@ describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json' do path '/auth-tests/basic' do post 'Authenticates with basic auth' do - tags 'Auth Test' + tags 'Auth Tests' operationId 'testBasicAuth' security [ basic_auth: [] ] @@ -19,4 +19,42 @@ describe 'Auth Tests API', type: :request, swagger_doc: 'v1/swagger.json' do end end end + + path '/auth-tests/api-key' do + post 'Authenticates with an api key' do + tags 'Auth Tests' + operationId 'testApiKey' + security [ api_key: [] ] + + response '204', 'Valid credentials' do + let(:api_key) { 'foobar' } + run_test! + end + + response '401', 'Invalid credentials' do + let(:api_key) { 'barfoo' } + run_test! + end + end + end + + path '/auth-tests/basic-and-api-key' do + post 'Authenticates with basic auth and api key' do + tags 'Auth Tests' + operationId 'testBasicAndApiKey' + security [ { basic_auth: [], api_key: [] } ] + + response '204', 'Valid credentials' do + let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" } + let(:api_key) { 'foobar' } + run_test! + end + + response '401', 'Invalid credentials' do + let(:Authorization) { "Basic #{::Base64.strict_encode64('jsmith:jspass')}" } + let(:api_key) { 'barfoo' } + run_test! + end + end + end end diff --git a/test-app/spec/integration/blogs_spec.rb b/test-app/spec/integration/blogs_spec.rb index fe7e8c0..abca570 100644 --- a/test-app/spec/integration/blogs_spec.rb +++ b/test-app/spec/integration/blogs_spec.rb @@ -91,7 +91,7 @@ describe 'Blogs API', type: :request, swagger_doc: 'v1/swagger.json' do let(:id) { blog.id } let(:blog) { Blog.create(title: 'foo', content: 'bar') } - put 'upload a blog thumbnail' do + put 'Uploads a blog thumbnail' do tags 'Blogs' description 'Upload a thumbnail for specific blog by id' operationId 'uploadThumbnailBlog' diff --git a/test-app/spec/swagger_helper.rb b/test-app/spec/swagger_helper.rb index 1575a58..6a8f4e5 100644 --- a/test-app/spec/swagger_helper.rb +++ b/test-app/spec/swagger_helper.rb @@ -54,10 +54,7 @@ RSpec.configure do |config| name: 'api_key', in: :query } - }, - security: [ - { api_key: [] } - ] + } } } end diff --git a/test-app/swagger/v1/swagger.json b/test-app/swagger/v1/swagger.json index 6489e76..8531c7a 100644 --- a/test-app/swagger/v1/swagger.json +++ b/test-app/swagger/v1/swagger.json @@ -9,7 +9,7 @@ "post": { "summary": "Authenticates with basic auth", "tags": [ - "Auth Test" + "Auth Tests" ], "operationId": "testBasicAuth", "security": [ @@ -29,6 +29,57 @@ } } }, + "/auth-tests/api-key": { + "post": { + "summary": "Authenticates with an api key", + "tags": [ + "Auth Tests" + ], + "operationId": "testApiKey", + "security": [ + { + "api_key": [ + + ] + } + ], + "responses": { + "204": { + "description": "Valid credentials" + }, + "401": { + "description": "Invalid credentials" + } + } + } + }, + "/auth-tests/basic-and-api-key": { + "post": { + "summary": "Authenticates with basic auth and api key", + "tags": [ + "Auth Tests" + ], + "operationId": "testBasicAndApiKey", + "security": [ + { + "basic_auth": [ + + ], + "api_key": [ + + ] + } + ], + "responses": { + "204": { + "description": "Valid credentials" + }, + "401": { + "description": "Invalid credentials" + } + } + } + }, "/blogs": { "post": { "summary": "Creates a blog", @@ -149,7 +200,7 @@ } ], "put": { - "summary": "upload a blog thumbnail", + "summary": "Uploads a blog thumbnail", "tags": [ "Blogs" ], @@ -226,12 +277,5 @@ "name": "api_key", "in": "query" } - }, - "security": [ - { - "api_key": [ - - ] - } - ] + } } \ No newline at end of file