]> review.fuel-infra Code Review - openstack-build/neutron-build.git/commitdiff
Add hook for policy enforcement
authorKevin Benton <blak111@gmail.com>
Thu, 11 Jun 2015 14:13:12 +0000 (07:13 -0700)
committerKevin Benton <kevinbenton@buttewifi.com>
Sat, 1 Aug 2015 18:57:09 +0000 (18:57 +0000)
Add a hook to enforce policy.json.

Partially-Implements: blueprint wsgi-pecan-switch
Change-Id: I49096f2e6ea70477dbbd399fd81f33bd19dbc453

neutron/newapi/app.py
neutron/newapi/controllers/root.py
neutron/newapi/hooks/__init__.py
neutron/newapi/hooks/policy_enforcement.py [new file with mode: 0644]
neutron/tests/functional/newapi/test_functional.py

index b41439b485277f969233a8816ab1b5a9e746b7ed..23fa577560ed81a585eeb1ddd9d5cfd595fe7088 100644 (file)
@@ -48,6 +48,7 @@ def setup_app(*args, **kwargs):
         hooks.AttributePopulationHook(),  # priority 120
         hooks.OwnershipValidationHook(),  # priority 125
         hooks.QuotaEnforcementHook(),  # priority 130
+        hooks.PolicyHook(),  # priority 135
     ]
 
     app = pecan.make_app(
index a9ba2c62a49929732f010edcfa2a7b7c18fd85a3..221755f943b7630dfd069a56fd1e32cd6eb2c27c 100644 (file)
@@ -62,8 +62,6 @@ class GeneralController(object):
 
     @expose(generic=True)
     def index(self):
-        if pecan.request.method != 'GET':
-            pecan.abort(405)
         return self.get()
 
     def get(self):
index 0b06df40ca35e6bfd8c5a6c7ae02fc120d4a047b..975c983158b34e75717f2c6ea1d43a1693058319 100644 (file)
@@ -16,6 +16,7 @@
 from neutron.newapi.hooks import attribute_population
 from neutron.newapi.hooks import context
 from neutron.newapi.hooks import ownership_validation
+from neutron.newapi.hooks import policy_enforcement
 from neutron.newapi.hooks import quota_enforcement
 from neutron.newapi.hooks import resource_identifier
 from neutron.newapi.hooks import translation
@@ -26,4 +27,5 @@ ContextHook = context.ContextHook
 ResourceIdentifierHook = resource_identifier.ResourceIdentifierHook
 AttributePopulationHook = attribute_population.AttributePopulationHook
 OwnershipValidationHook = ownership_validation.OwnershipValidationHook
+PolicyHook = policy_enforcement.PolicyHook
 QuotaEnforcementHook = quota_enforcement.QuotaEnforcementHook
diff --git a/neutron/newapi/hooks/policy_enforcement.py b/neutron/newapi/hooks/policy_enforcement.py
new file mode 100644 (file)
index 0000000..4b0696c
--- /dev/null
@@ -0,0 +1,126 @@
+# Copyright (c) 2015 Mirantis, Inc.
+# 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.
+
+import copy
+import simplejson
+
+from oslo_policy import policy as oslo_policy
+from oslo_utils import excutils
+import pecan
+from pecan import hooks
+import webob
+
+from neutron.common import constants as const
+from neutron.newapi.hooks import attribute_population
+from neutron import policy
+
+
+class PolicyHook(hooks.PecanHook):
+    priority = 135
+    ACTION_MAP = {'POST': 'create', 'PUT': 'update', 'GET': 'get',
+                  'DELETE': 'delete'}
+
+    def before(self, state):
+        if state.request.method not in self.ACTION_MAP:
+            pecan.abort(405)
+        rtype = state.request.resource_type
+        if not rtype:
+            return
+        is_update = (state.request.method == 'PUT')
+        items = state.request.resources
+        policy.init()
+        action = '%s_%s' % (self.ACTION_MAP[state.request.method], rtype)
+        for item in items:
+            if is_update:
+                obj = copy.copy(state.request.original_object)
+                obj.update(item)
+                obj[const.ATTRIBUTES_TO_UPDATE] = item.keys()
+                item = obj
+            try:
+                policy.enforce(state.request.context, action, item,
+                               pluralized=attribute_population._plural(rtype))
+            except oslo_policy.PolicyNotAuthorized:
+                with excutils.save_and_reraise_exception() as ctxt:
+                    # If a tenant is modifying it's own object, it's safe to
+                    # return a 403. Otherwise, pretend that it doesn't exist
+                    # to avoid giving away information.
+                    context = state.request.context
+                    if (is_update and
+                            context.tenant_id != obj['tenant_id']):
+                        ctxt.reraise = False
+                msg = _('The resource could not be found.')
+                raise webob.exc.HTTPNotFound(msg)
+
+    def after(self, state):
+        resource_type = getattr(state.request, 'resource_type', None)
+        if not resource_type:
+            # can't filter a resource we don't recognize
+            return
+        # NOTE(kevinbenton): extension listing isn't controlled by policy
+        if resource_type == 'extension':
+            return
+        try:
+            data = state.response.json
+        except simplejson.JSONDecodeError:
+            return
+        if not data:
+            return
+        if resource_type in data:
+            # singular response
+            data[resource_type] = self._get_filtered_item(
+                state.request.context, resource_type, data[resource_type])
+        elif attribute_population._plural(resource_type) in data:
+            # plural response
+            plural = attribute_population._plural(resource_type)
+            data[plural] = [self._get_filtered_item(state.request.context,
+                                                    resource_type, item)
+                            for item in data[plural]]
+        state.response.json = data
+
+    def _get_filtered_item(self, context, resource_type, data):
+        to_exclude = self._exclude_attributes_by_policy(context,
+                                                        resource_type, data)
+        return self._filter_attributes(context, data, to_exclude)
+
+    def _filter_attributes(self, context, data, fields_to_strip):
+        return dict(item for item in data.items()
+                    if (item[0] not in fields_to_strip))
+
+    def _exclude_attributes_by_policy(self, context, resource_type, data):
+        """Identifies attributes to exclude according to authZ policies.
+
+        Return a list of attribute names which should be stripped from the
+        response returned to the user because the user is not authorized
+        to see them.
+        """
+        attributes_to_exclude = []
+        for attr_name in data.keys():
+            attr_data = attribute_population._attributes_for_resource(
+                resource_type).get(attr_name)
+            if attr_data and attr_data['is_visible']:
+                if policy.check(
+                    context,
+                    # NOTE(kevinbenton): this used to reference a
+                    # _plugin_handlers dict, why?
+                    'get_%s:%s' % (resource_type, attr_name),
+                    data,
+                    might_not_exist=True,
+                    pluralized=attribute_population._plural(resource_type)):
+                    # this attribute is visible, check next one
+                    continue
+            # if the code reaches this point then either the policy check
+            # failed or the attribute was not visible in the first place
+            attributes_to_exclude.append(attr_name)
+        return attributes_to_exclude
index 617e52c9e1b6affd851cfd68fbcfd23ec0b01072..bc7089c8d43aa1b5b4a4f009b748339668aae0c8 100644 (file)
@@ -217,3 +217,7 @@ class TestEnforcementHooks(PecanFunctionalTest):
     def test_quota_enforcement(self):
         # TODO(kevinbenton): this test should do something
         pass
+
+    def test_policy_enforcement(self):
+        # TODO(kevinbenton): this test should do something
+        pass