Add Angular keystone user creation action
- fix users rest API to handle description, project, and enabled attribute properly - fix hz-password-match to work, but also so it uses model-based checks rather than HTML element lookups - add schema form password-confirm type To Test - set 'users_panel': True in settings.py Co-Authored-By: Cindy Lu <clu@us.ibm.com> Co-Authored-By: Richard Jones <r1chardj0n3s@gmail.com> Co-Authored-By: Shu Muto <shu-mutou@rf.jp.nec.com> Change-Id: I2e8f5ac1dbfae9634fa5b72f7f6c0278d5a81249 Partially-Implements: blueprint ng-users
This commit is contained in:
parent
e23048774b
commit
4c8f4a0306
|
@ -31,49 +31,42 @@
|
|||
* @restrict A
|
||||
*
|
||||
* @scope
|
||||
* hzPasswordMatch - id of element to validate against
|
||||
* hzPasswordMatch - model value to validate against
|
||||
*
|
||||
* @example:
|
||||
* <form name="form">
|
||||
* <input type='password' id="psw" ng-model="user.psw" name="psw">
|
||||
* <input type='password' ng-model="user.cnf" hz-password-match="psw">
|
||||
* <input type='password' ng-model="user.cnf" hz-password-match="user.psw">
|
||||
* </form>
|
||||
*
|
||||
* Note that id and name are required for the password input.
|
||||
* This directive uses the form model and id for validation check.
|
||||
*/
|
||||
angular
|
||||
.module('horizon.framework.util.validators')
|
||||
.directive('hzPasswordMatch', hzPasswordMatch);
|
||||
|
||||
function hzPasswordMatch() {
|
||||
var directive = {
|
||||
return {
|
||||
restrict: 'A',
|
||||
require: 'ngModel',
|
||||
link: link
|
||||
};
|
||||
|
||||
return directive;
|
||||
|
||||
///////////
|
||||
|
||||
function link(scope, element, attr, ctrl) {
|
||||
|
||||
function link(scope, element, attr, ngModelCtrl) {
|
||||
/**
|
||||
* this ensures that typing in either input
|
||||
* will trigger the password match
|
||||
*/
|
||||
var pwElement = angular.element('#' + attr.hzPasswordMatch);
|
||||
pwElement.on('keyup change', passwordCheck);
|
||||
element.on('keyup change', passwordCheck);
|
||||
scope.$watch(attr.hzPasswordMatch, function(value) {
|
||||
scope.passwordConfirm = value;
|
||||
ngModelCtrl.$validate();
|
||||
});
|
||||
|
||||
// helper function to check that password matches
|
||||
function passwordCheck() {
|
||||
scope.$apply(function () {
|
||||
var match = ctrl.$modelValue === pwElement.val();
|
||||
ctrl.$setValidity('match', match);
|
||||
});
|
||||
}
|
||||
ngModelCtrl.$validators.check = function (modelValue, viewValue) {
|
||||
var value = modelValue || viewValue;
|
||||
return value === scope.passwordConfirm;
|
||||
};
|
||||
} // end of link
|
||||
} // end of directive
|
||||
})();
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
var markup =
|
||||
'<form>' +
|
||||
'<input type="password" ng-model="user.password" id="password" name="password">' +
|
||||
'<input type="password" ng-model="user.cpassword" hz-password-match="password">' +
|
||||
'<input type="password" ng-model="user.cpassword" hz-password-match="user.password">' +
|
||||
'</form>';
|
||||
|
||||
beforeEach(module('horizon.framework.widgets'));
|
||||
|
|
|
@ -117,6 +117,10 @@
|
|||
template: base + 'radio-buttons.html',
|
||||
builder: defaults
|
||||
},
|
||||
'password-confirm': {
|
||||
template: base + 'password-confirm.html',
|
||||
builder: defaults
|
||||
},
|
||||
help: {
|
||||
template: base + 'help.html',
|
||||
builder: defaults
|
||||
|
|
|
@ -49,7 +49,7 @@
|
|||
'__name', 'textarea', 'fieldset', 'array', 'tabarray', 'tabs', 'section',
|
||||
'conditional', 'select', 'checkbox', 'checkboxes', 'number',
|
||||
'password', 'submit', 'button', 'radios', 'radios-inline', 'radiobuttons',
|
||||
'help', 'default'
|
||||
'password-confirm', 'help', 'default'
|
||||
];
|
||||
expect(fields).toEqual(expectedFields);
|
||||
});
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
<div class="form-group hz-input {$::form.htmlClass$}"
|
||||
ng-class="{'has-error': form.disableErrorState !== true && hasError(), 'has-success': form.disableSuccessState !== true && hasSuccess(), 'has-feedback': form.feedback !== false }">
|
||||
<label class="control-label {$::form.labelHtmlClass$}" ng-class="{'sr-only': !showTitle()}" for="{$::form.key.slice(-1)[0]$}">
|
||||
<span>{$::form.title$}</span>
|
||||
<span ng-if="form.required" class="hz-icon-required fa fa-asterisk"></span>
|
||||
</label>
|
||||
|
||||
<input ng-if="!form.fieldAddonLeft && !form.fieldAddonRight"
|
||||
ng-show="form.key"
|
||||
type="password"
|
||||
step="any"
|
||||
sf-changed="form"
|
||||
placeholder="{$::form.placeholder$}"
|
||||
class="form-control {$::form.fieldHtmlClass$}"
|
||||
id="{$::form.key.slice(-1)[0]$}"
|
||||
sf-field-model
|
||||
ng-disabled="form.readonly"
|
||||
schema-validate="form"
|
||||
name="{$::form.key.slice(-1)[0]$}"
|
||||
aria-describedby="{$::form.key.slice(-1)[0] + 'Status'$}"
|
||||
hz-password-match="{$::form.match$}">
|
||||
|
||||
<div ng-if="form.fieldAddonLeft || form.fieldAddonRight"
|
||||
ng-class="{'input-group': (form.fieldAddonLeft || form.fieldAddonRight)}">
|
||||
<span ng-if="form.fieldAddonLeft"
|
||||
class="input-group-addon"
|
||||
ng-bind-html="form.fieldAddonLeft"></span>
|
||||
<input ng-show="form.key"
|
||||
type="password"
|
||||
step="any"
|
||||
sf-changed="form"
|
||||
placeholder="{$::form.placeholder$}"
|
||||
class="form-control {$::form.fieldHtmlClass$}"
|
||||
id="{$::form.key.slice(-1)[0]$}"
|
||||
sf-field-model
|
||||
ng-disabled="form.readonly"
|
||||
schema-validate="form"
|
||||
name="{$::form.key.slice(-1)[0]$}"
|
||||
aria-describedby="{$::form.key.slice(-1)[0] + 'Status'$}"
|
||||
hz-password-match="{$::form.match$}">
|
||||
|
||||
<span ng-if="form.fieldAddonRight"
|
||||
class="input-group-addon"
|
||||
ng-bind-html="form.fieldAddonRight"></span>
|
||||
</div>
|
||||
|
||||
<span ng-if="form.feedback !== false"
|
||||
class="form-control-feedback"
|
||||
ng-class="evalInScope(form.feedback) || {'fa': true, 'fa-check': hasSuccess(), 'fa-times': hasError() }"
|
||||
aria-hidden="true"></span>
|
||||
|
||||
<span ng-if="hasError() || hasSuccess()"
|
||||
id="{$::form.key.slice(-1)[0] + 'Status'$}"
|
||||
class="sr-only">{$ hasSuccess() ? '(success)' : '(error)' $}</span>
|
||||
|
||||
<div class="help-block" sf-message="form.description"></div>
|
||||
</div>
|
|
@ -71,20 +71,22 @@ class Users(generic.View):
|
|||
|
||||
Create a user using the parameters supplied in the POST
|
||||
application/json object. The base parameters are name (string), email
|
||||
(string, optional), password (string, optional), project_id (string,
|
||||
optional), enabled (boolean, defaults to true). The user will be
|
||||
created in the default domain.
|
||||
(string, optional), password (string), project (string,
|
||||
optional), enabled (boolean, defaults to true), description
|
||||
(string, optional). The user will be created in the default domain.
|
||||
|
||||
This action returns the new user object on success.
|
||||
"""
|
||||
domain = api.keystone.get_default_domain(request)
|
||||
|
||||
new_user = api.keystone.user_create(
|
||||
request,
|
||||
name=request.DATA['name'],
|
||||
email=request.DATA.get('email') or None,
|
||||
password=request.DATA.get('password'),
|
||||
project=request.DATA.get('project_id') or None,
|
||||
enabled=True,
|
||||
project=request.DATA.get('project') or None,
|
||||
enabled=request.DATA.get('enabled', True),
|
||||
description=request.DATA.get('description') or None,
|
||||
domain=domain.id
|
||||
)
|
||||
|
||||
|
@ -260,6 +262,17 @@ class Role(generic.View):
|
|||
api.keystone.role_update(request, id, request.DATA['name'])
|
||||
|
||||
|
||||
@urls.register
|
||||
class DefaultDomain(generic.View):
|
||||
"""API for default domain of the user."""
|
||||
url_regex = r'keystone/default_domain/$'
|
||||
|
||||
@rest_utils.ajax()
|
||||
def get(self, request):
|
||||
"""Get a default domain of the user."""
|
||||
return api.keystone.get_default_domain(request).to_dict()
|
||||
|
||||
|
||||
@urls.register
|
||||
class Domains(generic.View):
|
||||
"""API over all domains."""
|
||||
|
|
|
@ -0,0 +1,66 @@
|
|||
/**
|
||||
* Copyright 2016 99Cloud
|
||||
*
|
||||
* 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 overview
|
||||
* @ngname horizon.dashboard.identity.users.actions
|
||||
*
|
||||
* @description
|
||||
* Provides all of the actions for users.
|
||||
*/
|
||||
angular
|
||||
.module('horizon.dashboard.identity.users.actions', [
|
||||
'horizon.framework.conf'
|
||||
])
|
||||
.run(registerUserActions)
|
||||
.config(config);
|
||||
|
||||
registerUserActions.$inject = [
|
||||
'horizon.framework.conf.resource-type-registry.service',
|
||||
'horizon.dashboard.identity.users.actions.create.service',
|
||||
'horizon.dashboard.identity.users.resourceType'
|
||||
];
|
||||
|
||||
function registerUserActions(
|
||||
registry,
|
||||
createService,
|
||||
userResourceTypeCode
|
||||
) {
|
||||
var userResourceType = registry.getResourceType(userResourceTypeCode);
|
||||
userResourceType.globalActions
|
||||
.append({
|
||||
id: 'createUserAction',
|
||||
service: createService,
|
||||
template: {
|
||||
type: 'create',
|
||||
text: gettext('Create User')
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
config.$inject = [
|
||||
'$provide',
|
||||
'$windowProvider'
|
||||
];
|
||||
|
||||
function config($provide, $windowProvider) {
|
||||
var path = $windowProvider.$get().STATIC_URL + 'dashboard/identity/users/actions/';
|
||||
$provide.constant('horizon.dashboard.identity.users.actions.basePath', path);
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,86 @@
|
|||
/**
|
||||
* Copyright 2016 99Cloud
|
||||
*
|
||||
* 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';
|
||||
|
||||
angular
|
||||
.module('horizon.dashboard.identity.users')
|
||||
.factory('horizon.dashboard.identity.users.actions.create.service', createService);
|
||||
|
||||
createService.$inject = [
|
||||
'$q',
|
||||
'horizon.dashboard.identity.users.resourceType',
|
||||
'horizon.dashboard.identity.users.actions.basePath',
|
||||
'horizon.dashboard.identity.users.actions.workflow.service',
|
||||
'horizon.app.core.openstack-service-api.keystone',
|
||||
'horizon.app.core.openstack-service-api.policy',
|
||||
'horizon.framework.util.actions.action-result.service',
|
||||
'horizon.framework.widgets.form.ModalFormService',
|
||||
'horizon.framework.widgets.toast.service'
|
||||
];
|
||||
|
||||
/**
|
||||
* @ngDoc factory
|
||||
* @name horizon.dashboard.identity.users.actions.create.service
|
||||
* @Description A service to open the user wizard.
|
||||
*/
|
||||
function createService(
|
||||
$q,
|
||||
resourceType,
|
||||
basePath,
|
||||
workflow,
|
||||
keystone,
|
||||
policy,
|
||||
actionResultService,
|
||||
modal,
|
||||
toast
|
||||
) {
|
||||
var message = {
|
||||
success: gettext('User %s was successfully created.')
|
||||
};
|
||||
|
||||
return {
|
||||
allowed: allowed,
|
||||
perform: perform,
|
||||
submit: submit
|
||||
};
|
||||
|
||||
//////////////
|
||||
|
||||
function allowed() {
|
||||
return policy.ifAllowed({ rules: [['identity', 'identity:create_user']] });
|
||||
}
|
||||
|
||||
function perform() {
|
||||
var config = workflow.init("create");
|
||||
config.title = gettext("Create User");
|
||||
return modal.open(config).then(submit);
|
||||
}
|
||||
|
||||
function submit(context) {
|
||||
return keystone.createUser(context.model).then(success);
|
||||
}
|
||||
|
||||
function success(response) {
|
||||
var user = response.data;
|
||||
toast.add('success', interpolate(message.success, [user.name]));
|
||||
return actionResultService.getActionResult()
|
||||
.created(resourceType, user.id)
|
||||
.result;
|
||||
}
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* Copyright 2016 99Cloud
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
* not use self 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';
|
||||
|
||||
describe('horizon.dashboard.identity.users.actions.create.service', function() {
|
||||
|
||||
var $q, $scope, keystone, service, modal, policy, resourceType, toast;
|
||||
|
||||
///////////////////////
|
||||
|
||||
beforeEach(module('horizon.framework'));
|
||||
beforeEach(module('horizon.app.core'));
|
||||
beforeEach(module('horizon.dashboard.identity.users'));
|
||||
|
||||
beforeEach(inject(function($injector, _$rootScope_, _$q_) {
|
||||
$scope = _$rootScope_.$new();
|
||||
$q = _$q_;
|
||||
service = $injector.get('horizon.dashboard.identity.users.actions.create.service');
|
||||
toast = $injector.get('horizon.framework.widgets.toast.service');
|
||||
modal = $injector.get('horizon.framework.widgets.form.ModalFormService');
|
||||
keystone = $injector.get('horizon.app.core.openstack-service-api.keystone');
|
||||
policy = $injector.get('horizon.app.core.openstack-service-api.policy');
|
||||
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']);
|
||||
|
||||
service.allowed().then(handler.success);
|
||||
$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 open the modal', function() {
|
||||
spyOn(modal, 'open').and.returnValue($q.defer().promise);
|
||||
spyOn(keystone, 'getVersion').and.returnValue($q.defer().promise);
|
||||
spyOn(keystone, 'getDefaultDomain').and.returnValue($q.defer().promise);
|
||||
spyOn(keystone, 'getProjects').and.returnValue($q.defer().promise);
|
||||
spyOn(keystone, 'getRoles').and.returnValue($q.defer().promise);
|
||||
|
||||
service.perform();
|
||||
$scope.$apply();
|
||||
|
||||
expect(modal.open).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should submit create user request to keystone', function() {
|
||||
var deferred = $q.defer();
|
||||
spyOn(keystone, 'createUser').and.returnValue(deferred.promise);
|
||||
spyOn(toast, 'add').and.callFake(angular.noop);
|
||||
var handler = jasmine.createSpyObj('handler', ['success']);
|
||||
|
||||
deferred.resolve({data: {name: 'saved', id: '12345'}});
|
||||
service.submit({model: {name: 'entered'}}).then(handler.success);
|
||||
|
||||
$scope.$apply();
|
||||
|
||||
expect(keystone.createUser).toHaveBeenCalledWith({name: 'entered'});
|
||||
expect(toast.add).toHaveBeenCalledWith('success', 'User saved was successfully created.');
|
||||
|
||||
expect(handler.success).toHaveBeenCalled();
|
||||
var result = handler.success.calls.first().args[0];
|
||||
expect(result.created).toEqual([{type: resourceType, id: '12345'}]);
|
||||
});
|
||||
|
||||
});
|
||||
})();
|
|
@ -0,0 +1 @@
|
|||
<p translate>Create a new user and set related properties including the Primary Project and Role.</p>
|
|
@ -0,0 +1,176 @@
|
|||
/**
|
||||
* Copyright 2017 NEC Corporation
|
||||
*
|
||||
* 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 factory
|
||||
* @name horizon.dashboard.identity.users.actions.workflow.service
|
||||
* @ngController
|
||||
*
|
||||
* @description
|
||||
* Workflow for creating/updating user
|
||||
*/
|
||||
angular
|
||||
.module('horizon.dashboard.identity.users.actions')
|
||||
.factory('horizon.dashboard.identity.users.actions.workflow.service', UserWorkflow);
|
||||
|
||||
UserWorkflow.$inject = [
|
||||
'horizon.app.core.openstack-service-api.keystone',
|
||||
'horizon.dashboard.identity.users.basePath'
|
||||
];
|
||||
|
||||
function UserWorkflow(keystone, basePath) {
|
||||
|
||||
var workflow = {
|
||||
init: init
|
||||
};
|
||||
|
||||
function init(action) {
|
||||
var schema = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
domain_name: {
|
||||
title: gettext('Domain Name'),
|
||||
type: 'string',
|
||||
readOnly: true
|
||||
},
|
||||
domain_id: {
|
||||
title: gettext('Domain Id'),
|
||||
type: 'string',
|
||||
readOnly: true
|
||||
},
|
||||
name: {
|
||||
title: gettext('User Name'),
|
||||
type: 'string'
|
||||
},
|
||||
email: {
|
||||
title: gettext('Email'),
|
||||
type: 'string'
|
||||
},
|
||||
password: {
|
||||
title: gettext('Password'),
|
||||
type: 'string'
|
||||
},
|
||||
project: {
|
||||
title: gettext('Primary Project'),
|
||||
type: 'string'
|
||||
},
|
||||
role: {
|
||||
title: gettext('Role'),
|
||||
type: 'string'
|
||||
},
|
||||
description: {
|
||||
title: gettext('Description'),
|
||||
type: 'string'
|
||||
},
|
||||
enabled: {
|
||||
title: gettext('Enabled'),
|
||||
type: 'boolean',
|
||||
default: true
|
||||
}
|
||||
},
|
||||
required: ['name', 'password', 'project', 'role', 'enabled']
|
||||
};
|
||||
|
||||
var form = [
|
||||
{
|
||||
type: 'section',
|
||||
htmlClass: 'row',
|
||||
items: [
|
||||
{
|
||||
type: 'section',
|
||||
htmlClass: 'col-sm-12',
|
||||
items: [
|
||||
{
|
||||
type: 'template',
|
||||
templateUrl: basePath + "actions/workflow/info." + action + ".help.html"
|
||||
},
|
||||
{ key: 'domain_name' },
|
||||
{ key: 'domain_id' },
|
||||
{ key: 'name' },
|
||||
{ key: 'email' },
|
||||
{
|
||||
key: 'password',
|
||||
type: 'password'
|
||||
},
|
||||
{
|
||||
key: 'confirm',
|
||||
type: 'password-confirm',
|
||||
title: 'Confirm Password',
|
||||
match: 'model.password'
|
||||
},
|
||||
{
|
||||
key: 'project',
|
||||
type: 'select',
|
||||
titleMap: []
|
||||
},
|
||||
{
|
||||
key: 'role',
|
||||
type: 'select',
|
||||
titleMap: []
|
||||
},
|
||||
{ key: 'description' },
|
||||
{ key: 'enabled' }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
var model = {};
|
||||
|
||||
var config = {
|
||||
schema: schema,
|
||||
form: form,
|
||||
model: model
|
||||
};
|
||||
|
||||
keystone.getVersion().then(function (response) {
|
||||
var apiVersion = response.data.version;
|
||||
var domainName = config.form[0].items[0].items[1];
|
||||
var domainId = config.form[0].items[0].items[2];
|
||||
if (apiVersion !== "3") {
|
||||
domainName.condition = true;
|
||||
domainId.condition = true;
|
||||
}
|
||||
return apiVersion;
|
||||
});
|
||||
keystone.getDefaultDomain().then(function (response) {
|
||||
config.model.domain_name = response.data.name;
|
||||
config.model.domain_id = response.data.id;
|
||||
return response.data;
|
||||
});
|
||||
keystone.getProjects().then(function (response) {
|
||||
var projectField = config.form[0].items[0].items[7];
|
||||
projectField.titleMap = response.data.items.map(function each(item) {
|
||||
return {value: item.id, name: item.name};
|
||||
});
|
||||
});
|
||||
keystone.getRoles().then(function (response) {
|
||||
var roleField = config.form[0].items[0].items[8];
|
||||
roleField.titleMap = response.data.items.map(function each(item) {
|
||||
return {value: item.id, name: item.name};
|
||||
});
|
||||
});
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
return workflow;
|
||||
}
|
||||
})();
|
|
@ -0,0 +1,71 @@
|
|||
/**
|
||||
* 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';
|
||||
|
||||
describe('horizon.dashboard.identity.users.actions.workflow.service', function() {
|
||||
|
||||
var $q, $scope, workflow, keystone;
|
||||
|
||||
beforeEach(module('horizon.framework'));
|
||||
beforeEach(module('horizon.app.core'));
|
||||
beforeEach(module('horizon.dashboard.identity.users'));
|
||||
|
||||
beforeEach(inject(function($injector, _$rootScope_, _$q_) {
|
||||
$scope = _$rootScope_.$new();
|
||||
$q = _$q_;
|
||||
workflow = $injector.get('horizon.dashboard.identity.users.actions.workflow.service');
|
||||
keystone = $injector.get('horizon.app.core.openstack-service-api.keystone');
|
||||
}));
|
||||
|
||||
function testInitWorkflow(version) {
|
||||
var deferredVersion = $q.defer();
|
||||
var deferredDefaultDomain = $q.defer();
|
||||
var deferredProjects = $q.defer();
|
||||
var deferredRoles = $q.defer();
|
||||
spyOn(keystone, 'getVersion').and.returnValue(deferredVersion.promise);
|
||||
spyOn(keystone, 'getDefaultDomain').and.returnValue(deferredDefaultDomain.promise);
|
||||
spyOn(keystone, 'getProjects').and.returnValue(deferredProjects.promise);
|
||||
spyOn(keystone, 'getRoles').and.returnValue(deferredRoles.promise);
|
||||
deferredVersion.resolve({data: {version: version}});
|
||||
deferredDefaultDomain.resolve({data: {items: [{name: 'dom1', id: '12345'}]}});
|
||||
deferredProjects.resolve({data: {items: [{name: 'proj1', id: '12345'}]}});
|
||||
deferredRoles.resolve({data: {items: [{name: 'role1', id: '12345'}]}});
|
||||
|
||||
var config = workflow.init();
|
||||
$scope.$apply();
|
||||
|
||||
expect(config.schema).toBeDefined();
|
||||
expect(config.form).toBeDefined();
|
||||
expect(config.model).toBeDefined();
|
||||
return config;
|
||||
}
|
||||
|
||||
it('should create workflow config for creation using Keystone V3', function() {
|
||||
var config = testInitWorkflow('3');
|
||||
|
||||
expect(config.form[0].items[0].items[1].condition).not.toBeDefined();
|
||||
expect(config.form[0].items[0].items[2].condition).not.toBeDefined();
|
||||
});
|
||||
|
||||
it('should create workflow config and the config does not show domain info ' +
|
||||
'when use Keystone V2', function() {
|
||||
var config = testInitWorkflow('2');
|
||||
|
||||
expect(config.form[0].items[0].items[1].condition).toBe(true);
|
||||
expect(config.form[0].items[0].items[2].condition).toBe(true);
|
||||
});
|
||||
});
|
||||
})();
|
|
@ -1,4 +1,5 @@
|
|||
<hz-resource-panel resource-type-name="OS::Keystone::User">
|
||||
<hz-resource-table resource-type-name="OS::Keystone::User">
|
||||
<hz-resource-table resource-type-name="OS::Keystone::User"
|
||||
track-by="trackBy">
|
||||
</hz-resource-table>
|
||||
</hz-resource-panel>
|
||||
|
|
|
@ -29,9 +29,11 @@
|
|||
angular
|
||||
.module('horizon.dashboard.identity.users', [
|
||||
'ngRoute',
|
||||
'horizon.dashboard.identity.users.details'
|
||||
'horizon.dashboard.identity.users.details',
|
||||
'horizon.dashboard.identity.users.actions'
|
||||
])
|
||||
.constant('horizon.dashboard.identity.users.resourceType', 'OS::Keystone::User')
|
||||
.constant('horizon.dashboard.identity.users.validationRules', validationRules())
|
||||
.run(run)
|
||||
.config(config);
|
||||
|
||||
|
@ -120,6 +122,19 @@
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @ngdoc constant
|
||||
* @name horizon.dashboard.identity.users.events.validationRules
|
||||
* @description constants for use in validation fields
|
||||
*/
|
||||
function validationRules() {
|
||||
return {
|
||||
integer: /^[0-9]+$/,
|
||||
validatePassword: /.*/,
|
||||
fieldMaxLength: 255
|
||||
};
|
||||
}
|
||||
|
||||
config.$inject = [
|
||||
'$provide',
|
||||
'$windowProvider',
|
||||
|
|
|
@ -19,7 +19,7 @@
|
|||
describe('loading the module', function () {
|
||||
var registry;
|
||||
|
||||
beforeEach(module('horizon.app.core.openstack-service-api'));
|
||||
beforeEach(module('horizon.app.core'));
|
||||
beforeEach(module('horizon.dashboard.identity.users'));
|
||||
beforeEach(module('horizon.framework'));
|
||||
beforeEach(inject(function($injector) {
|
||||
|
|
|
@ -57,7 +57,17 @@
|
|||
* Returns a promise for the users data.
|
||||
*/
|
||||
function getUsersPromise(params) {
|
||||
return keystone.getUsers(params);
|
||||
return keystone.getUsers(params).then(modifyResponse);
|
||||
|
||||
function modifyResponse(response) {
|
||||
return {data: {items: response.data.items.map(modifyItem)}};
|
||||
|
||||
function modifyItem(item) {
|
||||
item.trackBy = item.id + item.name + item.email + item.project +
|
||||
item.description + item.enabled;
|
||||
return item;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
|
|
|
@ -41,7 +41,9 @@
|
|||
spyOn(keystone, 'getUsers').and.returnValue(deferred.promise);
|
||||
|
||||
var result = service.getUsersPromise();
|
||||
deferred.resolve({data: {items: [{id: 1, name: 'puff'}]}});
|
||||
deferred.resolve({data: {items: [{id: '1', name: 'puff'}]}});
|
||||
|
||||
scope.$apply();
|
||||
|
||||
expect(keystone.getUsers).toHaveBeenCalled();
|
||||
expect(result.$$state.value.data.items[0].name).toBe('puff');
|
||||
|
@ -58,7 +60,7 @@
|
|||
|
||||
service.getUserPromise(1);
|
||||
deferredVersion.resolve({data: {version: ''}});
|
||||
deferredUser.resolve({data: {id: 1, name: 'puff'}});
|
||||
deferredUser.resolve({data: {id: '1', name: 'puff'}});
|
||||
|
||||
scope.$apply();
|
||||
expect(keystone.getVersion).toHaveBeenCalled();
|
||||
|
@ -73,7 +75,7 @@
|
|||
|
||||
service.getUserPromise(1);
|
||||
deferredVersion.resolve({data: {version: 2}});
|
||||
deferredUser.resolve({data: {id: 1, name: 'puff'}});
|
||||
deferredUser.resolve({data: {id: '1', name: 'puff'}});
|
||||
|
||||
scope.$apply();
|
||||
expect(keystone.getVersion).toHaveBeenCalled();
|
||||
|
@ -92,10 +94,10 @@
|
|||
|
||||
var result = service.getUserPromise(1);
|
||||
deferredVersion.resolve({data: {version: 3}});
|
||||
deferredUser.resolve({data: {id: 1, name: 'puff', domain_id: 29,
|
||||
deferredUser.resolve({data: {id: '1', name: 'puff', domain_id: 29,
|
||||
default_project_id: 26}});
|
||||
deferredProject.resolve({data: {name: 'puff_project'}});
|
||||
deferredDomain.resolve({data: {id: 1, name: 'puff_domain'}});
|
||||
deferredDomain.resolve({data: {id: '1', name: 'puff_domain'}});
|
||||
|
||||
scope.$apply();
|
||||
expect(keystone.getVersion).toHaveBeenCalled();
|
||||
|
|
|
@ -42,6 +42,7 @@
|
|||
getRole: getRole,
|
||||
editRole: editRole,
|
||||
deleteRole: deleteRole,
|
||||
getDefaultDomain: getDefaultDomain,
|
||||
getDomains: getDomains,
|
||||
createDomain: createDomain,
|
||||
deleteDomains: deleteDomains,
|
||||
|
@ -256,6 +257,13 @@
|
|||
}
|
||||
|
||||
// Domains
|
||||
function getDefaultDomain() {
|
||||
return apiService.get('/api/keystone/default_domain/')
|
||||
.error(function () {
|
||||
toastService.add('error', gettext('Unable to retrieve the default domain.'));
|
||||
});
|
||||
}
|
||||
|
||||
function getDomains() {
|
||||
return apiService.get('/api/keystone/domains/')
|
||||
.error(function () {
|
||||
|
|
|
@ -209,6 +209,12 @@
|
|||
42
|
||||
]
|
||||
},
|
||||
{
|
||||
"func": "getDefaultDomain",
|
||||
"method": "get",
|
||||
"path": "/api/keystone/default_domain/",
|
||||
"error": "Unable to retrieve the default domain."
|
||||
},
|
||||
{
|
||||
"func": "getDomains",
|
||||
"method": "get",
|
||||
|
|
|
@ -95,37 +95,40 @@ class KeystoneRestTestCase(test.TestCase):
|
|||
def test_user_create_full(self):
|
||||
self._test_user_create(
|
||||
'{"name": "bob", '
|
||||
'"password": "sekrit", "project_id": "project123", '
|
||||
'"email": "spam@company.example"}',
|
||||
'"password": "sekrit", "project": "123", '
|
||||
'"email": "spam@company.example", '
|
||||
'"description": "hello, puff"}',
|
||||
{
|
||||
'name': 'bob',
|
||||
'password': 'sekrit',
|
||||
'email': 'spam@company.example',
|
||||
'project': 'project123',
|
||||
'project': '123',
|
||||
'domain': 'the_domain',
|
||||
'enabled': True
|
||||
'enabled': True,
|
||||
'description': 'hello, puff'
|
||||
}
|
||||
)
|
||||
|
||||
def test_user_create_existing_role(self):
|
||||
self._test_user_create(
|
||||
'{"name": "bob", '
|
||||
'"password": "sekrit", "project_id": "project123", '
|
||||
'"password": "sekrit", "project": "123", '
|
||||
'"email": "spam@company.example"}',
|
||||
{
|
||||
'name': 'bob',
|
||||
'password': 'sekrit',
|
||||
'email': 'spam@company.example',
|
||||
'project': 'project123',
|
||||
'project': '123',
|
||||
'domain': 'the_domain',
|
||||
'enabled': True
|
||||
'enabled': True,
|
||||
'description': None
|
||||
}
|
||||
)
|
||||
|
||||
def test_user_create_no_project(self):
|
||||
self._test_user_create(
|
||||
'{"name": "bob", '
|
||||
'"password": "sekrit", "project_id": "", '
|
||||
'"password": "sekrit", "project": "", '
|
||||
'"email": "spam@company.example"}',
|
||||
{
|
||||
'name': 'bob',
|
||||
|
@ -133,20 +136,22 @@ class KeystoneRestTestCase(test.TestCase):
|
|||
'email': 'spam@company.example',
|
||||
'project': None,
|
||||
'domain': 'the_domain',
|
||||
'enabled': True
|
||||
'enabled': True,
|
||||
'description': None
|
||||
}
|
||||
)
|
||||
|
||||
def test_user_create_partial(self):
|
||||
self._test_user_create(
|
||||
'{"name": "bob"}',
|
||||
'{"name": "bob", "project": ""}',
|
||||
{
|
||||
'name': 'bob',
|
||||
'password': None,
|
||||
'email': None,
|
||||
'project': None,
|
||||
'domain': 'the_domain',
|
||||
'enabled': True
|
||||
'enabled': True,
|
||||
'description': None
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -365,6 +370,13 @@ class KeystoneRestTestCase(test.TestCase):
|
|||
#
|
||||
# Domains
|
||||
#
|
||||
@mock.patch.object(keystone.api, 'keystone')
|
||||
def test_default_domain_get(self, kc):
|
||||
request = self.mock_rest_request()
|
||||
response = keystone.DefaultDomain().get(request)
|
||||
self.assertStatusCode(response, 200)
|
||||
self.assertEqual(response.json, {})
|
||||
|
||||
@mock.patch.object(keystone.api, 'keystone')
|
||||
def test_domain_get(self, kc):
|
||||
request = self.mock_rest_request()
|
||||
|
|
Loading…
Reference in New Issue