Support can_edit_user and can_edit_role settings in Angularized panels

The actions on Angularized users panel and roles panel does not support
following setting.

OPENSTACK_KEYSTONE_BACKEND = {
    'can_edit_user': True,
    'can_edit_role': True,
}

This patch enables to support these settings.

To enable this settings, add 'OPENSTACK_KEYSTONE_BACKEND' into
'REST_API_REQUIRED_SETTINGS' setting.

Change-Id: I7888bd2c2977dc010911d2e7ecf42815354e081b
Closes-Bug: #1779268
This commit is contained in:
Shu Muto 2018-06-29 16:53:39 +09:00
parent ab03c13711
commit e20882d3b3
23 changed files with 198 additions and 36 deletions

View File

@ -772,12 +772,18 @@ Default:
'OPENSTACK_HYPERVISOR_FEATURES',
'LAUNCH_INSTANCE_DEFAULTS',
'OPENSTACK_IMAGE_FORMATS',
'OPENSTACK_KEYSTONE_DEFAULT_DOMAIN'
'OPENSTACK_KEYSTONE_BACKEND',
'OPENSTACK_KEYSTONE_DEFAULT_DOMAIN',
'CREATE_IMAGE_DEFAULTS',
'ENFORCE_PASSWORD_CHECK'
]
This setting allows you to expose configuration values over Horizons internal
REST API, so that the AngularJS panels can access them. Please be cautious
about which values are listed here (and thus exposed on the frontend)
about which values are listed here (and thus exposed on the frontend).
For security purpose, this exposure of settings should be recognized explicitly
by operator. So ``REST_API_REQUIRED_SETTINGS`` is not set by default.
Please refer ``local_settings.py.example`` and confirm your ``local_settings.py``.
SELECTABLE_THEMES
---------------------

View File

@ -22,6 +22,7 @@
.factory('horizon.dashboard.identity.roles.actions.create.service', createService);
createService.$inject = [
'$q',
'horizon.dashboard.identity.roles.resourceType',
'horizon.dashboard.identity.roles.role-schema',
'horizon.app.core.openstack-service-api.keystone',
@ -38,6 +39,7 @@
* @Description A service to handle the Create Role modal.
*/
function createService(
$q,
resourceType,
schema,
keystoneAPI,
@ -58,7 +60,10 @@
//////////////
function allowed() {
return policy.ifAllowed({ rules: [['identity', 'identity:create_role']] });
return $q.all([
keystoneAPI.canEditIdentity('role'),
policy.ifAllowed({ rules: [['identity', 'identity:create_role']] })
]);
}
function perform() {

View File

@ -38,23 +38,43 @@
resType = $injector.get('horizon.dashboard.identity.roles.resourceType');
}));
it('should check the policy if the user is allowed to create roles', function() {
var deferred = $q.defer();
spyOn(policyAPI, 'ifAllowed').and.returnValue(deferred.promise);
deferred.resolve({allowed: true});
var handler = jasmine.createSpyObj('handler', ['success']);
it('should allow if can_edit_role is set True in OPENSTACK_KEYSTONE_BACKEND', function() {
//for canEditRole
var deferredCanEditRole = $q.defer();
deferredCanEditRole.resolve(true);
spyOn(keystoneAPI, 'canEditIdentity').and.returnValue(deferredCanEditRole.promise);
service.allowed().then(handler.success);
//for ifAllowed
var deferredIfAllowed = $q.defer();
deferredIfAllowed.resolve(true);
spyOn(policyAPI, 'ifAllowed').and.returnValue(deferredIfAllowed.promise);
var allowed = service.allowed({id: '1234'});
$scope.$apply();
expect(handler.success).toHaveBeenCalled();
var allowed = handler.success.calls.first().args[0];
expect(allowed).toBeTruthy();
expect(policyAPI.ifAllowed).toHaveBeenCalledWith(
{ rules: [['identity', 'identity:create_role']] });
});
it('should allow if can_edit_role is set True in OPENSTACK_KEYSTONE_BACKEND', function() {
//for canEditRole
var deferredCanEditRole = $q.defer();
deferredCanEditRole.resolve(false);
spyOn(keystoneAPI, 'canEditIdentity').and.returnValue(deferredCanEditRole.promise);
//for ifAllowed
var deferredIfAllowed = $q.defer();
deferredIfAllowed.resolve(true);
spyOn(policyAPI, 'ifAllowed').and.returnValue(deferredIfAllowed.promise);
var allowed = service.allowed({id: '1234'});
$scope.$apply();
// reject
expect(allowed.$$state.status).toEqual(1);
});
it('should open the modal with the correct parameters', function() {
var deferred = $q.defer();
spyOn(modalFormService, 'open').and.returnValue(deferred.promise);

View File

@ -22,6 +22,7 @@
.factory('horizon.dashboard.identity.roles.actions.delete.service', deleteRoleService);
deleteRoleService.$inject = [
'$q',
'horizon.app.core.openstack-service-api.keystone',
'horizon.app.core.openstack-service-api.policy',
'horizon.framework.util.actions.action-result.service',
@ -41,6 +42,7 @@
* On cancel, do nothing.
*/
function deleteRoleService(
$q,
keystone,
policy,
actionResultService,
@ -57,7 +59,10 @@
//////////////
function allowed() {
return policy.ifAllowed({rules: [[ 'identity', 'identity:delete_role' ]]});
return $q.all([
keystone.canEditIdentity('role'),
policy.ifAllowed({ rules: [['identity', 'identity:delete_role']] })
]);
}
function perform(items, scope) {

View File

@ -20,9 +20,10 @@
beforeEach(module('horizon.dashboard.identity.roles'));
beforeEach(module('horizon.framework'));
var deleteModalService, service, keystoneAPI, policyAPI;
var deleteModalService, $scope, service, keystoneAPI, policyAPI;
beforeEach(inject(function($injector) {
beforeEach(inject(function($injector, _$rootScope_) {
$scope = _$rootScope_.$new();
service = $injector.get('horizon.dashboard.identity.roles.actions.delete.service');
keystoneAPI = $injector.get('horizon.app.core.openstack-service-api.keystone');
deleteModalService = $injector.get('horizon.framework.widgets.modal.deleteModalService');
@ -72,8 +73,11 @@
describe('allow method', function() {
it('should use default policy if batch action', function test() {
spyOn(keystoneAPI, 'canEditIdentity');
spyOn(policyAPI, 'ifAllowed');
service.allowed();
$scope.$apply();
expect(keystoneAPI.canEditIdentity).toHaveBeenCalled();
expect(policyAPI.ifAllowed).toHaveBeenCalled();
});
}); // end of allowed

View File

@ -22,6 +22,7 @@
.factory('horizon.dashboard.identity.roles.actions.edit.service', editService);
editService.$inject = [
'$q',
'horizon.dashboard.identity.roles.resourceType',
'horizon.dashboard.identity.roles.role-schema',
'horizon.app.core.openstack-service-api.keystone',
@ -37,6 +38,7 @@
* @Description A service to handle the Edit Role modal.
*/
function editService(
$q,
resourceType,
schema,
keystoneAPI,
@ -58,7 +60,10 @@
//////////////
function allowed() {
return policy.ifAllowed({ rules: [['identity', 'identity:update_role']] });
return $q.all([
keystoneAPI.canEditIdentity('role'),
policy.ifAllowed({ rules: [['identity', 'identity:update_role']] })
]);
}
function perform(role) {

View File

@ -92,8 +92,10 @@
describe('allow method', function() {
it('should use default policy if batch action', function test() {
spyOn(keystoneAPI, 'canEditIdentity');
spyOn(policyAPI, 'ifAllowed');
service.allowed();
expect(keystoneAPI.canEditIdentity).toHaveBeenCalled();
expect(policyAPI.ifAllowed).toHaveBeenCalled();
});
}); // end of allowed

View File

@ -62,7 +62,10 @@
//////////////
function allowed() {
return policy.ifAllowed({ rules: [['identity', 'identity:create_user']] });
return $q.all([
keystone.canEditIdentity('user'),
policy.ifAllowed({ rules: [['identity', 'identity:create_user']] })
]);
}
function perform() {

View File

@ -38,23 +38,43 @@
resourceType = $injector.get('horizon.dashboard.identity.users.resourceType');
}));
it('should check the policy if the user is allowed to create user', function() {
var deferred = $q.defer();
spyOn(policy, 'ifAllowed').and.returnValue(deferred.promise);
deferred.resolve({allowed: true});
var handler = jasmine.createSpyObj('handler', ['success']);
it('should allow if can_edit_user is set True in OPENSTACK_KEYSTONE_BACKEND', function() {
//for canEditUser
var deferredCanEditUser = $q.defer();
deferredCanEditUser.resolve(true);
spyOn(keystone, 'canEditIdentity').and.returnValue(deferredCanEditUser.promise);
service.allowed().then(handler.success);
//for ifAllowed
var deferredIfAllowed = $q.defer();
deferredIfAllowed.resolve(true);
spyOn(policy, 'ifAllowed').and.returnValue(deferredIfAllowed.promise);
var allowed = service.allowed({id: '1234'});
$scope.$apply();
expect(handler.success).toHaveBeenCalled();
var allowed = handler.success.calls.first().args[0];
expect(allowed).toBeTruthy();
expect(policy.ifAllowed).toHaveBeenCalledWith(
{ rules: [['identity', 'identity:create_user']] });
});
it('should allow if can_edit_user is set True in OPENSTACK_KEYSTONE_BACKEND', function() {
//for canEditUser
var deferredCanEditUser = $q.defer();
deferredCanEditUser.resolve(false);
spyOn(keystone, 'canEditIdentity').and.returnValue(deferredCanEditUser.promise);
//for ifAllowed
var deferredIfAllowed = $q.defer();
deferredIfAllowed.resolve(true);
spyOn(policy, 'ifAllowed').and.returnValue(deferredIfAllowed.promise);
var allowed = service.allowed({id: '1234'});
$scope.$apply();
// reject
expect(allowed.$$state.status).toEqual(1);
});
it('should open the modal', function() {
spyOn(modal, 'open').and.returnValue($q.defer().promise);
spyOn(keystone, 'getVersion').and.returnValue($q.defer().promise);

View File

@ -63,7 +63,10 @@
//////////////
function allowed() {
return policy.ifAllowed({rules: [[ 'identity', 'identity:delete_user' ]]});
return $q.all([
keystone.canEditIdentity('user'),
policy.ifAllowed({ rules: [['identity', 'identity:delete_user']] })
]);
}
function perform(items, scope) {

View File

@ -37,6 +37,9 @@
var keystoneAPI = {
deleteUser: function() {
return;
},
canEditIdentity: function() {
return;
}
};
@ -75,6 +78,7 @@
beforeEach(module('horizon.app.core.openstack-service-api', function($provide) {
$provide.value('horizon.app.core.openstack-service-api.keystone', keystoneAPI);
$provide.value('horizon.app.core.openstack-service-api.policy', policyAPI);
spyOn(keystoneAPI, 'canEditIdentity').and.callThrough();
spyOn(policyAPI, 'ifAllowed').and.callThrough();
}));
@ -134,7 +138,7 @@
it('should call policy check', function() {
service.allowed();
$scope.$apply();
expect(keystoneAPI.canEditIdentity).toHaveBeenCalled();
expect(policyAPI.ifAllowed).toHaveBeenCalled();
});
});

View File

@ -58,6 +58,7 @@
function allowed(selected) {
return $q.all([
keystone.canEditIdentity('user'),
$qExtensions.booleanAsPromise(selected.enabled),
policy.ifAllowed({ rules: [['identity', 'identity:update_user']] })
]);

View File

@ -35,6 +35,9 @@
var deferred = $q.defer();
spyOn(keystone, 'editUser').and.returnValue(deferred.promise);
deferred.resolve({});
var deferredCanEdit = $q.defer();
spyOn(keystone, 'canEditIdentity').and.returnValue(deferredCanEdit.promise);
deferredCanEdit.resolve(true);
policy = $injector.get('horizon.app.core.openstack-service-api.policy');
var allowedPromise = $q.defer();
spyOn(policy, 'ifAllowed').and.returnValue(allowedPromise.promise);

View File

@ -58,6 +58,7 @@
function allowed(selected) {
return $q.all([
keystone.canEditIdentity('user'),
$qExtensions.booleanAsPromise(!selected.enabled),
policy.ifAllowed({ rules: [['identity', 'identity:update_user']] })
]);

View File

@ -35,6 +35,9 @@
var deferred = $q.defer();
spyOn(keystone, 'editUser').and.returnValue(deferred.promise);
deferred.resolve({});
var deferredCanEdit = $q.defer();
spyOn(keystone, 'canEditIdentity').and.returnValue(deferredCanEdit.promise);
deferredCanEdit.resolve(true);
policy = $injector.get('horizon.app.core.openstack-service-api.policy');
var allowedPromise = $q.defer();
spyOn(policy, 'ifAllowed').and.returnValue(allowedPromise.promise);

View File

@ -62,7 +62,10 @@
//////////////
function allowed() {
return policy.ifAllowed({ rules: [['identity', 'identity:update_user']] });
return $q.all([
keystone.canEditIdentity('user'),
policy.ifAllowed({ rules: [['identity', 'identity:update_user']] })
]);
}
// eslint-disable-next-line no-unused-vars

View File

@ -40,6 +40,9 @@
var deferred = $q.defer();
spyOn(policy, 'ifAllowed').and.returnValue(deferred.promise);
deferred.resolve({allowed: true});
var deferredCanEdit = $q.defer();
spyOn(keystone, 'canEditIdentity').and.returnValue(deferredCanEdit.promise);
deferredCanEdit.resolve(true);
var handler = jasmine.createSpyObj('handler', ['success']);
service.allowed().then(handler.success);

View File

@ -60,7 +60,10 @@
//////////////
function allowed() {
return policy.ifAllowed({ rules: [['identity', 'identity:update_user']] });
return $q.all([
keystone.canEditIdentity('user'),
policy.ifAllowed({ rules: [['identity', 'identity:update_user']] })
]);
}
function perform(selected) {

View File

@ -39,6 +39,9 @@
var deferred = $q.defer();
spyOn(policy, 'ifAllowed').and.returnValue(deferred.promise);
deferred.resolve({allowed: true});
var deferredCanEdit = $q.defer();
spyOn(keystone, 'canEditIdentity').and.returnValue(deferredCanEdit.promise);
deferredCanEdit.resolve(true);
var handler = jasmine.createSpyObj('handler', ['success']);
service.allowed().then(handler.success);

View File

@ -790,6 +790,7 @@ SECURITY_GROUP_RULES = {
REST_API_REQUIRED_SETTINGS = ['OPENSTACK_HYPERVISOR_FEATURES',
'LAUNCH_INSTANCE_DEFAULTS',
'OPENSTACK_IMAGE_FORMATS',
'OPENSTACK_KEYSTONE_BACKEND',
'OPENSTACK_KEYSTONE_DEFAULT_DOMAIN',
'CREATE_IMAGE_DEFAULTS',
'ENFORCE_PASSWORD_CHECK']

View File

@ -22,11 +22,12 @@
keystoneAPI.$inject = [
'$q',
'horizon.app.core.openstack-service-api.settings',
'horizon.framework.util.http.service',
'horizon.framework.widgets.toast.service'
];
function keystoneAPI($q, apiService, toastService) {
function keystoneAPI($q, settingAPI, apiService, toastService) {
var service = {
getVersion: getVersion,
getUsers: getUsers,
@ -64,7 +65,8 @@
getGroup: getGroup,
editGroup: editGroup,
deleteGroup: deleteGroup,
deleteGroups: deleteGroups
deleteGroups: deleteGroups,
canEditIdentity: canEditIdentity
};
return service;
@ -385,6 +387,26 @@
});
}
/**
* @name canEditIdentity
* @description
* Returns the promise for can_edit_* setting in OPENSTACK_KEYSTONE_BACKEND.
* @returns {object} Deferred promiss
*/
function canEditIdentity(type) {
var deferred = $q.defer();
settingAPI.getSetting('OPENSTACK_KEYSTONE_BACKEND', false).then(success);
return deferred.promise;
function success(response) {
if (response["can_edit_" + type]) {
deferred.resolve();
} else {
deferred.reject();
}
}
}
/**
* @name serviceCatalog
* @description

View File

@ -18,7 +18,7 @@
'use strict';
describe('Keystone API', function() {
var testCall, service;
var testCall, service, settings;
var apiService = {};
var toastService = {};
@ -31,9 +31,10 @@
beforeEach(module('horizon.app.core.openstack-service-api'));
beforeEach(inject(['horizon.app.core.openstack-service-api.keystone', function(keystoneAPI) {
service = keystoneAPI;
}]));
beforeEach(inject(function($injector) {
service = $injector.get('horizon.app.core.openstack-service-api.keystone');
settings = $injector.get('horizon.app.core.openstack-service-api.settings');
}));
it('defines the service', function() {
expect(service).toBeDefined();
@ -508,6 +509,32 @@
});
});
describe('canEditIdentity', function () {
var deferred, $timeout;
beforeEach(inject(function (_$q_, _$timeout_) {
deferred = _$q_.defer();
$timeout = _$timeout_;
}));
it('should resolve true if can_edit_group is set True', function() {
deferred.resolve({can_edit_group: true});
spyOn(settings, 'getSettings').and.returnValue(deferred.promise);
var canEdit = service.canEditIdentity('group');
$timeout.flush();
expect(canEdit).toBeTruthy();
});
it('should resolve false if can_edit_group is set False', function() {
deferred.resolve({can_edit_group: false});
spyOn(settings, 'getSettings').and.returnValue(deferred.promise);
var canEdit = service.canEditIdentity('group');
$timeout.flush();
// reject
expect(canEdit.$$state.status).toEqual(2);
});
});
});
})();

View File

@ -0,0 +1,15 @@
---
fixes:
- |
[:bug:`1779268`] Supported ``can_edit_*`` settings in Angularized identity
panels. To enable this settings in Angularized identity panels, add
``OPENSTACK_KEYSTONE_BACKEND`` into ``REST_API_REQUIRED_SETTINGS`` on
``local_settings.py``. For more detail, see
`REST_API_REQUIRED_SETTINGS <https://docs.openstack.org/horizon/latest/configuration/settings.html#rest-api-required-settings>`__
in horizon settings documentation.
upgrade:
- |
Add ``OPENSTACK_KEYSTONE_BACKEND`` manually into
``REST_API_REQUIRED_SETTINGS`` on ``local_settings.py``, if your deployment
uses Angularized identity panels and needs to enable ``can_edit_*``
settings in ``OPENSTACK_KEYSTONE_BACKEND``.