Skip to content

Security validation #195

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Feb 4, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions openapi_core/schema/components/factories.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
from openapi_core.compat import lru_cache
from openapi_core.schema.components.models import Components
from openapi_core.schema.schemas.generators import SchemasGenerator
from openapi_core.schema.security_schemes.generators import (
SecuritySchemesGenerator,
)


class ComponentsFactory(object):
Expand All @@ -15,15 +18,18 @@ def create(self, components_spec):
schemas_spec = components_deref.get('schemas', {})
responses_spec = components_deref.get('responses', {})
parameters_spec = components_deref.get('parameters', {})
request_bodies_spec = components_deref.get('request_bodies', {})
request_bodies_spec = components_deref.get('requestBodies', {})
security_schemes_spec = components_deref.get('securitySchemes', {})

schemas = self.schemas_generator.generate(schemas_spec)
responses = self._generate_response(responses_spec)
parameters = self._generate_parameters(parameters_spec)
request_bodies = self._generate_request_bodies(request_bodies_spec)
security_schemes = self._generate_security_schemes(
security_schemes_spec)
return Components(
schemas=list(schemas), responses=responses, parameters=parameters,
request_bodies=request_bodies,
request_bodies=request_bodies, security_schemes=security_schemes,
)

@property
Expand All @@ -39,3 +45,7 @@ def _generate_parameters(self, parameters_spec):

def _generate_request_bodies(self, request_bodies_spec):
return request_bodies_spec

def _generate_security_schemes(self, security_schemes_spec):
return SecuritySchemesGenerator(self.dereferencer).generate(
security_schemes_spec)
5 changes: 4 additions & 1 deletion openapi_core/schema/components/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,11 @@ class Components(object):

def __init__(
self, schemas=None, responses=None, parameters=None,
request_bodies=None):
request_bodies=None, security_schemes=None):
self.schemas = schemas and dict(schemas) or {}
self.responses = responses and dict(responses) or {}
self.parameters = parameters and dict(parameters) or {}
self.request_bodies = request_bodies and dict(request_bodies) or {}
self.security_schemes = (
security_schemes and dict(security_schemes) or {}
)
22 changes: 10 additions & 12 deletions openapi_core/schema/operations/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
from openapi_core.schema.parameters.generators import ParametersGenerator
from openapi_core.schema.request_bodies.factories import RequestBodyFactory
from openapi_core.schema.responses.generators import ResponsesGenerator
from openapi_core.schema.security.factories import SecurityRequirementFactory
from openapi_core.schema.security_requirements.generators import (
SecurityRequirementsGenerator,
)
from openapi_core.schema.servers.generators import ServersGenerator


Expand Down Expand Up @@ -39,16 +41,12 @@ def generate(self, path_name, path):
tags_list = operation_deref.get('tags', [])
summary = operation_deref.get('summary')
description = operation_deref.get('description')
security_requirements_list = operation_deref.get('security', [])
security_spec = operation_deref.get('security', [])
servers_spec = operation_deref.get('servers', [])

servers = self.servers_generator.generate(servers_spec)

security = None
if security_requirements_list:
security = list(map(
self.security_requirement_factory.create,
security_requirements_list))
security = self.security_requirements_generator.generate(
security_spec)

external_docs = None
if 'externalDocs' in operation_deref:
Expand All @@ -67,10 +65,10 @@ def generate(self, path_name, path):
Operation(
http_method, path_name, responses, list(parameters),
summary=summary, description=description,
external_docs=external_docs, security=security,
external_docs=external_docs, security=list(security),
request_body=request_body, deprecated=deprecated,
operation_id=operation_id, tags=list(tags_list),
servers=servers,
servers=list(servers),
),
)

Expand All @@ -96,8 +94,8 @@ def request_body_factory(self):

@property
@lru_cache()
def security_requirement_factory(self):
return SecurityRequirementFactory(self.dereferencer)
def security_requirements_generator(self):
return SecurityRequirementsGenerator(self.dereferencer)

@property
@lru_cache()
Expand Down
14 changes: 0 additions & 14 deletions openapi_core/schema/security/factories.py

This file was deleted.

9 changes: 0 additions & 9 deletions openapi_core/schema/security/models.py

This file was deleted.

15 changes: 15 additions & 0 deletions openapi_core/schema/security_requirements/generators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""OpenAPI core security requirements generators module"""
from openapi_core.schema.security_requirements.models import (
SecurityRequirement,
)


class SecurityRequirementsGenerator(object):

def __init__(self, dereferencer):
self.dereferencer = dereferencer

def generate(self, security_spec):
security_deref = self.dereferencer.dereference(security_spec)
for security_requirement_spec in security_deref:
yield SecurityRequirement(security_requirement_spec)
6 changes: 6 additions & 0 deletions openapi_core/schema/security_requirements/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""OpenAPI core security requirements models module"""


class SecurityRequirement(dict):
"""Represents an OpenAPI Security Requirement."""
pass
Empty file.
27 changes: 27 additions & 0 deletions openapi_core/schema/security_schemes/enums.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"""OpenAPI core security schemes enums module"""
from enum import Enum


class SecuritySchemeType(Enum):

API_KEY = 'apiKey'
HTTP = 'http'
OAUTH2 = 'oauth2'
OPEN_ID_CONNECT = 'openIdConnect'


class ApiKeyLocation(Enum):

QUERY = 'query'
HEADER = 'header'
COOKIE = 'cookie'

@classmethod
def has_value(cls, value):
return (any(value == item.value for item in cls))


class HttpAuthScheme(Enum):

BASIC = 'basic'
BEARER = 'bearer'
37 changes: 37 additions & 0 deletions openapi_core/schema/security_schemes/generators.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
"""OpenAPI core security schemes generators module"""
import logging

from six import iteritems

from openapi_core.schema.security_schemes.models import SecurityScheme

log = logging.getLogger(__name__)


class SecuritySchemesGenerator(object):

def __init__(self, dereferencer):
self.dereferencer = dereferencer

def generate(self, security_schemes_spec):
security_schemes_deref = self.dereferencer.dereference(
security_schemes_spec)

for scheme_name, scheme_spec in iteritems(security_schemes_deref):
scheme_deref = self.dereferencer.dereference(scheme_spec)
scheme_type = scheme_deref['type']
description = scheme_deref.get('description')
name = scheme_deref.get('name')
apikey_in = scheme_deref.get('in')
scheme = scheme_deref.get('scheme')
bearer_format = scheme_deref.get('bearerFormat')
flows = scheme_deref.get('flows')
open_id_connect_url = scheme_deref.get('openIdConnectUrl')

scheme = SecurityScheme(
scheme_type, description=description, name=name,
apikey_in=apikey_in, scheme=scheme,
bearer_format=bearer_format, flows=flows,
open_id_connect_url=open_id_connect_url,
)
yield scheme_name, scheme
22 changes: 22 additions & 0 deletions openapi_core/schema/security_schemes/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"""OpenAPI core security schemes models module"""
from openapi_core.schema.security_schemes.enums import (
SecuritySchemeType, ApiKeyLocation, HttpAuthScheme,
)


class SecurityScheme(object):
"""Represents an OpenAPI Security Scheme."""

def __init__(
self, scheme_type, description=None, name=None, apikey_in=None,
scheme=None, bearer_format=None, flows=None,
open_id_connect_url=None,
):
self.type = SecuritySchemeType(scheme_type)
self.description = description
self.name = name
self.apikey_in = apikey_in and ApiKeyLocation(apikey_in)
self.scheme = scheme and HttpAuthScheme(scheme)
self.bearer_format = bearer_format
self.flows = flows
self.open_id_connect_url = open_id_connect_url
14 changes: 14 additions & 0 deletions openapi_core/schema/specs/factories.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
from openapi_core.schema.infos.factories import InfoFactory
from openapi_core.schema.paths.generators import PathsGenerator
from openapi_core.schema.schemas.registries import SchemaRegistry
from openapi_core.schema.security_requirements.generators import (
SecurityRequirementsGenerator,
)
from openapi_core.schema.servers.generators import ServersGenerator
from openapi_core.schema.specs.models import Spec

Expand All @@ -29,6 +32,7 @@ def create(self, spec_dict, spec_url=''):
servers_spec = spec_dict_deref.get('servers', [])
paths = spec_dict_deref.get('paths', {})
components_spec = spec_dict_deref.get('components', {})
security_spec = spec_dict_deref.get('security', [])

if not servers_spec:
servers_spec = [
Expand All @@ -39,8 +43,13 @@ def create(self, spec_dict, spec_url=''):
servers = self.servers_generator.generate(servers_spec)
paths = self.paths_generator.generate(paths)
components = self.components_factory.create(components_spec)

security = self.security_requirements_generator.generate(
security_spec)

spec = Spec(
info, list(paths), servers=list(servers), components=components,
security=list(security),
_resolver=self.spec_resolver,
)
return spec
Expand Down Expand Up @@ -74,3 +83,8 @@ def paths_generator(self):
@lru_cache()
def components_factory(self):
return ComponentsFactory(self.dereferencer, self.schemas_registry)

@property
@lru_cache()
def security_requirements_generator(self):
return SecurityRequirementsGenerator(self.dereferencer)
4 changes: 3 additions & 1 deletion openapi_core/schema/specs/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@ class Spec(object):
"""Represents an OpenAPI Specification for a service."""

def __init__(
self, info, paths, servers=None, components=None, _resolver=None):
self, info, paths, servers=None, components=None,
security=None, _resolver=None):
self.info = info
self.paths = paths and dict(paths)
self.servers = servers or []
self.components = components
self.security = security

self._resolver = _resolver

Expand Down
Empty file.
5 changes: 5 additions & 0 deletions openapi_core/security/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
from openapi_core.exceptions import OpenAPIError


class SecurityError(OpenAPIError):
pass
19 changes: 19 additions & 0 deletions openapi_core/security/factories.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from openapi_core.schema.security_schemes.enums import SecuritySchemeType
from openapi_core.security.providers import (
ApiKeyProvider, HttpProvider, UnsupportedProvider,
)


class SecurityProviderFactory(object):

PROVIDERS = {
SecuritySchemeType.API_KEY: ApiKeyProvider,
SecuritySchemeType.HTTP: HttpProvider,
}

def create(self, scheme):
if scheme.type == SecuritySchemeType.API_KEY:
return ApiKeyProvider(scheme)
elif scheme.type == SecuritySchemeType.HTTP:
return HttpProvider(scheme)
return UnsupportedProvider(scheme)
47 changes: 47 additions & 0 deletions openapi_core/security/providers.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import base64
import binascii
import warnings

from openapi_core.security.exceptions import SecurityError


class BaseProvider(object):

def __init__(self, scheme):
self.scheme = scheme


class UnsupportedProvider(BaseProvider):

def __call__(self, request):
warnings.warn("Unsupported scheme type")


class ApiKeyProvider(BaseProvider):

def __call__(self, request):
source = getattr(request.parameters, self.scheme.apikey_in.value)
if self.scheme.name not in source:
raise SecurityError("Missing api key parameter.")
return source.get(self.scheme.name)


class HttpProvider(BaseProvider):

def __call__(self, request):
if 'Authorization' not in request.parameters.header:
raise SecurityError('Missing authorization header.')
auth_header = request.parameters.header['Authorization']
try:
auth_type, encoded_credentials = auth_header.split(' ', 1)
except ValueError:
raise SecurityError('Could not parse authorization header.')

if auth_type.lower() != self.scheme.scheme.value:
raise SecurityError(
'Unknown authorization method %s' % auth_type)
try:
return base64.b64decode(
encoded_credentials.encode('ascii')).decode('latin1')
except binascii.Error:
raise SecurityError('Invalid base64 encoding.')
15 changes: 15 additions & 0 deletions openapi_core/validation/exceptions.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""OpenAPI core validation exceptions module"""
import attr

from openapi_core.exceptions import OpenAPIError


class ValidationError(OpenAPIError):
pass


@attr.s(hash=True)
class InvalidSecurity(ValidationError):

def __str__(self):
return "Security not valid for any requirement"
1 change: 1 addition & 0 deletions openapi_core/validation/request/datatypes.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,4 @@ def full_url_pattern(self):
class RequestValidationResult(BaseValidationResult):
body = attr.ib(default=None)
parameters = attr.ib(factory=RequestParameters)
security = attr.ib(default=None)
Loading