Enforce integer API parameter checking

This patch is adding more checking to integer type parameters at the API
layer. It also adds a missing test case for the 'limit' parameter used in
event indexing.

Change-Id: If4588c9b9ded38db99b8727a1cfb5c88d6fa18de
This commit is contained in:
tengqm 2015-03-04 21:29:32 +08:00
parent 6950453e12
commit 69cee431cb
4 changed files with 172 additions and 7 deletions

View File

@ -15,6 +15,7 @@ import itertools
from heat.api.openstack.v1 import util from heat.api.openstack.v1 import util
from heat.common import identifier from heat.common import identifier
from heat.common import param_utils
from heat.common import serializers from heat.common import serializers
from heat.common import wsgi from heat.common import wsgi
from heat.rpc import api as rpc_api from heat.rpc import api as rpc_api
@ -81,9 +82,11 @@ class ResourceController(object):
Lists summary information for all resources Lists summary information for all resources
""" """
# Though nested_depth is defaulted in the RPC API, this prevents empty nested_depth = 0
# strings from being passed, thus breaking the code in the engine. key = rpc_api.PARAM_NESTED_DEPTH
nested_depth = int(req.params.get('nested_depth') or 0) if key in req.params:
nested_depth = param_utils.extract_int(key, req.params[key])
res_list = self.rpc_client.list_stack_resources(req.context, res_list = self.rpc_client.list_stack_resources(req.context,
identity, identity,
nested_depth) nested_depth)

View File

@ -199,6 +199,11 @@ class StackController(object):
params[rpc_api.PARAM_SHOW_NESTED] = param_utils.extract_bool( params[rpc_api.PARAM_SHOW_NESTED] = param_utils.extract_bool(
params[rpc_api.PARAM_SHOW_NESTED]) params[rpc_api.PARAM_SHOW_NESTED])
show_nested = params[rpc_api.PARAM_SHOW_NESTED] show_nested = params[rpc_api.PARAM_SHOW_NESTED]
key = rpc_api.PARAM_LIMIT
if key in params:
params[key] = param_utils.extract_int(key, params[key])
# get the with_count value, if invalid, raise ValueError # get the with_count value, if invalid, raise ValueError
with_count = False with_count = False
if req.params.get('with_count'): if req.params.get('with_count'):
@ -282,12 +287,17 @@ class StackController(object):
""" """
data = InstantiationData(body) data = InstantiationData(body)
args = data.args()
key = rpc_api.PARAM_TIMEOUT
if key in args:
args[key] = param_utils.extract_int(key, args[key])
result = self.rpc_client.create_stack(req.context, result = self.rpc_client.create_stack(req.context,
data.stack_name(), data.stack_name(),
data.template(), data.template(),
data.environment(), data.environment(),
data.files(), data.files(),
data.args()) args)
formatted_stack = stacks_view.format_stack( formatted_stack = stacks_view.format_stack(
req, req,
@ -354,12 +364,17 @@ class StackController(object):
""" """
data = InstantiationData(body) data = InstantiationData(body)
args = data.args()
key = rpc_api.PARAM_TIMEOUT
if key in args:
args[key] = param_utils.extract_int(key, args[key])
self.rpc_client.update_stack(req.context, self.rpc_client.update_stack(req.context,
identity, identity,
data.template(), data.template(),
data.environment(), data.environment(),
data.files(), data.files(),
data.args()) args)
raise exc.HTTPAccepted() raise exc.HTTPAccepted()
@ -370,12 +385,18 @@ class StackController(object):
Add the flag patch to the args so the engine code can distinguish Add the flag patch to the args so the engine code can distinguish
""" """
data = InstantiationData(body, patch=True) data = InstantiationData(body, patch=True)
args = data.args()
key = rpc_api.PARAM_TIMEOUT
if key in args:
args[key] = param_utils.extract_int(key, args[key])
self.rpc_client.update_stack(req.context, self.rpc_client.update_stack(req.context,
identity, identity,
data.template(), data.template(),
data.environment(), data.environment(),
data.files(), data.files(),
data.args()) args)
raise exc.HTTPAccepted() raise exc.HTTPAccepted()

View File

@ -17,10 +17,12 @@ PARAM_KEYS = (
PARAM_TIMEOUT, PARAM_DISABLE_ROLLBACK, PARAM_ADOPT_STACK_DATA, PARAM_TIMEOUT, PARAM_DISABLE_ROLLBACK, PARAM_ADOPT_STACK_DATA,
PARAM_SHOW_DELETED, PARAM_SHOW_NESTED, PARAM_EXISTING, PARAM_SHOW_DELETED, PARAM_SHOW_NESTED, PARAM_EXISTING,
PARAM_CLEAR_PARAMETERS, PARAM_GLOBAL_TENANT, PARAM_LIMIT, PARAM_CLEAR_PARAMETERS, PARAM_GLOBAL_TENANT, PARAM_LIMIT,
PARAM_NESTED_DEPTH,
) = ( ) = (
'timeout_mins', 'disable_rollback', 'adopt_stack_data', 'timeout_mins', 'disable_rollback', 'adopt_stack_data',
'show_deleted', 'show_nested', 'existing', 'show_deleted', 'show_nested', 'existing',
'clear_parameters', 'global_tenant', 'limit', 'clear_parameters', 'global_tenant', 'limit',
'nested_depth',
) )
STACK_KEYS = ( STACK_KEYS = (

View File

@ -393,7 +393,7 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
def test_index_whitelists_pagination_params(self, mock_call, mock_enforce): def test_index_whitelists_pagination_params(self, mock_call, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'index', True) self._mock_enforce_setup(mock_enforce, 'index', True)
params = { params = {
'limit': 'fake limit', 'limit': 10,
'sort_keys': 'fake sort keys', 'sort_keys': 'fake sort keys',
'marker': 'fake marker', 'marker': 'fake marker',
'sort_dir': 'fake sort dir', 'sort_dir': 'fake sort dir',
@ -415,6 +415,19 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
self.assertIn('tenant_safe', engine_args) self.assertIn('tenant_safe', engine_args)
self.assertNotIn('balrog', engine_args) self.assertNotIn('balrog', engine_args)
@mock.patch.object(rpc_client.EngineClient, 'call')
def test_index_limit_not_int(self, mock_call, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'index', True)
params = {'limit': 'not-an-int'}
req = self._get('/stacks', params=params)
ex = self.assertRaises(ValueError,
self.controller.index, req,
tenant_id=self.tenant)
self.assertEqual("Only integer is acceptable by 'limit'.",
six.text_type(ex))
self.assertFalse(mock_call.called)
@mock.patch.object(rpc_client.EngineClient, 'call') @mock.patch.object(rpc_client.EngineClient, 'call')
def test_index_whitelist_filter_params(self, mock_call, mock_enforce): def test_index_whitelist_filter_params(self, mock_call, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'index', True) self._mock_enforce_setup(mock_enforce, 'index', True)
@ -802,6 +815,27 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
self.assertEqual(expected, response) self.assertEqual(expected, response)
self.m.VerifyAll() self.m.VerifyAll()
def test_adopt_timeout_not_int(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'create', True)
identity = identifier.HeatIdentifier(self.tenant, 'wordpress', '1')
body = {'template': None,
'stack_name': identity.stack_name,
'parameters': {},
'timeout_mins': 'not-an-int',
'adopt_stack_data': 'does not matter'}
req = self._post('/stacks', json.dumps(body))
mock_call = self.patchobject(rpc_client.EngineClient, 'call')
ex = self.assertRaises(ValueError,
self.controller.create, req,
tenant_id=self.tenant, body=body)
self.assertEqual("Only integer is acceptable by 'timeout_mins'.",
six.text_type(ex))
self.assertFalse(mock_call.called)
def test_adopt_error(self, mock_enforce): def test_adopt_error(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'create', True) self._mock_enforce_setup(mock_enforce, 'create', True)
identity = identifier.HeatIdentifier(self.tenant, 'wordpress', '1') identity = identifier.HeatIdentifier(self.tenant, 'wordpress', '1')
@ -993,6 +1027,27 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
self.assertEqual('StackExists', resp.json['error']['type']) self.assertEqual('StackExists', resp.json['error']['type'])
self.m.VerifyAll() self.m.VerifyAll()
def test_create_timeout_not_int(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'create', True)
stack_name = "wordpress"
template = {u'Foo': u'bar'}
parameters = {u'InstanceType': u'm1.xlarge'}
body = {'template': template,
'stack_name': stack_name,
'parameters': parameters,
'timeout_mins': 'not-an-int'}
req = self._post('/stacks', json.dumps(body))
mock_call = self.patchobject(rpc_client.EngineClient, 'call')
ex = self.assertRaises(ValueError,
self.controller.create, req,
tenant_id=self.tenant, body=body)
self.assertEqual("Only integer is acceptable by 'timeout_mins'.",
six.text_type(ex))
self.assertFalse(mock_call.called)
def test_create_err_denied_policy(self, mock_enforce): def test_create_err_denied_policy(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'create', False) self._mock_enforce_setup(mock_enforce, 'create', False)
stack_name = "wordpress" stack_name = "wordpress"
@ -1485,6 +1540,30 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
self.assertEqual('StackNotFound', resp.json['error']['type']) self.assertEqual('StackNotFound', resp.json['error']['type'])
self.m.VerifyAll() self.m.VerifyAll()
def test_update_timeout_not_int(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'update', True)
identity = identifier.HeatIdentifier(self.tenant, 'wibble', '6')
template = {u'Foo': u'bar'}
parameters = {u'InstanceType': u'm1.xlarge'}
body = {'template': template,
'parameters': parameters,
'files': {},
'timeout_mins': 'not-int'}
req = self._put('/stacks/%(stack_name)s/%(stack_id)s' % identity,
json.dumps(body))
mock_call = self.patchobject(rpc_client.EngineClient, 'call')
ex = self.assertRaises(ValueError,
self.controller.update, req,
tenant_id=identity.tenant,
stack_name=identity.stack_name,
stack_id=identity.stack_id,
body=body)
self.assertEqual("Only integer is acceptable by 'timeout_mins'.",
six.text_type(ex))
self.assertFalse(mock_call.called)
def test_update_err_denied_policy(self, mock_enforce): def test_update_err_denied_policy(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'update', False) self._mock_enforce_setup(mock_enforce, 'update', False)
identity = identifier.HeatIdentifier(self.tenant, 'wibble', '6') identity = identifier.HeatIdentifier(self.tenant, 'wibble', '6')
@ -1579,6 +1658,30 @@ class StackControllerTest(ControllerTest, common.HeatTestCase):
body=body) body=body)
self.m.VerifyAll() self.m.VerifyAll()
def test_update_with_patch_timeout_not_int(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'update_patch', True)
identity = identifier.HeatIdentifier(self.tenant, 'wordpress', '6')
template = {u'Foo': u'bar'}
parameters = {u'InstanceType': u'm1.xlarge'}
body = {'template': template,
'parameters': parameters,
'files': {},
'timeout_mins': 'not-int'}
req = self._patch('/stacks/%(stack_name)s/%(stack_id)s' % identity,
json.dumps(body))
mock_call = self.patchobject(rpc_client.EngineClient, 'call')
ex = self.assertRaises(ValueError,
self.controller.update_patch, req,
tenant_id=identity.tenant,
stack_name=identity.stack_name,
stack_id=identity.stack_id,
body=body)
self.assertEqual("Only integer is acceptable by 'timeout_mins'.",
six.text_type(ex))
self.assertFalse(mock_call.called)
def test_update_with_existing_and_default_parameters( def test_update_with_existing_and_default_parameters(
self, mock_enforce): self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'update_patch', True) self._mock_enforce_setup(mock_enforce, 'update_patch', True)
@ -2122,6 +2225,25 @@ class ResourceControllerTest(ControllerTest, common.HeatTestCase):
self.assertEqual([], result['resources']) self.assertEqual([], result['resources'])
self.m.VerifyAll() self.m.VerifyAll()
def test_index_nested_depth_not_int(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'index', True)
stack_identity = identifier.HeatIdentifier(self.tenant,
'rubbish', '1')
req = self._get(stack_identity._tenant_path() + '/resources',
{'nested_depth': 'non-int'})
mock_call = self.patchobject(rpc_client.EngineClient, 'call')
ex = self.assertRaises(ValueError,
self.controller.index, req,
tenant_id=self.tenant,
stack_name=stack_identity.stack_name,
stack_id=stack_identity.stack_id)
self.assertEqual("Only integer is acceptable by 'nested_depth'.",
six.text_type(ex))
self.assertFalse(mock_call.called)
def test_index_denied_policy(self, mock_enforce): def test_index_denied_policy(self, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'index', False) self._mock_enforce_setup(mock_enforce, 'index', False)
res_name = 'WikiDatabase' res_name = 'WikiDatabase'
@ -2868,6 +2990,23 @@ class EventControllerTest(ControllerTest, common.HeatTestCase):
self.assertIsNone(engine_args['filters']) self.assertIsNone(engine_args['filters'])
self.assertNotIn('balrog', engine_args) self.assertNotIn('balrog', engine_args)
@mock.patch.object(rpc_client.EngineClient, 'call')
def test_index_limit_not_int(self, mock_call, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'index', True)
sid = identifier.HeatIdentifier(self.tenant, 'wibble', '6')
req = self._get(sid._tenant_path() + '/events',
params={'limit': 'not-an-int'})
ex = self.assertRaises(ValueError,
self.controller.index, req,
tenant_id=self.tenant,
stack_name=sid.stack_name,
stack_id=sid.stack_id)
self.assertEqual("Only integer is acceptable by 'limit'.",
six.text_type(ex))
self.assertFalse(mock_call.called)
@mock.patch.object(rpc_client.EngineClient, 'call') @mock.patch.object(rpc_client.EngineClient, 'call')
def test_index_whitelist_filter_params(self, mock_call, mock_enforce): def test_index_whitelist_filter_params(self, mock_call, mock_enforce):
self._mock_enforce_setup(mock_enforce, 'index', True) self._mock_enforce_setup(mock_enforce, 'index', True)