Skip to content

Commit

Permalink
Improve entity validation in volumes APIs
Browse files Browse the repository at this point in the history
Fixes bug #1048565

Use the new Controller.is_valid_body() helper to validate the entity
body in various volumes related POST/PUT handlers and return 422
as appropriate.

Change-Id: I04127972981522c1ed81903893396c4f9665bcd3
  • Loading branch information
markmc committed Sep 13, 2012
1 parent 3dcb3fd commit dcecb58
Show file tree
Hide file tree
Showing 8 changed files with 145 additions and 57 deletions.
21 changes: 9 additions & 12 deletions cinder/api/openstack/volume/contrib/types_extra_specs.py
Expand Up @@ -50,7 +50,7 @@ def extraspec_sel(obj, do_raise=False):
return xmlutil.MasterTemplate(root, 1)


class VolumeTypeExtraSpecsController(object):
class VolumeTypeExtraSpecsController(wsgi.Controller):
""" The volume type extra specs API controller for the OpenStack API """

def _get_extra_specs(self, context, type_id):
Expand All @@ -60,11 +60,6 @@ def _get_extra_specs(self, context, type_id):
specs_dict[key] = value
return dict(extra_specs=specs_dict)

def _check_body(self, body):
if not body:
expl = _('No Request Body')
raise webob.exc.HTTPBadRequest(explanation=expl)

def _check_type(self, context, type_id):
try:
volume_types.get_volume_type(context, type_id)
Expand All @@ -83,12 +78,13 @@ def index(self, req, type_id):
def create(self, req, type_id, body=None):
context = req.environ['cinder.context']
authorize(context)

if not self.is_valid_body(body, 'extra_specs'):
raise webob.exc.HTTPUnprocessableEntity()

self._check_type(context, type_id)
self._check_body(body)
specs = body.get('extra_specs')
if not isinstance(specs, dict):
expl = _('Malformed extra specs')
raise webob.exc.HTTPBadRequest(explanation=expl)

specs = body['extra_specs']
db.volume_type_extra_specs_update_or_create(context,
type_id,
specs)
Expand All @@ -98,8 +94,9 @@ def create(self, req, type_id, body=None):
def update(self, req, type_id, id, body=None):
context = req.environ['cinder.context']
authorize(context)
if not body:
raise webob.exc.HTTPUnprocessableEntity()
self._check_type(context, type_id)
self._check_body(body)
if not id in body:
expl = _('Request body and URI mismatch')
raise webob.exc.HTTPBadRequest(explanation=expl)
Expand Down
7 changes: 2 additions & 5 deletions cinder/api/openstack/volume/contrib/types_manage.py
Expand Up @@ -42,13 +42,10 @@ def _create(self, req, body):
context = req.environ['cinder.context']
authorize(context)

if not body or body == "":
raise webob.exc.HTTPUnprocessableEntity()

vol_type = body.get('volume_type', None)
if vol_type is None or vol_type == "":
if not self.is_valid_body(body, 'volume_type'):
raise webob.exc.HTTPUnprocessableEntity()

vol_type = body['volume_type']
name = vol_type.get('name', None)
specs = vol_type.get('extra_specs', {})

Expand Down
6 changes: 3 additions & 3 deletions cinder/api/openstack/volume/snapshots.py
Expand Up @@ -84,7 +84,7 @@ def construct(self):
return xmlutil.MasterTemplate(root, 1)


class SnapshotsController(object):
class SnapshotsController(wsgi.Controller):
"""The Volumes API controller for the OpenStack API."""

def __init__(self, ext_mgr=None):
Expand Down Expand Up @@ -148,8 +148,8 @@ def create(self, req, body):
"""Creates a new snapshot."""
context = req.environ['cinder.context']

if not body:
return exc.HTTPUnprocessableEntity()
if not self.is_valid_body(body, 'snapshot'):
raise exc.HTTPUnprocessableEntity()

snapshot = body['snapshot']
volume_id = snapshot['volume_id']
Expand Down
7 changes: 3 additions & 4 deletions cinder/api/openstack/volume/volumes.py
Expand Up @@ -199,7 +199,7 @@ def default(self, string):
return {'body': {'volume': volume}}


class VolumeController(object):
class VolumeController(wsgi.Controller):
"""The Volumes API controller for the OpenStack API."""

def __init__(self, ext_mgr):
Expand Down Expand Up @@ -276,11 +276,10 @@ def _image_uuid_from_href(self, image_href):
@wsgi.deserializers(xml=CreateDeserializer)
def create(self, req, body):
"""Creates a new volume."""
context = req.environ['cinder.context']

if not body:
if not self.is_valid_body(body, 'volume'):
raise exc.HTTPUnprocessableEntity()

context = req.environ['cinder.context']
volume = body['volume']

kwargs = {}
Expand Down
60 changes: 42 additions & 18 deletions cinder/tests/api/openstack/volume/contrib/test_types_extra_specs.py
Expand Up @@ -118,15 +118,6 @@ def test_create(self):

self.assertEqual('value1', res_dict['extra_specs']['key1'])

def test_create_empty_body(self):
self.stubs.Set(cinder.db,
'volume_type_extra_specs_update_or_create',
return_create_volume_type_extra_specs)

req = fakes.HTTPRequest.blank(self.api_path)
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.create,
req, 1, '')

def test_update_item(self):
self.stubs.Set(cinder.db,
'volume_type_extra_specs_update_or_create',
Expand All @@ -138,15 +129,6 @@ def test_update_item(self):

self.assertEqual('value1', res_dict['key1'])

def test_update_item_empty_body(self):
self.stubs.Set(cinder.db,
'volume_type_extra_specs_update_or_create',
return_create_volume_type_extra_specs)

req = fakes.HTTPRequest.blank(self.api_path + '/key1')
self.assertRaises(webob.exc.HTTPBadRequest, self.controller.update,
req, 1, 'key1', '')

def test_update_item_too_many_keys(self):
self.stubs.Set(cinder.db,
'volume_type_extra_specs_update_or_create',
Expand Down Expand Up @@ -200,3 +182,45 @@ def test_update_show_serializer(self):
self.assertEqual('key1', tree.tag)
self.assertEqual('value1', tree.text)
self.assertEqual(0, len(tree))


class VolumeTypeExtraSpecsUnprocessableEntityTestCase(test.TestCase):

"""
Tests of places we throw 422 Unprocessable Entity from
"""

def setUp(self):
super(VolumeTypeExtraSpecsUnprocessableEntityTestCase, self).setUp()
self.controller = types_extra_specs.VolumeTypeExtraSpecsController()

def _unprocessable_extra_specs_create(self, body):
req = fakes.HTTPRequest.blank('/v2/fake/types/1/extra_specs')
req.method = 'POST'

self.assertRaises(webob.exc.HTTPUnprocessableEntity,
self.controller.create, req, '1', body)

def test_create_no_body(self):
self._unprocessable_extra_specs_create(body=None)

def test_create_missing_volume(self):
body = {'foo': {'a': 'b'}}
self._unprocessable_extra_specs_create(body=body)

def test_create_malformed_entity(self):
body = {'extra_specs': 'string'}
self._unprocessable_extra_specs_create(body=body)

def _unprocessable_extra_specs_update(self, body):
req = fakes.HTTPRequest.blank('/v2/fake/types/1/extra_specs')
req.method = 'POST'

self.assertRaises(webob.exc.HTTPUnprocessableEntity,
self.controller.update, req, '1', body)

def test_update_no_body(self):
self._unprocessable_extra_specs_update(body=None)

def test_update_empty_body(self):
self._unprocessable_extra_specs_update(body={})
33 changes: 26 additions & 7 deletions cinder/tests/api/openstack/volume/contrib/test_types_manage.py
Expand Up @@ -92,12 +92,31 @@ def test_create(self):
self.assertEqual(1, len(res_dict))
self.assertEqual('vol_type_1', res_dict['volume_type']['name'])

def test_create_empty_body(self):
self.stubs.Set(volume_types, 'create',
return_volume_types_create)
self.stubs.Set(volume_types, 'get_volume_type_by_name',
return_volume_types_get_by_name)

req = fakes.HTTPRequest.blank('/v1/fake/types')
class VolumeTypesUnprocessableEntityTestCase(test.TestCase):

"""
Tests of places we throw 422 Unprocessable Entity from
"""

def setUp(self):
super(VolumeTypesUnprocessableEntityTestCase, self).setUp()
self.controller = types_manage.VolumeTypesManageController()

def _unprocessable_volume_type_create(self, body):
req = fakes.HTTPRequest.blank('/v2/fake/types')
req.method = 'POST'

self.assertRaises(webob.exc.HTTPUnprocessableEntity,
self.controller._create, req, '')
self.controller._create, req, body)

def test_create_no_body(self):
self._unprocessable_volume_type_create(body=None)

def test_create_missing_volume(self):
body = {'foo': {'a': 'b'}}
self._unprocessable_volume_type_create(body=body)

def test_create_malformed_entity(self):
body = {'volume_type': 'string'}
self._unprocessable_volume_type_create(body=body)
29 changes: 29 additions & 0 deletions cinder/tests/api/openstack/volume/test_snapshots.py
Expand Up @@ -334,3 +334,32 @@ def test_snapshot_index_detail_serializer(self):
self.assertEqual(len(raw_snapshots), len(tree))
for idx, child in enumerate(tree):
self._verify_snapshot(raw_snapshots[idx], child)


class SnapshotsUnprocessableEntityTestCase(test.TestCase):

"""
Tests of places we throw 422 Unprocessable Entity from
"""

def setUp(self):
super(SnapshotsUnprocessableEntityTestCase, self).setUp()
self.controller = snapshots.SnapshotsController()

def _unprocessable_snapshot_create(self, body):
req = fakes.HTTPRequest.blank('/v2/fake/snapshots')
req.method = 'POST'

self.assertRaises(webob.exc.HTTPUnprocessableEntity,
self.controller.create, req, body)

def test_create_no_body(self):
self._unprocessable_snapshot_create(body=None)

def test_create_missing_snapshot(self):
body = {'foo': {'a': 'b'}}
self._unprocessable_snapshot_create(body=body)

def test_create_malformed_entity(self):
body = {'snapshot': 'string'}
self._unprocessable_snapshot_create(body=body)
39 changes: 31 additions & 8 deletions cinder/tests/api/openstack/volume/test_volumes.py
Expand Up @@ -103,14 +103,6 @@ def test_volume_creation_fails_with_bad_size(self):
req,
body)

def test_volume_create_no_body(self):
body = {}
req = fakes.HTTPRequest.blank('/v1/volumes')
self.assertRaises(webob.exc.HTTPUnprocessableEntity,
self.controller.create,
req,
body)

def test_volume_create_with_image_id(self):
self.stubs.Set(volume_api.API, "create", fakes.stub_volume_create)
self.ext_mgr.extensions = {'os-image-create': 'fake'}
Expand Down Expand Up @@ -625,3 +617,34 @@ def test_full_volume(self):
},
}
self.assertEquals(request['body'], expected)


class VolumesUnprocessableEntityTestCase(test.TestCase):

"""
Tests of places we throw 422 Unprocessable Entity from
"""

def setUp(self):
super(VolumesUnprocessableEntityTestCase, self).setUp()
self.ext_mgr = extensions.ExtensionManager()
self.ext_mgr.extensions = {}
self.controller = volumes.VolumeController(self.ext_mgr)

def _unprocessable_volume_create(self, body):
req = fakes.HTTPRequest.blank('/v2/fake/volumes')
req.method = 'POST'

self.assertRaises(webob.exc.HTTPUnprocessableEntity,
self.controller.create, req, body)

def test_create_no_body(self):
self._unprocessable_volume_create(body=None)

def test_create_missing_volume(self):
body = {'foo': {'a': 'b'}}
self._unprocessable_volume_create(body=body)

def test_create_malformed_entity(self):
body = {'volume': 'string'}
self._unprocessable_volume_create(body=body)

0 comments on commit dcecb58

Please sign in to comment.