From fff8a7d4a50252a32c366dfb430f852ffa71a4db Mon Sep 17 00:00:00 2001 From: Hampapur Ajay Date: Wed, 8 Oct 2014 16:51:48 -0700 Subject: [PATCH] Validate for blacklist chars in name at create Closes-Bug: 1379066 Change-Id: I9d289076b5c929d9137c795f60a0892175e4b5e5 --- src/api-lib/vnc_api.py | 2 + .../api-server/tests/test_crud_basic.py | 98 ++++++++++++++++++- src/config/api-server/vnc_cfg_api_server.py | 23 +++++ src/config/common/exceptions.py | 12 +++ src/config/common/tests/test_common.py | 3 + src/config/common/tests/test_utils.py | 16 +++ .../vnc_openstack/neutron_plugin_db.py | 2 +- 7 files changed, 154 insertions(+), 2 deletions(-) diff --git a/src/api-lib/vnc_api.py b/src/api-lib/vnc_api.py index c7d638455d8..0e11b53d996 100644 --- a/src/api-lib/vnc_api.py +++ b/src/api-lib/vnc_api.py @@ -369,6 +369,8 @@ def _request_server(self, op, url, data=None, retry_on_error=True, retry_after_a elif status == 503 or status == 504: time.sleep(1) continue + elif status == 400: + raise BadRequest(status, content) else: # Unknown Error raise HttpError(status, content) # end while True diff --git a/src/config/api-server/tests/test_crud_basic.py b/src/config/api-server/tests/test_crud_basic.py index ff10b755a7a..2338dac5d10 100644 --- a/src/config/api-server/tests/test_crud_basic.py +++ b/src/config/api-server/tests/test_crud_basic.py @@ -642,7 +642,7 @@ def err_rabbitq_put(*args, **kwargs): logger.info("Creating objects to hit max rabbit pending.") # every create updates project quota - test_objs = self._create_test_objects(count=max_pend_upd/2+1) + test_objs = self._create_test_objects(count=max_pend_upd) def asserts_on_max_pending(): self.assertEqual(e.status_code, 500) @@ -738,6 +738,102 @@ def test_sandesh_trace(self): print top_elem[0][0][-1].text self.assertThat(top_elem[0][0][-1].text, Contains('delete')) self.assertThat(top_elem[0][0][-1].text, Contains(test_obj.name)) + + def test_dup_create_with_same_uuid(self): + dom_name = self.id() + '-domain' + logger.info('Creating Domain %s', dom_name) + domain_obj = Domain(dom_name) + self._vnc_lib.domain_create(domain_obj) + + project_name = self.id() + '-project' + logger.info('Creating Project %s', project_name) + orig_project_obj = Project(project_name, domain_obj) + self._vnc_lib.project_create(orig_project_obj) + + logger.info('Creating Dup Project in default domain with same uuid') + dup_project_obj = Project(project_name) + dup_project_obj.uuid = orig_project_obj.uuid + with ExpectedException(RefsExistError) as e: + self._vnc_lib.project_create(dup_project_obj) + + def test_put_on_wrong_type(self): + vn_name = self.id()+'-vn' + vn_obj = VirtualNetwork(vn_name) + self._add_detail('Creating network with name %s' %(vn_name)) + self._vnc_lib.virtual_network_create(vn_obj) + listen_port = self._api_server._args.listen_port + uri = '/network-ipam/%s' %(vn_obj.uuid) + self._add_detail('Trying to update uuid as network-ipam, expecting 404') + code, msg = self._http_put(uri, json.dumps({'network-ipam': {'display_name': 'foobar'}})) + self.assertThat(code, Equals(404)) + + self._add_detail('Updating display_name as network, expecting success') + uri = '/virtual-network/%s' %(vn_obj.uuid) + code, msg = self._http_put(uri, json.dumps({'virtual-network': {'display_name': 'foobar'}})) + self.assertThat(code, Equals(200)) + rb_obj = self._vnc_lib.virtual_network_read(id=vn_obj.uuid) + self.assertThat(rb_obj.display_name, Equals('foobar')) + + def test_floatingip_as_instanceip(self): + ipam_fixt = self.useFixture(NetworkIpamTestFixtureGen(self._vnc_lib)) + + project_fixt = self.useFixture(ProjectTestFixtureGen(self._vnc_lib, 'default-project')) + + subnet_vnc = IpamSubnetType(subnet=SubnetType('1.1.1.0', 24)) + vnsn_data = VnSubnetsType([subnet_vnc]) + logger.info("Creating a virtual network") + logger.info("Creating subnet 1.1.1.0/24") + vn_fixt = self.useFixture(VirtualNetworkTestFixtureGen(self._vnc_lib, + network_ipam_ref_infos=[(ipam_fixt.getObj(), vnsn_data)])) + vn_fixt.getObj().set_router_external(True) + self._vnc_lib.virtual_network_update(vn_fixt.getObj()) + + logger.info("Fetching floating-ip-pool") + fip_pool_fixt = self.useFixture( + FloatingIpPoolTestFixtureGen(self._vnc_lib, 'floating-ip-pool', + parent_fixt=vn_fixt)) + + logger.info("Creating auto-alloc floating-ip") + fip_fixt = self.useFixture( + FloatingIpTestFixtureGen( + self._vnc_lib, 'fip1', parent_fixt=fip_pool_fixt, + project_refs=[project_fixt.getObj()])) + ip_allocated = fip_fixt.getObj().floating_ip_address + + logger.info("Creating auto-alloc instance-ip, expecting an error") + with self.assertRaises(cfgm_common.exceptions.PermissionDenied): + iip_fixt = self.useFixture( + InstanceIpTestFixtureGen( + self._vnc_lib, 'iip1', auto_prop_val=False, + instance_ip_address=ip_allocated, + virtual_network_refs=[vn_fixt.getObj()])) + # end test_floatingip_as_instanceip + + def test_name_with_blacklist_char(self): + vn_name = self.id()+'-vn<1>' + vn_obj = VirtualNetwork(vn_name) + with ExpectedException(BadRequest) as e: + self._vnc_lib.virtual_network_create(vn_obj) + + vn_name = self.id()+'-vn' + vn_obj = VirtualNetwork(vn_name) + self._vnc_lib.virtual_network_create(vn_obj) + self.assertTill(self.ifmap_has_ident, obj=vn_obj) + + rt_name = self.id()+'-route-target<1>' + rt_obj = RouteTarget(rt_name) + with ExpectedException(BadRequest) as e: + self._vnc_lib.route_target_create(rt_obj) + + rt_name = self.id()+'-route-target:1' + rt_obj = RouteTarget(rt_name) + self._vnc_lib.route_target_create(rt_obj) + self.assertTill(self.ifmap_has_ident, obj=rt_obj) + + self._vnc_lib.virtual_network_delete(id=vn_obj.uuid) + self._vnc_lib.route_target_delete(id=rt_obj.uuid) + # end test_name_with_blacklist_char + # end class TestVncCfgApiServer class TestLocalAuth(test_case.ApiServerTestCase): diff --git a/src/config/api-server/vnc_cfg_api_server.py b/src/config/api-server/vnc_cfg_api_server.py index 3d7c7598003..2737985420e 100644 --- a/src/config/api-server/vnc_cfg_api_server.py +++ b/src/config/api-server/vnc_cfg_api_server.py @@ -201,6 +201,8 @@ class VncApiServer(VncApiServerGen): """ This is the manager class co-ordinating all classes present in the package """ + _INVALID_NAME_CHARS = set('<>:') + def __new__(cls, *args, **kwargs): obj = super(VncApiServer, cls).__new__(cls, *args, **kwargs) bottle.route('/', 'GET', obj.homepage_http_get) @@ -236,6 +238,7 @@ def __init__(self, args_str=None): self._get_common = self._http_get_common self._put_common = self._http_put_common self._delete_common = self._http_delete_common + self._post_validate = self._http_post_validate self._post_common = self._http_post_common # Type overrides from generated code @@ -1266,6 +1269,26 @@ def _http_delete_common(self, request, obj_type, uuid, parent_type): return self._permissions.check_perms_write(request, parent_uuid) # end _http_delete_common + def _http_post_validate(self, obj_type=None, obj_dict=None): + if not obj_dict: + return + + def _check_field_present(fname): + fval = obj_dict.get(fname) + if not fval: + bottle.abort(400, "Bad Request, no %s in POST body" %(fname)) + return fval + fq_name = _check_field_present('fq_name') + if obj_type[:].replace('-','_') == 'route_target': + invalid_chars = self._INVALID_NAME_CHARS - set(':') + else: + invalid_chars = self._INVALID_NAME_CHARS + if any((c in invalid_chars) for c in fq_name[-1]): + bottle.abort(400, + "Bad Request, name has one of invalid chars %s" + %(invalid_chars)) + # end _http_post_validate + def _http_post_common(self, request, obj_type, obj_dict): # If not connected to zookeeper do not allow operations that # causes the state change diff --git a/src/config/common/exceptions.py b/src/config/common/exceptions.py index 0cabc1dc24e..970d6248964 100644 --- a/src/config/common/exceptions.py +++ b/src/config/common/exceptions.py @@ -9,6 +9,18 @@ class VncError(Exception): # end class VncError +class BadRequest(Exception): + def __init__(self, status_code, content): + self.status_code = status_code + self.content = content + # end __init__ + + def __str__(self): + return 'HTTP Status: %s Content: %s' % (self.status_code, self.content) + # end __str__ +# end class BadRequest + + class NoIdError(VncError): def __init__(self, unknown_id): diff --git a/src/config/common/tests/test_common.py b/src/config/common/tests/test_common.py index 93b3c55c157..280b308211f 100644 --- a/src/config/common/tests/test_common.py +++ b/src/config/common/tests/test_common.py @@ -27,6 +27,8 @@ bottle.catchall=False import inspect +import novaclient +import novaclient.client def lineno(): """Returns the current line number in our program.""" @@ -276,6 +278,7 @@ def setUp(self): self._api_svr_app = TestApp(bottle.app(), extra_environ=extra_env) self._vnc_lib = VncApi('u', 'p', api_server_host=self._api_server_ip, api_server_port=self._api_server_port) + self._vnc_lib._headers['X-Role'] = 'admin' self._api_server_session = requests.Session() adapter = requests.adapters.HTTPAdapter() diff --git a/src/config/common/tests/test_utils.py b/src/config/common/tests/test_utils.py index 93acc76777d..0dc5608e7d4 100644 --- a/src/config/common/tests/test_utils.py +++ b/src/config/common/tests/test_utils.py @@ -35,6 +35,22 @@ def stub(*args, **kwargs): pass +class FakeApiConfigLog(object): + _all_logs = [] + send = stub + def __init__(self, *args, **kwargs): + FakeApiConfigLog._all_logs.append(kwargs['api_log']) + + @classmethod + def _print(cls): + for log in cls._all_logs: + x = copy.deepcopy(log.__dict__) + #body = x.pop('body') + #pprint(json.loads(body)) + pprint(x) + print "\n" +# class FakeApiConfigLog + class CassandraCFs(object): _all_cfs = {} diff --git a/src/config/vnc_openstack/vnc_openstack/neutron_plugin_db.py b/src/config/vnc_openstack/vnc_openstack/neutron_plugin_db.py index cf4eb62a435..86a293eb2cd 100644 --- a/src/config/vnc_openstack/vnc_openstack/neutron_plugin_db.py +++ b/src/config/vnc_openstack/vnc_openstack/neutron_plugin_db.py @@ -570,7 +570,7 @@ def _resource_create(self, resource_type, obj): obj.name += '-' + obj.uuid obj.fq_name[-1] += '-' + obj.uuid obj_uuid = getattr(self._vnc_lib, resource_type + '_create')(obj) - except PermissionDenied as e: + except (PermissionDenied, BadRequest) as e: exc_info = {'type': 'BadRequest', 'message': str(e)} bottle.abort(400, json.dumps(exc_info))