Merge "Added validations"

This commit is contained in:
Zuul 2018-11-19 13:58:29 +00:00 committed by Gerrit Code Review
commit c1ae8b36b0
9 changed files with 103 additions and 100 deletions

View File

@ -22,7 +22,7 @@ use = egg:Paste#urlmap
/healthcheck: events_healthcheck /healthcheck: events_healthcheck
[pipeline:events_api_v1] [pipeline:events_api_v1]
pipeline = error_trap request_id auth sizelimit middleware api_v1_app pipeline = error_trap request_id auth sizelimit api_v1_app
[pipeline:events_version] [pipeline:events_version]
pipeline = error_trap versionapp pipeline = error_trap versionapp
@ -48,9 +48,6 @@ paste.filter_factory = oslo_middleware.catch_errors:CatchErrors.factory
[filter:request_id] [filter:request_id]
paste.filter_factory = oslo_middleware.request_id:RequestId.factory paste.filter_factory = oslo_middleware.request_id:RequestId.factory
[filter:middleware]
paste.filter_factory = monasca_events_api.middleware.validation_middleware:ValidationMiddleware.factory
[filter:sizelimit] [filter:sizelimit]
use = egg:oslo.middleware#sizelimit use = egg:oslo.middleware#sizelimit

View File

@ -14,8 +14,12 @@
import six import six
from falcon import HTTPUnprocessableEntity
from oslo_log import log from oslo_log import log
from voluptuous import All
from voluptuous import Any from voluptuous import Any
from voluptuous import Length
from voluptuous import MultipleInvalid
from voluptuous import Required from voluptuous import Required
from voluptuous import Schema from voluptuous import Schema
@ -23,7 +27,8 @@ from voluptuous import Schema
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
default_schema = Schema({Required("events"): Any(list, dict), default_schema = Schema({Required("events"): All(Any(list, dict),
Length(min=1)),
Required("timestamp"): six.text_type}) Required("timestamp"): six.text_type})
@ -33,7 +38,10 @@ def validate_body(request_body):
Method validate if body contain all required fields, Method validate if body contain all required fields,
and check if all value have correct type. and check if all value have correct type.
:param request_body: body :param request_body: body
""" """
default_schema(request_body) try:
default_schema(request_body)
except MultipleInvalid as ex:
LOG.exception(ex)
raise HTTPUnprocessableEntity(description=ex.error_message)

View File

@ -13,13 +13,12 @@
# under the License. # under the License.
import falcon import falcon
from oslo_log import log
from voluptuous import MultipleInvalid
from monasca_events_api.app.common import helpers from monasca_events_api.app.common import helpers
from monasca_events_api.app.controller.v1 import body_validation from monasca_events_api.app.controller.v1 import body_validation
from monasca_events_api.app.controller.v1 import bulk_processor from monasca_events_api.app.controller.v1 import bulk_processor
from monasca_events_api.app.core.model import prepare_message_to_sent from monasca_events_api.app.core.model import prepare_message_to_sent
from oslo_log import log
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -51,6 +50,7 @@ class Events(object):
policy_action = 'events_api:agent_required' policy_action = 'events_api:agent_required'
try: try:
req.validate(self.SUPPORTED_CONTENT_TYPES)
request_body = helpers.read_json_msg_body(req) request_body = helpers.read_json_msg_body(req)
req.can(policy_action) req.can(policy_action)
project_id = req.project_id project_id = req.project_id
@ -58,14 +58,22 @@ class Events(object):
messages = prepare_message_to_sent(request_body) messages = prepare_message_to_sent(request_body)
self._processor.send_message(messages, event_project_id=project_id) self._processor.send_message(messages, event_project_id=project_id)
res.status = falcon.HTTP_200 res.status = falcon.HTTP_200
except MultipleInvalid as ex: except falcon.HTTPUnprocessableEntity as ex:
LOG.error('Entire bulk package was rejected, unsupported body') LOG.error('Entire bulk package was rejected, unsupported body')
LOG.exception(ex) LOG.exception(ex)
res.status = falcon.HTTP_422 raise ex
except falcon.HTTPUnsupportedMediaType as ex:
LOG.error('Entire bulk package was rejected, '
'unsupported media type')
LOG.exception(ex)
raise ex
except Exception as ex: except Exception as ex:
LOG.error('Entire bulk package was rejected') LOG.error('Entire bulk package was rejected')
LOG.exception(ex) LOG.exception(ex)
res.status = falcon.HTTP_400 _title = ex.title if hasattr(ex, 'title') else None
_descr = ex.description if hasattr(ex, 'description') else None
raise falcon.HTTPError(falcon.HTTP_400,
title=_title, description=_descr)
@property @property
def version(self): def version(self):

View File

@ -17,6 +17,7 @@ from monasca_common.policy import policy_engine as policy
from oslo_log import log from oslo_log import log
from monasca_events_api.app.core import request_contex from monasca_events_api.app.core import request_contex
from monasca_events_api.app.core import validation
from monasca_events_api import policies from monasca_events_api import policies
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
@ -38,5 +39,19 @@ class Request(falcon.Request):
self.is_admin = policy.check_is_admin(self.context) self.is_admin = policy.check_is_admin(self.context)
self.project_id = self.context.project_id self.project_id = self.context.project_id
def validate(self, content_types):
"""Performs common request validation
Validation checklist (in that order):
* :py:func:`validation.validate_content_type`
:param content_types: allowed content-types handler supports
:type content_types: list
:raises Exception: if any of the validation fails
"""
validation.validate_content_type(self, content_types)
def can(self, action, target=None): def can(self, action, target=None):
return self.context.can(action, target) return self.context.can(action, target)

View File

@ -1,4 +1,4 @@
# Copyright 2017 FUJITSU LIMITED # Copyright 2018 FUJITSU LIMITED
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may # Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain # not use this file except in compliance with the License. You may obtain
@ -14,54 +14,37 @@
import falcon import falcon
from oslo_log import log from oslo_log import log
from oslo_middleware import base
from monasca_events_api import config
CONF = config.CONF
LOG = log.getLogger(__name__) LOG = log.getLogger(__name__)
SUPPORTED_CONTENT_TYPES = ('application/json',)
def validate_content_type(req, allowed):
"""Validates content type.
def _validate_content_type(req): Method validates request against correct
"""Validate content type. content type.
Function validates request against correct content type. If content-type cannot be established (i.e. header is missing),
If Content-Type cannot be established (i.e. header is missing),
:py:class:`falcon.HTTPMissingHeader` is thrown. :py:class:`falcon.HTTPMissingHeader` is thrown.
If Content-Type is not **application/json**(supported contents If content-type is not **application/json** or **text/plain**,
types are define in SUPPORTED_CONTENT_TYPES variable),
:py:class:`falcon.HTTPUnsupportedMediaType` is thrown. :py:class:`falcon.HTTPUnsupportedMediaType` is thrown.
:param falcon.Request req: current request :param falcon.Request req: current request
:param iterable allowed: allowed content type
:exception: :py:class:`falcon.HTTPMissingHeader` :exception: :py:class:`falcon.HTTPMissingHeader`
:exception: :py:class:`falcon.HTTPUnsupportedMediaType` :exception: :py:class:`falcon.HTTPUnsupportedMediaType`
""" """
content_type = req.content_type content_type = req.content_type
LOG.debug('Content-type is {0}'.format(content_type))
LOG.debug('Content-Type is %s', content_type)
if content_type is None or len(content_type) == 0: if content_type is None or len(content_type) == 0:
raise falcon.HTTPMissingHeader('Content-Type') raise falcon.HTTPMissingHeader('Content-Type')
if content_type not in SUPPORTED_CONTENT_TYPES: if content_type not in allowed:
types = ','.join(SUPPORTED_CONTENT_TYPES) sup_types = ', '.join(allowed)
details = ('Only [{0}] are accepted as events representation'. details = ('Only [%s] are accepted as logs representations'
format(types)) % str(sup_types))
raise falcon.HTTPUnsupportedMediaType(description=details) raise falcon.HTTPUnsupportedMediaType(description=details)
class ValidationMiddleware(base.ConfigurableMiddleware):
"""Middleware that validates request content.
"""
@staticmethod
def process_request(req):
_validate_content_type(req)
return

View File

@ -12,7 +12,7 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from voluptuous import MultipleInvalid from falcon.errors import HTTPUnprocessableEntity
from monasca_events_api.app.controller.v1.body_validation import validate_body from monasca_events_api.app.controller.v1.body_validation import validate_body
from monasca_events_api.tests.unit import base from monasca_events_api.tests.unit import base
@ -22,26 +22,36 @@ class TestBodyValidation(base.BaseTestCase):
def test_missing_events_filed(self): def test_missing_events_filed(self):
body = {'timestamp': '2012-10-29T13:42:11Z+0200'} body = {'timestamp': '2012-10-29T13:42:11Z+0200'}
self.assertRaises(MultipleInvalid, validate_body, body) self.assertRaises(HTTPUnprocessableEntity, validate_body, body)
def test_missing_timestamp_field(self): def test_missing_timestamp_field(self):
body = {'events': []} body = {'events': [{'event': {'payload': 'test'}}]}
self.assertRaises(MultipleInvalid, validate_body, body) self.assertRaises(HTTPUnprocessableEntity, validate_body, body)
def test_empty_events_as_list(self):
body = {'events': [], 'timestamp': u'2012-10-29T13:42:11Z+0200'}
self.assertRaises(HTTPUnprocessableEntity, validate_body, body)
def test_empty_events_as_dict(self):
body = {'events': {}, 'timestamp': u'2012-10-29T13:42:11Z+0200'}
self.assertRaises(HTTPUnprocessableEntity, validate_body, body)
def test_empty_body(self): def test_empty_body(self):
body = {} body = {}
self.assertRaises(MultipleInvalid, validate_body, body) self.assertRaises(HTTPUnprocessableEntity, validate_body, body)
def test_incorrect_timestamp_type(self): def test_incorrect_timestamp_type(self):
body = {'events': [], 'timestamp': 9000} body = {'events': [], 'timestamp': 9000}
self.assertRaises(MultipleInvalid, validate_body, body) self.assertRaises(HTTPUnprocessableEntity, validate_body, body)
def test_incorrect_events_type(self): def test_incorrect_events_type(self):
body = {'events': 'over9000', 'timestamp': '2012-10-29T13:42:11Z+0200'} body = {'events': 'over9000', 'timestamp': '2012-10-29T13:42:11Z+0200'}
self.assertRaises(MultipleInvalid, validate_body, body) self.assertRaises(HTTPUnprocessableEntity, validate_body, body)
def test_correct_body(self): def test_correct_body(self):
body = [{'events': [], 'timestamp': u'2012-10-29T13:42:11Z+0200'}, body = [{'events': [{'event': {'payload': 'test'}}],
{'events': {}, 'timestamp': u'2012-10-29T13:42:11Z+0200'}] 'timestamp': u'2012-10-29T13:42:11Z+0200'},
{'events': {'event': {'payload': 'test'}},
'timestamp': u'2012-10-29T13:42:11Z+0200'}]
for b in body: for b in body:
validate_body(b) validate_body(b)

View File

@ -125,6 +125,35 @@ class TestEventsApi(base.BaseApiTestCase):
) )
self.assertEqual(falcon.HTTP_422, self.srmock.status) self.assertEqual(falcon.HTTP_422, self.srmock.status)
def test_should_fail_missing_content_type(self, bulk_processor):
events_resource = _init_resource(self)
events_resource._processor = bulk_processor
body = {'timestamp': '2012-10-29T13:42:11Z+0200'}
self.simulate_request(
path=ENDPOINT,
method='POST',
headers={
'X_ROLES': 'monasca-user'
},
body=json.dumps(body)
)
self.assertEqual(falcon.HTTP_400, self.srmock.status)
def test_should_fail_wrong_content_type(self, bulk_processor):
events_resource = _init_resource(self)
events_resource._processor = bulk_processor
body = {'timestamp': '2012-10-29T13:42:11Z+0200'}
self.simulate_request(
path=ENDPOINT,
method='POST',
headers={
'Content-Type': 'text/plain',
'X_ROLES': 'monasca-user'
},
body=json.dumps(body)
)
self.assertEqual(falcon.HTTP_415, self.srmock.status)
class TestApiEventsVersion(base.BaseApiTestCase): class TestApiEventsVersion(base.BaseApiTestCase):
@mock.patch('monasca_events_api.app.controller.v1.' @mock.patch('monasca_events_api.app.controller.v1.'

View File

@ -1,47 +0,0 @@
# Copyright 2017 FUJITSU LIMITED
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import falcon
from monasca_events_api.middleware import validation_middleware as vm
from monasca_events_api.tests.unit import base
class FakeRequest(object):
def __init__(self, content=None, length=0):
self.content_type = content if content else None
self.content_length = (length if length is not None and length > 0
else None)
class TestValidation(base.BaseTestCase):
def setUp(self):
super(TestValidation, self).setUp()
def test_should_validate_right_content_type(self):
req = FakeRequest('application/json')
vm._validate_content_type(req)
def test_should_fail_missing_content_type(self):
req = FakeRequest()
self.assertRaises(falcon.HTTPMissingHeader,
vm._validate_content_type,
req)
def test_should_fail_unsupported_content_type(self):
req = FakeRequest('test/plain')
self.assertRaises(falcon.HTTPUnsupportedMediaType,
vm._validate_content_type,
req)