diff --git a/openapi_core/schema/operations/generators.py b/openapi_core/schema/operations/generators.py index c149856..d30166c 100644 --- a/openapi_core/schema/operations/generators.py +++ b/openapi_core/schema/operations/generators.py @@ -42,12 +42,16 @@ class OperationsGenerator(object): tags_list = operation_deref.get('tags', []) summary = operation_deref.get('summary') description = operation_deref.get('description') - security_spec = operation_deref.get('security', []) servers_spec = operation_deref.get('servers', []) servers = self.servers_generator.generate(servers_spec) - security = self.security_requirements_generator.generate( - security_spec) + + security = None + if 'security' in operation_deref: + security_spec = operation_deref.get('security') + security = self.security_requirements_generator.generate( + security_spec) + extensions = self.extensions_generator.generate(operation_deref) external_docs = None @@ -67,7 +71,7 @@ class OperationsGenerator(object): Operation( http_method, path_name, responses, list(parameters), summary=summary, description=description, - external_docs=external_docs, security=list(security), + external_docs=external_docs, security=security, request_body=request_body, deprecated=deprecated, operation_id=operation_id, tags=list(tags_list), servers=list(servers), extensions=extensions, diff --git a/openapi_core/schema/operations/models.py b/openapi_core/schema/operations/models.py index f7bc773..f2acaa1 100644 --- a/openapi_core/schema/operations/models.py +++ b/openapi_core/schema/operations/models.py @@ -18,7 +18,7 @@ class Operation(object): self.summary = summary self.description = description self.external_docs = external_docs - self.security = security + self.security = security and list(security) self.request_body = request_body self.deprecated = deprecated self.operation_id = operation_id diff --git a/openapi_core/validation/request/validators.py b/openapi_core/validation/request/validators.py index 6d26058..4e5c4af 100644 --- a/openapi_core/validation/request/validators.py +++ b/openapi_core/validation/request/validators.py @@ -87,7 +87,10 @@ class RequestValidator(BaseValidator): ) def _get_security(self, request, operation): - security = operation.security or self.spec.security + security = self.spec.security + if operation.security is not None: + security = operation.security + if not security: return {} diff --git a/tests/integration/data/v3.0/security_override.yaml b/tests/integration/data/v3.0/security_override.yaml new file mode 100644 index 0000000..8d096ff --- /dev/null +++ b/tests/integration/data/v3.0/security_override.yaml @@ -0,0 +1,41 @@ +openapi: "3.0.0" +info: + title: Minimal OpenAPI specification with security override + version: "0.1" +security: + - api_key: [] +paths: + /resource/{resId}: + parameters: + - name: resId + in: path + required: true + description: the ID of the resource to retrieve + schema: + type: string + get: + responses: + default: + description: Default security. + post: + security: + - petstore_auth: + - write:pets + - read:pets + responses: + default: + description: Override security. + put: + security: [] + responses: + default: + description: Remove security. +components: + securitySchemes: + api_key: + type: apiKey + name: api_key + in: query + petstore_auth: + type: http + scheme: basic diff --git a/tests/integration/schema/test_spec.py b/tests/integration/schema/test_spec.py index a2e31f0..0537306 100644 --- a/tests/integration/schema/test_spec.py +++ b/tests/integration/schema/test_spec.py @@ -152,14 +152,15 @@ class TestPetstore(object): assert variable.default == variable_spec['default'] assert variable.enum == variable_spec.get('enum') - security_spec = operation_spec.get('security', []) - for idx, security_req in enumerate(operation.security): - assert type(security_req) == SecurityRequirement + security_spec = operation_spec.get('security') + if security_spec is not None: + for idx, security_req in enumerate(operation.security): + assert type(security_req) == SecurityRequirement - security_req_spec = security_spec[idx] - for scheme_name in security_req: - security_req[scheme_name] == security_req_spec[ - scheme_name] + security_req_spec = security_spec[idx] + for scheme_name in security_req: + security_req[scheme_name] == security_req_spec[ + scheme_name] responses_spec = operation_spec.get('responses') diff --git a/tests/integration/validation/test_security_override.py b/tests/integration/validation/test_security_override.py new file mode 100644 index 0000000..370012c --- /dev/null +++ b/tests/integration/validation/test_security_override.py @@ -0,0 +1,86 @@ +from base64 import b64encode + +import pytest +from six import text_type + +from openapi_core.shortcuts import create_spec +from openapi_core.validation.exceptions import InvalidSecurity +from openapi_core.validation.request.validators import RequestValidator +from openapi_core.testing import MockRequest + + +@pytest.fixture +def request_validator(spec): + return RequestValidator(spec) + + +@pytest.fixture('class') +def spec(factory): + spec_dict = factory.spec_from_file("data/v3.0/security_override.yaml") + return create_spec(spec_dict) + + +class TestSecurityOverride(object): + + host_url = 'http://petstore.swagger.io' + + api_key = '12345' + + @property + def api_key_encoded(self): + api_key_bytes = self.api_key.encode('utf8') + api_key_bytes_enc = b64encode(api_key_bytes) + return text_type(api_key_bytes_enc, 'utf8') + + def test_default(self, request_validator): + args = {'api_key': self.api_key} + request = MockRequest( + self.host_url, 'get', '/resource/one', args=args) + + result = request_validator.validate(request) + + assert not result.errors + assert result.security == { + 'api_key': self.api_key, + } + + def test_default_invalid(self, request_validator): + request = MockRequest(self.host_url, 'get', '/resource/one') + + result = request_validator.validate(request) + + assert type(result.errors[0]) == InvalidSecurity + assert result.security is None + + def test_override(self, request_validator): + authorization = 'Basic ' + self.api_key_encoded + headers = { + 'Authorization': authorization, + } + request = MockRequest( + self.host_url, 'post', '/resource/one', headers=headers) + + result = request_validator.validate(request) + + assert not result.errors + assert result.security == { + 'petstore_auth': self.api_key_encoded, + } + + def test_override_invalid(self, request_validator): + request = MockRequest( + self.host_url, 'post', '/resource/one') + + result = request_validator.validate(request) + + assert type(result.errors[0]) == InvalidSecurity + assert result.security is None + + def test_remove(self, request_validator): + request = MockRequest( + self.host_url, 'put', '/resource/one') + + result = request_validator.validate(request) + + assert not result.errors + assert result.security == {}