Browse Source

Merge "Added validations"

Zuul 5 months ago
parent
commit
c1ae8b36b0

+ 1
- 4
etc/monasca/events-api-paste.ini View File

@@ -22,7 +22,7 @@ use = egg:Paste#urlmap
22 22
 /healthcheck: events_healthcheck
23 23
 
24 24
 [pipeline:events_api_v1]
25
-pipeline = error_trap request_id auth sizelimit middleware api_v1_app
25
+pipeline = error_trap request_id auth sizelimit api_v1_app
26 26
 
27 27
 [pipeline:events_version]
28 28
 pipeline = error_trap  versionapp
@@ -48,9 +48,6 @@ paste.filter_factory = oslo_middleware.catch_errors:CatchErrors.factory
48 48
 [filter:request_id]
49 49
 paste.filter_factory = oslo_middleware.request_id:RequestId.factory
50 50
 
51
-[filter:middleware]
52
-paste.filter_factory = monasca_events_api.middleware.validation_middleware:ValidationMiddleware.factory
53
-
54 51
 [filter:sizelimit]
55 52
 use = egg:oslo.middleware#sizelimit
56 53
 

+ 11
- 3
monasca_events_api/app/controller/v1/body_validation.py View File

@@ -14,8 +14,12 @@
14 14
 
15 15
 import six
16 16
 
17
+from falcon import HTTPUnprocessableEntity
17 18
 from oslo_log import log
19
+from voluptuous import All
18 20
 from voluptuous import Any
21
+from voluptuous import Length
22
+from voluptuous import MultipleInvalid
19 23
 from voluptuous import Required
20 24
 from voluptuous import Schema
21 25
 
@@ -23,7 +27,8 @@ from voluptuous import Schema
23 27
 LOG = log.getLogger(__name__)
24 28
 
25 29
 
26
-default_schema = Schema({Required("events"): Any(list, dict),
30
+default_schema = Schema({Required("events"): All(Any(list, dict),
31
+                         Length(min=1)),
27 32
                          Required("timestamp"): six.text_type})
28 33
 
29 34
 
@@ -33,7 +38,10 @@ def validate_body(request_body):
33 38
      Method validate if body contain all required fields,
34 39
      and check if all value have correct type.
35 40
 
36
-
37 41
     :param request_body: body
38 42
     """
39
-    default_schema(request_body)
43
+    try:
44
+        default_schema(request_body)
45
+    except MultipleInvalid as ex:
46
+        LOG.exception(ex)
47
+        raise HTTPUnprocessableEntity(description=ex.error_message)

+ 13
- 5
monasca_events_api/app/controller/v1/events.py View File

@@ -13,13 +13,12 @@
13 13
 # under the License.
14 14
 
15 15
 import falcon
16
-from oslo_log import log
17
-from voluptuous import MultipleInvalid
18 16
 
19 17
 from monasca_events_api.app.common import helpers
20 18
 from monasca_events_api.app.controller.v1 import body_validation
21 19
 from monasca_events_api.app.controller.v1 import bulk_processor
22 20
 from monasca_events_api.app.core.model import prepare_message_to_sent
21
+from oslo_log import log
23 22
 
24 23
 LOG = log.getLogger(__name__)
25 24
 
@@ -51,6 +50,7 @@ class Events(object):
51 50
         policy_action = 'events_api:agent_required'
52 51
 
53 52
         try:
53
+            req.validate(self.SUPPORTED_CONTENT_TYPES)
54 54
             request_body = helpers.read_json_msg_body(req)
55 55
             req.can(policy_action)
56 56
             project_id = req.project_id
@@ -58,14 +58,22 @@ class Events(object):
58 58
             messages = prepare_message_to_sent(request_body)
59 59
             self._processor.send_message(messages, event_project_id=project_id)
60 60
             res.status = falcon.HTTP_200
61
-        except MultipleInvalid as ex:
61
+        except falcon.HTTPUnprocessableEntity as ex:
62 62
             LOG.error('Entire bulk package was rejected, unsupported body')
63 63
             LOG.exception(ex)
64
-            res.status = falcon.HTTP_422
64
+            raise ex
65
+        except falcon.HTTPUnsupportedMediaType as ex:
66
+            LOG.error('Entire bulk package was rejected, '
67
+                      'unsupported media type')
68
+            LOG.exception(ex)
69
+            raise ex
65 70
         except Exception as ex:
66 71
             LOG.error('Entire bulk package was rejected')
67 72
             LOG.exception(ex)
68
-            res.status = falcon.HTTP_400
73
+            _title = ex.title if hasattr(ex, 'title') else None
74
+            _descr = ex.description if hasattr(ex, 'description') else None
75
+            raise falcon.HTTPError(falcon.HTTP_400,
76
+                                   title=_title, description=_descr)
69 77
 
70 78
     @property
71 79
     def version(self):

+ 15
- 0
monasca_events_api/app/core/request.py View File

@@ -17,6 +17,7 @@ from monasca_common.policy import policy_engine as policy
17 17
 from oslo_log import log
18 18
 
19 19
 from monasca_events_api.app.core import request_contex
20
+from monasca_events_api.app.core import validation
20 21
 from monasca_events_api import policies
21 22
 
22 23
 LOG = log.getLogger(__name__)
@@ -38,5 +39,19 @@ class Request(falcon.Request):
38 39
         self.is_admin = policy.check_is_admin(self.context)
39 40
         self.project_id = self.context.project_id
40 41
 
42
+    def validate(self, content_types):
43
+        """Performs common request validation
44
+
45
+        Validation checklist (in that order):
46
+
47
+        * :py:func:`validation.validate_content_type`
48
+
49
+        :param content_types: allowed content-types handler supports
50
+        :type content_types: list
51
+        :raises Exception: if any of the validation fails
52
+
53
+        """
54
+        validation.validate_content_type(self, content_types)
55
+
41 56
     def can(self, action, target=None):
42 57
         return self.context.can(action, target)

monasca_events_api/middleware/validation_middleware.py → monasca_events_api/app/core/validation.py View File

@@ -1,4 +1,4 @@
1
-# Copyright 2017 FUJITSU LIMITED
1
+# Copyright 2018 FUJITSU LIMITED
2 2
 #
3 3
 # Licensed under the Apache License, Version 2.0 (the "License"); you may
4 4
 # not use this file except in compliance with the License. You may obtain
@@ -14,54 +14,37 @@
14 14
 
15 15
 import falcon
16 16
 from oslo_log import log
17
-from oslo_middleware import base
18 17
 
19
-from monasca_events_api import config
20
-
21
-CONF = config.CONF
22 18
 LOG = log.getLogger(__name__)
23 19
 
24
-SUPPORTED_CONTENT_TYPES = ('application/json',)
25
-
26 20
 
27
-def _validate_content_type(req):
28
-    """Validate content type.
21
+def validate_content_type(req, allowed):
22
+    """Validates content type.
29 23
 
30
-    Function validates request against correct content type.
24
+    Method validates request against correct
25
+    content type.
31 26
 
32
-    If Content-Type cannot be established (i.e. header is missing),
27
+    If content-type cannot be established (i.e. header is missing),
33 28
     :py:class:`falcon.HTTPMissingHeader` is thrown.
34
-    If Content-Type is not **application/json**(supported contents
35
-
36
-    types are define in SUPPORTED_CONTENT_TYPES variable),
29
+    If content-type is not **application/json** or **text/plain**,
37 30
     :py:class:`falcon.HTTPUnsupportedMediaType` is thrown.
38 31
 
32
+
39 33
     :param falcon.Request req: current request
34
+    :param iterable allowed: allowed content type
40 35
 
41 36
     :exception: :py:class:`falcon.HTTPMissingHeader`
42 37
     :exception: :py:class:`falcon.HTTPUnsupportedMediaType`
43 38
     """
44 39
     content_type = req.content_type
45
-    LOG.debug('Content-type is {0}'.format(content_type))
40
+
41
+    LOG.debug('Content-Type is %s', content_type)
46 42
 
47 43
     if content_type is None or len(content_type) == 0:
48 44
         raise falcon.HTTPMissingHeader('Content-Type')
49 45
 
50
-    if content_type not in SUPPORTED_CONTENT_TYPES:
51
-        types = ','.join(SUPPORTED_CONTENT_TYPES)
52
-        details = ('Only [{0}] are accepted as events representation'.
53
-                   format(types))
46
+    if content_type not in allowed:
47
+        sup_types = ', '.join(allowed)
48
+        details = ('Only [%s] are accepted as logs representations'
49
+                   % str(sup_types))
54 50
         raise falcon.HTTPUnsupportedMediaType(description=details)
55
-
56
-
57
-class ValidationMiddleware(base.ConfigurableMiddleware):
58
-    """Middleware that validates request content.
59
-
60
-    """
61
-
62
-    @staticmethod
63
-    def process_request(req):
64
-
65
-        _validate_content_type(req)
66
-
67
-        return

+ 0
- 0
monasca_events_api/middleware/__init__.py View File


+ 19
- 9
monasca_events_api/tests/unit/test_body_validation.py View File

@@ -12,7 +12,7 @@
12 12
 # License for the specific language governing permissions and limitations
13 13
 # under the License.
14 14
 
15
-from voluptuous import MultipleInvalid
15
+from falcon.errors import HTTPUnprocessableEntity
16 16
 
17 17
 from monasca_events_api.app.controller.v1.body_validation import validate_body
18 18
 from monasca_events_api.tests.unit import base
@@ -22,26 +22,36 @@ class TestBodyValidation(base.BaseTestCase):
22 22
 
23 23
     def test_missing_events_filed(self):
24 24
         body = {'timestamp': '2012-10-29T13:42:11Z+0200'}
25
-        self.assertRaises(MultipleInvalid, validate_body, body)
25
+        self.assertRaises(HTTPUnprocessableEntity, validate_body, body)
26 26
 
27 27
     def test_missing_timestamp_field(self):
28
-        body = {'events': []}
29
-        self.assertRaises(MultipleInvalid, validate_body, body)
28
+        body = {'events': [{'event': {'payload': 'test'}}]}
29
+        self.assertRaises(HTTPUnprocessableEntity, validate_body, body)
30
+
31
+    def test_empty_events_as_list(self):
32
+        body = {'events': [], 'timestamp': u'2012-10-29T13:42:11Z+0200'}
33
+        self.assertRaises(HTTPUnprocessableEntity, validate_body, body)
34
+
35
+    def test_empty_events_as_dict(self):
36
+        body = {'events': {}, 'timestamp': u'2012-10-29T13:42:11Z+0200'}
37
+        self.assertRaises(HTTPUnprocessableEntity, validate_body, body)
30 38
 
31 39
     def test_empty_body(self):
32 40
         body = {}
33
-        self.assertRaises(MultipleInvalid, validate_body, body)
41
+        self.assertRaises(HTTPUnprocessableEntity, validate_body, body)
34 42
 
35 43
     def test_incorrect_timestamp_type(self):
36 44
         body = {'events': [], 'timestamp': 9000}
37
-        self.assertRaises(MultipleInvalid, validate_body, body)
45
+        self.assertRaises(HTTPUnprocessableEntity, validate_body, body)
38 46
 
39 47
     def test_incorrect_events_type(self):
40 48
         body = {'events': 'over9000', 'timestamp': '2012-10-29T13:42:11Z+0200'}
41
-        self.assertRaises(MultipleInvalid, validate_body, body)
49
+        self.assertRaises(HTTPUnprocessableEntity, validate_body, body)
42 50
 
43 51
     def test_correct_body(self):
44
-        body = [{'events': [], 'timestamp': u'2012-10-29T13:42:11Z+0200'},
45
-                {'events': {}, 'timestamp': u'2012-10-29T13:42:11Z+0200'}]
52
+        body = [{'events': [{'event': {'payload': 'test'}}],
53
+                 'timestamp': u'2012-10-29T13:42:11Z+0200'},
54
+                {'events': {'event': {'payload': 'test'}},
55
+                 'timestamp': u'2012-10-29T13:42:11Z+0200'}]
46 56
         for b in body:
47 57
             validate_body(b)

+ 29
- 0
monasca_events_api/tests/unit/test_events_v1.py View File

@@ -125,6 +125,35 @@ class TestEventsApi(base.BaseApiTestCase):
125 125
         )
126 126
         self.assertEqual(falcon.HTTP_422, self.srmock.status)
127 127
 
128
+    def test_should_fail_missing_content_type(self, bulk_processor):
129
+        events_resource = _init_resource(self)
130
+        events_resource._processor = bulk_processor
131
+        body = {'timestamp': '2012-10-29T13:42:11Z+0200'}
132
+        self.simulate_request(
133
+            path=ENDPOINT,
134
+            method='POST',
135
+            headers={
136
+                'X_ROLES': 'monasca-user'
137
+            },
138
+            body=json.dumps(body)
139
+        )
140
+        self.assertEqual(falcon.HTTP_400, self.srmock.status)
141
+
142
+    def test_should_fail_wrong_content_type(self, bulk_processor):
143
+        events_resource = _init_resource(self)
144
+        events_resource._processor = bulk_processor
145
+        body = {'timestamp': '2012-10-29T13:42:11Z+0200'}
146
+        self.simulate_request(
147
+            path=ENDPOINT,
148
+            method='POST',
149
+            headers={
150
+                'Content-Type': 'text/plain',
151
+                'X_ROLES': 'monasca-user'
152
+            },
153
+            body=json.dumps(body)
154
+        )
155
+        self.assertEqual(falcon.HTTP_415, self.srmock.status)
156
+
128 157
 
129 158
 class TestApiEventsVersion(base.BaseApiTestCase):
130 159
     @mock.patch('monasca_events_api.app.controller.v1.'

+ 0
- 47
monasca_events_api/tests/unit/test_validation_middleware.py View File

@@ -1,47 +0,0 @@
1
-# Copyright 2017 FUJITSU LIMITED
2
-#
3
-# Licensed under the Apache License, Version 2.0 (the "License"); you may
4
-# not use this file except in compliance with the License. You may obtain
5
-# a copy of the License at
6
-#
7
-#      http://www.apache.org/licenses/LICENSE-2.0
8
-#
9
-# Unless required by applicable law or agreed to in writing, software
10
-# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11
-# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12
-# License for the specific language governing permissions and limitations
13
-# under the License.
14
-
15
-import falcon
16
-
17
-from monasca_events_api.middleware import validation_middleware as vm
18
-from monasca_events_api.tests.unit import base
19
-
20
-
21
-class FakeRequest(object):
22
-    def __init__(self, content=None, length=0):
23
-        self.content_type = content if content else None
24
-        self.content_length = (length if length is not None and length > 0
25
-                               else None)
26
-
27
-
28
-class TestValidation(base.BaseTestCase):
29
-
30
-    def setUp(self):
31
-        super(TestValidation, self).setUp()
32
-
33
-    def test_should_validate_right_content_type(self):
34
-        req = FakeRequest('application/json')
35
-        vm._validate_content_type(req)
36
-
37
-    def test_should_fail_missing_content_type(self):
38
-        req = FakeRequest()
39
-        self.assertRaises(falcon.HTTPMissingHeader,
40
-                          vm._validate_content_type,
41
-                          req)
42
-
43
-    def test_should_fail_unsupported_content_type(self):
44
-        req = FakeRequest('test/plain')
45
-        self.assertRaises(falcon.HTTPUnsupportedMediaType,
46
-                          vm._validate_content_type,
47
-                          req)

Loading…
Cancel
Save