Merge "Refactor policy: Support precision control of power action"
This commit is contained in:
commit
acdb81876b
@ -26,8 +26,12 @@
|
||||
"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"
|
||||
# Associate floating IP to instance
|
||||
|
@ -91,9 +91,15 @@ 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'),
|
||||
@ -102,6 +108,10 @@ instance_policies = [
|
||||
description='Associate a floating ip with an instance'),
|
||||
]
|
||||
|
||||
FUNC_PARAMS_INTERESTED = {
|
||||
'power': ['target']
|
||||
}
|
||||
|
||||
|
||||
def list_policies():
|
||||
policies = (default_policies
|
||||
@ -171,6 +181,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):
|
||||
@ -190,7 +215,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
|
||||
@ -236,8 +261,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)
|
||||
|
||||
|
66
mogan/tests/unit/common/test_policy.py
Normal file
66
mogan/tests/unit/common/test_policy.py
Normal 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'])
|
Loading…
Reference in New Issue
Block a user