Show qos_specs_id based on policy

Right now qos_specs_id is only shown to an admin user
when showing a volume type. This patch changes that to
be based on policy to allow for more flexibility. It
also adds unit tests for showing a volume type
with policy permissions for qos_specs_id as well as
extra_specs.

APIImpact

Change-Id: I4e6e99b8992b6941ba247bee90493cc2adba7f0b
Closes-bug: #1512876
This commit is contained in:
Nate Potter 2015-11-04 15:45:11 +00:00
parent 16aac2a659
commit 9150dc58df
6 changed files with 119 additions and 22 deletions

View File

@ -25,7 +25,9 @@ import webob
from cinder.api.openstack import wsgi from cinder.api.openstack import wsgi
from cinder.api import xmlutil from cinder.api import xmlutil
from cinder import exception
from cinder.i18n import _ from cinder.i18n import _
import cinder.policy
from cinder import utils from cinder import utils
@ -78,6 +80,14 @@ def validate_key_names(key_names_list):
return True return True
def validate_policy(context, action):
try:
cinder.policy.enforce_action(context, action)
return True
except exception.PolicyNotAuthorized:
return False
def get_pagination_params(params, max_limit=None): def get_pagination_params(params, max_limit=None):
"""Return marker, limit, offset tuple from request. """Return marker, limit, offset tuple from request.

View File

@ -22,14 +22,11 @@ from cinder.api import common
from cinder.api.openstack import wsgi from cinder.api.openstack import wsgi
from cinder.api.v2.views import types as views_types from cinder.api.v2.views import types as views_types
from cinder.api import xmlutil from cinder.api import xmlutil
from cinder import context as ctx
from cinder import exception from cinder import exception
from cinder.i18n import _ from cinder.i18n import _
from cinder import utils from cinder import utils
from cinder.volume import volume_types from cinder.volume import volume_types
import cinder.policy
def make_voltype(elem): def make_voltype(elem):
elem.set('id') elem.set('id')
@ -61,18 +58,6 @@ class VolumeTypesController(wsgi.Controller):
_view_builder_class = views_types.ViewBuilder _view_builder_class = views_types.ViewBuilder
def _validate_policy(self, context):
target = {
'project_id': context.project_id,
'user_id': context.user_id,
}
try:
action = 'volume_extension:access_types_extra_specs'
cinder.policy.enforce(context, action, target)
return True
except Exception:
return False
@wsgi.serializers(xml=VolumeTypesTemplate) @wsgi.serializers(xml=VolumeTypesTemplate)
def index(self, req): def index(self, req):
"""Returns the list of volume types.""" """Returns the list of volume types."""
@ -85,9 +70,6 @@ class VolumeTypesController(wsgi.Controller):
"""Return a single volume type item.""" """Return a single volume type item."""
context = req.environ['cinder.context'] context = req.environ['cinder.context']
if not context.is_admin and self._validate_policy(context):
context = ctx.get_admin_context()
# get default volume type # get default volume type
if id is not None and id == 'default': if id is not None and id == 'default':
vol_type = volume_types.get_default_volume_type() vol_type = volume_types.get_default_volume_type()
@ -134,8 +116,6 @@ class VolumeTypesController(wsgi.Controller):
# to filters. # to filters.
filters = {} filters = {}
context = req.environ['cinder.context'] context = req.environ['cinder.context']
if not context.is_admin and self._validate_policy(context):
context = ctx.get_admin_context()
if context.is_admin: if context.is_admin:
# Only admin has query access to all volume types # Only admin has query access to all volume types
filters['is_public'] = self._parse_is_public( filters['is_public'] = self._parse_is_public(

View File

@ -26,9 +26,14 @@ class ViewBuilder(common.ViewBuilder):
name=volume_type.get('name'), name=volume_type.get('name'),
is_public=volume_type.get('is_public'), is_public=volume_type.get('is_public'),
description=volume_type.get('description')) description=volume_type.get('description'))
if context.is_admin: if common.validate_policy(
trimmed['qos_specs_id'] = volume_type.get('qos_specs_id') context,
'volume_extension:access_types_extra_specs'):
trimmed['extra_specs'] = volume_type.get('extra_specs') trimmed['extra_specs'] = volume_type.get('extra_specs')
if common.validate_policy(
context,
'volume_extension:access_types_qos_specs_id'):
trimmed['qos_specs_id'] = volume_type.get('qos_specs_id')
return trimmed if brief else dict(volume_type=trimmed) return trimmed if brief else dict(volume_type=trimmed)
def index(self, request, volume_types): def index(self, request, volume_types):

View File

@ -16,10 +16,12 @@
import uuid import uuid
from lxml import etree from lxml import etree
import mock
from oslo_utils import timeutils from oslo_utils import timeutils
import six import six
import webob import webob
import cinder.api.common as common
from cinder.api.v2 import types from cinder.api.v2 import types
from cinder.api.v2.views import types as views_types from cinder.api.v2.views import types as views_types
from cinder import context from cinder import context
@ -322,6 +324,103 @@ class VolumeTypesApiTest(test.TestCase):
) )
self.assertDictMatch(expected_volume_type, output['volume_type']) self.assertDictMatch(expected_volume_type, output['volume_type'])
def test_view_builder_show_qos_specs_id_policy(self):
with mock.patch.object(common,
'validate_policy',
side_effect=[False, True]):
view_builder = views_types.ViewBuilder()
now = timeutils.utcnow().isoformat()
raw_volume_type = dict(
name='new_type',
description='new_type_desc',
qos_specs_id='new_id',
is_public=True,
deleted=False,
created_at=now,
updated_at=now,
extra_specs={},
deleted_at=None,
id=42,
)
request = fakes.HTTPRequest.blank("/v2")
output = view_builder.show(request, raw_volume_type)
self.assertIn('volume_type', output)
expected_volume_type = dict(
name='new_type',
description='new_type_desc',
qos_specs_id='new_id',
is_public=True,
id=42,
)
self.assertDictMatch(expected_volume_type, output['volume_type'])
def test_view_builder_show_extra_specs_policy(self):
with mock.patch.object(common,
'validate_policy',
side_effect=[True, False]):
view_builder = views_types.ViewBuilder()
now = timeutils.utcnow().isoformat()
raw_volume_type = dict(
name='new_type',
description='new_type_desc',
qos_specs_id='new_id',
is_public=True,
deleted=False,
created_at=now,
updated_at=now,
extra_specs={},
deleted_at=None,
id=42,
)
request = fakes.HTTPRequest.blank("/v2")
output = view_builder.show(request, raw_volume_type)
self.assertIn('volume_type', output)
expected_volume_type = dict(
name='new_type',
description='new_type_desc',
extra_specs={},
is_public=True,
id=42,
)
self.assertDictMatch(expected_volume_type, output['volume_type'])
def test_view_builder_show_pass_all_policy(self):
with mock.patch.object(common,
'validate_policy',
side_effect=[True, True]):
view_builder = views_types.ViewBuilder()
now = timeutils.utcnow().isoformat()
raw_volume_type = dict(
name='new_type',
description='new_type_desc',
qos_specs_id='new_id',
is_public=True,
deleted=False,
created_at=now,
updated_at=now,
extra_specs={},
deleted_at=None,
id=42,
)
request = fakes.HTTPRequest.blank("/v2")
output = view_builder.show(request, raw_volume_type)
self.assertIn('volume_type', output)
expected_volume_type = dict(
name='new_type',
description='new_type_desc',
qos_specs_id='new_id',
extra_specs={},
is_public=True,
id=42,
)
self.assertDictMatch(expected_volume_type, output['volume_type'])
def test_view_builder_list(self): def test_view_builder_list(self):
view_builder = views_types.ViewBuilder() view_builder = views_types.ViewBuilder()

View File

@ -49,6 +49,8 @@
"volume_extension:volume_actions:upload_image": "", "volume_extension:volume_actions:upload_image": "",
"volume_extension:types_manage": "", "volume_extension:types_manage": "",
"volume_extension:types_extra_specs": "", "volume_extension:types_extra_specs": "",
"volume_extension:access_types_qos_specs_id": "rule:admin_api",
"volume_extension:access_types_extra_specs": "rule:admin_api",
"volume_extension:volume_type_access": "", "volume_extension:volume_type_access": "",
"volume_extension:volume_type_access:addProjectAccess": "rule:admin_api", "volume_extension:volume_type_access:addProjectAccess": "rule:admin_api",
"volume_extension:volume_type_access:removeProjectAccess": "rule:admin_api", "volume_extension:volume_type_access:removeProjectAccess": "rule:admin_api",

View File

@ -26,6 +26,7 @@
"volume_extension:types_manage": "rule:admin_api", "volume_extension:types_manage": "rule:admin_api",
"volume_extension:types_extra_specs": "rule:admin_api", "volume_extension:types_extra_specs": "rule:admin_api",
"volume_extension:access_types_qos_specs_id": "rule:admin_api",
"volume_extension:access_types_extra_specs": "rule:admin_api", "volume_extension:access_types_extra_specs": "rule:admin_api",
"volume_extension:volume_type_access": "rule:admin_or_owner", "volume_extension:volume_type_access": "rule:admin_or_owner",
"volume_extension:volume_type_access:addProjectAccess": "rule:admin_api", "volume_extension:volume_type_access:addProjectAccess": "rule:admin_api",