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:
parent
fec4ac6b1b
commit
6aa7024e61
72
horizon/static/horizon/js/angular/services/hz.api.policy.js
Normal file
72
horizon/static/horizon/js/angular/services/hz.api.policy.js
Normal 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]);
|
||||
}());
|
@ -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>
|
||||
|
@ -28,3 +28,4 @@ import glance #flake8: noqa
|
||||
import keystone #flake8: noqa
|
||||
import network #flake8: noqa
|
||||
import nova #flake8: noqa
|
||||
import policy #flake8: noqa
|
||||
|
51
openstack_dashboard/api/rest/policy.py
Normal file
51
openstack_dashboard/api/rest/policy.py
Normal 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}
|
78
openstack_dashboard/test/api_tests/policy_rest_tests.py
Normal file
78
openstack_dashboard/test/api_tests/policy_rest_tests.py
Normal 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}')
|
Loading…
Reference in New Issue
Block a user