From d2c6dfb3d3286b25d77d0bb8341df739a1a81405 Mon Sep 17 00:00:00 2001 From: TommyLike Date: Wed, 27 Sep 2017 16:44:47 +0800 Subject: [PATCH] [policy in code] Add support for group, g-snapshot resources This patch adds policy in code support for group&group snapshot resources and depends on the backup patch [1]. [1]: https://review.openstack.org/#/c/507015/ Change-Id: If95a8aaa70614902a06420d1afa487827f8a3f03 Partial-Implements: blueprint policy-in-code --- cinder/api/v3/group_specs.py | 19 ++-- cinder/api/v3/group_types.py | 15 +--- cinder/api/v3/views/group_types.py | 5 +- cinder/group/api.py | 75 +++++----------- cinder/policies/__init__.py | 10 +++ cinder/policies/group_actions.py | 93 ++++++++++++++++++++ cinder/policies/group_snapshot_actions.py | 40 +++++++++ cinder/policies/group_snapshots.py | 87 ++++++++++++++++++ cinder/policies/group_types.py | 86 ++++++++++++++++++ cinder/policies/groups.py | 75 ++++++++++++++++ cinder/tests/unit/api/v3/test_group_types.py | 28 ++---- cinder/tests/unit/group/test_groups_api.py | 69 ++++----------- cinder/tests/unit/policy.json | 6 -- etc/cinder/policy.json | 24 ----- 14 files changed, 447 insertions(+), 185 deletions(-) create mode 100644 cinder/policies/group_actions.py create mode 100644 cinder/policies/group_snapshot_actions.py create mode 100644 cinder/policies/group_snapshots.py create mode 100644 cinder/policies/group_types.py create mode 100644 cinder/policies/groups.py diff --git a/cinder/api/v3/group_specs.py b/cinder/api/v3/group_specs.py index e851839048b..0af077f055c 100644 --- a/cinder/api/v3/group_specs.py +++ b/cinder/api/v3/group_specs.py @@ -23,7 +23,7 @@ from cinder.api.openstack import wsgi from cinder import db from cinder import exception from cinder.i18n import _ -from cinder import policy +from cinder.policies import group_types as policy from cinder import rpc from cinder import utils from cinder.volume import group_types @@ -32,13 +32,6 @@ from cinder.volume import group_types class GroupTypeSpecsController(wsgi.Controller): """The group type specs API controller for the OpenStack API.""" - def _check_policy(self, context): - target = { - 'project_id': context.project_id, - 'user_id': context.user_id, - } - policy.enforce(context, 'group:group_types_specs', target) - def _get_group_specs(self, context, group_type_id): group_specs = db.group_type_specs_get(context, group_type_id) specs_dict = {} @@ -56,7 +49,7 @@ class GroupTypeSpecsController(wsgi.Controller): def index(self, req, group_type_id): """Returns the list of group specs for a given group type.""" context = req.environ['cinder.context'] - self._check_policy(context) + context.authorize(policy.SPEC_POLICY) self._check_type(context, group_type_id) return self._get_group_specs(context, group_type_id) @@ -64,7 +57,7 @@ class GroupTypeSpecsController(wsgi.Controller): @wsgi.response(http_client.ACCEPTED) def create(self, req, group_type_id, body=None): context = req.environ['cinder.context'] - self._check_policy(context) + context.authorize(policy.SPEC_POLICY) self.assert_valid_body(body, 'group_specs') self._check_type(context, group_type_id) @@ -84,7 +77,7 @@ class GroupTypeSpecsController(wsgi.Controller): @wsgi.Controller.api_version(mv.GROUP_TYPE) def update(self, req, group_type_id, id, body=None): context = req.environ['cinder.context'] - self._check_policy(context) + context.authorize(policy.SPEC_POLICY) if not body: expl = _('Request body empty') @@ -113,7 +106,7 @@ class GroupTypeSpecsController(wsgi.Controller): def show(self, req, group_type_id, id): """Return a single extra spec item.""" context = req.environ['cinder.context'] - self._check_policy(context) + context.authorize(policy.SPEC_POLICY) self._check_type(context, group_type_id) specs = self._get_group_specs(context, group_type_id) @@ -128,7 +121,7 @@ class GroupTypeSpecsController(wsgi.Controller): def delete(self, req, group_type_id, id): """Deletes an existing group spec.""" context = req.environ['cinder.context'] - self._check_policy(context) + context.authorize(policy.SPEC_POLICY) self._check_type(context, group_type_id) diff --git a/cinder/api/v3/group_types.py b/cinder/api/v3/group_types.py index c7fbad10216..be66b17d9f3 100644 --- a/cinder/api/v3/group_types.py +++ b/cinder/api/v3/group_types.py @@ -26,7 +26,7 @@ from cinder.api.openstack import wsgi from cinder.api.v3.views import group_types as views_types from cinder import exception from cinder.i18n import _ -from cinder import policy +from cinder.policies import group_types as policy from cinder import rpc from cinder import utils from cinder.volume import group_types @@ -37,13 +37,6 @@ class GroupTypesController(wsgi.Controller): _view_builder_class = views_types.ViewBuilder - def _check_policy(self, context): - target = { - 'project_id': context.project_id, - 'user_id': context.user_id, - } - policy.enforce(context, 'group:group_types_manage', target) - @utils.if_notifications_enabled def _notify_group_type_error(self, context, method, err, group_type=None, id=None, name=None): @@ -61,7 +54,7 @@ class GroupTypesController(wsgi.Controller): def create(self, req, body): """Creates a new group type.""" context = req.environ['cinder.context'] - self._check_policy(context) + context.authorize(policy.MANAGE_POLICY) self.assert_valid_body(body, 'group_type') @@ -108,7 +101,7 @@ class GroupTypesController(wsgi.Controller): def update(self, req, id, body): # Update description for a given group type. context = req.environ['cinder.context'] - self._check_policy(context) + context.authorize(policy.MANAGE_POLICY) self.assert_valid_body(body, 'group_type') @@ -168,7 +161,7 @@ class GroupTypesController(wsgi.Controller): def delete(self, req, id): """Deletes an existing group type.""" context = req.environ['cinder.context'] - self._check_policy(context) + context.authorize(policy.MANAGE_POLICY) try: grp_type = group_types.get_group_type(context, id) diff --git a/cinder/api/v3/views/group_types.py b/cinder/api/v3/views/group_types.py index 6313d7712ef..e415163fa23 100644 --- a/cinder/api/v3/views/group_types.py +++ b/cinder/api/v3/views/group_types.py @@ -14,6 +14,7 @@ # under the License. from cinder.api import common +from cinder.policies import group_types as policy class ViewBuilder(common.ViewBuilder): @@ -25,9 +26,7 @@ class ViewBuilder(common.ViewBuilder): name=group_type.get('name'), description=group_type.get('description'), is_public=group_type.get('is_public')) - if common.validate_policy( - context, - 'group:access_group_types_specs'): + if context.authorize(policy.SHOW_ACCESS_POLICY, fatal=False): trimmed['group_specs'] = group_type.get('group_specs') return trimmed if brief else dict(group_type=trimmed) diff --git a/cinder/group/api.py b/cinder/group/api.py index 0bcbc14c69f..8b75f8982d0 100644 --- a/cinder/group/api.py +++ b/cinder/group/api.py @@ -18,8 +18,6 @@ Handles all requests relating to groups. """ -import functools - from oslo_config import cfg from oslo_log import log as logging from oslo_utils import excutils @@ -31,9 +29,11 @@ from cinder.db import base from cinder import exception from cinder.i18n import _ from cinder import objects -from cinder.objects import base as objects_base from cinder.objects import fields as c_fields -import cinder.policy +from cinder.policies import group_actions as gp_action_policy +from cinder.policies import group_snapshot_actions as gsnap_action_policy +from cinder.policies import group_snapshots as gsnap_policy +from cinder.policies import groups as group_policy from cinder import quota from cinder import quota_utils from cinder.scheduler import rpcapi as scheduler_rpcapi @@ -57,37 +57,6 @@ VALID_ADD_VOL_TO_GROUP_STATUS = ( 'in-use') -def wrap_check_policy(func): - """Check policy corresponding to the wrapped methods prior to execution. - - This decorator requires the first 3 args of the wrapped function - to be (self, context, group) - """ - @functools.wraps(func) - def wrapped(self, context, target_obj, *args, **kwargs): - check_policy(context, func.__name__, target_obj) - return func(self, context, target_obj, *args, **kwargs) - - return wrapped - - -def check_policy(context, action, target_obj=None): - target = { - 'project_id': context.project_id, - 'user_id': context.user_id, - } - - if isinstance(target_obj, objects_base.CinderObject): - # Turn object into dict so target.update can work - target.update( - target_obj.obj_to_primitive()['versioned_object.data'] or {}) - else: - target.update(target_obj or {}) - - _action = 'group:%s' % action - cinder.policy.enforce(context, _action, target) - - class API(base.Base): """API for interacting with the volume manager for groups.""" @@ -130,7 +99,7 @@ class API(base.Base): def create(self, context, name, description, group_type, volume_types, availability_zone=None): - check_policy(context, 'create') + context.authorize(group_policy.CREATE_POLICY) req_volume_types = [] # NOTE: Admin context is required to get extra_specs of volume_types. @@ -196,7 +165,7 @@ class API(base.Base): def create_from_src(self, context, name, description=None, group_snapshot_id=None, source_group_id=None): - check_policy(context, 'create') + context.authorize(group_policy.CREATE_POLICY) # Populate group_type_id and volume_type_ids group_type_id = None @@ -514,9 +483,8 @@ class API(base.Base): finally: LOG.error("Failed to update quota for group %s.", group.id) - @wrap_check_policy def delete(self, context, group, delete_volumes=False): - + context.authorize(gp_action_policy.DELETE_POLICY, target_obj=group) if not group.host: self.update_quota(context, group, -1, group.project_id) @@ -602,10 +570,10 @@ class API(base.Base): self.volume_rpcapi.delete_group(context, group) - @wrap_check_policy def update(self, context, group, name, description, add_volumes, remove_volumes): """Update group.""" + context.authorize(group_policy.UPDATE_POLICY, target_obj=group) # Validate name. if name == group.name: name = None @@ -806,12 +774,12 @@ class API(base.Base): def get(self, context, group_id): group = objects.Group.get_by_id(context, group_id) - check_policy(context, 'get', group) + context.authorize(group_policy.GET_POLICY, target_obj=group) return group def get_all(self, context, filters=None, marker=None, limit=None, offset=None, sort_keys=None, sort_dirs=None): - check_policy(context, 'get_all') + context.authorize(group_policy.GET_ALL_POLICY) if filters is None: filters = {} @@ -830,10 +798,9 @@ class API(base.Base): sort_dirs=sort_dirs) return groups - @wrap_check_policy def reset_status(self, context, group, status): """Reset status of generic group""" - + context.authorize(gp_action_policy.RESET_STATUS, target_obj=group) if status not in c_fields.GroupStatus.ALL: msg = _("Group status: %(status)s is invalid, valid status " "are: %(valid)s.") % {'status': status, @@ -844,8 +811,8 @@ class API(base.Base): group.update(field) group.save() - @wrap_check_policy def create_group_snapshot(self, context, group, name, description): + context.authorize(gsnap_policy.CREATE_POLICY, target_obj=group) group.assert_not_frozen() options = {'group_id': group.id, 'user_id': context.user_id, @@ -884,7 +851,7 @@ class API(base.Base): return group_snapshot def delete_group_snapshot(self, context, group_snapshot, force=False): - check_policy(context, 'delete_group_snapshot') + context.authorize(gsnap_policy.DELETE_POLICY) group_snapshot.assert_not_frozen() values = {'status': 'deleting'} expected = {'status': ('available', 'error')} @@ -911,12 +878,12 @@ class API(base.Base): group_snapshot) def update_group_snapshot(self, context, group_snapshot, fields): - check_policy(context, 'update_group_snapshot') + context.authorize(gsnap_policy.UPDATE_POLICY) group_snapshot.update(fields) group_snapshot.save() def get_group_snapshot(self, context, group_snapshot_id): - check_policy(context, 'get_group_snapshot') + context.authorize(gsnap_policy.GET_POLICY) group_snapshots = objects.GroupSnapshot.get_by_id(context, group_snapshot_id) return group_snapshots @@ -924,7 +891,7 @@ class API(base.Base): def get_all_group_snapshots(self, context, filters=None, marker=None, limit=None, offset=None, sort_keys=None, sort_dirs=None): - check_policy(context, 'get_all_group_snapshots') + context.authorize(gsnap_policy.GET_ALL_POLICY) filters = filters or {} if context.is_admin and 'all_tenants' in filters: @@ -943,7 +910,7 @@ class API(base.Base): def reset_group_snapshot_status(self, context, gsnapshot, status): """Reset status of group snapshot""" - check_policy(context, 'reset_group_snapshot_status') + context.authorize(gsnap_action_policy.RESET_STATUS) if status not in c_fields.GroupSnapshotStatus.ALL: msg = _("Group snapshot status: %(status)s is invalid, " "valid statuses are: " @@ -969,8 +936,8 @@ class API(base.Base): raise exception.InvalidVolumeType(reason=msg) # Replication group API (Tiramisu) - @wrap_check_policy def enable_replication(self, context, group): + context.authorize(gp_action_policy.ENABLE_REP, target_obj=group) self._check_type(group) valid_status = [c_fields.GroupStatus.AVAILABLE] @@ -1030,8 +997,8 @@ class API(base.Base): self.volume_rpcapi.enable_replication(context, group) - @wrap_check_policy def disable_replication(self, context, group): + context.authorize(gp_action_policy.DISABLE_REP, target_obj=group) self._check_type(group) valid_status = [c_fields.GroupStatus.AVAILABLE, @@ -1080,10 +1047,10 @@ class API(base.Base): self.volume_rpcapi.disable_replication(context, group) - @wrap_check_policy def failover_replication(self, context, group, allow_attached_volume=False, secondary_backend_id=None): + context.authorize(gp_action_policy.FAILOVER_REP, target_obj=group) self._check_type(group) valid_status = [c_fields.GroupStatus.AVAILABLE] @@ -1148,8 +1115,8 @@ class API(base.Base): allow_attached_volume, secondary_backend_id) - @wrap_check_policy def list_replication_targets(self, context, group): + context.authorize(gp_action_policy.LIST_REP, target_obj=group) self._check_type(group) return self.volume_rpcapi.list_replication_targets(context, group) diff --git a/cinder/policies/__init__.py b/cinder/policies/__init__.py index 250534cd52a..50a29999dd0 100644 --- a/cinder/policies/__init__.py +++ b/cinder/policies/__init__.py @@ -20,6 +20,11 @@ from cinder.policies import backup_actions from cinder.policies import backups from cinder.policies import base from cinder.policies import clusters +from cinder.policies import group_actions +from cinder.policies import group_snapshot_actions +from cinder.policies import group_snapshots +from cinder.policies import group_types +from cinder.policies import groups from cinder.policies import manageable_snapshots from cinder.policies import messages from cinder.policies import snapshot_actions @@ -41,4 +46,9 @@ def list_rules(): manageable_snapshots.list_rules(), backups.list_rules(), backup_actions.list_rules(), + groups.list_rules(), + group_types.list_rules(), + group_snapshots.list_rules(), + group_snapshot_actions.list_rules(), + group_actions.list_rules(), ) diff --git a/cinder/policies/group_actions.py b/cinder/policies/group_actions.py new file mode 100644 index 00000000000..04294ef0245 --- /dev/null +++ b/cinder/policies/group_actions.py @@ -0,0 +1,93 @@ +# Copyright (c) 2017 Huawei Technologies Co., Ltd. +# 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. + +from oslo_policy import policy + +from cinder.policies import base + + +RESET_STATUS = 'group:reset_status' +ENABLE_REP = 'group:enable_replication' +DISABLE_REP = 'group:disable_replication' +FAILOVER_REP = 'group:failover_replication' +LIST_REP = 'group:list_replication_targets' +DELETE_POLICY = 'group:delete' + +group_actions_policies = [ + policy.DocumentedRuleDefault( + name=DELETE_POLICY, + check_str=base.RULE_ADMIN_OR_OWNER, + description="Delete group.", + operations=[ + { + 'method': 'POST', + 'path': '/groups/{group_id}/action (delete)' + } + ]), + policy.DocumentedRuleDefault( + name=RESET_STATUS, + check_str=base.RULE_ADMIN_API, + description="Reset status of group.", + operations=[ + { + 'method': 'POST', + 'path': '/groups/{group_id}/action (reset_status)' + } + ]), + policy.DocumentedRuleDefault( + name=ENABLE_REP, + check_str=base.RULE_ADMIN_OR_OWNER, + description="Enable replication.", + operations=[ + { + 'method': 'POST', + 'path': '/groups/{group_id}/action (enable_replication)' + } + ]), + policy.DocumentedRuleDefault( + name=DISABLE_REP, + check_str=base.RULE_ADMIN_OR_OWNER, + description="Disable replication.", + operations=[ + { + 'method': 'POST', + 'path': '/groups/{group_id}/action (disable_replication)' + } + ]), + policy.DocumentedRuleDefault( + name=FAILOVER_REP, + check_str=base.RULE_ADMIN_OR_OWNER, + description="Fail over replication.", + operations=[ + { + 'method': 'POST', + 'path': '/groups/{group_id}/action (failover_replication)' + } + ]), + policy.DocumentedRuleDefault( + name=LIST_REP, + check_str=base.RULE_ADMIN_OR_OWNER, + description="List failover replication.", + operations=[ + { + 'method': 'POST', + 'path': '/groups/{group_id}/action (list_replication_targets)' + } + ]), +] + + +def list_rules(): + return group_actions_policies diff --git a/cinder/policies/group_snapshot_actions.py b/cinder/policies/group_snapshot_actions.py new file mode 100644 index 00000000000..6a766d602e6 --- /dev/null +++ b/cinder/policies/group_snapshot_actions.py @@ -0,0 +1,40 @@ +# Copyright (c) 2017 Huawei Technologies Co., Ltd. +# 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. + +from oslo_policy import policy + +from cinder.policies import base + + +RESET_STATUS = 'group:reset_group_snapshot_status' + + +group_snapshot_actions_policies = [ + policy.DocumentedRuleDefault( + name=RESET_STATUS, + check_str=base.RULE_ADMIN_OR_OWNER, + description="Reset status of group snapshot.", + operations=[ + { + 'method': 'POST', + 'path': + '/group_snapshots/{g_snapshot_id}/action (reset_status)' + } + ]), +] + + +def list_rules(): + return group_snapshot_actions_policies diff --git a/cinder/policies/group_snapshots.py b/cinder/policies/group_snapshots.py new file mode 100644 index 00000000000..8413977832f --- /dev/null +++ b/cinder/policies/group_snapshots.py @@ -0,0 +1,87 @@ +# Copyright (c) 2017 Huawei Technologies Co., Ltd. +# 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. + +from oslo_policy import policy + +from cinder.policies import base + + +CREATE_POLICY = 'group:create_group_snapshot' +DELETE_POLICY = 'group:delete_group_snapshot' +UPDATE_POLICY = 'group:update_group_snapshot' +GET_POLICY = 'group:get_group_snapshot' +GET_ALL_POLICY = 'group:get_all_group_snapshots' + + +group_snapshots_policies = [ + policy.DocumentedRuleDefault( + name=GET_ALL_POLICY, + check_str=base.RULE_ADMIN_OR_OWNER, + description="List group snapshots.", + operations=[ + { + 'method': 'GET', + 'path': '/group_snapshots' + }, + { + 'method': 'GET', + 'path': '/group_snapshots/detail' + } + ]), + policy.DocumentedRuleDefault( + name=CREATE_POLICY, + check_str="", + description="Create group snapshot.", + operations=[ + { + 'method': 'POST', + 'path': '/group_snapshots' + } + ]), + policy.DocumentedRuleDefault( + name=GET_POLICY, + check_str=base.RULE_ADMIN_OR_OWNER, + description="Show group snapshot.", + operations=[ + { + 'method': 'GET', + 'path': '/group_snapshots/{group_snapshot_id}' + } + ]), + policy.DocumentedRuleDefault( + name=DELETE_POLICY, + check_str=base.RULE_ADMIN_OR_OWNER, + description="Delete group snapshot.", + operations=[ + { + 'method': 'DELETE', + 'path': '/group_snapshots/{group_snapshot_id}' + } + ]), + policy.DocumentedRuleDefault( + name=UPDATE_POLICY, + check_str=base.RULE_ADMIN_OR_OWNER, + description="Update group snapshot.", + operations=[ + { + 'method': 'PUT', + 'path': '/group_snapshots/{group_snapshot_id}' + } + ]), +] + + +def list_rules(): + return group_snapshots_policies diff --git a/cinder/policies/group_types.py b/cinder/policies/group_types.py new file mode 100644 index 00000000000..fbe31653fae --- /dev/null +++ b/cinder/policies/group_types.py @@ -0,0 +1,86 @@ +# Copyright (c) 2017 Huawei Technologies Co., Ltd. +# 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. + +from oslo_policy import policy + +from cinder.policies import base + + +MANAGE_POLICY = 'group:group_types_manage' +SHOW_ACCESS_POLICY = 'group:access_group_types_specs' +SPEC_POLICY = 'group:group_types_specs' + + +group_types_policies = [ + policy.DocumentedRuleDefault( + name=MANAGE_POLICY, + check_str=base.RULE_ADMIN_API, + description="Create, update or delete a group type.", + operations=[ + { + 'method': 'POST', + 'path': '/group_types/' + }, + { + 'method': 'PUT', + 'path': '/group_types/{group_type_id}' + }, + { + 'method': 'DELETE', + 'path': '/group_types/{group_type_id}' + } + + ]), + policy.DocumentedRuleDefault( + name=SHOW_ACCESS_POLICY, + check_str=base.RULE_ADMIN_API, + description="Show group type with type specs attributes.", + operations=[ + { + 'method': 'GET', + 'path': '/group_types/{group_type_id}' + } + ]), + policy.DocumentedRuleDefault( + name=SPEC_POLICY, + check_str=base.RULE_ADMIN_API, + description="Create, show, update and delete group type spec.", + operations=[ + { + 'method': 'GET', + 'path': '/group_types/{group_type_id}/group_specs/{g_spec_id}' + }, + { + 'method': 'GET', + 'path': '/group_types/{group_type_id}/group_specs' + }, + { + 'method': 'POST', + 'path': '/group_types/{group_type_id}/group_specs' + }, + { + 'method': 'PUT', + 'path': '/group_types/{group_type_id}/group_specs/{g_spec_id}' + }, + { + 'method': 'DELETE', + 'path': '/group_types/{group_type_id}/group_specs/{g_spec_id}' + } + ]), +] + + +def list_rules(): + return group_types_policies diff --git a/cinder/policies/groups.py b/cinder/policies/groups.py new file mode 100644 index 00000000000..6d983e1dc3d --- /dev/null +++ b/cinder/policies/groups.py @@ -0,0 +1,75 @@ +# Copyright (c) 2017 Huawei Technologies Co., Ltd. +# 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. + +from oslo_policy import policy + +from cinder.policies import base + +CREATE_POLICY = 'group:create' +UPDATE_POLICY = 'group:update' +GET_POLICY = 'group:get' +GET_ALL_POLICY = 'group:get_all' + + +groups_policies = [ + policy.DocumentedRuleDefault( + name=GET_ALL_POLICY, + check_str=base.RULE_ADMIN_OR_OWNER, + description="List groups.", + operations=[ + { + 'method': 'GET', + 'path': '/groups' + }, + { + 'method': 'GET', + 'path': '/groups/detail' + } + ]), + policy.DocumentedRuleDefault( + name=CREATE_POLICY, + check_str="", + description="Create group.", + operations=[ + { + 'method': 'POST', + 'path': '/groups' + } + ]), + policy.DocumentedRuleDefault( + name=GET_POLICY, + check_str=base.RULE_ADMIN_OR_OWNER, + description="Show group.", + operations=[ + { + 'method': 'GET', + 'path': '/groups/{group_id}' + } + ]), + policy.DocumentedRuleDefault( + name=UPDATE_POLICY, + check_str=base.RULE_ADMIN_OR_OWNER, + description="Update group.", + operations=[ + { + 'method': 'PUT', + 'path': '/groups/{group_id}' + } + ]), +] + + +def list_rules(): + return groups_policies diff --git a/cinder/tests/unit/api/v3/test_group_types.py b/cinder/tests/unit/api/v3/test_group_types.py index b979e108ee1..c7aa5dc3375 100644 --- a/cinder/tests/unit/api/v3/test_group_types.py +++ b/cinder/tests/unit/api/v3/test_group_types.py @@ -22,7 +22,6 @@ from oslo_utils import timeutils import six import webob -import cinder.api.common as common from cinder.api import microversions as mv from cinder.api.v3 import group_specs as v3_group_specs from cinder.api.v3 import group_types as v3_group_types @@ -462,8 +461,8 @@ class GroupTypesApiTest(test.TestCase): self.assertDictEqual(expected_group_type, output['group_type']) def __test_view_builder_show_qos_specs_id_policy(self): - with mock.patch.object(common, - 'validate_policy', + with mock.patch.object(context.RequestContext, + 'authorize', side_effect=[False, True]): view_builder = views_types.ViewBuilder() now = timeutils.utcnow().isoformat() @@ -492,8 +491,8 @@ class GroupTypesApiTest(test.TestCase): self.assertDictEqual(expected_group_type, output['group_type']) def test_view_builder_show_group_specs_policy(self): - with mock.patch.object(common, - 'validate_policy', + with mock.patch.object(context.RequestContext, + 'authorize', side_effect=[True, False]): view_builder = views_types.ViewBuilder() now = timeutils.utcnow().isoformat() @@ -524,9 +523,9 @@ class GroupTypesApiTest(test.TestCase): self.assertDictEqual(expected_group_type, output['group_type']) def test_view_builder_show_pass_all_policy(self): - with mock.patch.object(common, - 'validate_policy', - side_effect=[True, True]): + with mock.patch.object(context.RequestContext, + 'authorize', + side_effect=[True, False]): view_builder = views_types.ViewBuilder() now = timeutils.utcnow().isoformat() raw_group_type = dict( @@ -625,16 +624,3 @@ class GroupTypesApiTest(test.TestCase): ) self.assertDictEqual(expected_group_type, output['group_types'][i]) - - def test_check_policy(self): - self.controller._check_policy(self.ctxt) - - self.assertRaises(exception.PolicyNotAuthorized, - self.controller._check_policy, - self.user_ctxt) - - self.specs_controller._check_policy(self.ctxt) - - self.assertRaises(exception.PolicyNotAuthorized, - self.specs_controller._check_policy, - self.user_ctxt) diff --git a/cinder/tests/unit/group/test_groups_api.py b/cinder/tests/unit/group/test_groups_api.py index 80d5228d50b..4daba871cd8 100644 --- a/cinder/tests/unit/group/test_groups_api.py +++ b/cinder/tests/unit/group/test_groups_api.py @@ -49,19 +49,16 @@ class GroupAPITestCase(test.TestCase): fake.USER_ID, fake.PROJECT_ID, auth_token=True) @mock.patch('cinder.objects.Group.get_by_id') - @mock.patch('cinder.group.api.check_policy') - def test_get(self, mock_policy, mock_group_get): - fake_group = 'fake_group' + def test_get(self, mock_group_get): + fake_group = {'name': 'fake_group'} mock_group_get.return_value = fake_group grp = self.group_api.get(self.ctxt, fake.GROUP_ID) self.assertEqual(fake_group, grp) - mock_policy.assert_called_once_with(self.ctxt, 'get', mock.ANY) @ddt.data(True, False) @mock.patch('cinder.objects.GroupList.get_all') @mock.patch('cinder.objects.GroupList.get_all_by_project') - @mock.patch('cinder.group.api.check_policy') - def test_get_all(self, is_admin, mock_policy, mock_get_all_by_project, + def test_get_all(self, is_admin, mock_get_all_by_project, mock_get_all): self.group_api.LOG = mock.Mock() fake_groups = ['fake_group1', 'fake_group2'] @@ -73,11 +70,9 @@ class GroupAPITestCase(test.TestCase): grps = self.group_api.get_all(self.ctxt, filters={'all_tenants': True}) self.assertEqual(fake_groups, grps) - mock_policy.assert_called_once_with(self.ctxt, 'get_all') else: grps = self.group_api.get_all(self.user_ctxt) self.assertEqual(fake_groups_by_project, grps) - mock_policy.assert_called_once_with(self.user_ctxt, 'get_all') @mock.patch('cinder.volume.rpcapi.VolumeAPI.delete_group') @mock.patch('cinder.db.volume_get_all_by_generic_group') @@ -87,8 +82,7 @@ class GroupAPITestCase(test.TestCase): @mock.patch('cinder.objects.Group') @mock.patch('cinder.db.group_type_get') @mock.patch('cinder.db.volume_types_get_by_name_or_id') - @mock.patch('cinder.group.api.check_policy') - def test_create_delete(self, mock_policy, mock_volume_types_get, + def test_create_delete(self, mock_volume_types_get, mock_group_type_get, mock_group, mock_update_quota, mock_cast_create_group, mock_volumes_update, mock_volume_get_all, @@ -108,7 +102,6 @@ class GroupAPITestCase(test.TestCase): fake.GROUP_TYPE_ID, [fake.VOLUME_TYPE_ID], availability_zone='nova') - mock_policy.assert_called_with(self.ctxt, 'create') self.assertEqual(grp.obj_to_primitive(), ret_group.obj_to_primitive()) ret_group.host = "test_host@fakedrv#fakepool" @@ -119,15 +112,13 @@ class GroupAPITestCase(test.TestCase): mock_volume_get_all.assert_called_once_with(mock.ANY, ret_group.id) mock_volumes_update.assert_called_once_with(self.ctxt, []) mock_rpc_delete_group.assert_called_once_with(self.ctxt, ret_group) - mock_policy.assert_called_with(self.ctxt, 'delete', mock.ANY) @mock.patch('cinder.group.api.API._cast_create_group') @mock.patch('cinder.group.api.API.update_quota') @mock.patch('cinder.objects.Group') @mock.patch('cinder.db.group_type_get_by_name') @mock.patch('cinder.db.volume_types_get_by_name_or_id') - @mock.patch('cinder.group.api.check_policy') - def test_create_with_group_name(self, mock_policy, mock_volume_types_get, + def test_create_with_group_name(self, mock_volume_types_get, mock_group_type_get, mock_group, mock_update_quota, mock_cast_create_group): mock_volume_types_get.return_value = [{'id': fake.VOLUME_TYPE_ID}] @@ -149,14 +140,12 @@ class GroupAPITestCase(test.TestCase): mock_group_type_get.assert_called_once_with(self.ctxt, "fake-grouptype-name") - mock_policy.assert_called_with(self.ctxt, 'create') @mock.patch('cinder.group.api.API._cast_create_group') @mock.patch('cinder.group.api.API.update_quota') @mock.patch('cinder.db.group_type_get_by_name') @mock.patch('cinder.db.volume_types_get_by_name_or_id') - @mock.patch('cinder.group.api.check_policy') - def test_create_with_multi_types(self, mock_policy, mock_volume_types_get, + def test_create_with_multi_types(self, mock_volume_types_get, mock_group_type_get, mock_update_quota, mock_cast_create_group): @@ -180,12 +169,10 @@ class GroupAPITestCase(test.TestCase): "fake-grouptype-name") mock_volume_types_get.assert_called_once_with(mock.ANY, volume_type_names) - mock_policy.assert_called_with(self.ctxt, 'create') @mock.patch('oslo_utils.timeutils.utcnow') @mock.patch('cinder.objects.Group') - @mock.patch('cinder.group.api.check_policy') - def test_reset_status(self, mock_policy, mock_group, mock_time_util): + def test_reset_status(self, mock_group, mock_time_util): mock_time_util.return_value = "time_now" self.group_api.reset_status(self.ctxt, mock_group, fields.GroupStatus.AVAILABLE) @@ -194,15 +181,12 @@ class GroupAPITestCase(test.TestCase): 'status': fields.GroupStatus.AVAILABLE} mock_group.update.assert_called_once_with(update_field) mock_group.save.assert_called_once_with() - mock_policy.assert_called_once_with(self.ctxt, - 'reset_status', mock.ANY) @mock.patch.object(GROUP_QUOTAS, "reserve") @mock.patch('cinder.objects.Group') @mock.patch('cinder.db.group_type_get_by_name') @mock.patch('cinder.db.volume_types_get_by_name_or_id') - @mock.patch('cinder.group.api.check_policy') - def test_create_group_failed_update_quota(self, mock_policy, + def test_create_group_failed_update_quota(self, mock_volume_types_get, mock_group_type_get, mock_group, mock_group_quota_reserve): @@ -230,7 +214,6 @@ class GroupAPITestCase(test.TestCase): "fake-grouptype-name", [fake.VOLUME_TYPE_ID], availability_zone='nova') - mock_policy.assert_called_with(self.ctxt, 'create') @mock.patch('cinder.objects.Group') @mock.patch('cinder.db.volume_get') @@ -254,8 +237,7 @@ class GroupAPITestCase(test.TestCase): @mock.patch('cinder.objects.Group') @mock.patch('cinder.db.group_type_get') @mock.patch('cinder.db.volume_types_get_by_name_or_id') - @mock.patch('cinder.group.api.check_policy') - def test_update(self, mock_policy, mock_volume_types_get, + def test_update(self, mock_volume_types_get, mock_group_type_get, mock_group, mock_update_quota, mock_cast_create_group, mock_volume_get_all, mock_rpc_update_group): @@ -313,23 +295,19 @@ class GroupAPITestCase(test.TestCase): mock_rpc_update_group.assert_called_once_with(self.ctxt, ret_group, add_volumes=vol1.id, remove_volumes=vol2.id) - mock_policy.assert_called_with(self.ctxt, 'update', mock.ANY) @mock.patch('cinder.objects.GroupSnapshot.get_by_id') - @mock.patch('cinder.group.api.check_policy') - def test_get_group_snapshot(self, mock_policy, mock_group_snap): + def test_get_group_snapshot(self, mock_group_snap): fake_group_snap = 'fake_group_snap' mock_group_snap.return_value = fake_group_snap grp_snap = self.group_api.get_group_snapshot( self.ctxt, fake.GROUP_SNAPSHOT_ID) self.assertEqual(fake_group_snap, grp_snap) - mock_policy.assert_called_with(self.ctxt, 'get_group_snapshot') @ddt.data(True, False) @mock.patch('cinder.objects.GroupSnapshotList.get_all') @mock.patch('cinder.objects.GroupSnapshotList.get_all_by_project') - @mock.patch('cinder.group.api.check_policy') - def test_get_all_group_snapshots(self, is_admin, mock_policy, + def test_get_all_group_snapshots(self, is_admin, mock_get_all_by_project, mock_get_all): fake_group_snaps = ['fake_group_snap1', 'fake_group_snap2'] @@ -341,25 +319,19 @@ class GroupAPITestCase(test.TestCase): grp_snaps = self.group_api.get_all_group_snapshots( self.ctxt, filters={'all_tenants': True}) self.assertEqual(fake_group_snaps, grp_snaps) - mock_policy.assert_called_with(self.ctxt, - 'get_all_group_snapshots') else: grp_snaps = self.group_api.get_all_group_snapshots( self.user_ctxt) self.assertEqual(fake_group_snaps_by_project, grp_snaps) - mock_policy.assert_called_with(self.user_ctxt, - 'get_all_group_snapshots') @mock.patch('cinder.objects.GroupSnapshot') - @mock.patch('cinder.group.api.check_policy') - def test_update_group_snapshot(self, mock_policy, mock_group_snap): + def test_update_group_snapshot(self, mock_group_snap): grp_snap_update = {"name": "new_name", "description": "This is a new description"} self.group_api.update_group_snapshot(self.ctxt, mock_group_snap, grp_snap_update) mock_group_snap.update.assert_called_once_with(grp_snap_update) mock_group_snap.save.assert_called_once_with() - mock_policy.assert_called_with(self.ctxt, 'update_group_snapshot') @mock.patch('cinder.volume.rpcapi.VolumeAPI.delete_group_snapshot') @mock.patch('cinder.volume.rpcapi.VolumeAPI.create_group_snapshot') @@ -367,8 +339,7 @@ class GroupAPITestCase(test.TestCase): @mock.patch('cinder.objects.Group') @mock.patch('cinder.objects.GroupSnapshot') @mock.patch('cinder.objects.SnapshotList.get_all_for_group_snapshot') - @mock.patch('cinder.group.api.check_policy') - def test_create_delete_group_snapshot(self, mock_policy, + def test_create_delete_group_snapshot(self, mock_snap_get_all, mock_group_snap, mock_group, mock_create_in_db, @@ -399,12 +370,9 @@ class GroupAPITestCase(test.TestCase): ret_group_snap.id) mock_create_api.assert_called_once_with(self.ctxt, ret_group_snap) - mock_policy.assert_called_once_with(self.ctxt, 'create_group_snapshot', - mock.ANY) ret_group_snap.assert_not_frozen = mock.Mock(return_value=True) self.group_api.delete_group_snapshot(self.ctxt, ret_group_snap) mock_delete_api.assert_called_once_with(mock.ANY, ret_group_snap) - mock_policy.assert_called_with(self.ctxt, 'delete_group_snapshot') @mock.patch('cinder.objects.VolumeType.get_by_name_or_id') @mock.patch('cinder.db.group_volume_type_mapping_create') @@ -484,8 +452,7 @@ class GroupAPITestCase(test.TestCase): @mock.patch('cinder.objects.Group.get_by_id') @mock.patch('cinder.volume.rpcapi.VolumeAPI.create_group_from_src') @mock.patch('cinder.objects.VolumeList.get_all_by_generic_group') - @mock.patch('cinder.group.api.check_policy') - def test_create_group_from_group(self, mock_policy, mock_volume_get_all, + def test_create_group_from_group(self, mock_volume_get_all, mock_rpc_create_group_from_src, mock_group_get, mock_volume_api_create, @@ -548,8 +515,7 @@ class GroupAPITestCase(test.TestCase): @mock.patch('cinder.group.api.API.update_quota') @mock.patch('cinder.objects.GroupSnapshot.get_by_id') @mock.patch('cinder.objects.SnapshotList.get_all_for_group_snapshot') - @mock.patch('cinder.group.api.check_policy') - def test_create_from_src(self, mock_policy, mock_snap_get_all, + def test_create_from_src(self, mock_snap_get_all, mock_group_snap_get, mock_update_quota, mock_create_from_group, mock_create_from_snap): @@ -606,8 +572,7 @@ class GroupAPITestCase(test.TestCase): @mock.patch('oslo_utils.timeutils.utcnow') @mock.patch('cinder.objects.GroupSnapshot') - @mock.patch('cinder.group.api.check_policy') - def test_reset_group_snapshot_status(self, mock_policy, + def test_reset_group_snapshot_status(self, mock_group_snapshot, mock_time_util): mock_time_util.return_value = "time_now" @@ -618,8 +583,6 @@ class GroupAPITestCase(test.TestCase): 'status': fields.GroupSnapshotStatus.ERROR} mock_group_snapshot.update.assert_called_once_with(update_field) mock_group_snapshot.save.assert_called_once_with() - mock_policy.assert_called_once_with(self.ctxt, - 'reset_group_snapshot_status') def test_create_group_from_src_frozen(self): service = utils.create_service(self.ctxt, {'frozen': True}) diff --git a/cinder/tests/unit/policy.json b/cinder/tests/unit/policy.json index a578d37bb06..4de049755bd 100644 --- a/cinder/tests/unit/policy.json +++ b/cinder/tests/unit/policy.json @@ -109,18 +109,12 @@ "consistencygroup:get_cgsnapshot": "", "consistencygroup:get_all_cgsnapshots": "", - "group:group_types_manage": "rule:admin_api", - "group:group_types_specs": "rule:admin_api", - "group:access_group_types_specs": "rule:admin_api", - "group:group_type_access": "rule:admin_or_owner", - "group:create" : "", "group:delete": "", "group:update": "", "group:get": "", "group:get_all": "", - "group:create_group_snapshot": "", "group:delete_group_snapshot": "", "group:update_group_snapshot": "", "group:get_group_snapshot": "", diff --git a/etc/cinder/policy.json b/etc/cinder/policy.json index 0b1c1c2a38f..9d39182595c 100644 --- a/etc/cinder/policy.json +++ b/etc/cinder/policy.json @@ -89,30 +89,6 @@ "consistencygroup:get_cgsnapshot": "group:nobody", "consistencygroup:get_all_cgsnapshots": "group:nobody", - "group:group_types_manage": "rule:admin_api", - "group:group_types_specs": "rule:admin_api", - "group:access_group_types_specs": "rule:admin_api", - "group:group_type_access": "rule:admin_or_owner", - - "group:create" : "", - "group:delete": "rule:admin_or_owner", - "group:update": "rule:admin_or_owner", - "group:get": "rule:admin_or_owner", - "group:get_all": "rule:admin_or_owner", - - "group:create_group_snapshot": "", - "group:delete_group_snapshot": "rule:admin_or_owner", - "group:update_group_snapshot": "rule:admin_or_owner", - "group:get_group_snapshot": "rule:admin_or_owner", - "group:get_all_group_snapshots": "rule:admin_or_owner", - "group:reset_group_snapshot_status":"rule:admin_api", - "group:reset_status":"rule:admin_api", - - "group:enable_replication": "rule:admin_or_owner", - "group:disable_replication": "rule:admin_or_owner", - "group:failover_replication": "rule:admin_or_owner", - "group:list_replication_targets": "rule:admin_or_owner", - "scheduler_extension:scheduler_stats:get_pools" : "rule:admin_api", }