Refactor policy: Support precision control of power action

Change-Id: I7dfbbdc69873c72df4a0121cb6764905b669d2c9
This commit is contained in:
liusheng 2017-01-25 10:51:42 +08:00
parent 5efdff9a94
commit 7611ec20b8
3 changed files with 102 additions and 6 deletions

View File

@ -26,7 +26,11 @@
"mogan:instance:delete": "rule:default"
# Update Instance records
"mogan:instance:update": "rule:default"
# Change Instance power status
"mogan:instance:set_power_state": "rule:default"
# Start an instance
"mogan:instance:set_power_state:on": "rule:default"
# Stop an instance
"mogan:instance:set_power_state:off": "rule:default"
# Reboot an instance
"mogan:instance:set_power_state:reboot": "rule:default"
# Get Instance network information
"mogan:instance:get_networks": "rule:default"

View File

@ -91,14 +91,24 @@ instance_policies = [
policy.RuleDefault('mogan:instance:update',
'rule:default',
description='Update Instance records'),
policy.RuleDefault('mogan:instance:set_power_state',
policy.RuleDefault('mogan:instance:set_power_state:on',
'rule:default',
description='Change Instance power status'),
description='Start an instance'),
policy.RuleDefault('mogan:instance:set_power_state:off',
'rule:default',
description='Stop an instance'),
policy.RuleDefault('mogan:instance:set_power_state:reboot',
'rule:default',
description='Reboot an instance'),
policy.RuleDefault('mogan:instance:get_networks',
'rule:default',
description='Get Instance network information'),
]
FUNC_PARAMS_INTERESTED = {
'power': ['target']
}
def list_policies():
policies = (default_policies
@ -168,6 +178,21 @@ def authorize(rule, target, creds, *args, **kwargs):
raise exception.HTTPForbidden(resource=rule)
def _add_action_extra(action, fn, *args, **kwargs):
func_name = fn.__name__
if func_name in FUNC_PARAMS_INTERESTED:
fn_args = fn.__dict__['_pecan']['argspec'][0][1:]
for param in FUNC_PARAMS_INTERESTED[func_name]:
if param in kwargs:
if kwargs[param]:
action = '%s:%s' % (action, kwargs[param])
elif param in fn_args:
param_value = args[fn_args.index(param)]
if param_value:
action = '%s:%s' % (action, param_value)
return action
# NOTE(Shaohe Feng): This decorator MUST appear first (the outermost
# decorator) on an API method for it to work correctly
def authorize_wsgi(api_name, act=None, need_target=True):
@ -187,7 +212,7 @@ def authorize_wsgi(api_name, act=None, need_target=True):
...
"""
def wraper(fn):
action = "%s:%s" % (api_name, (act or fn.func_name))
action = "%s:%s" % (api_name, (act or fn.__name__))
# In this authorize method, we return a dict data when authorization
# fails or exception comes out. Maybe we can consider to use
@ -233,8 +258,9 @@ def authorize_wsgi(api_name, act=None, need_target=True):
# the credentials with itself.
target = {'project_id': context.tenant,
'user_id': context.user}
action_with_extra = _add_action_extra(action, fn, *args, **kwargs)
try:
authorize(action, target, credentials)
authorize(action_with_extra, target, credentials)
except Exception:
return return_error(403)

View File

@ -0,0 +1,66 @@
# 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 mock
from oslo_context import context
from mogan.common import policy
from mogan.tests import base
from mogan.tests.unit.objects import utils
class TestAuthorizeWsgi(base.TestCase):
def setUp(self):
super(TestAuthorizeWsgi, self).setUp()
self.fake_controller = mock.MagicMock()
self.ctxt = context.RequestContext(
tenant='c18e8a1a870d4c08a0b51ced6e0b6459',
user='cdbf77d47f1d4d04ad9b7ff62b672467')
self.test_inst = utils.get_test_instance(self.ctxt)
self.fake_controller._get_resource.return_value = self.test_inst
def power(self, instance_uuid, target):
pass
power.__dict__['_pecan'] = {
'argspec': [['self', 'instance_uuid', 'target']]}
self.fake_power = power
@mock.patch('pecan.request')
def test_authorize_power_action_owner(self, mocked_pecan_request):
mocked_pecan_request.context = self.ctxt
policy.authorize_wsgi("mogan:instance", "set_power_state")(
self.fake_power)(self.fake_controller, 'fake_instance_id', 'off')
@mock.patch('pecan.request')
def test_authorize_power_action_admin(self, mocked_pecan_request):
mocked_pecan_request.context = context.get_admin_context()
policy.authorize_wsgi("mogan:instance", "set_power_state")(
self.fake_power)(self.fake_controller, 'fake_instance_id', 'off')
@mock.patch('pecan.response')
@mock.patch('pecan.request')
def test_authorize_power_action_failed(self, mocked_pecan_request,
mocked_pecan_response):
mocked_pecan_request.context = context.RequestContext(
tenant='non-exist-tenant',
user='non-exist-user')
data = policy.authorize_wsgi("mogan:instance", "set_power_state")(
self.fake_power)(self.fake_controller, 'fake_instance_id',
'reboot')
self.assertEqual(403, mocked_pecan_response.status)
self.assertEqual('Access was denied to the following resource: '
'mogan:instance:set_power_state:reboot',
data['faultstring'])