mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-11-02 19:47:54 +00:00
Dynamic OpenAPI UI (#6209)
This commit is contained in:
@@ -1,2 +1,3 @@
|
|||||||
import AuthConfig from './_base';
|
import AuthConfig from './_base';
|
||||||
|
|
||||||
export default AuthConfig.extend();
|
export default AuthConfig.extend();
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
import AuthConfig from './_base';
|
import AuthConfig from './_base';
|
||||||
|
|
||||||
export default AuthConfig.extend();
|
export default AuthConfig.extend();
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
import AuthConfig from './_base';
|
import AuthConfig from './_base';
|
||||||
|
|
||||||
export default AuthConfig.extend();
|
export default AuthConfig.extend();
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
import AuthConfig from './_base';
|
import AuthConfig from './_base';
|
||||||
|
|
||||||
export default AuthConfig.extend();
|
export default AuthConfig.extend();
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
import AuthConfig from './_base';
|
import AuthConfig from './_base';
|
||||||
|
|
||||||
export default AuthConfig.extend();
|
export default AuthConfig.extend();
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
import AuthConfig from './_base';
|
import AuthConfig from './_base';
|
||||||
|
|
||||||
export default AuthConfig.extend();
|
export default AuthConfig.extend();
|
||||||
|
|||||||
@@ -1,2 +1,3 @@
|
|||||||
import AuthConfig from './_base';
|
import AuthConfig from './_base';
|
||||||
|
|
||||||
export default AuthConfig.extend();
|
export default AuthConfig.extend();
|
||||||
|
|||||||
@@ -7,4 +7,8 @@ export default Adapter.extend({
|
|||||||
}
|
}
|
||||||
return `/v1/${role.backend}/sign/${role.name}`;
|
return `/v1/${role.backend}/sign/${role.name}`;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
pathForType() {
|
||||||
|
return 'sign';
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,7 +13,6 @@ export default Adapter.extend({
|
|||||||
}
|
}
|
||||||
return url;
|
return url;
|
||||||
},
|
},
|
||||||
|
|
||||||
optionsForQuery(id) {
|
optionsForQuery(id) {
|
||||||
let data = {};
|
let data = {};
|
||||||
if (!id) {
|
if (!id) {
|
||||||
|
|||||||
@@ -7,16 +7,20 @@ export default ApplicationAdapter.extend({
|
|||||||
return path ? url + '/' + path : url;
|
return path ? url + '/' + path : url;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
internalURL(path) {
|
||||||
|
let url = `/${this.urlPrefix()}/internal/ui/mounts`;
|
||||||
|
if (path) {
|
||||||
|
url = `${url}/${path}`;
|
||||||
|
}
|
||||||
|
return url;
|
||||||
|
},
|
||||||
|
|
||||||
pathForType() {
|
pathForType() {
|
||||||
return 'mounts';
|
return 'mounts';
|
||||||
},
|
},
|
||||||
|
|
||||||
query(store, type, query) {
|
query(store, type, query) {
|
||||||
let url = `/${this.urlPrefix()}/internal/ui/mounts`;
|
return this.ajax(this.internalURL(query.path), 'GET');
|
||||||
if (query.path) {
|
|
||||||
url = `${url}/${query.path}`;
|
|
||||||
}
|
|
||||||
return this.ajax(url, 'GET');
|
|
||||||
},
|
},
|
||||||
|
|
||||||
createRecord(store, type, snapshot) {
|
createRecord(store, type, snapshot) {
|
||||||
|
|||||||
@@ -34,6 +34,10 @@ export default ApplicationAdapter.extend({
|
|||||||
return url;
|
return url;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
pathForType() {
|
||||||
|
return 'mounts';
|
||||||
|
},
|
||||||
|
|
||||||
optionsForQuery(id, action, wrapTTL) {
|
optionsForQuery(id, action, wrapTTL) {
|
||||||
let data = {};
|
let data = {};
|
||||||
if (action === 'query') {
|
if (action === 'query') {
|
||||||
|
|||||||
@@ -8,10 +8,11 @@ const AuthConfigBase = Component.extend({
|
|||||||
model: null,
|
model: null,
|
||||||
|
|
||||||
flashMessages: service(),
|
flashMessages: service(),
|
||||||
|
router: service(),
|
||||||
|
wizard: service(),
|
||||||
saveModel: task(function*() {
|
saveModel: task(function*() {
|
||||||
try {
|
try {
|
||||||
yield this.get('model').save();
|
yield this.model.save();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// AdapterErrors are handled by the error-message component
|
// AdapterErrors are handled by the error-message component
|
||||||
// in the form
|
// in the form
|
||||||
@@ -20,7 +21,11 @@ const AuthConfigBase = Component.extend({
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.get('flashMessages').success('The configuration was saved successfully.');
|
if (this.wizard.currentMachine === 'authentication' && this.wizard.featureState === 'config') {
|
||||||
|
this.wizard.transitionFeatureMachine(this.wizard.featureState, 'CONTINUE');
|
||||||
|
}
|
||||||
|
this.router.transitionTo('vault.cluster.access.methods').followRedirects();
|
||||||
|
this.flashMessages.success('The configuration was saved successfully.');
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import AuthConfigComponent from './config';
|
import AuthConfigComponent from './config';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
import { task } from 'ember-concurrency';
|
import { task } from 'ember-concurrency';
|
||||||
import DS from 'ember-data';
|
import DS from 'ember-data';
|
||||||
|
|
||||||
export default AuthConfigComponent.extend({
|
export default AuthConfigComponent.extend({
|
||||||
|
router: service(),
|
||||||
|
wizard: service(),
|
||||||
saveModel: task(function*() {
|
saveModel: task(function*() {
|
||||||
const model = this.get('model');
|
let data = this.model.config.serialize();
|
||||||
let data = model.get('config').serialize();
|
data.description = this.model.description;
|
||||||
data.description = model.get('description');
|
|
||||||
try {
|
try {
|
||||||
yield model.tune(data);
|
yield this.model.tune(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// AdapterErrors are handled by the error-message component
|
// AdapterErrors are handled by the error-message component
|
||||||
// in the form
|
// in the form
|
||||||
@@ -17,6 +19,10 @@ export default AuthConfigComponent.extend({
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.get('flashMessages').success('The configuration options were saved successfully.');
|
if (this.wizard.currentMachine === 'authentication' && this.wizard.featureState === 'config') {
|
||||||
|
this.wizard.transitionFeatureMachine(this.wizard.featureState, 'CONTINUE');
|
||||||
|
}
|
||||||
|
this.router.transitionTo('vault.cluster.access.methods').followRedirects();
|
||||||
|
this.flashMessages.success('The configuration was saved successfully.');
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ export default Component.extend({
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
onMountSuccess() {},
|
onMountSuccess() {},
|
||||||
onConfigError() {},
|
|
||||||
/*
|
/*
|
||||||
* @param String
|
* @param String
|
||||||
* @public
|
* @public
|
||||||
@@ -41,18 +40,18 @@ export default Component.extend({
|
|||||||
*/
|
*/
|
||||||
mountModel: null,
|
mountModel: null,
|
||||||
|
|
||||||
showConfig: false,
|
showEnable: false,
|
||||||
|
|
||||||
init() {
|
init() {
|
||||||
this._super(...arguments);
|
this._super(...arguments);
|
||||||
const type = this.get('mountType');
|
const type = this.mountType;
|
||||||
const modelType = type === 'secret' ? 'secret-engine' : 'auth-method';
|
const modelType = type === 'secret' ? 'secret-engine' : 'auth-method';
|
||||||
const model = this.get('store').createRecord(modelType);
|
const model = this.store.createRecord(modelType);
|
||||||
this.set('mountModel', model);
|
this.set('mountModel', model);
|
||||||
},
|
},
|
||||||
|
|
||||||
mountTypes: computed('mountType', function() {
|
mountTypes: computed('mountType', function() {
|
||||||
return this.get('mountType') === 'secret' ? ENGINES : METHODS;
|
return this.mountType === 'secret' ? ENGINES : METHODS;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
willDestroy() {
|
willDestroy() {
|
||||||
@@ -60,44 +59,10 @@ export default Component.extend({
|
|||||||
this.get('mountModel').rollbackAttributes();
|
this.get('mountModel').rollbackAttributes();
|
||||||
},
|
},
|
||||||
|
|
||||||
getConfigModelType(methodType) {
|
|
||||||
let mountType = this.get('mountType');
|
|
||||||
// will be something like secret-aws
|
|
||||||
// or auth-azure
|
|
||||||
let key = `${mountType}-${methodType}`;
|
|
||||||
let noConfig = ['auth-approle', 'auth-alicloud'];
|
|
||||||
if (mountType === 'secret' || noConfig.includes(key)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (methodType === 'aws') {
|
|
||||||
return 'auth-config/aws/client';
|
|
||||||
}
|
|
||||||
return `auth-config/${methodType}`;
|
|
||||||
},
|
|
||||||
|
|
||||||
changeConfigModel(methodType) {
|
|
||||||
let mount = this.get('mountModel');
|
|
||||||
if (this.get('mountType') === 'secret') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
let configRef = mount.hasMany('authConfigs').value();
|
|
||||||
let currentConfig = configRef && configRef.get('firstObject');
|
|
||||||
if (currentConfig) {
|
|
||||||
// rollbackAttributes here will remove the the config model from the store
|
|
||||||
// because `isNew` will be true
|
|
||||||
currentConfig.rollbackAttributes();
|
|
||||||
currentConfig.unloadRecord();
|
|
||||||
}
|
|
||||||
let configType = this.getConfigModelType(methodType);
|
|
||||||
if (!configType) return;
|
|
||||||
let config = this.get('store').createRecord(configType);
|
|
||||||
config.set('backend', mount);
|
|
||||||
},
|
|
||||||
|
|
||||||
checkPathChange(type) {
|
checkPathChange(type) {
|
||||||
let mount = this.get('mountModel');
|
let mount = this.mountModel;
|
||||||
let currentPath = mount.get('path');
|
let currentPath = mount.path;
|
||||||
let list = this.get('mountTypes');
|
let list = this.mountTypes;
|
||||||
// if the current path matches a type (meaning the user hasn't altered it),
|
// if the current path matches a type (meaning the user hasn't altered it),
|
||||||
// change it here to match the new type
|
// change it here to match the new type
|
||||||
let isUnchanged = list.findBy('type', currentPath);
|
let isUnchanged = list.findBy('type', currentPath);
|
||||||
@@ -107,7 +72,7 @@ export default Component.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
mountBackend: task(function*() {
|
mountBackend: task(function*() {
|
||||||
const mountModel = this.get('mountModel');
|
const mountModel = this.mountModel;
|
||||||
const { type, path } = mountModel.getProperties('type', 'path');
|
const { type, path } = mountModel.getProperties('type', 'path');
|
||||||
try {
|
try {
|
||||||
yield mountModel.save();
|
yield mountModel.save();
|
||||||
@@ -116,74 +81,27 @@ export default Component.extend({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let mountType = this.get('mountType');
|
let mountType = this.mountType;
|
||||||
mountType = mountType === 'secret' ? `${mountType}s engine` : `${mountType} method`;
|
mountType = mountType === 'secret' ? `${mountType}s engine` : `${mountType} method`;
|
||||||
this.get('flashMessages').success(`Successfully mounted the ${type} ${mountType} at ${path}.`);
|
this.flashMessages.success(`Successfully mounted the ${type} ${mountType} at ${path}.`);
|
||||||
if (this.get('mountType') === 'secret') {
|
yield this.onMountSuccess(type, path);
|
||||||
yield this.get('onMountSuccess')(type, path);
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
yield this.get('saveConfig').perform(mountModel);
|
|
||||||
}).drop(),
|
|
||||||
|
|
||||||
advanceWizard() {
|
|
||||||
this.get('wizard').transitionFeatureMachine(
|
|
||||||
this.get('wizard.featureState'),
|
|
||||||
'CONTINUE',
|
|
||||||
this.get('mountModel').get('type')
|
|
||||||
);
|
|
||||||
},
|
|
||||||
saveConfig: task(function*(mountModel) {
|
|
||||||
const configRef = mountModel.hasMany('authConfigs').value();
|
|
||||||
const { type, path } = mountModel.getProperties('type', 'path');
|
|
||||||
if (!configRef) {
|
|
||||||
this.advanceWizard();
|
|
||||||
yield this.get('onMountSuccess')(type, path);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const config = configRef.get('firstObject');
|
|
||||||
try {
|
|
||||||
if (config && Object.keys(config.changedAttributes()).length) {
|
|
||||||
yield config.save();
|
|
||||||
this.advanceWizard();
|
|
||||||
this.get('flashMessages').success(
|
|
||||||
`The config for ${type} ${this.get('mountType')} method at ${path} was saved successfully.`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
yield this.get('onMountSuccess')(type, path);
|
|
||||||
} catch (err) {
|
|
||||||
this.get('flashMessages').danger(
|
|
||||||
`There was an error saving the configuration for ${type} ${this.get(
|
|
||||||
'mountType'
|
|
||||||
)} method at ${path}. ${err.errors.join(' ')}`
|
|
||||||
);
|
|
||||||
yield this.get('onConfigError')(mountModel.id);
|
|
||||||
}
|
|
||||||
}).drop(),
|
}).drop(),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
onTypeChange(path, value) {
|
onTypeChange(path, value) {
|
||||||
if (path === 'type') {
|
if (path === 'type') {
|
||||||
this.get('wizard').set('componentState', value);
|
this.wizard.set('componentState', value);
|
||||||
this.changeConfigModel(value);
|
|
||||||
this.checkPathChange(value);
|
this.checkPathChange(value);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleShowConfig(value) {
|
toggleShowEnable(value) {
|
||||||
this.set('showConfig', value);
|
this.set('showEnable', value);
|
||||||
if (value === true && this.get('wizard.featureState') === 'idle') {
|
if (value === true && this.wizard.featureState === 'idle') {
|
||||||
this.get('wizard').transitionFeatureMachine(
|
this.wizard.transitionFeatureMachine(this.wizard.featureState, 'CONTINUE', this.mountModel.type);
|
||||||
this.get('wizard.featureState'),
|
|
||||||
'CONTINUE',
|
|
||||||
this.get('mountModel').get('type')
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
this.get('wizard').transitionFeatureMachine(
|
this.wizard.transitionFeatureMachine(this.wizard.featureState, 'RESET', this.mountModel.type);
|
||||||
this.get('wizard.featureState'),
|
|
||||||
'RESET',
|
|
||||||
this.get('mountModel').get('type')
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,14 +4,10 @@ import Controller from '@ember/controller';
|
|||||||
export default Controller.extend({
|
export default Controller.extend({
|
||||||
wizard: service(),
|
wizard: service(),
|
||||||
actions: {
|
actions: {
|
||||||
onMountSuccess: function(type) {
|
onMountSuccess: function(type, path) {
|
||||||
let transition = this.transitionToRoute('vault.cluster.access.methods');
|
this.wizard.transitionFeatureMachine(this.wizard.featureState, 'CONTINUE', type);
|
||||||
return transition.followRedirects().then(() => {
|
let transition = this.transitionToRoute('vault.cluster.settings.auth.configure', path);
|
||||||
this.get('wizard').transitionFeatureMachine(this.get('wizard.featureState'), 'CONTINUE', type);
|
return transition.followRedirects();
|
||||||
});
|
|
||||||
},
|
|
||||||
onConfigError: function(modelId) {
|
|
||||||
return this.transitionToRoute('vault.cluster.settings.auth.configure', modelId);
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -22,16 +22,16 @@ export default {
|
|||||||
{ type: 'render', level: 'step', component: 'wizard/auth-enable' },
|
{ type: 'render', level: 'step', component: 'wizard/auth-enable' },
|
||||||
],
|
],
|
||||||
on: {
|
on: {
|
||||||
CONTINUE: 'list',
|
CONTINUE: 'config',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
list: {
|
config: {
|
||||||
onEntry: [
|
onEntry: [
|
||||||
{ type: 'render', level: 'step', component: 'wizard/auth-list' },
|
|
||||||
{ type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
|
{ type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
|
||||||
|
{ type: 'render', level: 'step', component: 'wizard/auth-config' },
|
||||||
],
|
],
|
||||||
on: {
|
on: {
|
||||||
DETAILS: 'details',
|
CONTINUE: 'details',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
details: {
|
details: {
|
||||||
|
|||||||
@@ -2,5 +2,8 @@ import DS from 'ember-data';
|
|||||||
const { belongsTo } = DS;
|
const { belongsTo } = DS;
|
||||||
|
|
||||||
export default DS.Model.extend({
|
export default DS.Model.extend({
|
||||||
backend: belongsTo('auth-method', { readOnly: true, async: false }),
|
backend: belongsTo('auth-method', { inverse: 'authConfigs', readOnly: true, async: false }),
|
||||||
|
getHelpUrl: function(backend) {
|
||||||
|
return `/v1/auth/${backend}/config?help=1`;
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { computed } from '@ember/object';
|
import { computed } from '@ember/object';
|
||||||
import DS from 'ember-data';
|
import DS from 'ember-data';
|
||||||
|
|
||||||
import AuthConfig from '../auth-config';
|
import AuthConfig from '../auth-config';
|
||||||
|
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
|
||||||
import fieldToAttrs from 'vault/utils/field-to-attrs';
|
import fieldToAttrs from 'vault/utils/field-to-attrs';
|
||||||
|
|
||||||
const { attr } = DS;
|
const { attr } = DS;
|
||||||
|
|
||||||
export default AuthConfig.extend({
|
export default AuthConfig.extend({
|
||||||
|
useOpenAPI: true,
|
||||||
tenantId: attr('string', {
|
tenantId: attr('string', {
|
||||||
label: 'Tenant ID',
|
label: 'Tenant ID',
|
||||||
helpText: 'The tenant ID for the Azure Active Directory organization',
|
helpText: 'The tenant ID for the Azure Active Directory organization',
|
||||||
@@ -26,12 +27,16 @@ export default AuthConfig.extend({
|
|||||||
googleCertsEndpoint: attr('string'),
|
googleCertsEndpoint: attr('string'),
|
||||||
|
|
||||||
fieldGroups: computed(function() {
|
fieldGroups: computed(function() {
|
||||||
const groups = [
|
let groups = [
|
||||||
{ default: ['tenantId', 'resource'] },
|
{ default: ['tenantId', 'resource'] },
|
||||||
{
|
{
|
||||||
'Azure Options': ['clientId', 'clientSecret'],
|
'Azure Options': ['clientId', 'clientSecret'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
if (this.newFields) {
|
||||||
|
groups = combineFieldGroups(groups, this.newFields, []);
|
||||||
|
}
|
||||||
|
|
||||||
return fieldToAttrs(this, groups);
|
return fieldToAttrs(this, groups);
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import { computed } from '@ember/object';
|
import { computed } from '@ember/object';
|
||||||
import DS from 'ember-data';
|
import DS from 'ember-data';
|
||||||
|
|
||||||
import AuthConfig from '../auth-config';
|
import AuthConfig from '../auth-config';
|
||||||
|
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
|
||||||
import fieldToAttrs from 'vault/utils/field-to-attrs';
|
import fieldToAttrs from 'vault/utils/field-to-attrs';
|
||||||
|
|
||||||
const { attr } = DS;
|
const { attr } = DS;
|
||||||
|
|
||||||
export default AuthConfig.extend({
|
export default AuthConfig.extend({
|
||||||
|
useOpenAPI: true,
|
||||||
|
// We have to leave this here because the backend doesn't support the file type yet.
|
||||||
credentials: attr('string', {
|
credentials: attr('string', {
|
||||||
editType: 'file',
|
editType: 'file',
|
||||||
}),
|
}),
|
||||||
@@ -14,12 +16,15 @@ export default AuthConfig.extend({
|
|||||||
googleCertsEndpoint: attr('string'),
|
googleCertsEndpoint: attr('string'),
|
||||||
|
|
||||||
fieldGroups: computed(function() {
|
fieldGroups: computed(function() {
|
||||||
const groups = [
|
let groups = [
|
||||||
{ default: ['credentials'] },
|
{ default: ['credentials'] },
|
||||||
{
|
{
|
||||||
'Google Cloud Options': ['googleCertsEndpoint'],
|
'Google Cloud Options': ['googleCertsEndpoint'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
if (this.newFields) {
|
||||||
|
groups = combineFieldGroups(groups, this.newFields, []);
|
||||||
|
}
|
||||||
return fieldToAttrs(this, groups);
|
return fieldToAttrs(this, groups);
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,24 +1,28 @@
|
|||||||
import { computed } from '@ember/object';
|
import { computed } from '@ember/object';
|
||||||
import DS from 'ember-data';
|
import DS from 'ember-data';
|
||||||
|
|
||||||
import AuthConfig from '../auth-config';
|
import AuthConfig from '../auth-config';
|
||||||
import fieldToAttrs from 'vault/utils/field-to-attrs';
|
import fieldToAttrs from 'vault/utils/field-to-attrs';
|
||||||
|
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
|
||||||
|
|
||||||
const { attr } = DS;
|
const { attr } = DS;
|
||||||
|
|
||||||
export default AuthConfig.extend({
|
export default AuthConfig.extend({
|
||||||
|
useOpenAPI: true,
|
||||||
organization: attr('string'),
|
organization: attr('string'),
|
||||||
baseUrl: attr('string', {
|
baseUrl: attr('string', {
|
||||||
label: 'Base URL',
|
label: 'Base URL',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
fieldGroups: computed(function() {
|
fieldGroups: computed(function() {
|
||||||
const groups = [
|
let groups = [
|
||||||
{ default: ['organization'] },
|
{ default: ['organization'] },
|
||||||
{
|
{
|
||||||
'GitHub Options': ['baseUrl'],
|
'GitHub Options': ['baseUrl'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
if (this.newFields) {
|
||||||
|
groups = combineFieldGroups(groups, this.newFields, []);
|
||||||
|
}
|
||||||
|
|
||||||
return fieldToAttrs(this, groups);
|
return fieldToAttrs(this, groups);
|
||||||
}),
|
}),
|
||||||
|
|||||||
@@ -2,36 +2,34 @@ import { computed } from '@ember/object';
|
|||||||
import DS from 'ember-data';
|
import DS from 'ember-data';
|
||||||
|
|
||||||
import AuthConfig from '../auth-config';
|
import AuthConfig from '../auth-config';
|
||||||
|
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
|
||||||
import fieldToAttrs from 'vault/utils/field-to-attrs';
|
import fieldToAttrs from 'vault/utils/field-to-attrs';
|
||||||
|
|
||||||
const { attr } = DS;
|
const { attr } = DS;
|
||||||
|
|
||||||
export default AuthConfig.extend({
|
export default AuthConfig.extend({
|
||||||
|
useOpenAPI: true,
|
||||||
kubernetesHost: attr('string', {
|
kubernetesHost: attr('string', {
|
||||||
label: 'Kubernetes Host',
|
|
||||||
helpText:
|
helpText:
|
||||||
'Host must be a host string, a host:port pair, or a URL to the base of the Kubernetes API server',
|
'Host must be a host string, a host:port pair, or a URL to the base of the Kubernetes API server',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
kubernetesCaCert: attr('string', {
|
kubernetesCaCert: attr('string', {
|
||||||
label: 'Kubernetes CA Certificate',
|
|
||||||
editType: 'file',
|
editType: 'file',
|
||||||
helpText: 'PEM encoded CA cert for use by the TLS client used to talk with the Kubernetes API',
|
helpText: 'PEM encoded CA cert for use by the TLS client used to talk with the Kubernetes API',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
tokenReviewerJwt: attr('string', {
|
tokenReviewerJwt: attr('string', {
|
||||||
label: 'Token Reviewer JWT',
|
|
||||||
helpText:
|
helpText:
|
||||||
'A service account JWT used to access the TokenReview API to validate other JWTs during login. If not set the JWT used for login will be used to access the API',
|
'A service account JWT used to access the TokenReview API to validate other JWTs during login. If not set the JWT used for login will be used to access the API',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
pemKeys: attr({
|
pemKeys: attr({
|
||||||
label: 'Service account verification keys',
|
|
||||||
editType: 'stringArray',
|
editType: 'stringArray',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
fieldGroups: computed(function() {
|
fieldGroups: computed(function() {
|
||||||
const groups = [
|
let groups = [
|
||||||
{
|
{
|
||||||
default: ['kubernetesHost', 'kubernetesCaCert'],
|
default: ['kubernetesHost', 'kubernetesCaCert'],
|
||||||
},
|
},
|
||||||
@@ -39,6 +37,10 @@ export default AuthConfig.extend({
|
|||||||
'Kubernetes Options': ['tokenReviewerJwt', 'pemKeys'],
|
'Kubernetes Options': ['tokenReviewerJwt', 'pemKeys'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
if (this.newFields) {
|
||||||
|
groups = combineFieldGroups(groups, this.newFields, []);
|
||||||
|
}
|
||||||
|
|
||||||
return fieldToAttrs(this, groups);
|
return fieldToAttrs(this, groups);
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -3,97 +3,50 @@ import DS from 'ember-data';
|
|||||||
|
|
||||||
import AuthConfig from '../auth-config';
|
import AuthConfig from '../auth-config';
|
||||||
import fieldToAttrs from 'vault/utils/field-to-attrs';
|
import fieldToAttrs from 'vault/utils/field-to-attrs';
|
||||||
|
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
|
||||||
|
|
||||||
const { attr } = DS;
|
const { attr } = DS;
|
||||||
|
|
||||||
export default AuthConfig.extend({
|
export default AuthConfig.extend({
|
||||||
url: attr('string', {
|
useOpenAPI: true,
|
||||||
label: 'URL',
|
|
||||||
}),
|
|
||||||
starttls: attr('boolean', {
|
|
||||||
defaultValue: false,
|
|
||||||
label: 'Issue StartTLS command after establishing an unencrypted connection',
|
|
||||||
}),
|
|
||||||
tlsMinVersion: attr('string', {
|
|
||||||
label: 'Minimum TLS Version',
|
|
||||||
defaultValue: 'tls12',
|
|
||||||
possibleValues: ['tls10', 'tls11', 'tls12'],
|
|
||||||
}),
|
|
||||||
|
|
||||||
tlsMaxVersion: attr('string', {
|
|
||||||
label: 'Maximum TLS Version',
|
|
||||||
defaultValue: 'tls12',
|
|
||||||
possibleValues: ['tls10', 'tls11', 'tls12'],
|
|
||||||
}),
|
|
||||||
insecureTls: attr('boolean', {
|
|
||||||
defaultValue: false,
|
|
||||||
label: 'Skip LDAP server SSL certificate verification',
|
|
||||||
}),
|
|
||||||
certificate: attr('string', {
|
|
||||||
label: 'CA certificate to verify LDAP server certificate',
|
|
||||||
editType: 'file',
|
|
||||||
}),
|
|
||||||
|
|
||||||
binddn: attr('string', {
|
binddn: attr('string', {
|
||||||
label: 'Name of Object to bind (binddn)',
|
|
||||||
helpText: 'Used when performing user search. Example: cn=vault,ou=Users,dc=example,dc=com',
|
helpText: 'Used when performing user search. Example: cn=vault,ou=Users,dc=example,dc=com',
|
||||||
}),
|
}),
|
||||||
bindpass: attr('string', {
|
bindpass: attr('string', {
|
||||||
label: 'Password',
|
|
||||||
helpText: 'Used along with binddn when performing user search',
|
helpText: 'Used along with binddn when performing user search',
|
||||||
sensitive: true,
|
sensitive: true,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
userdn: attr('string', {
|
userdn: attr('string', {
|
||||||
label: 'User DN',
|
|
||||||
helpText: 'Base DN under which to perform user search. Example: ou=Users,dc=example,dc=com',
|
helpText: 'Base DN under which to perform user search. Example: ou=Users,dc=example,dc=com',
|
||||||
}),
|
}),
|
||||||
userattr: attr('string', {
|
userattr: attr('string', {
|
||||||
label: 'User Attribute',
|
|
||||||
defaultValue: 'cn',
|
|
||||||
helpText:
|
helpText:
|
||||||
'Attribute on user attribute object matching the username passed when authenticating. Examples: sAMAccountName, cn, uid',
|
'Attribute on user attribute object matching the username passed when authenticating. Examples: sAMAccountName, cn, uid',
|
||||||
}),
|
}),
|
||||||
discoverdn: attr('boolean', {
|
|
||||||
defaultValue: false,
|
|
||||||
label: 'Use anonymous bind to discover the bind DN of a user',
|
|
||||||
}),
|
|
||||||
denyNullBind: attr('boolean', {
|
|
||||||
defaultValue: true,
|
|
||||||
label: 'Prevent users from bypassing authentication when providing an empty password',
|
|
||||||
}),
|
|
||||||
upndomain: attr('string', {
|
upndomain: attr('string', {
|
||||||
label: 'User Principal (UPN) Domain',
|
|
||||||
helpText:
|
helpText:
|
||||||
'The userPrincipalDomain used to construct the UPN string for the authenticating user. The constructed UPN will appear as [username]@UPNDomain. Example: example.com, which will cause vault to bind as username@example.com.',
|
'The userPrincipalDomain used to construct the UPN string for the authenticating user. The constructed UPN will appear as [username]@UPNDomain. Example: example.com, which will cause vault to bind as username@example.com.',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
groupfilter: attr('string', {
|
groupfilter: attr('string', {
|
||||||
label: 'Group Filter',
|
|
||||||
helpText:
|
helpText:
|
||||||
'Go template used when constructing the group membership query. The template can access the following context variables: [UserDN, Username]. The default is (|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}})), which is compatible with several common directory schemas. To support nested group resolution for Active Directory, instead use the following query: (&(objectClass=group)(member:1.2.840.113556.1.4.1941:={{.UserDN}}))',
|
'Go template used when constructing the group membership query. The template can access the following context variables: [UserDN, Username]. The default is (|(memberUid={{.Username}})(member={{.UserDN}})(uniqueMember={{.UserDN}})), which is compatible with several common directory schemas. To support nested group resolution for Active Directory, instead use the following query: (&(objectClass=group)(member:1.2.840.113556.1.4.1941:={{.UserDN}}))',
|
||||||
}),
|
}),
|
||||||
groupdn: attr('string', {
|
groupdn: attr('string', {
|
||||||
label: 'Group DN',
|
|
||||||
helpText:
|
helpText:
|
||||||
'LDAP search base for group membership search. This can be the root containing either groups or users. Example: ou=Groups,dc=example,dc=com',
|
'LDAP search base for group membership search. This can be the root containing either groups or users. Example: ou=Groups,dc=example,dc=com',
|
||||||
}),
|
}),
|
||||||
groupattr: attr('string', {
|
groupattr: attr('string', {
|
||||||
label: 'Group Attribute',
|
|
||||||
defaultValue: 'cn',
|
|
||||||
|
|
||||||
helpText:
|
helpText:
|
||||||
'LDAP attribute to follow on objects returned by groupfilter in order to enumerate user group membership. Examples: for groupfilter queries returning group objects, use: cn. For queries returning user objects, use: memberOf. The default is cn.',
|
'LDAP attribute to follow on objects returned by groupfilter in order to enumerate user group membership. Examples: for groupfilter queries returning group objects, use: cn. For queries returning user objects, use: memberOf. The default is cn.',
|
||||||
}),
|
}),
|
||||||
useTokenGroups: attr('boolean', {
|
useTokenGroups: attr('boolean', {
|
||||||
defaultValue: false,
|
|
||||||
label: 'Use Token Groups',
|
|
||||||
helpText:
|
helpText:
|
||||||
'Use the Active Directory tokenGroups constructed attribute to find the group memberships. This returns all security groups for the user, including nested groups. In an Active Directory environment with a large number of groups this method offers increased performance. Selecting this will cause Group DN, Attribute, and Filter to be ignored.',
|
'Use the Active Directory tokenGroups constructed attribute to find the group memberships. This returns all security groups for the user, including nested groups. In an Active Directory environment with a large number of groups this method offers increased performance. Selecting this will cause Group DN, Attribute, and Filter to be ignored.',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
fieldGroups: computed(function() {
|
fieldGroups: computed(function() {
|
||||||
const groups = [
|
let groups = [
|
||||||
{
|
{
|
||||||
default: ['url'],
|
default: ['url'],
|
||||||
},
|
},
|
||||||
@@ -117,6 +70,9 @@ export default AuthConfig.extend({
|
|||||||
'Customize Group Membership Search': ['groupfilter', 'groupattr', 'groupdn', 'useTokenGroups'],
|
'Customize Group Membership Search': ['groupfilter', 'groupattr', 'groupdn', 'useTokenGroups'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
if (this.newFields) {
|
||||||
|
groups = combineFieldGroups(groups, this.newFields, []);
|
||||||
|
}
|
||||||
return fieldToAttrs(this, groups);
|
return fieldToAttrs(this, groups);
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,32 +2,31 @@ import { computed } from '@ember/object';
|
|||||||
import DS from 'ember-data';
|
import DS from 'ember-data';
|
||||||
import AuthConfig from '../auth-config';
|
import AuthConfig from '../auth-config';
|
||||||
import fieldToAttrs from 'vault/utils/field-to-attrs';
|
import fieldToAttrs from 'vault/utils/field-to-attrs';
|
||||||
|
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
|
||||||
|
|
||||||
const { attr } = DS;
|
const { attr } = DS;
|
||||||
|
|
||||||
export default AuthConfig.extend({
|
export default AuthConfig.extend({
|
||||||
|
useOpenAPI: true,
|
||||||
orgName: attr('string', {
|
orgName: attr('string', {
|
||||||
label: 'Organization Name',
|
|
||||||
helpText: 'Name of the organization to be used in the Okta API',
|
helpText: 'Name of the organization to be used in the Okta API',
|
||||||
}),
|
}),
|
||||||
apiToken: attr('string', {
|
apiToken: attr('string', {
|
||||||
label: 'API Token',
|
|
||||||
helpText:
|
helpText:
|
||||||
'Okta API token. This is required to query Okta for user group membership. If this is not supplied only locally configured groups will be enabled.',
|
'Okta API token. This is required to query Okta for user group membership. If this is not supplied only locally configured groups will be enabled.',
|
||||||
}),
|
}),
|
||||||
baseUrl: attr('string', {
|
baseUrl: attr('string', {
|
||||||
label: 'Base URL',
|
|
||||||
helpText:
|
helpText:
|
||||||
'If set, will be used as the base domain for API requests. Examples are okta.com, oktapreview.com, and okta-emea.com',
|
'If set, will be used as the base domain for API requests. Examples are okta.com, oktapreview.com, and okta-emea.com',
|
||||||
}),
|
}),
|
||||||
bypassOktaMfa: attr('boolean', {
|
bypassOktaMfa: attr('boolean', {
|
||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
label: 'Bypass Okta MFA',
|
|
||||||
helpText:
|
helpText:
|
||||||
"Useful if Vault's built-in MFA mechanisms. Will also cause certain other statuses to be ignored, such as PASSWORD_EXPIRED",
|
"Useful if Vault's built-in MFA mechanisms. Will also cause certain other statuses to be ignored, such as PASSWORD_EXPIRED",
|
||||||
}),
|
}),
|
||||||
|
|
||||||
fieldGroups: computed(function() {
|
fieldGroups: computed(function() {
|
||||||
const groups = [
|
let groups = [
|
||||||
{
|
{
|
||||||
default: ['orgName'],
|
default: ['orgName'],
|
||||||
},
|
},
|
||||||
@@ -35,6 +34,10 @@ export default AuthConfig.extend({
|
|||||||
Options: ['apiToken', 'baseUrl', 'bypassOktaMfa'],
|
Options: ['apiToken', 'baseUrl', 'bypassOktaMfa'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
if (this.newFields) {
|
||||||
|
groups = combineFieldGroups(groups, this.newFields, []);
|
||||||
|
}
|
||||||
|
|
||||||
return fieldToAttrs(this, groups);
|
return fieldToAttrs(this, groups);
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,38 +1,18 @@
|
|||||||
import { computed } from '@ember/object';
|
import { computed } from '@ember/object';
|
||||||
import DS from 'ember-data';
|
import DS from 'ember-data';
|
||||||
import AuthConfig from '../auth-config';
|
import AuthConfig from '../auth-config';
|
||||||
|
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
|
||||||
import fieldToAttrs from 'vault/utils/field-to-attrs';
|
import fieldToAttrs from 'vault/utils/field-to-attrs';
|
||||||
|
|
||||||
const { attr } = DS;
|
const { attr } = DS;
|
||||||
|
|
||||||
export default AuthConfig.extend({
|
export default AuthConfig.extend({
|
||||||
|
useOpenAPI: true,
|
||||||
host: attr('string'),
|
host: attr('string'),
|
||||||
|
|
||||||
port: attr('number', {
|
|
||||||
defaultValue: 1812,
|
|
||||||
}),
|
|
||||||
|
|
||||||
secret: attr('string'),
|
secret: attr('string'),
|
||||||
|
|
||||||
unregisteredUserPolicies: attr('string', {
|
|
||||||
label: 'Policies for unregistered users',
|
|
||||||
}),
|
|
||||||
|
|
||||||
dialTimeout: attr('number', {
|
|
||||||
defaultValue: 10,
|
|
||||||
}),
|
|
||||||
|
|
||||||
nasPort: attr('number', {
|
|
||||||
defaultValue: 10,
|
|
||||||
label: 'NAS Port',
|
|
||||||
}),
|
|
||||||
|
|
||||||
nasIdentifier: attr('string', {
|
|
||||||
label: 'NAS Identifier',
|
|
||||||
}),
|
|
||||||
|
|
||||||
fieldGroups: computed(function() {
|
fieldGroups: computed(function() {
|
||||||
const groups = [
|
let groups = [
|
||||||
{
|
{
|
||||||
default: ['host', 'secret'],
|
default: ['host', 'secret'],
|
||||||
},
|
},
|
||||||
@@ -40,6 +20,10 @@ export default AuthConfig.extend({
|
|||||||
'RADIUS Options': ['port', 'nasPort', 'nasIdentifier', 'dialTimeout', 'unregisteredUserPolicies'],
|
'RADIUS Options': ['port', 'nasPort', 'nasIdentifier', 'dialTimeout', 'unregisteredUserPolicies'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
if (this.newFields) {
|
||||||
|
groups = combineFieldGroups(groups, this.newFields, []);
|
||||||
|
}
|
||||||
|
|
||||||
return fieldToAttrs(this, groups);
|
return fieldToAttrs(this, groups);
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { copy } from 'ember-copy';
|
|||||||
import { computed } from '@ember/object';
|
import { computed } from '@ember/object';
|
||||||
import DS from 'ember-data';
|
import DS from 'ember-data';
|
||||||
import Certificate from './pki-certificate';
|
import Certificate from './pki-certificate';
|
||||||
|
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
|
||||||
const { attr } = DS;
|
const { attr } = DS;
|
||||||
|
|
||||||
export default Certificate.extend({
|
export default Certificate.extend({
|
||||||
@@ -10,7 +10,7 @@ export default Certificate.extend({
|
|||||||
readOnly: true,
|
readOnly: true,
|
||||||
defaultValue: false,
|
defaultValue: false,
|
||||||
}),
|
}),
|
||||||
|
useOpenAPI: true,
|
||||||
csr: attr('string', {
|
csr: attr('string', {
|
||||||
label: 'Certificate Signing Request (CSR)',
|
label: 'Certificate Signing Request (CSR)',
|
||||||
editType: 'textarea',
|
editType: 'textarea',
|
||||||
@@ -18,11 +18,14 @@ export default Certificate.extend({
|
|||||||
|
|
||||||
fieldGroups: computed('signVerbatim', function() {
|
fieldGroups: computed('signVerbatim', function() {
|
||||||
const options = { Options: ['altNames', 'ipSans', 'ttl', 'excludeCnFromSans', 'otherSans'] };
|
const options = { Options: ['altNames', 'ipSans', 'ttl', 'excludeCnFromSans', 'otherSans'] };
|
||||||
const groups = [
|
let groups = [
|
||||||
{
|
{
|
||||||
default: ['csr', 'commonName', 'format', 'signVerbatim'],
|
default: ['csr', 'commonName', 'format', 'signVerbatim'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
if (this.newFields) {
|
||||||
|
groups = combineFieldGroups(groups, this.newFields, []);
|
||||||
|
}
|
||||||
if (this.get('signVerbatim') === false) {
|
if (this.get('signVerbatim') === false) {
|
||||||
groups.push(options);
|
groups.push(options);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ export default DS.Model.extend({
|
|||||||
fieldValue: 'id',
|
fieldValue: 'id',
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
}),
|
}),
|
||||||
|
useOpenAPI: false,
|
||||||
// credentialTypes are for backwards compatibility.
|
// credentialTypes are for backwards compatibility.
|
||||||
// we use this to populate "credentialType" in
|
// we use this to populate "credentialType" in
|
||||||
// the serializer. if there is more than one, the
|
// the serializer. if there is more than one, the
|
||||||
@@ -52,17 +53,15 @@ export default DS.Model.extend({
|
|||||||
editType: 'json',
|
editType: 'json',
|
||||||
}),
|
}),
|
||||||
fields: computed('credentialType', function() {
|
fields: computed('credentialType', function() {
|
||||||
let keys;
|
let credentialType = this.credentialType;
|
||||||
let credentialType = this.get('credentialType');
|
|
||||||
let keysForType = {
|
let keysForType = {
|
||||||
iam_user: ['name', 'credentialType', 'policyArns', 'policyDocument'],
|
iam_user: ['name', 'credentialType', 'policyArns', 'policyDocument'],
|
||||||
assumed_role: ['name', 'credentialType', 'roleArns', 'policyDocument'],
|
assumed_role: ['name', 'credentialType', 'roleArns', 'policyDocument'],
|
||||||
federation_token: ['name', 'credentialType', 'policyDocument'],
|
federation_token: ['name', 'credentialType', 'policyDocument'],
|
||||||
};
|
};
|
||||||
keys = keysForType[credentialType];
|
|
||||||
return expandAttributeMeta(this, keys);
|
|
||||||
}),
|
|
||||||
|
|
||||||
|
return expandAttributeMeta(this, keysForType[credentialType]);
|
||||||
|
}),
|
||||||
updatePath: lazyCapabilities(apiPath`${'backend'}/roles/${'id'}`, 'backend', 'id'),
|
updatePath: lazyCapabilities(apiPath`${'backend'}/roles/${'id'}`, 'backend', 'id'),
|
||||||
canDelete: alias('updatePath.canDelete'),
|
canDelete: alias('updatePath.canDelete'),
|
||||||
canEdit: alias('updatePath.canUpdate'),
|
canEdit: alias('updatePath.canUpdate'),
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { computed } from '@ember/object';
|
|||||||
import DS from 'ember-data';
|
import DS from 'ember-data';
|
||||||
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
|
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
|
||||||
import fieldToAttrs from 'vault/utils/field-to-attrs';
|
import fieldToAttrs from 'vault/utils/field-to-attrs';
|
||||||
|
import { combineFieldGroups } from 'vault/utils/openapi-to-attrs';
|
||||||
const { attr } = DS;
|
const { attr } = DS;
|
||||||
|
|
||||||
export default DS.Model.extend({
|
export default DS.Model.extend({
|
||||||
@@ -15,101 +15,10 @@ export default DS.Model.extend({
|
|||||||
fieldValue: 'id',
|
fieldValue: 'id',
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
}),
|
}),
|
||||||
keyType: attr('string', {
|
useOpenAPI: true,
|
||||||
possibleValues: ['rsa', 'ec'],
|
getHelpUrl: function(backend) {
|
||||||
}),
|
return `/v1/${backend}/roles/example?help=1`;
|
||||||
ttl: attr({
|
},
|
||||||
label: 'TTL',
|
|
||||||
editType: 'ttl',
|
|
||||||
}),
|
|
||||||
maxTtl: attr({
|
|
||||||
label: 'Max TTL',
|
|
||||||
editType: 'ttl',
|
|
||||||
}),
|
|
||||||
allowLocalhost: attr('boolean', {}),
|
|
||||||
allowedDomains: attr('string', {}),
|
|
||||||
allowBareDomains: attr('boolean', {}),
|
|
||||||
allowSubdomains: attr('boolean', {}),
|
|
||||||
allowGlobDomains: attr('boolean', {}),
|
|
||||||
allowAnyName: attr('boolean', {}),
|
|
||||||
enforceHostnames: attr('boolean', {}),
|
|
||||||
allowIpSans: attr('boolean', {
|
|
||||||
defaultValue: true,
|
|
||||||
label: 'Allow clients to request IP Subject Alternative Names (SANs)',
|
|
||||||
}),
|
|
||||||
allowedOtherSans: attr({
|
|
||||||
editType: 'stringArray',
|
|
||||||
label: 'Allowed Other SANs',
|
|
||||||
}),
|
|
||||||
serverFlag: attr('boolean', {
|
|
||||||
defaultValue: true,
|
|
||||||
}),
|
|
||||||
clientFlag: attr('boolean', {
|
|
||||||
defaultValue: true,
|
|
||||||
}),
|
|
||||||
codeSigningFlag: attr('boolean', {}),
|
|
||||||
emailProtectionFlag: attr('boolean', {}),
|
|
||||||
keyBits: attr('number', {
|
|
||||||
defaultValue: 2048,
|
|
||||||
}),
|
|
||||||
keyUsage: attr('string', {
|
|
||||||
defaultValue: 'DigitalSignature,KeyAgreement,KeyEncipherment',
|
|
||||||
editType: 'stringArray',
|
|
||||||
}),
|
|
||||||
extKeyUsageOids: attr({
|
|
||||||
label: 'Custom extended key usage OIDs',
|
|
||||||
editType: 'stringArray',
|
|
||||||
}),
|
|
||||||
requireCn: attr('boolean', {
|
|
||||||
label: 'Require common name',
|
|
||||||
defaultValue: true,
|
|
||||||
}),
|
|
||||||
useCsrCommonName: attr('boolean', {
|
|
||||||
label: 'Use CSR common name',
|
|
||||||
defaultValue: true,
|
|
||||||
}),
|
|
||||||
useCsrSans: attr('boolean', {
|
|
||||||
defaultValue: true,
|
|
||||||
label: 'Use CSR subject alternative names (SANs)',
|
|
||||||
}),
|
|
||||||
ou: attr({
|
|
||||||
label: 'OU (OrganizationalUnit)',
|
|
||||||
editType: 'stringArray',
|
|
||||||
}),
|
|
||||||
organization: attr({
|
|
||||||
editType: 'stringArray',
|
|
||||||
}),
|
|
||||||
country: attr({
|
|
||||||
editType: 'stringArray',
|
|
||||||
}),
|
|
||||||
locality: attr({
|
|
||||||
editType: 'stringArray',
|
|
||||||
label: 'Locality/City',
|
|
||||||
}),
|
|
||||||
province: attr({
|
|
||||||
editType: 'stringArray',
|
|
||||||
label: 'Province/State',
|
|
||||||
}),
|
|
||||||
streetAddress: attr({
|
|
||||||
editType: 'stringArray',
|
|
||||||
}),
|
|
||||||
postalCode: attr({
|
|
||||||
editType: 'stringArray',
|
|
||||||
}),
|
|
||||||
generateLease: attr('boolean', {}),
|
|
||||||
noStore: attr('boolean', {}),
|
|
||||||
policyIdentifiers: attr({
|
|
||||||
editType: 'stringArray',
|
|
||||||
}),
|
|
||||||
basicConstraintsValidForNonCA: attr('boolean', {
|
|
||||||
label: 'Mark Basic Constraints valid when issuing non-CA certificates.',
|
|
||||||
}),
|
|
||||||
notBeforeDuration: attr({
|
|
||||||
label: 'Not Before Duration',
|
|
||||||
editType: 'ttl',
|
|
||||||
defaultValue: '30s',
|
|
||||||
}),
|
|
||||||
|
|
||||||
updatePath: lazyCapabilities(apiPath`${'backend'}/roles/${'id'}`, 'backend', 'id'),
|
updatePath: lazyCapabilities(apiPath`${'backend'}/roles/${'id'}`, 'backend', 'id'),
|
||||||
canDelete: alias('updatePath.canDelete'),
|
canDelete: alias('updatePath.canDelete'),
|
||||||
canEdit: alias('updatePath.canUpdate'),
|
canEdit: alias('updatePath.canUpdate'),
|
||||||
@@ -125,7 +34,7 @@ export default DS.Model.extend({
|
|||||||
canSignVerbatim: alias('signVerbatimPath.canUpdate'),
|
canSignVerbatim: alias('signVerbatimPath.canUpdate'),
|
||||||
|
|
||||||
fieldGroups: computed(function() {
|
fieldGroups: computed(function() {
|
||||||
const groups = [
|
let groups = [
|
||||||
{ default: ['name', 'keyType'] },
|
{ default: ['name', 'keyType'] },
|
||||||
{
|
{
|
||||||
Options: [
|
Options: [
|
||||||
@@ -167,10 +76,13 @@ export default DS.Model.extend({
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Advanced: ['generateLease', 'noStore', 'basicConstraintsValidForNonCA', 'policyIdentifiers'],
|
Advanced: ['generateLease', 'noStore', 'basicConstraintsValidForNonCa', 'policyIdentifiers'],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
let excludedFields = ['extKeyUsage'];
|
||||||
|
if (this.newFields) {
|
||||||
|
groups = combineFieldGroups(groups, this.newFields, excludedFields);
|
||||||
|
}
|
||||||
return fieldToAttrs(this, groups);
|
return fieldToAttrs(this, groups);
|
||||||
}),
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,7 +38,12 @@ const CA_FIELDS = [
|
|||||||
'allowUserKeyIds',
|
'allowUserKeyIds',
|
||||||
'keyIdFormat',
|
'keyIdFormat',
|
||||||
];
|
];
|
||||||
|
|
||||||
export default DS.Model.extend({
|
export default DS.Model.extend({
|
||||||
|
useOpenAPI: true,
|
||||||
|
getHelpUrl: function(backend) {
|
||||||
|
return `/v1/${backend}/roles/example?help=1`;
|
||||||
|
},
|
||||||
zeroAddress: attr('boolean', {
|
zeroAddress: attr('boolean', {
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
}),
|
}),
|
||||||
@@ -46,12 +51,12 @@ export default DS.Model.extend({
|
|||||||
readOnly: true,
|
readOnly: true,
|
||||||
}),
|
}),
|
||||||
name: attr('string', {
|
name: attr('string', {
|
||||||
label: 'Role name',
|
label: 'Role Name',
|
||||||
fieldValue: 'id',
|
fieldValue: 'id',
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
}),
|
}),
|
||||||
keyType: attr('string', {
|
keyType: attr('string', {
|
||||||
possibleValues: ['ca', 'otp'],
|
possibleValues: ['ca', 'otp'], //overriding the API which also lists 'dynamic' as a type though it is deprecated
|
||||||
}),
|
}),
|
||||||
adminUser: attr('string', {
|
adminUser: attr('string', {
|
||||||
helpText: 'Username of the admin user at the remote host',
|
helpText: 'Username of the admin user at the remote host',
|
||||||
@@ -68,25 +73,14 @@ export default DS.Model.extend({
|
|||||||
'List of domains for which a client can request a certificate (e.g. `example.com`, or `*` to allow all)',
|
'List of domains for which a client can request a certificate (e.g. `example.com`, or `*` to allow all)',
|
||||||
}),
|
}),
|
||||||
cidrList: attr('string', {
|
cidrList: attr('string', {
|
||||||
label: 'CIDR list',
|
|
||||||
helpText: 'List of CIDR blocks for which this role is applicable',
|
helpText: 'List of CIDR blocks for which this role is applicable',
|
||||||
}),
|
}),
|
||||||
excludeCidrList: attr('string', {
|
excludeCidrList: attr('string', {
|
||||||
label: 'Exclude CIDR list',
|
|
||||||
helpText: 'List of CIDR blocks that are not accepted by this role',
|
helpText: 'List of CIDR blocks that are not accepted by this role',
|
||||||
}),
|
}),
|
||||||
port: attr('number', {
|
port: attr('number', {
|
||||||
defaultValue: 22,
|
|
||||||
helpText: 'Port number for the SSH connection (default is `22`)',
|
helpText: 'Port number for the SSH connection (default is `22`)',
|
||||||
}),
|
}),
|
||||||
ttl: attr({
|
|
||||||
label: 'TTL',
|
|
||||||
editType: 'ttl',
|
|
||||||
}),
|
|
||||||
maxTtl: attr({
|
|
||||||
label: 'Max TTL',
|
|
||||||
editType: 'ttl',
|
|
||||||
}),
|
|
||||||
allowedCriticalOptions: attr('string', {
|
allowedCriticalOptions: attr('string', {
|
||||||
helpText: 'List of critical options that certificates have when signed',
|
helpText: 'List of critical options that certificates have when signed',
|
||||||
}),
|
}),
|
||||||
@@ -114,11 +108,9 @@ export default DS.Model.extend({
|
|||||||
'Specifies if host certificates that are requested are allowed to be subdomains of those listed in Allowed Domains',
|
'Specifies if host certificates that are requested are allowed to be subdomains of those listed in Allowed Domains',
|
||||||
}),
|
}),
|
||||||
allowUserKeyIds: attr('boolean', {
|
allowUserKeyIds: attr('boolean', {
|
||||||
label: 'Allow user key IDs',
|
|
||||||
helpText: 'Specifies if users can override the key ID for a signed certificate with the "key_id" field',
|
helpText: 'Specifies if users can override the key ID for a signed certificate with the "key_id" field',
|
||||||
}),
|
}),
|
||||||
keyIdFormat: attr('string', {
|
keyIdFormat: attr('string', {
|
||||||
label: 'Key ID format',
|
|
||||||
helpText: 'When supplied, this value specifies a custom format for the key id of a signed certificate',
|
helpText: 'When supplied, this value specifies a custom format for the key id of a signed certificate',
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ export default DS.Model.extend({
|
|||||||
role: attr('object', {
|
role: attr('object', {
|
||||||
readOnly: true,
|
readOnly: true,
|
||||||
}),
|
}),
|
||||||
publicKey: attr('string'),
|
publicKey: attr('string', {
|
||||||
|
label: 'Public Key',
|
||||||
|
editType: 'textarea',
|
||||||
|
}),
|
||||||
ttl: attr({
|
ttl: attr({
|
||||||
label: 'TTL',
|
label: 'TTL',
|
||||||
editType: 'ttl',
|
editType: 'ttl',
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { inject as service } from '@ember/service';
|
|||||||
import Route from '@ember/routing/route';
|
import Route from '@ember/routing/route';
|
||||||
export default Route.extend({
|
export default Route.extend({
|
||||||
flashMessages: service(),
|
flashMessages: service(),
|
||||||
|
oldModel: null,
|
||||||
model(params) {
|
model(params) {
|
||||||
let { backend } = params;
|
let { backend } = params;
|
||||||
return this.store
|
return this.store
|
||||||
|
|||||||
@@ -1,15 +1,28 @@
|
|||||||
import { resolve } from 'rsvp';
|
import { resolve } from 'rsvp';
|
||||||
import Route from '@ember/routing/route';
|
import Route from '@ember/routing/route';
|
||||||
|
import { getOwner } from '@ember/application';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
|
||||||
const SUPPORTED_DYNAMIC_BACKENDS = ['ssh', 'aws', 'pki'];
|
const SUPPORTED_DYNAMIC_BACKENDS = ['ssh', 'aws', 'pki'];
|
||||||
|
|
||||||
export default Route.extend({
|
export default Route.extend({
|
||||||
templateName: 'vault/cluster/secrets/backend/credentials',
|
templateName: 'vault/cluster/secrets/backend/credentials',
|
||||||
|
pathHelp: service('path-help'),
|
||||||
|
|
||||||
backendModel() {
|
backendModel() {
|
||||||
return this.modelFor('vault.cluster.secrets.backend');
|
return this.modelFor('vault.cluster.secrets.backend');
|
||||||
},
|
},
|
||||||
|
|
||||||
|
beforeModel() {
|
||||||
|
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
|
||||||
|
if (backend != 'ssh') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let modelType = 'ssh-otp-credential';
|
||||||
|
let owner = getOwner(this);
|
||||||
|
return this.pathHelp.getNewModel(modelType, backend, owner);
|
||||||
|
},
|
||||||
|
|
||||||
model(params) {
|
model(params) {
|
||||||
let role = params.secret;
|
let role = params.secret;
|
||||||
let backendModel = this.backendModel();
|
let backendModel = this.backendModel();
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
import { set } from '@ember/object';
|
import { set } from '@ember/object';
|
||||||
import { hash, all } from 'rsvp';
|
import { hash, all } from 'rsvp';
|
||||||
import Route from '@ember/routing/route';
|
import Route from '@ember/routing/route';
|
||||||
|
import { getOwner } from '@ember/application';
|
||||||
import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends';
|
import { supportedSecretBackends } from 'vault/helpers/supported-secret-backends';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
|
||||||
const SUPPORTED_BACKENDS = supportedSecretBackends();
|
const SUPPORTED_BACKENDS = supportedSecretBackends();
|
||||||
|
|
||||||
@@ -19,24 +21,29 @@ export default Route.extend({
|
|||||||
},
|
},
|
||||||
|
|
||||||
templateName: 'vault/cluster/secrets/backend/list',
|
templateName: 'vault/cluster/secrets/backend/list',
|
||||||
|
pathHelp: service('path-help'),
|
||||||
|
|
||||||
beforeModel() {
|
beforeModel() {
|
||||||
let { backend } = this.paramsFor('vault.cluster.secrets.backend');
|
let owner = getOwner(this);
|
||||||
let { secret } = this.paramsFor(this.routeName);
|
let { secret } = this.paramsFor(this.routeName);
|
||||||
let backendModel = this.store.peekRecord('secret-engine', backend);
|
let { backend, tab } = this.paramsFor('vault.cluster.secrets.backend');
|
||||||
let type = backendModel && backendModel.get('engineType');
|
let secretEngine = this.store.peekRecord('secret-engine', backend);
|
||||||
|
let type = secretEngine && secretEngine.get('engineType');
|
||||||
if (!type || !SUPPORTED_BACKENDS.includes(type)) {
|
if (!type || !SUPPORTED_BACKENDS.includes(type)) {
|
||||||
return this.transitionTo('vault.cluster.secrets');
|
return this.transitionTo('vault.cluster.secrets');
|
||||||
}
|
}
|
||||||
if (this.routeName === 'vault.cluster.secrets.backend.list' && !secret.endsWith('/')) {
|
if (this.routeName === 'vault.cluster.secrets.backend.list' && !secret.endsWith('/')) {
|
||||||
return this.replaceWith('vault.cluster.secrets.backend.list', secret + '/');
|
return this.replaceWith('vault.cluster.secrets.backend.list', secret + '/');
|
||||||
}
|
}
|
||||||
this.store.unloadAll('capabilities');
|
let modelType = this.getModelType(backend, tab);
|
||||||
|
return this.pathHelp.getNewModel(modelType, owner, backend).then(() => {
|
||||||
|
this.store.unloadAll('capabilities');
|
||||||
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
getModelType(backend, tab) {
|
getModelType(backend, tab) {
|
||||||
let backendModel = this.store.peekRecord('secret-engine', backend);
|
let secretEngine = this.store.peekRecord('secret-engine', backend);
|
||||||
let type = backendModel.get('engineType');
|
let type = secretEngine.get('engineType');
|
||||||
let types = {
|
let types = {
|
||||||
transit: 'transit-key',
|
transit: 'transit-key',
|
||||||
ssh: 'role-ssh',
|
ssh: 'role-ssh',
|
||||||
@@ -44,8 +51,8 @@ export default Route.extend({
|
|||||||
pki: tab === 'certs' ? 'pki-certificate' : 'role-pki',
|
pki: tab === 'certs' ? 'pki-certificate' : 'role-pki',
|
||||||
// secret or secret-v2
|
// secret or secret-v2
|
||||||
cubbyhole: 'secret',
|
cubbyhole: 'secret',
|
||||||
kv: backendModel.get('modelTypeForKV'),
|
kv: secretEngine.get('modelTypeForKV'),
|
||||||
generic: backendModel.get('modelTypeForKV'),
|
generic: secretEngine.get('modelTypeForKV'),
|
||||||
};
|
};
|
||||||
return types[type];
|
return types[type];
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import { set } from '@ember/object';
|
import { set } from '@ember/object';
|
||||||
import { hash, resolve } from 'rsvp';
|
import { hash, resolve } from 'rsvp';
|
||||||
|
import { inject as service } from '@ember/service';
|
||||||
|
import DS from 'ember-data';
|
||||||
import Route from '@ember/routing/route';
|
import Route from '@ember/routing/route';
|
||||||
import utils from 'vault/lib/key-utils';
|
import utils from 'vault/lib/key-utils';
|
||||||
|
import { getOwner } from '@ember/application';
|
||||||
import UnloadModelRoute from 'vault/mixins/unload-model-route';
|
import UnloadModelRoute from 'vault/mixins/unload-model-route';
|
||||||
import DS from 'ember-data';
|
|
||||||
|
|
||||||
export default Route.extend(UnloadModelRoute, {
|
export default Route.extend(UnloadModelRoute, {
|
||||||
|
pathHelp: service('path-help'),
|
||||||
capabilities(secret) {
|
capabilities(secret) {
|
||||||
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
|
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
|
||||||
let backendModel = this.modelFor('vault.cluster.secrets.backend');
|
let backendModel = this.modelFor('vault.cluster.secrets.backend');
|
||||||
@@ -35,15 +38,27 @@ export default Route.extend(UnloadModelRoute, {
|
|||||||
// perhaps in the future we could recurse _for_ users, but for now, just kick them
|
// perhaps in the future we could recurse _for_ users, but for now, just kick them
|
||||||
// back to the list
|
// back to the list
|
||||||
const { secret } = this.paramsFor(this.routeName);
|
const { secret } = this.paramsFor(this.routeName);
|
||||||
const parentKey = utils.parentKeyForKey(secret);
|
return this.buildModel(secret).then(() => {
|
||||||
const mode = this.routeName.split('.').pop();
|
const parentKey = utils.parentKeyForKey(secret);
|
||||||
if (mode === 'edit' && utils.keyIsFolder(secret)) {
|
const mode = this.routeName.split('.').pop();
|
||||||
if (parentKey) {
|
if (mode === 'edit' && utils.keyIsFolder(secret)) {
|
||||||
return this.transitionTo('vault.cluster.secrets.backend.list', parentKey);
|
if (parentKey) {
|
||||||
} else {
|
return this.transitionTo('vault.cluster.secrets.backend.list', parentKey);
|
||||||
return this.transitionTo('vault.cluster.secrets.backend.list-root');
|
} else {
|
||||||
|
return this.transitionTo('vault.cluster.secrets.backend.list-root');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
buildModel(secret) {
|
||||||
|
const { backend } = this.paramsFor('vault.cluster.secrets.backend');
|
||||||
|
let modelType = this.modelType(backend, secret);
|
||||||
|
if (['secret', 'secret-v2'].includes(modelType)) {
|
||||||
|
return resolve();
|
||||||
}
|
}
|
||||||
|
let owner = getOwner(this);
|
||||||
|
return this.pathHelp.getNewModel(modelType, owner, backend);
|
||||||
},
|
},
|
||||||
|
|
||||||
modelType(backend, secret) {
|
modelType(backend, secret) {
|
||||||
|
|||||||
@@ -14,6 +14,10 @@ export default Route.extend(UnloadModel, {
|
|||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
pathForType() {
|
||||||
|
return 'sign';
|
||||||
|
},
|
||||||
|
|
||||||
model(params) {
|
model(params) {
|
||||||
const role = params.secret;
|
const role = params.secret;
|
||||||
const backendModel = this.backendModel();
|
const backendModel = this.backendModel();
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import Route from '@ember/routing/route';
|
|||||||
import RSVP from 'rsvp';
|
import RSVP from 'rsvp';
|
||||||
import DS from 'ember-data';
|
import DS from 'ember-data';
|
||||||
import UnloadModelRoute from 'vault/mixins/unload-model-route';
|
import UnloadModelRoute from 'vault/mixins/unload-model-route';
|
||||||
|
import { getOwner } from '@ember/application';
|
||||||
|
|
||||||
export default Route.extend(UnloadModelRoute, {
|
export default Route.extend(UnloadModelRoute, {
|
||||||
modelPath: 'model.model',
|
modelPath: 'model.model',
|
||||||
wizard: service(),
|
pathHelp: service('path-help'),
|
||||||
|
|
||||||
modelType(backendType, section) {
|
modelType(backendType, section) {
|
||||||
const MODELS = {
|
const MODELS = {
|
||||||
'aws-client': 'auth-config/aws/client',
|
'aws-client': 'auth-config/aws/client',
|
||||||
@@ -25,15 +27,22 @@ export default Route.extend(UnloadModelRoute, {
|
|||||||
return MODELS[`${backendType}-${section}`];
|
return MODELS[`${backendType}-${section}`];
|
||||||
},
|
},
|
||||||
|
|
||||||
|
beforeModel() {
|
||||||
|
const { section_name } = this.paramsFor(this.routeName);
|
||||||
|
if (section_name === 'options') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const { method } = this.paramsFor('vault.cluster.settings.auth.configure');
|
||||||
|
const backend = this.modelFor('vault.cluster.settings.auth.configure');
|
||||||
|
const modelType = this.modelType(backend.type, section_name);
|
||||||
|
let owner = getOwner(this);
|
||||||
|
return this.pathHelp.getNewModel(modelType, owner, method);
|
||||||
|
},
|
||||||
|
|
||||||
model(params) {
|
model(params) {
|
||||||
const backend = this.modelFor('vault.cluster.settings.auth.configure');
|
const backend = this.modelFor('vault.cluster.settings.auth.configure');
|
||||||
const { section_name: section } = params;
|
const { section_name: section } = params;
|
||||||
if (section === 'options') {
|
if (section === 'options') {
|
||||||
this.get('wizard').transitionFeatureMachine(
|
|
||||||
this.get('wizard.featureState'),
|
|
||||||
'EDIT',
|
|
||||||
backend.get('type')
|
|
||||||
);
|
|
||||||
return RSVP.hash({
|
return RSVP.hash({
|
||||||
model: backend,
|
model: backend,
|
||||||
section,
|
section,
|
||||||
@@ -47,11 +56,6 @@ export default Route.extend(UnloadModelRoute, {
|
|||||||
}
|
}
|
||||||
const model = this.store.peekRecord(modelType, backend.id);
|
const model = this.store.peekRecord(modelType, backend.id);
|
||||||
if (model) {
|
if (model) {
|
||||||
this.get('wizard').transitionFeatureMachine(
|
|
||||||
this.get('wizard.featureState'),
|
|
||||||
'EDIT',
|
|
||||||
backend.get('type')
|
|
||||||
);
|
|
||||||
return RSVP.hash({
|
return RSVP.hash({
|
||||||
model,
|
model,
|
||||||
section,
|
section,
|
||||||
@@ -60,11 +64,6 @@ export default Route.extend(UnloadModelRoute, {
|
|||||||
return this.store
|
return this.store
|
||||||
.findRecord(modelType, backend.id)
|
.findRecord(modelType, backend.id)
|
||||||
.then(config => {
|
.then(config => {
|
||||||
this.get('wizard').transitionFeatureMachine(
|
|
||||||
this.get('wizard.featureState'),
|
|
||||||
'EDIT',
|
|
||||||
backend.get('type')
|
|
||||||
);
|
|
||||||
config.set('backend', backend);
|
config.set('backend', backend);
|
||||||
return RSVP.hash({
|
return RSVP.hash({
|
||||||
model: config,
|
model: config,
|
||||||
|
|||||||
59
ui/app/services/path-help.js
Normal file
59
ui/app/services/path-help.js
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
/*
|
||||||
|
This service is used to pull an OpenAPI document describing the
|
||||||
|
shape of data at a specific path to hydrate a model with attrs it
|
||||||
|
has less (or no) information about.
|
||||||
|
*/
|
||||||
|
import Service from '@ember/service';
|
||||||
|
|
||||||
|
import { getOwner } from '@ember/application';
|
||||||
|
import { expandOpenApiProps, combineAttributes } from 'vault/utils/openapi-to-attrs';
|
||||||
|
import { resolve } from 'rsvp';
|
||||||
|
|
||||||
|
export function sanitizePath(path) {
|
||||||
|
//remove whitespace + remove trailing and leading slashes
|
||||||
|
return path.trim().replace(/^\/+|\/+$/g, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Service.extend({
|
||||||
|
attrs: null,
|
||||||
|
ajax(url, options = {}) {
|
||||||
|
let appAdapter = getOwner(this).lookup(`adapter:application`);
|
||||||
|
let { data } = options;
|
||||||
|
return appAdapter.ajax(url, 'GET', {
|
||||||
|
data,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
//Makes a call to grab the OpenAPI document.
|
||||||
|
//Returns relevant information from OpenAPI
|
||||||
|
//as determined by the expandOpenApiProps util
|
||||||
|
getProps(helpUrl, backend) {
|
||||||
|
return this.ajax(helpUrl, backend).then(help => {
|
||||||
|
let path = Object.keys(help.openapi.paths)[0];
|
||||||
|
let props = help.openapi.paths[path].post.requestBody.content['application/json'].schema.properties;
|
||||||
|
return expandOpenApiProps(props);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getNewModel(modelType, owner, backend) {
|
||||||
|
let name = `model:${modelType}`;
|
||||||
|
let newModel = owner.factoryFor(name).class;
|
||||||
|
if (newModel.merged || newModel.prototype.useOpenAPI !== true) {
|
||||||
|
return resolve();
|
||||||
|
}
|
||||||
|
let helpUrl = newModel.prototype.getHelpUrl(backend);
|
||||||
|
|
||||||
|
return this.getProps(helpUrl, backend).then(props => {
|
||||||
|
if (owner.hasRegistration(name) && !newModel.merged) {
|
||||||
|
let { attrs, newFields } = combineAttributes(newModel.attributes, props);
|
||||||
|
newModel = newModel.extend(attrs, { newFields });
|
||||||
|
} else {
|
||||||
|
//generate a whole new model
|
||||||
|
}
|
||||||
|
|
||||||
|
newModel.reopenClass({ merged: true });
|
||||||
|
owner.unregister(name);
|
||||||
|
owner.register(name, newModel);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
});
|
||||||
@@ -11,8 +11,13 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||||
<button type="submit" data-test-save-config=true class="button is-primary {{if saveModel.isRunning 'loading'}}" disabled={{saveModel.isRunning}}>
|
<button
|
||||||
|
type="submit"
|
||||||
|
data-test-save-config="true"
|
||||||
|
class="button is-primary {{if saveModel.isRunning "loading"}}"
|
||||||
|
disabled={{saveModel.isRunning}}
|
||||||
|
>
|
||||||
Save
|
Save
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -7,8 +7,13 @@
|
|||||||
{{/each}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||||
<button type="submit" data-test-save-config=true class="button is-primary {{if saveModel.isRunning 'loading'}}" disabled={{saveModel.isRunning}}>
|
<button
|
||||||
|
type="submit"
|
||||||
|
data-test-save-config="true"
|
||||||
|
class="button is-primary {{if saveModel.isRunning "loading"}}"
|
||||||
|
disabled={{saveModel.isRunning}}
|
||||||
|
>
|
||||||
Update Options
|
Update Options
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -1,7 +1,19 @@
|
|||||||
{{#unless
|
{{#unless
|
||||||
(or
|
(or
|
||||||
(and attr.options.editType (not-eq attr.options.editType "textarea"))
|
|
||||||
(eq attr.type "boolean")
|
(eq attr.type "boolean")
|
||||||
|
(contains
|
||||||
|
attr.options.editType
|
||||||
|
(array
|
||||||
|
"boolean"
|
||||||
|
"searchSelect"
|
||||||
|
"mountAccessor"
|
||||||
|
"kv"
|
||||||
|
"file"
|
||||||
|
"ttl"
|
||||||
|
"stringArray"
|
||||||
|
"json"
|
||||||
|
)
|
||||||
|
)
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
<label for="{{attr.name}}" class="is-label">
|
<label for="{{attr.name}}" class="is-label">
|
||||||
@@ -114,6 +126,7 @@
|
|||||||
}}
|
}}
|
||||||
{{else if (eq attr.options.editType "stringArray")}}
|
{{else if (eq attr.options.editType "stringArray")}}
|
||||||
{{string-list
|
{{string-list
|
||||||
|
data-test-input=attr.name
|
||||||
label=labelString
|
label=labelString
|
||||||
warning=attr.options.warning
|
warning=attr.options.warning
|
||||||
helpText=attr.options.helpText
|
helpText=attr.options.helpText
|
||||||
@@ -124,9 +137,10 @@
|
|||||||
<MaskedInput
|
<MaskedInput
|
||||||
@value={{or (get model valuePath) attr.options.defaultValue}}
|
@value={{or (get model valuePath) attr.options.defaultValue}}
|
||||||
@placeholder=""
|
@placeholder=""
|
||||||
@allowCopy=true
|
@allowCopy="true"
|
||||||
/>
|
/>
|
||||||
{{else if (or (eq attr.type 'number') (eq attr.type 'string'))}}
|
|
||||||
|
{{else if (or (eq attr.type "number") (eq attr.type "string"))}}
|
||||||
<div class="control">
|
<div class="control">
|
||||||
{{#if (eq attr.options.editType "textarea")}}
|
{{#if (eq attr.options.editType "textarea")}}
|
||||||
<textarea
|
<textarea
|
||||||
|
|||||||
@@ -1,96 +1,124 @@
|
|||||||
<PageHeader as |p|>
|
<PageHeader as |p|>
|
||||||
<p.levelLeft>
|
<p.levelLeft>
|
||||||
<h1 class="title is-3" data-test-mount-form-header=true>
|
<h1 class="title is-3" data-test-mount-form-header="true">
|
||||||
{{#if showConfig}}
|
{{#if showEnable}}
|
||||||
{{#with (find-by 'type' mountModel.type mountTypes) as |typeInfo|}}
|
{{#with (find-by "type" mountModel.type mountTypes) as |typeInfo|}}
|
||||||
<ICon @size=24 @glyph={{concat "enable/" (or typeInfo.glyph typeInfo.type)}} @class="has-text-grey-light" />
|
<ICon
|
||||||
|
@size="24"
|
||||||
|
@glyph={{concat "enable/" (or typeInfo.glyph typeInfo.type)}}
|
||||||
|
@class="has-text-grey-light"
|
||||||
|
/>
|
||||||
|
|
||||||
{{#if (eq mountType "auth")}}
|
{{#if (eq mountType "auth")}}
|
||||||
Enable {{typeInfo.displayName}} authentication method
|
{{concat "Enable " typeInfo.displayName " authentication method"}}
|
||||||
{{else}}
|
{{else}}
|
||||||
Enable {{typeInfo.displayName}} secrets engine
|
{{concat "Enable " typeInfo.displayName "secrets engine"}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{/with}}
|
{{/with}}
|
||||||
|
{{else if (eq mountType "auth")}}
|
||||||
|
Enable an authentication method
|
||||||
{{else}}
|
{{else}}
|
||||||
{{#if (eq mountType "auth")}}
|
Enable a secrets engine
|
||||||
Enable an authentication method
|
|
||||||
{{else}}
|
|
||||||
Enable a secrets engine
|
|
||||||
{{/if}}
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</h1>
|
</h1>
|
||||||
</p.levelLeft>
|
</p.levelLeft>
|
||||||
</PageHeader>
|
</PageHeader>
|
||||||
<form {{action (perform mountBackend) on="submit"}}>
|
<form {{action (perform mountBackend) on="submit"}}>
|
||||||
<div class="box is-sideless is-fullwidth is-marginless">
|
<div class="box is-sideless is-fullwidth is-marginless">
|
||||||
<NamespaceReminder @mode="enable" @noun={{if (eq mountType "auth") "auth method" "secret engine"}} />
|
<NamespaceReminder
|
||||||
|
@mode="enable"
|
||||||
|
@noun={{if (eq mountType "auth") "auth method" "secret engine"}}
|
||||||
|
/>
|
||||||
|
|
||||||
{{message-error model=mountModel}}
|
{{message-error model=mountModel}}
|
||||||
{{#if showConfig}}
|
{{#if showEnable}}
|
||||||
{{form-field-groups model=mountModel onChange=(action "onTypeChange") renderGroup="default"}}
|
{{form-field-groups
|
||||||
{{#if mountModel.authConfigs.firstObject}}
|
model=mountModel
|
||||||
{{form-field-groups model=mountModel.authConfigs.firstObject}}
|
onChange=(action "onTypeChange")
|
||||||
{{/if}}
|
renderGroup="default"
|
||||||
{{form-field-groups model=mountModel onChange=(action "onTypeChange") renderGroup="Method Options"}}
|
}}
|
||||||
|
{{form-field-groups
|
||||||
|
model=mountModel
|
||||||
|
onChange=(action "onTypeChange")
|
||||||
|
renderGroup="Method Options"
|
||||||
|
}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{#each (array "generic" "cloud" "infra") as |category|}}
|
{{#each (array "generic" "cloud" "infra") as |category|}}
|
||||||
<h3 class="title box-radio-header">
|
<h3 class="title box-radio-header">
|
||||||
{{capitalize category}}
|
{{capitalize category}}
|
||||||
</h3>
|
</h3>
|
||||||
<div class="box-radio-container">
|
<div class="box-radio-container">
|
||||||
{{#each (filter-by "category" category mountTypes) as |type|}}
|
{{#each (filter-by "category" category mountTypes) as |type|}}
|
||||||
<label
|
<label
|
||||||
for={{type.type}}
|
for={{type.type}}
|
||||||
class="box-radio{{if (eq mountModel.type type.type) " is-selected"}}"
|
class="box-radio
|
||||||
data-test-mount-type-radio
|
{{if (eq mountModel.type type.type) " is-selected"}}"
|
||||||
data-test-mount-type={{type.type}}
|
data-test-mount-type-radio
|
||||||
>
|
data-test-mount-type={{type.type}}
|
||||||
<ICon @size=36 @excludeIconClass={{true}} @glyph={{concat "enable/" (or type.glyph type.type)}} @class="has-text-grey-light" />
|
>
|
||||||
{{type.displayName}}
|
<ICon
|
||||||
<RadioButton
|
@size="36"
|
||||||
@value={{type.type}}
|
@excludeIconClass={{true}}
|
||||||
@radioClass="radio"
|
@glyph={{concat "enable/" (or type.glyph type.type)}}
|
||||||
@groupValue={{mountModel.type}}
|
@class="has-text-grey-light"
|
||||||
@changed={{queue (action (mut mountModel.type)) (action "onTypeChange" "type")}}
|
/>
|
||||||
@name="mount-type"
|
|
||||||
@radioId={{type.type}}
|
{{type.displayName}}
|
||||||
/>
|
<RadioButton
|
||||||
<label for={{type.type}}></label>
|
@value={{type.type}}
|
||||||
</label>
|
@radioClass="radio"
|
||||||
{{/each}}
|
@groupValue={{mountModel.type}}
|
||||||
|
@changed={{queue
|
||||||
|
(action (mut mountModel.type))
|
||||||
|
(action "onTypeChange" "type")
|
||||||
|
}}
|
||||||
|
@name="mount-type"
|
||||||
|
@radioId={{type.type}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label for={{type.type}}></label>
|
||||||
|
</label>
|
||||||
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||||
{{#if showConfig}}
|
{{#if showEnable}}
|
||||||
<div class="control">
|
<div class="control">
|
||||||
<button type="submit" data-test-mount-submit=true class="button is-primary {{if mountBackend.isRunning 'loading'}}" disabled={{mountBackend.isRunning}}>
|
<button
|
||||||
{{#if (eq mountType "auth")}}
|
type="submit"
|
||||||
Enable Method
|
data-test-mount-submit="true"
|
||||||
{{else}}
|
class="button is-primary {{if mountBackend.isRunning "loading"}}"
|
||||||
Enable Engine
|
disabled={{mountBackend.isRunning}}
|
||||||
{{/if}}
|
>
|
||||||
</button>
|
{{#if (eq mountType "auth")}}
|
||||||
</div>
|
Enable Method
|
||||||
<div class="control">
|
{{else}}
|
||||||
<button
|
Enable Engine
|
||||||
data-test-mount-back
|
{{/if}}
|
||||||
type="button"
|
</button>
|
||||||
class="button"
|
</div>
|
||||||
onclick={{action "toggleShowConfig" false}}
|
<div class="control">
|
||||||
>
|
<button
|
||||||
Back
|
data-test-mount-back
|
||||||
</button>
|
type="button"
|
||||||
</div>
|
class="button"
|
||||||
|
onclick={{action "toggleShowEnable" false}}
|
||||||
|
>
|
||||||
|
Back
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
{{else}}
|
{{else}}
|
||||||
<button
|
<button
|
||||||
data-test-mount-next
|
data-test-mount-next
|
||||||
type="button"
|
type="button"
|
||||||
class="button is-primary"
|
class="button is-primary"
|
||||||
onclick={{action "toggleShowConfig" true}}
|
onclick={{action "toggleShowEnable" true}}
|
||||||
disabled={{not mountModel.type}}
|
disabled={{not mountModel.type}}
|
||||||
>
|
>
|
||||||
Next
|
Next
|
||||||
</button>
|
</button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -2,17 +2,12 @@
|
|||||||
<label class="title is-5" data-test-string-list-label="true">
|
<label class="title is-5" data-test-string-list-label="true">
|
||||||
{{label}}
|
{{label}}
|
||||||
{{#if helpText}}
|
{{#if helpText}}
|
||||||
{{#info-tooltip}}
|
{{#info-tooltip}}{{helpText}}{{/info-tooltip}}
|
||||||
{{helpText}}
|
|
||||||
{{/info-tooltip}}
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</label>
|
</label>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#if warning}}
|
{{#if warning}}
|
||||||
<AlertBanner
|
<AlertBanner @type="warning" @message={{warning}} />
|
||||||
@type="warning"
|
|
||||||
@message={{warning}}
|
|
||||||
/>
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
{{#each inputList as |data index|}}
|
{{#each inputList as |data index|}}
|
||||||
<div class="field is-grouped" data-test-string-list-row="{{index}}">
|
<div class="field is-grouped" data-test-string-list-row="{{index}}">
|
||||||
@@ -30,14 +25,29 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="control">
|
<div class="control">
|
||||||
{{#if (eq (inc index) inputList.length)}}
|
{{#if (eq (inc index) inputList.length)}}
|
||||||
<button type="button" class="button is-outlined is-primary" {{action "addInput"}} data-test-string-list-button="add">
|
<button
|
||||||
|
type="button"
|
||||||
|
class="button is-outlined is-primary"
|
||||||
|
data-test-string-list-button="add"
|
||||||
|
{{action "addInput"}}
|
||||||
|
>
|
||||||
Add
|
Add
|
||||||
</button>
|
</button>
|
||||||
{{else}}
|
{{else}}
|
||||||
<button type="button" class="button is-expanded is-icon" {{action "removeInput" index}} data-test-string-list-button="delete" >
|
<button
|
||||||
{{i-con size=22 glyph='trash-a' excludeIconClass=true class="is-large has-text-grey-light"}}
|
type="button"
|
||||||
|
class="button is-expanded is-icon"
|
||||||
|
data-test-string-list-button="delete"
|
||||||
|
{{action "removeInput" index}}
|
||||||
|
>
|
||||||
|
{{i-con
|
||||||
|
size=22
|
||||||
|
glyph="trash-a"
|
||||||
|
excludeIconClass=true
|
||||||
|
class="is-large has-text-grey-light"
|
||||||
|
}}
|
||||||
</button>
|
</button>
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{/each}}
|
{{/each}}
|
||||||
10
ui/app/templates/components/wizard/auth-config.hbs
Normal file
10
ui/app/templates/components/wizard/auth-config.hbs
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<WizardSection
|
||||||
|
@headerText="Configuring Your Auth Method"
|
||||||
|
@docText="Docs: Authentication Methods"
|
||||||
|
@docPath="/docs/auth/index.html"
|
||||||
|
@instructions="Click the 'Save' link to save any extra configuration. Saving without filling anything in will use the defaults."
|
||||||
|
>
|
||||||
|
<p>
|
||||||
|
You can update your new auth method configuration here.
|
||||||
|
</p>
|
||||||
|
</WizardSection>
|
||||||
@@ -2,9 +2,9 @@
|
|||||||
@headerText="Entering Auth Method details"
|
@headerText="Entering Auth Method details"
|
||||||
@docText="Docs: Authentication Methods"
|
@docText="Docs: Authentication Methods"
|
||||||
@docPath="/docs/auth/index.html"
|
@docPath="/docs/auth/index.html"
|
||||||
@instructions='Customize your new method and click "Enable Method".'
|
@instructions="Name your method and click 'Enable Method'."
|
||||||
>
|
>
|
||||||
<p>
|
<p>
|
||||||
Great! Now you can customize this method with a name and description that makes sense for your team, and fill out any options that are specific to this method.
|
Great! Now you can customize this method with a name and fill out general configuration under "Method Options".
|
||||||
</p>
|
</p>
|
||||||
</WizardSection>
|
</WizardSection>
|
||||||
@@ -1,23 +1,38 @@
|
|||||||
<div class="field">
|
<div class="field">
|
||||||
{{#unless (or attr.options.editType (eq attr.type 'boolean'))}}
|
{{#unless
|
||||||
|
(or
|
||||||
|
(contains
|
||||||
|
attr.options.editType
|
||||||
|
(array
|
||||||
|
"boolean"
|
||||||
|
"searchSelect"
|
||||||
|
"mountAccessor"
|
||||||
|
"kv"
|
||||||
|
"file"
|
||||||
|
"ttl"
|
||||||
|
"stringArray"
|
||||||
|
"json"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
(eq attr.type "boolean")
|
||||||
|
)
|
||||||
|
}}
|
||||||
<label for="{{attr.name}}" class="is-label">
|
<label for="{{attr.name}}" class="is-label">
|
||||||
{{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
{{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||||||
{{#if attr.options.helpText}}
|
{{#if attr.options.helpText}}
|
||||||
{{#info-tooltip}}
|
{{#info-tooltip}}{{attr.options.helpText}}{{/info-tooltip}}
|
||||||
{{attr.options.helpText}}
|
|
||||||
{{/info-tooltip}}
|
|
||||||
{{/if}}
|
{{/if}}
|
||||||
</label>
|
</label>
|
||||||
{{/unless}}
|
{{/unless}}
|
||||||
{{#if attr.options.possibleValues}}
|
{{#if attr.options.possibleValues}}
|
||||||
<div class="control is-expanded" >
|
<div class="control is-expanded">
|
||||||
<div class="select is-fullwidth">
|
<div class="select is-fullwidth">
|
||||||
<select
|
<select
|
||||||
name="{{attr.name}}"
|
name="{{attr.name}}"
|
||||||
id="{{attr.name}}"
|
id="{{attr.name}}"
|
||||||
onchange={{action (mut (get model attr.name)) value="target.value"}}
|
onchange={{action (mut (get model attr.name)) value="target.value"}}
|
||||||
data-test-input={{attr.name}}
|
data-test-input={{attr.name}}
|
||||||
>
|
>
|
||||||
{{#each attr.options.possibleValues as |val|}}
|
{{#each attr.options.possibleValues as |val|}}
|
||||||
<option selected={{eq (get model attr.name) val}} value={{val}}>
|
<option selected={{eq (get model attr.name) val}} value={{val}}>
|
||||||
{{val}}
|
{{val}}
|
||||||
@@ -26,34 +41,48 @@
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{{else if (eq attr.options.editType 'ttl')}}
|
{{else if (eq attr.options.editType "ttl")}}
|
||||||
{{ttl-picker initialValue=(or (get model attr.name) attr.options.defaultValue) labelText=(if attr.options.label attr.options.label (humanize (dasherize attr.name))) setDefaultValue=false onChange=(action (mut (get model attr.name)))}}
|
{{ttl-picker
|
||||||
{{else if (or (eq attr.type 'number') (eq attr.type 'string'))}}
|
initialValue=(or (get model attr.name) attr.options.defaultValue)
|
||||||
|
labelText=(if
|
||||||
|
attr.options.label attr.options.label (humanize (dasherize attr.name))
|
||||||
|
)
|
||||||
|
setDefaultValue=false
|
||||||
|
onChange=(action (mut (get model attr.name)))
|
||||||
|
}}
|
||||||
|
{{else if (or (eq attr.type "number") (eq attr.type "string"))}}
|
||||||
<div class="control">
|
<div class="control">
|
||||||
{{input id=attr.name value=(get model (or attr.options.fieldValue attr.name)) class="input" data-test-input=attr.name}}
|
{{input
|
||||||
</div>
|
id=attr.name
|
||||||
{{else if (eq attr.type 'boolean')}}
|
value=(get model (or attr.options.fieldValue attr.name))
|
||||||
<div class="b-checkbox">
|
class="input"
|
||||||
<input type="checkbox"
|
data-test-input=attr.name
|
||||||
id="{{attr.name}}"
|
|
||||||
class="styled"
|
|
||||||
checked={{get model attr.name}}
|
|
||||||
onchange={{action (mut (get model attr.name)) value="target.checked"}}
|
|
||||||
data-test-input={{attr.name}}
|
|
||||||
/>
|
|
||||||
<label for="{{attr.name}}" class="is-label">
|
|
||||||
{{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
|
||||||
{{#if attr.options.helpText}}
|
|
||||||
{{#info-tooltip}}
|
|
||||||
{{attr.options.helpText}}
|
|
||||||
{{/info-tooltip}}
|
|
||||||
{{/if}}
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
{{else if (eq attr.type 'object')}}
|
|
||||||
{{json-editor
|
|
||||||
value=(if (get model attr.name) (stringify (get model attr.name)) emptyData)
|
|
||||||
valueUpdated=(action "codemirrorUpdated" attr.name)
|
|
||||||
}}
|
}}
|
||||||
{{/if}}
|
</div>
|
||||||
</div>
|
{{else if (eq attr.type "boolean")}}
|
||||||
|
<div class="b-checkbox">
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
id="{{attr.name}}"
|
||||||
|
class="styled"
|
||||||
|
checked={{get model attr.name}}
|
||||||
|
onchange={{action (mut (get model attr.name)) value="target.checked"}}
|
||||||
|
data-test-input={{attr.name}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
<label for="{{attr.name}}" class="is-label">
|
||||||
|
{{capitalize (or attr.options.label (humanize (dasherize attr.name)))}}
|
||||||
|
{{#if attr.options.helpText}}
|
||||||
|
{{#info-tooltip}}{{attr.options.helpText}}{{/info-tooltip}}
|
||||||
|
{{/if}}
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
{{else if (eq attr.type "object")}}
|
||||||
|
{{json-editor
|
||||||
|
value=(if
|
||||||
|
(get model attr.name) (stringify (get model attr.name)) emptyData
|
||||||
|
)
|
||||||
|
valueUpdated=(action "codemirrorUpdated" attr.name)
|
||||||
|
}}
|
||||||
|
{{/if}}
|
||||||
|
</div>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
{{#if (eq model.section "options") }}
|
{{#if (eq model.section "options")}}
|
||||||
{{auth-config-form/options model.model}}
|
{{auth-config-form/options model.model}}
|
||||||
{{else}}
|
{{else}}
|
||||||
{{auth-config-form/config model.model}}
|
{{auth-config-form/config model.model}}
|
||||||
{{/if}}
|
{{/if}}
|
||||||
@@ -1,4 +1 @@
|
|||||||
<MountBackendForm
|
<MountBackendForm @onMountSuccess={{action "onMountSuccess"}} />
|
||||||
@onMountSuccess={{action "onMountSuccess"}}
|
|
||||||
@onConfigError={{action "onConfigError"}}
|
|
||||||
/>
|
|
||||||
89
ui/app/utils/openapi-to-attrs.js
Normal file
89
ui/app/utils/openapi-to-attrs.js
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
import DS from 'ember-data';
|
||||||
|
const { attr } = DS;
|
||||||
|
import { assign } from '@ember/polyfills';
|
||||||
|
import { isEmpty } from '@ember/utils';
|
||||||
|
|
||||||
|
export const expandOpenApiProps = function(props) {
|
||||||
|
let attrs = {};
|
||||||
|
// expand all attributes
|
||||||
|
for (let prop in props) {
|
||||||
|
let details = props[prop];
|
||||||
|
if (details.deprecated === true) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (details.type === 'integer') {
|
||||||
|
details.type = 'number';
|
||||||
|
}
|
||||||
|
let editType = details.type;
|
||||||
|
if (details.format === 'seconds') {
|
||||||
|
editType = 'ttl';
|
||||||
|
} else if (details.items) {
|
||||||
|
editType = details.items.type + details.type.capitalize();
|
||||||
|
}
|
||||||
|
attrs[prop.camelize()] = {
|
||||||
|
editType: editType,
|
||||||
|
type: details.type,
|
||||||
|
};
|
||||||
|
if (details['x-vault-displayName']) {
|
||||||
|
attrs[prop.camelize()].label = details['x-vault-displayName'];
|
||||||
|
}
|
||||||
|
if (details['enum']) {
|
||||||
|
attrs[prop.camelize()].possibleValues = details['enum'];
|
||||||
|
}
|
||||||
|
if (details['x-vault-displayValue']) {
|
||||||
|
attrs[prop.camelize()].defaultValue = details['x-vault-displayValue'];
|
||||||
|
} else {
|
||||||
|
if (!isEmpty(details['default'])) {
|
||||||
|
attrs[prop.camelize()].defaultValue = details['default'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return attrs;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const combineAttributes = function(oldAttrs, newProps) {
|
||||||
|
let newAttrs = {};
|
||||||
|
let newFields = [];
|
||||||
|
oldAttrs.forEach(function(value, name) {
|
||||||
|
if (newProps[name]) {
|
||||||
|
newAttrs[name] = attr(newProps[name].type, assign({}, newProps[name], value.options));
|
||||||
|
} else {
|
||||||
|
newAttrs[name] = attr(value.type, value.options);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
for (let prop in newProps) {
|
||||||
|
if (newAttrs[prop]) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
newAttrs[prop] = attr(newProps[prop].type, newProps[prop]);
|
||||||
|
newFields.push(prop);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { attrs: newAttrs, newFields };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const combineFields = function(currentFields, newFields, excludedFields) {
|
||||||
|
let otherFields = newFields.filter(field => {
|
||||||
|
return !currentFields.includes(field) && !excludedFields.includes(field);
|
||||||
|
});
|
||||||
|
if (otherFields.length) {
|
||||||
|
currentFields = currentFields.concat(otherFields);
|
||||||
|
}
|
||||||
|
return currentFields;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const combineFieldGroups = function(currentGroups, newFields, excludedFields) {
|
||||||
|
let allFields = [];
|
||||||
|
for (let group of currentGroups) {
|
||||||
|
let fieldName = Object.keys(group)[0];
|
||||||
|
allFields = allFields.concat(group[fieldName]);
|
||||||
|
}
|
||||||
|
let otherFields = newFields.filter(field => {
|
||||||
|
return !allFields.includes(field) && !excludedFields.includes(field);
|
||||||
|
});
|
||||||
|
if (otherFields.length) {
|
||||||
|
currentGroups[0].default = currentGroups[0].default.concat(otherFields);
|
||||||
|
}
|
||||||
|
|
||||||
|
return currentGroups;
|
||||||
|
};
|
||||||
@@ -33,7 +33,7 @@ module('Acceptance | settings/auth/configure/section', function(hooks) {
|
|||||||
await withFlash(page.save(), () => {
|
await withFlash(page.save(), () => {
|
||||||
assert.equal(
|
assert.equal(
|
||||||
page.flash.latestMessage,
|
page.flash.latestMessage,
|
||||||
`The configuration options were saved successfully.`,
|
`The configuration was saved successfully.`,
|
||||||
'success flash shows'
|
'success flash shows'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -28,9 +28,11 @@ module('Acceptance | settings/auth/enable', function(hooks) {
|
|||||||
});
|
});
|
||||||
assert.equal(
|
assert.equal(
|
||||||
currentRouteName(),
|
currentRouteName(),
|
||||||
'vault.cluster.access.methods',
|
'vault.cluster.settings.auth.configure.section',
|
||||||
'redirects to the auth backend list page'
|
'redirects to the auth config page'
|
||||||
);
|
);
|
||||||
|
|
||||||
|
await listPage.visit();
|
||||||
assert.ok(listPage.findLinkById(path), 'mount is present in the list');
|
assert.ok(listPage.findLinkById(path), 'mount is present in the list');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,17 +5,33 @@ import { setupRenderingTest } from 'ember-qunit';
|
|||||||
import { render, settled } from '@ember/test-helpers';
|
import { render, settled } from '@ember/test-helpers';
|
||||||
import hbs from 'htmlbars-inline-precompile';
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
import sinon from 'sinon';
|
import sinon from 'sinon';
|
||||||
|
import Service from '@ember/service';
|
||||||
|
|
||||||
import { create } from 'ember-cli-page-object';
|
import { create } from 'ember-cli-page-object';
|
||||||
import authConfigForm from 'vault/tests/pages/components/auth-config-form/options';
|
import authConfigForm from 'vault/tests/pages/components/auth-config-form/options';
|
||||||
|
|
||||||
const component = create(authConfigForm);
|
const component = create(authConfigForm);
|
||||||
|
const routerService = Service.extend({
|
||||||
|
transitionTo() {
|
||||||
|
return {
|
||||||
|
followRedirects() {
|
||||||
|
return resolve();
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
replaceWith() {
|
||||||
|
return resolve();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
module('Integration | Component | auth-config-form options', function(hooks) {
|
module('Integration | Component | auth-config-form options', function(hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
|
|
||||||
hooks.beforeEach(function() {
|
hooks.beforeEach(function() {
|
||||||
this.owner.lookup('service:flash-messages').registerTypes(['success']);
|
this.owner.lookup('service:flash-messages').registerTypes(['success']);
|
||||||
|
this.owner.register('service:router', routerService);
|
||||||
|
this.router = this.owner.lookup('service:router');
|
||||||
|
|
||||||
component.setContext(this);
|
component.setContext(this);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -36,9 +36,6 @@ const routerService = Service.extend({
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
replaceWith() {
|
|
||||||
return resolve();
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
|
|
||||||
module('Integration | Component | auth form', function(hooks) {
|
module('Integration | Component | auth form', function(hooks) {
|
||||||
|
|||||||
@@ -71,59 +71,4 @@ module('Integration | Component | mount backend form', function(hooks) {
|
|||||||
assert.ok(enableRequest, 'it calls enable on an auth method');
|
assert.ok(enableRequest, 'it calls enable on an auth method');
|
||||||
assert.ok(spy.calledOnce, 'calls the passed success method');
|
assert.ok(spy.calledOnce, 'calls the passed success method');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it calls the correct jwt config', async function(assert) {
|
|
||||||
this.server.post('/v1/sys/auth/jwt', () => {
|
|
||||||
return [204, { 'Content-Type': 'application/json' }];
|
|
||||||
});
|
|
||||||
|
|
||||||
this.server.post('/v1/auth/jwt/config', () => {
|
|
||||||
return [
|
|
||||||
400,
|
|
||||||
{ 'Content-Type': 'application/json' },
|
|
||||||
JSON.stringify({ errors: ['there was an error'] }),
|
|
||||||
];
|
|
||||||
});
|
|
||||||
await render(hbs`<MountBackendForm />`);
|
|
||||||
|
|
||||||
await component.selectType('jwt');
|
|
||||||
await component.next();
|
|
||||||
await component.fillIn('oidcDiscoveryUrl', 'host');
|
|
||||||
component.submit();
|
|
||||||
|
|
||||||
later(() => run.cancelTimers(), 50);
|
|
||||||
await settled();
|
|
||||||
let configRequest = this.server.handledRequests.findBy('url', '/v1/auth/jwt/config');
|
|
||||||
assert.ok(configRequest, 'it calls the config url');
|
|
||||||
});
|
|
||||||
|
|
||||||
test('it calls mount config error', async function(assert) {
|
|
||||||
this.server.post('/v1/sys/auth/bar', () => {
|
|
||||||
return [204, { 'Content-Type': 'application/json' }];
|
|
||||||
});
|
|
||||||
this.server.post('/v1/auth/bar/config', () => {
|
|
||||||
return [
|
|
||||||
400,
|
|
||||||
{ 'Content-Type': 'application/json' },
|
|
||||||
JSON.stringify({ errors: ['there was an error'] }),
|
|
||||||
];
|
|
||||||
});
|
|
||||||
const spy = sinon.spy();
|
|
||||||
const spy2 = sinon.spy();
|
|
||||||
this.set('onMountSuccess', spy);
|
|
||||||
this.set('onConfigError', spy2);
|
|
||||||
await render(hbs`{{mount-backend-form onMountSuccess=onMountSuccess onConfigError=onConfigError}}`);
|
|
||||||
|
|
||||||
await component.selectType('kubernetes');
|
|
||||||
await component.next().path('bar');
|
|
||||||
// kubernetes requires a host + a cert / pem, so only filling the host will error
|
|
||||||
await component.fillIn('kubernetesHost', 'host');
|
|
||||||
component.submit();
|
|
||||||
later(() => run.cancelTimers(), 50);
|
|
||||||
await settled();
|
|
||||||
let enableRequest = this.server.handledRequests.findBy('url', '/v1/sys/auth/bar');
|
|
||||||
assert.ok(enableRequest, 'it calls enable on an auth method');
|
|
||||||
assert.equal(spy.callCount, 0, 'does not call the success method');
|
|
||||||
assert.ok(spy2.calledOnce, 'calls the passed error method');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export default create({
|
|||||||
toggleOptions: clickable('[data-test-toggle-group="Options"]'),
|
toggleOptions: clickable('[data-test-toggle-group="Options"]'),
|
||||||
name: fillable('[data-test-input="name"]'),
|
name: fillable('[data-test-input="name"]'),
|
||||||
allowAnyName: clickable('[data-test-input="allowAnyName"]'),
|
allowAnyName: clickable('[data-test-input="allowAnyName"]'),
|
||||||
allowedDomains: fillable('[data-test-input="allowedDomains"]'),
|
allowedDomains: fillable('[data-test-input="allowedDomains"] input'),
|
||||||
save: clickable('[data-test-role-create]'),
|
save: clickable('[data-test-role-create]'),
|
||||||
deleteBtn: clickable('[data-test-role-delete] button'),
|
deleteBtn: clickable('[data-test-role-delete] button'),
|
||||||
confirmBtn: clickable('[data-test-confirm-button]'),
|
confirmBtn: clickable('[data-test-confirm-button]'),
|
||||||
|
|||||||
@@ -23,16 +23,16 @@ module('Unit | Machine | auth-machine', function() {
|
|||||||
event: 'CONTINUE',
|
event: 'CONTINUE',
|
||||||
params: null,
|
params: null,
|
||||||
expectedResults: {
|
expectedResults: {
|
||||||
value: 'list',
|
value: 'config',
|
||||||
actions: [
|
actions: [
|
||||||
{ component: 'wizard/auth-list', level: 'step', type: 'render' },
|
{ type: 'render', level: 'feature', component: 'wizard/mounts-wizard' },
|
||||||
{ component: 'wizard/mounts-wizard', level: 'feature', type: 'render' },
|
{ type: 'render', level: 'step', component: 'wizard/auth-config' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
currentState: 'list',
|
currentState: 'config',
|
||||||
event: 'DETAILS',
|
event: 'CONTINUE',
|
||||||
expectedResults: {
|
expectedResults: {
|
||||||
value: 'details',
|
value: 'details',
|
||||||
actions: [
|
actions: [
|
||||||
|
|||||||
191
ui/tests/unit/utils/openapi-to-attrs-test.js
Normal file
191
ui/tests/unit/utils/openapi-to-attrs-test.js
Normal file
@@ -0,0 +1,191 @@
|
|||||||
|
import { expandOpenApiProps, combineAttributes, combineFieldGroups } from 'vault/utils/openapi-to-attrs';
|
||||||
|
import { module, test } from 'qunit';
|
||||||
|
import DS from 'ember-data';
|
||||||
|
const { attr } = DS;
|
||||||
|
|
||||||
|
module('Unit | Util | OpenAPI Data Utilities', function() {
|
||||||
|
const OPENAPI_RESPONSE_PROPS = {
|
||||||
|
ttl: {
|
||||||
|
type: 'string',
|
||||||
|
format: 'seconds',
|
||||||
|
'x-vault-displayName': 'TTL',
|
||||||
|
},
|
||||||
|
'awesome-people': {
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
'x-vault-displayValue': 'Grace Hopper,Lady Ada',
|
||||||
|
},
|
||||||
|
'favorite-ice-cream': {
|
||||||
|
type: 'string',
|
||||||
|
enum: ['vanilla', 'chocolate', 'strawberry'],
|
||||||
|
},
|
||||||
|
'default-value': {
|
||||||
|
default: 30,
|
||||||
|
'x-vault-displayValue': 300,
|
||||||
|
type: 'integer',
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
default: 30,
|
||||||
|
type: 'integer',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const EXPANDED_PROPS = {
|
||||||
|
ttl: {
|
||||||
|
editType: 'ttl',
|
||||||
|
type: 'string',
|
||||||
|
label: 'TTL',
|
||||||
|
},
|
||||||
|
awesomePeople: {
|
||||||
|
editType: 'stringArray',
|
||||||
|
type: 'array',
|
||||||
|
defaultValue: 'Grace Hopper,Lady Ada',
|
||||||
|
},
|
||||||
|
favoriteIceCream: {
|
||||||
|
editType: 'string',
|
||||||
|
type: 'string',
|
||||||
|
possibleValues: ['vanilla', 'chocolate', 'strawberry'],
|
||||||
|
},
|
||||||
|
defaultValue: {
|
||||||
|
editType: 'number',
|
||||||
|
type: 'number',
|
||||||
|
defaultValue: 300,
|
||||||
|
},
|
||||||
|
default: {
|
||||||
|
editType: 'number',
|
||||||
|
type: 'number',
|
||||||
|
defaultValue: 30,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const EXISTING_MODEL_ATTRS = [
|
||||||
|
{
|
||||||
|
key: 'name',
|
||||||
|
value: {
|
||||||
|
isAttribute: true,
|
||||||
|
name: 'name',
|
||||||
|
options: {
|
||||||
|
editType: 'string',
|
||||||
|
label: 'Role name',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'awesomePeople',
|
||||||
|
value: {
|
||||||
|
isAttribute: true,
|
||||||
|
name: 'awesomePeople',
|
||||||
|
options: {
|
||||||
|
label: 'People Who Are Awesome',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const COMBINED_ATTRS = {
|
||||||
|
name: attr('string', {
|
||||||
|
editType: 'string',
|
||||||
|
type: 'string',
|
||||||
|
label: 'Role name',
|
||||||
|
}),
|
||||||
|
ttl: attr('string', {
|
||||||
|
editType: 'ttl',
|
||||||
|
type: 'string',
|
||||||
|
label: 'TTL',
|
||||||
|
}),
|
||||||
|
awesomePeople: attr({
|
||||||
|
label: 'People Who Are Awesome',
|
||||||
|
editType: 'stringArray',
|
||||||
|
type: 'array',
|
||||||
|
defaultValue: 'Grace Hopper,Lady Ada',
|
||||||
|
}),
|
||||||
|
favoriteIceCream: attr('string', {
|
||||||
|
type: 'string',
|
||||||
|
editType: 'string',
|
||||||
|
possibleValues: ['vanilla', 'chocolate', 'strawberry'],
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
|
||||||
|
const NEW_FIELDS = ['one', 'two', 'three'];
|
||||||
|
|
||||||
|
test('it creates objects from OpenAPI schema props', function(assert) {
|
||||||
|
const generatedProps = expandOpenApiProps(OPENAPI_RESPONSE_PROPS);
|
||||||
|
for (let propName in EXPANDED_PROPS) {
|
||||||
|
assert.deepEqual(EXPANDED_PROPS[propName], generatedProps[propName], `correctly expands ${propName}`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it combines OpenAPI props with existing model attrs', function(assert) {
|
||||||
|
const combined = combineAttributes(EXISTING_MODEL_ATTRS, EXPANDED_PROPS);
|
||||||
|
for (let propName in EXISTING_MODEL_ATTRS) {
|
||||||
|
assert.deepEqual(COMBINED_ATTRS[propName], combined[propName]);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
test('it adds new fields from OpenAPI to fieldGroups except for exclusions', function(assert) {
|
||||||
|
let modelFieldGroups = [
|
||||||
|
{ default: ['name', 'awesomePeople'] },
|
||||||
|
{
|
||||||
|
Options: ['ttl'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const excludedFields = ['two'];
|
||||||
|
const expectedGroups = [
|
||||||
|
{ default: ['name', 'awesomePeople', 'one', 'three'] },
|
||||||
|
{
|
||||||
|
Options: ['ttl'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const newFieldGroups = combineFieldGroups(modelFieldGroups, NEW_FIELDS, excludedFields);
|
||||||
|
for (let groupName in modelFieldGroups) {
|
||||||
|
assert.deepEqual(
|
||||||
|
newFieldGroups[groupName],
|
||||||
|
expectedGroups[groupName],
|
||||||
|
'it incorporates all new fields except for those excluded'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
test('it adds all new fields from OpenAPI to fieldGroups when excludedFields is empty', function(assert) {
|
||||||
|
let modelFieldGroups = [
|
||||||
|
{ default: ['name', 'awesomePeople'] },
|
||||||
|
{
|
||||||
|
Options: ['ttl'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const excludedFields = [];
|
||||||
|
const expectedGroups = [
|
||||||
|
{ default: ['name', 'awesomePeople', 'one', 'two', 'three'] },
|
||||||
|
{
|
||||||
|
Options: ['ttl'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const nonExcludedFieldGroups = combineFieldGroups(modelFieldGroups, NEW_FIELDS, excludedFields);
|
||||||
|
for (let groupName in modelFieldGroups) {
|
||||||
|
assert.deepEqual(
|
||||||
|
nonExcludedFieldGroups[groupName],
|
||||||
|
expectedGroups[groupName],
|
||||||
|
'it incorporates all new fields'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
test('it keeps fields the same when there are no brand new fields from OpenAPI', function(assert) {
|
||||||
|
let modelFieldGroups = [
|
||||||
|
{ default: ['name', 'awesomePeople', 'two', 'one', 'three'] },
|
||||||
|
{
|
||||||
|
Options: ['ttl'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const excludedFields = [];
|
||||||
|
const expectedGroups = [
|
||||||
|
{ default: ['name', 'awesomePeople', 'two', 'one', 'three'] },
|
||||||
|
{
|
||||||
|
Options: ['ttl'],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
const fieldGroups = combineFieldGroups(modelFieldGroups, NEW_FIELDS, excludedFields);
|
||||||
|
for (let groupName in modelFieldGroups) {
|
||||||
|
assert.deepEqual(fieldGroups[groupName], expectedGroups[groupName], 'it incorporates all new fields');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user