]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Remove calls to policy.enforce from plugin and db logic
authorSalvatore Orlando <salv.orlando@gmail.com>
Thu, 14 Mar 2013 17:47:51 +0000 (18:47 +0100)
committerSalvatore Orlando <salv.orlando@gmail.com>
Mon, 29 Apr 2013 10:12:21 +0000 (12:12 +0200)
Blueprint make-authz-orthogonal

This patch implementes item #2 of the blueprint
Remove calls to policy.enforce when the policy check can be performed
safely at the API level, and modify policy.json to this aim.
This patch does not address enforce calls in the agent scheduler
extension, as that extension is currently not defined as a quantum.v2.api
resource class.
This patch also adds an API-level test case for the provider networks
extension, which was missing in Quantum and was necessary to validate
the API behaviour with the default policy settings.

Change-Id: I1c20a5870279bc5fce4470c90a210eae59675b0c

13 files changed:
etc/policy.json
quantum/db/l3_db.py
quantum/extensions/providernet.py
quantum/extensions/servicetype.py
quantum/openstack/common/policy.py
quantum/plugins/bigswitch/plugin.py
quantum/plugins/hyperv/hyperv_quantum_plugin.py
quantum/plugins/linuxbridge/lb_quantum_plugin.py
quantum/plugins/nec/nec_plugin.py
quantum/plugins/nicira/QuantumPlugin.py
quantum/plugins/nicira/extensions/nvp_qos.py
quantum/plugins/openvswitch/ovs_quantum_plugin.py
quantum/tests/unit/test_extension_pnet.py [new file with mode: 0644]

index f91494a31561bacb337db384f496fce1c429735a..42625665a9b92ba4721f548b3b80745b4d932c8f 100644 (file)
@@ -12,7 +12,6 @@
     "extension:provider_network:set": "rule:admin_only",
 
     "extension:router:view": "rule:regular_user",
-    "extension:router:set": "rule:admin_only",
 
     "extension:port_binding:view": "rule:admin_only",
     "extension:port_binding:set": "rule:admin_only",
     "get_network": "rule:admin_or_owner or rule:shared or rule:external",
     "create_network:shared": "rule:admin_only",
     "create_network:router:external": "rule:admin_only",
+    "create_network:provider:network_type": "rule:admin_only",
+    "create_network:provider:physical_network": "rule:admin_only",
+    "create_network:provider:segmentation_id": "rule:admin_only",
     "update_network": "rule:admin_or_owner",
+    "update_network:provider:network_type": "rule:admin_only",
+    "update_network:provider:physical_network": "rule:admin_only",
+    "update_network:provider:segmentation_id": "rule:admin_only",
     "delete_network": "rule:admin_or_owner",
 
     "create_port": "",
index f0209f1468a2dc0c307fc49e5f3b085f4a43b168..613789977e2b39b6420a41d7adf39cb0982437be 100644 (file)
@@ -36,7 +36,6 @@ from quantum.openstack.common.notifier import api as notifier_api
 from quantum.openstack.common import uuidutils
 from quantum import policy
 
-
 LOG = logging.getLogger(__name__)
 
 
@@ -770,11 +769,6 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
                             "extension:router:view",
                             network)
 
-    def _enforce_l3_set_auth(self, context, network):
-        return policy.enforce(context,
-                              "extension:router:set",
-                              network)
-
     def _network_is_external(self, context, net_id):
         try:
             context.session.query(ExternalNetwork).filter_by(
@@ -795,8 +789,6 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
         if not external_set:
             return
 
-        self._enforce_l3_set_auth(context, net_data)
-
         if external:
             # expects to be called within a plugin's session
             context.session.add(ExternalNetwork(network_id=net_id))
@@ -807,7 +799,6 @@ class L3_NAT_db_mixin(l3.RouterPluginBase):
         if not attributes.is_attr_set(new_value):
             return
 
-        self._enforce_l3_set_auth(context, net_data)
         existing_value = self._network_is_external(context, net_id)
 
         if existing_value == new_value:
index 314850aa6204b108a820c164f28ba1fdd99d545f..80a189d6f1809a8b21cef90e6d6f4f473554890c 100644 (file)
@@ -27,12 +27,15 @@ EXTENDED_ATTRIBUTES_2_0 = {
         NETWORK_TYPE: {'allow_post': True, 'allow_put': True,
                        'validate': {'type:string': None},
                        'default': attributes.ATTR_NOT_SPECIFIED,
+                       'enforce_policy': True,
                        'is_visible': True},
         PHYSICAL_NETWORK: {'allow_post': True, 'allow_put': True,
                            'default': attributes.ATTR_NOT_SPECIFIED,
+                           'enforce_policy': True,
                            'is_visible': True},
         SEGMENTATION_ID: {'allow_post': True, 'allow_put': True,
                           'convert_to': int,
+                          'enforce_policy': True,
                           'default': attributes.ATTR_NOT_SPECIFIED,
                           'is_visible': True},
     }
index 262a7b8eefbd6625035e8545a115b00538a4cd5c..a4a1bd4978bb680f06882e4072a92c47f09eb2e7 100644 (file)
@@ -204,3 +204,9 @@ class Servicetype(extensions.ExtensionDescriptor):
         return [extensions.ResourceExtension(COLLECTION_NAME,
                                              controller,
                                              attr_map=attr_map)]
+
+    def get_extended_resources(self, version):
+        if version == "2.0":
+            return dict(RESOURCE_ATTRIBUTE_MAP.items())
+        else:
+            return {}
index 88789ea904c926d581d4f928bdacc0029124e838..0b9eedc8786dc48822e9b36979d11f0f19dec31a 100644 (file)
@@ -738,7 +738,6 @@ class RuleCheck(Check):
 class RoleCheck(Check):
     def __call__(self, target, creds):
         """Check that there is a matching role in the cred dict."""
-
         return self.match.lower() in [x.lower() for x in creds['roles']]
 
 
index c979626e7b53a547e7ff8c8b4f476f9f2fa16e70..16a66bf64b0904c69081bab9365a9d575c948bd2 100644 (file)
@@ -1234,9 +1234,6 @@ class QuantumRestProxyV2(db_base_plugin_v2.QuantumDbPluginV2,
     def _check_view_auth(self, context, resource, action):
         return policy.check(context, action, resource)
 
-    def _enforce_set_auth(self, context, resource, action):
-        policy.enforce(context, action, resource)
-
     def _extend_port_dict_binding(self, context, port):
         if self._check_view_auth(context, port, self.binding_view):
             port[portbindings.VIF_TYPE] = portbindings.VIF_TYPE_OVS
index 9e27a815cf1f766429328ed547f98783d78b0367..4f657150a47efe7f1ad5f444e074151a8948f69b 100644 (file)
@@ -195,9 +195,6 @@ class HyperVQuantumPlugin(db_base_plugin_v2.QuantumDbPluginV2,
     def _check_view_auth(self, context, resource, action):
         return policy.check(context, action, resource)
 
-    def _enforce_set_auth(self, context, resource, action):
-        policy.enforce(context, action, resource)
-
     def _parse_network_vlan_ranges(self):
         self._network_vlan_ranges = {}
         for entry in cfg.CONF.HYPERV.network_vlan_ranges:
@@ -256,9 +253,6 @@ class HyperVQuantumPlugin(db_base_plugin_v2.QuantumDbPluginV2,
         # Provider specific network creation
         p.create_network(session, attrs)
 
-        if network_type_set:
-            self._enforce_set_auth(context, attrs, self.network_set)
-
     def create_network(self, context, network):
         session = context.session
         with session.begin(subtransactions=True):
@@ -310,8 +304,6 @@ class HyperVQuantumPlugin(db_base_plugin_v2.QuantumDbPluginV2,
     def update_network(self, context, id, network):
         network_attrs = network['network']
         self._check_provider_update(context, network_attrs)
-        # Authorize before exposing plugin details to client
-        self._enforce_set_auth(context, network_attrs, self.network_set)
 
         session = context.session
         with session.begin(subtransactions=True):
index e53c9b2b8c026480248209470105c1020c1594bc..8955ea0cbcfca11c2cb310ed58f5632fc07d8a67 100644 (file)
@@ -271,9 +271,6 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
     def _check_view_auth(self, context, resource, action):
         return policy.check(context, action, resource)
 
-    def _enforce_set_auth(self, context, resource, action):
-        policy.enforce(context, action, resource)
-
     def _add_network_vlan_range(self, physical_network, vlan_min, vlan_max):
         self._add_network(physical_network)
         self.network_vlan_ranges[physical_network].append((vlan_min, vlan_max))
@@ -314,9 +311,6 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
                 segmentation_id_set):
             return (None, None, None)
 
-        # Authorize before exposing plugin details to client
-        self._enforce_set_auth(context, attrs, self.network_set)
-
         if not network_type_set:
             msg = _("provider:network_type required")
             raise q_exc.InvalidInput(error_message=msg)
@@ -378,9 +372,6 @@ class LinuxBridgePluginV2(db_base_plugin_v2.QuantumDbPluginV2,
                 segmentation_id_set):
             return
 
-        # Authorize before exposing plugin details to client
-        self._enforce_set_auth(context, attrs, self.network_set)
-
         msg = _("Plugin does not support updating provider attributes")
         raise q_exc.InvalidInput(error_message=msg)
 
index 0e71acfcef5395d3063706c9e1a5c1a59f68ef87..2117132a3c22a9d2d9fa5e8e29fb91155ac919a4 100644 (file)
@@ -133,9 +133,6 @@ class NECPluginV2(nec_plugin_base.NECPluginV2Base,
     def _check_view_auth(self, context, resource, action):
         return policy.check(context, action, resource)
 
-    def _enforce_set_auth(self, context, resource, action):
-        policy.enforce(context, action, resource)
-
     def _update_resource_status(self, context, resource, id, status):
         """Update status of specified resource."""
         request = {}
index 984cbec999d9eec3a19f821786e66a667b123249..2b4273a695f228015c70fb9ecb0fd7dd2aa1963b 100644 (file)
@@ -142,8 +142,6 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
     novazone_cluster_map = {}
 
     provider_network_view = "extension:provider_network:view"
-    provider_network_set = "extension:provider_network:set"
-    port_security_enabled_create = "create_port:port_security_enabled"
     port_security_enabled_update = "update_port:port_security_enabled"
 
     def __init__(self, loglevel=None):
@@ -664,9 +662,6 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
     def _check_view_auth(self, context, resource, action):
         return policy.check(context, action, resource)
 
-    def _enforce_set_auth(self, context, resource, action):
-        return policy.enforce(context, action, resource)
-
     def _handle_provider_create(self, context, attrs):
         # NOTE(salvatore-orlando): This method has been borrowed from
         # the OpenvSwtich plugin, altough changed to match NVP specifics.
@@ -680,8 +675,6 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
                 segmentation_id_set):
             return
 
-        # Authorize before exposing plugin details to client
-        self._enforce_set_auth(context, attrs, self.provider_network_set)
         err_msg = None
         if not network_type_set:
             err_msg = _("%s required") % pnet.NETWORK_TYPE
@@ -1146,10 +1139,6 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
         # pass the value to the policy engine when the port is
         # ATTR_NOT_SPECIFIED is for the case where a port is created on a
         # shared network that is not owned by the tenant.
-        # TODO(arosen) fix policy engine to do this for us automatically.
-        if attr.is_attr_set(port['port'].get(psec.PORTSECURITY)):
-            self._enforce_set_auth(context, port,
-                                   self.port_security_enabled_create)
         port_data = port['port']
         notify_dhcp_agent = False
         with context.session.begin(subtransactions=True):
@@ -1215,9 +1204,6 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
         return port_data
 
     def update_port(self, context, id, port):
-        if attr.is_attr_set(port['port'].get(psec.PORTSECURITY)):
-            self._enforce_set_auth(context, port,
-                                   self.port_security_enabled_update)
         delete_security_groups = self._check_update_deletes_security_groups(
             port)
         has_security_groups = self._check_update_has_security_groups(port)
@@ -2035,8 +2021,6 @@ class NvpPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
 
     def create_qos_queue(self, context, qos_queue, check_policy=True):
         q = qos_queue.get('qos_queue')
-        if check_policy:
-            self._enforce_set_auth(context, q, ext_qos.qos_queue_create)
         self._validate_qos_queue(context, q)
         q['id'] = nvplib.create_lqueue(self.cluster,
                                        self._nvp_lqueue(q))
index 45bf485893e0017709ef534a13d4eef291bdb97f..169a6a92aa8a5f8b13f2ca7a2bc5a3d4390e96a9 100644 (file)
@@ -179,7 +179,8 @@ class Nvp_qos(object):
 
     def get_extended_resources(self, version):
         if version == "2.0":
-            return EXTENDED_ATTRIBUTES_2_0
+            return dict(EXTENDED_ATTRIBUTES_2_0.items() +
+                        RESOURCE_ATTRIBUTE_MAP.items())
         else:
             return {}
 
index eeb8bd944d45e8a39057e8e1091dd50ce256a8db..be937395acff022974330b4006e633d0399eeca4 100644 (file)
@@ -344,9 +344,6 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
     def _check_view_auth(self, context, resource, action):
         return policy.check(context, action, resource)
 
-    def _enforce_set_auth(self, context, resource, action):
-        policy.enforce(context, action, resource)
-
     def _extend_network_dict_provider(self, context, network):
         if self._check_view_auth(context, network, self.network_view):
             binding = ovs_db_v2.get_network_binding(context.session,
@@ -378,9 +375,6 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
                 segmentation_id_set):
             return (None, None, None)
 
-        # Authorize before exposing plugin details to client
-        self._enforce_set_auth(context, attrs, self.network_set)
-
         if not network_type_set:
             msg = _("provider:network_type required")
             raise q_exc.InvalidInput(error_message=msg)
@@ -455,9 +449,6 @@ class OVSQuantumPluginV2(db_base_plugin_v2.QuantumDbPluginV2,
                 segmentation_id_set):
             return
 
-        # Authorize before exposing plugin details to client
-        self._enforce_set_auth(context, attrs, self.network_set)
-
         msg = _("Plugin does not support updating provider attributes")
         raise q_exc.InvalidInput(error_message=msg)
 
diff --git a/quantum/tests/unit/test_extension_pnet.py b/quantum/tests/unit/test_extension_pnet.py
new file mode 100644 (file)
index 0000000..8a1d358
--- /dev/null
@@ -0,0 +1,157 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 VMware
+# All rights reserved.
+#
+#    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.
+#
+# @author: Salvatore Orlando, VMware
+#
+
+import mock
+from oslo.config import cfg
+from webob import exc as web_exc
+import webtest
+
+from quantum.api import extensions
+from quantum.api.v2 import attributes
+from quantum.api.v2 import router
+from quantum import context
+from quantum.extensions import providernet as pnet
+from quantum.manager import QuantumManager
+from quantum.openstack.common import uuidutils
+from quantum.tests.unit import test_api_v2
+from quantum.tests.unit import test_extensions
+from quantum.tests.unit import testlib_api
+
+
+class ProviderExtensionManager(object):
+
+    def get_resources(self):
+        return []
+
+    def get_actions(self):
+        return []
+
+    def get_request_extensions(self):
+        return []
+
+    def get_extended_resources(self, version):
+        return pnet.get_extended_resources(version)
+
+
+class ProvidernetExtensionTestCase(testlib_api.WebTestCase):
+    fmt = 'json'
+
+    def setUp(self):
+        super(ProvidernetExtensionTestCase, self).setUp()
+
+        plugin = 'quantum.quantum_plugin_base_v2.QuantumPluginBaseV2'
+        # Ensure 'stale' patched copies of the plugin are never returned
+        QuantumManager._instance = None
+
+        # Ensure existing ExtensionManager is not used
+        extensions.PluginAwareExtensionManager._instance = None
+
+        # Save the global RESOURCE_ATTRIBUTE_MAP
+        self.saved_attr_map = {}
+        for resource, attrs in attributes.RESOURCE_ATTRIBUTE_MAP.iteritems():
+            self.saved_attr_map[resource] = attrs.copy()
+
+        # Update the plugin and extensions path
+        cfg.CONF.set_override('core_plugin', plugin)
+        cfg.CONF.set_override('allow_pagination', True)
+        cfg.CONF.set_override('allow_sorting', True)
+        cfg.CONF.set_override('quota_network', -1, group='QUOTAS')
+        self._plugin_patcher = mock.patch(plugin, autospec=True)
+        self.plugin = self._plugin_patcher.start()
+        # Instantiate mock plugin and enable the 'provider' extension
+        QuantumManager.get_plugin().supported_extension_aliases = (
+            ["provider"])
+        ext_mgr = ProviderExtensionManager()
+        self.ext_mdw = test_extensions.setup_extensions_middleware(ext_mgr)
+        self.addCleanup(self._plugin_patcher.stop)
+        self.addCleanup(cfg.CONF.reset)
+        self.addCleanup(self._restore_attribute_map)
+        self.api = webtest.TestApp(router.APIRouter())
+
+    def _restore_attribute_map(self):
+        # Restore the global RESOURCE_ATTRIBUTE_MAP
+        attributes.RESOURCE_ATTRIBUTE_MAP = self.saved_attr_map
+
+    def _prepare_net_data(self):
+        return {'name': 'net1',
+                pnet.NETWORK_TYPE: 'sometype',
+                pnet.PHYSICAL_NETWORK: 'physnet',
+                pnet.SEGMENTATION_ID: 666}
+
+    def _put_network_with_provider_attrs(self, ctx, expect_errors=False):
+        data = self._prepare_net_data()
+        env = {'quantum.context': ctx}
+        instance = self.plugin.return_value
+        instance.get_network.return_value = {'tenant_id': ctx.tenant_id,
+                                             'shared': False}
+        net_id = uuidutils.generate_uuid()
+        res = self.api.put(test_api_v2._get_path('networks',
+                                                 id=net_id,
+                                                 fmt=self.fmt),
+                           self.serialize({'network': data}),
+                           extra_environ=env,
+                           expect_errors=expect_errors)
+        return res, data, net_id
+
+    def _post_network_with_provider_attrs(self, ctx, expect_errors=False):
+        data = self._prepare_net_data()
+        env = {'quantum.context': ctx}
+        res = self.api.post(test_api_v2._get_path('networks', fmt=self.fmt),
+                            self.serialize({'network': data}),
+                            content_type='application/' + self.fmt,
+                            extra_environ=env,
+                            expect_errors=expect_errors)
+        return res, data
+
+    def test_network_create_with_provider_attrs(self):
+        ctx = context.get_admin_context()
+        ctx.tenant_id = 'an_admin'
+        res, data = self._post_network_with_provider_attrs(ctx)
+        instance = self.plugin.return_value
+        exp_input = {'network': data}
+        exp_input['network'].update({'admin_state_up': True,
+                                     'tenant_id': 'an_admin',
+                                     'shared': False})
+        instance.create_network.assert_called_with(mock.ANY,
+                                                   network=exp_input)
+        self.assertEqual(res.status_int, web_exc.HTTPCreated.code)
+
+    def test_network_update_with_provider_attrs(self):
+        ctx = context.get_admin_context()
+        ctx.tenant_id = 'an_admin'
+        res, data, net_id = self._put_network_with_provider_attrs(ctx)
+        instance = self.plugin.return_value
+        exp_input = {'network': data}
+        instance.update_network.assert_called_with(mock.ANY,
+                                                   net_id,
+                                                   network=exp_input)
+        self.assertEqual(res.status_int, web_exc.HTTPOk.code)
+
+    def test_network_create_with_provider_attrs_noadmin_returns_403(self):
+        tenant_id = 'no_admin'
+        ctx = context.Context('', tenant_id, is_admin=False)
+        res, _1 = self._post_network_with_provider_attrs(ctx, True)
+        self.assertEqual(res.status_int, web_exc.HTTPForbidden.code)
+
+    def test_network_update_with_provider_attrs_noadmin_returns_404(self):
+        tenant_id = 'no_admin'
+        ctx = context.Context('', tenant_id, is_admin=False)
+        res, _1, _2 = self._put_network_with_provider_attrs(ctx, True)
+        self.assertEqual(res.status_int, web_exc.HTTPNotFound.code)