Adding policy rest endpoint for angular

Providing an endpoint for angular code to make policy checks
on the horizon server. There currently is no clientside cache
of the calls, that will be a follow-on work.

Implements blueprint: policy-for-angular
Change-Id: Ieacbc502440c2e3a2e32ec6bcaa002310e82a681
This commit is contained in:
David Lyle 2015-02-23 17:15:53 -07:00
parent fec4ac6b1b
commit 6aa7024e61
5 changed files with 203 additions and 0 deletions

View File

@ -0,0 +1,72 @@
/*
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.
*/
(function() {
'use strict';
/**
* @ngdoc service
* @name hz.api.policyAPI
* @description Provides a direct pass through to the policy engine in
* Horizon.
*/
function PolicyService(apiService) {
/**
* @name hz.api.policyAPI.check
* @description
* Check the passed in policy rule list to determine if the user has
* permission to perform the actions specified by the rules. The service
* APIs will ultimately reject actions that are not permitted. This is used
* for Role Based Access Control in the UI only. The required parameter
* should have the following structure:
*
* {
* "rules": [
* [ "compute", "compute:get_all" ],
* ],
* "target": {
* "project_id": "1"
* }
* }
*
* where "rules" is a list of rules (1 or greater in length) policy rules
* which are composed of a
* * service name -- maps the policy rule to a service
* * rule -- the policy rule to check
* and "target" key and value is optional. In some cases, policy rules
* require specific details about the object that is to be acted on.
* If added, it is merely a dictionary of keys and values.
*
*
* The following is the response if the check passes:
* {
* "allowed": true
* }
*
* The following is the response if the check fails:
* {
* "allowed": false
* }
*/
this.check = function (policy_rules) {
return apiService.post('/api/policy/', policy_rules)
.error(function() {
horizon.alert('warning', gettext('Policy check failed.'));
});
};
}
angular.module('hz.api')
.service('policyAPI', ['apiService', PolicyService]);
}());

View File

@ -28,6 +28,7 @@
<script src='{{ STATIC_URL }}horizon/js/angular/services/hz.api.glance.js'></script>
<script src='{{ STATIC_URL }}horizon/js/angular/services/hz.api.keystone.js'></script>
<script src='{{ STATIC_URL }}horizon/js/angular/services/hz.api.nova.js'></script>
<script src='{{ STATIC_URL }}horizon/js/angular/services/hz.api.policy.js'></script>
<script src='{{ STATIC_URL }}angular/widget.module.js'></script>
<script src='{{ STATIC_URL }}angular/help-panel/help-panel.js'></script>

View File

@ -28,3 +28,4 @@ import glance #flake8: noqa
import keystone #flake8: noqa
import network #flake8: noqa
import nova #flake8: noqa
import policy #flake8: noqa

View File

@ -0,0 +1,51 @@
#
# 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 django.views import generic
from openstack_dashboard import policy
from openstack_dashboard.api.rest import urls
from openstack_dashboard.api.rest import utils as rest_utils
@urls.register
class Policy(generic.View):
'''API for interacting with the policy engine.'''
url_regex = r'policy/$'
@rest_utils.ajax(data_required=True)
def post(self, request):
'''Check policy rules.
Check the group of policy rules supplied in the POST
application/json object. The policy target, if specified will also be
passed in to the policy check method as well.
The action returns an object with one key: "allowed" and the value
is the result of the policy check, True or False.
'''
rules = []
try:
rules_in = request.DATA['rules']
rules = tuple([tuple(rule) for rule in rules_in])
except Exception:
raise rest_utils.AjaxError(400, 'unexpected parameter format')
policy_target = request.DATA.get('target') or {}
result = policy.check(rules, request, policy_target)
return {"allowed": result}

View File

@ -0,0 +1,78 @@
# 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 django.test.utils import override_settings # noqa
from openstack_dashboard.api.rest import policy
from openstack_dashboard import policy_backend
from openstack_dashboard.test import helpers as test
class PolicyRestTestCase(test.TestCase):
@override_settings(POLICY_CHECK_FUNCTION=policy_backend.check)
def test_policy(self, body='{"rules": []}'):
request = self.mock_rest_request(body=body)
response = policy.Policy().post(request)
self.assertStatusCode(response, 200)
self.assertEqual(response.content, '{"allowed": true}')
@override_settings(POLICY_CHECK_FUNCTION=policy_backend.check)
def test_rule_alone(self):
body = '{"rules": [["compute", "compute:get_all" ]]}'
self.test_policy(body)
@override_settings(POLICY_CHECK_FUNCTION=policy_backend.check)
def test_multiple_rule(self):
body = '{"rules": [["compute", "compute:get_all"],' \
' ["compute", "compute:start"]]}'
self.test_policy(body)
@override_settings(POLICY_CHECK_FUNCTION=policy_backend.check)
def test_rule_with_empty_target(self):
body = '{"rules": [["compute", "compute:get_all"],' \
' ["compute", "compute:start"]],' \
' "target": {}}'
self.test_policy(body)
@override_settings(POLICY_CHECK_FUNCTION=policy_backend.check)
def test_rule_with_target(self):
body = '{"rules": [["compute", "compute:get_all"],' \
' ["compute", "compute:start"]],' \
' "target": {"project_id": "1"}}'
self.test_policy(body)
@override_settings(POLICY_CHECK_FUNCTION=policy_backend.check)
def test_policy_fail(self):
# admin only rule, default test case user should fail
request = self.mock_rest_request(
body='''{"rules": [["compute", "compute:unlock_override"]]}''')
response = policy.Policy().post(request)
self.assertStatusCode(response, 200)
self.assertEqual(response.content, '{"allowed": false}')
@override_settings(POLICY_CHECK_FUNCTION=policy_backend.check)
def test_policy_error(self):
# admin only rule, default test case user should fail
request = self.mock_rest_request(
body='''{"bad": "compute"}''')
response = policy.Policy().post(request)
self.assertStatusCode(response, 400)
class AdminPolicyRestTestCase(test.BaseAdminViewTests):
@override_settings(POLICY_CHECK_FUNCTION=policy_backend.check)
def test_rule_with_target(self):
body = '{"rules": [["compute", "compute:unlock_override"]]}'
request = self.mock_rest_request(body=body)
response = policy.Policy().post(request)
self.assertStatusCode(response, 200)
self.assertEqual(response.content, '{"allowed": true}')