Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ember-cp-validations on KV secret engine #11785

Merged
merged 16 commits into from
Jun 15, 2021
Merged
3 changes: 3 additions & 0 deletions changelog/11785.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
```release-note:improvement
ui: Add Validation to KV secret engine
```
12 changes: 11 additions & 1 deletion ui/app/components/mount-backend-form.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { inject as service } from '@ember/service';
import { computed } from '@ember/object';
import { computed, set } from '@ember/object';
import Component from '@ember/component';
import { task } from 'ember-concurrency';
import { methods } from 'vault/helpers/mountable-auth-methods';
Expand Down Expand Up @@ -49,6 +49,10 @@ export default Component.extend({
const modelType = type === 'secret' ? 'secret-engine' : 'auth-method';
const model = this.store.createRecord(modelType);
this.set('mountModel', model);

this.set('validationMessages', {
path: '',
});
},

mountTypes: computed('engines', 'mountType', function() {
Expand Down Expand Up @@ -99,6 +103,12 @@ export default Component.extend({
.withTestWaiter(),

actions: {
onKeyUp(name, value) {
this.mountModel.set('path', value);
this.mountModel.validations.attrs.path.isValid
? set(this.validationMessages, 'path', '')
: set(this.validationMessages, 'path', this.mountModel.validations.attrs.path.message);
},
onTypeChange(path, value) {
if (path === 'type') {
this.wizard.set('componentState', value);
Expand Down
50 changes: 42 additions & 8 deletions ui/app/components/secret-edit.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ export default Component.extend(FocusOnInsertMixin, WithNavToNearestAncestor, {
hasLintError: false,
isV2: false,

// cp-validation related properties
validationMessages: null,
validationErrorCount: 0,

init() {
this._super(...arguments);
let secrets = this.model.secretData;
Expand All @@ -79,9 +83,16 @@ export default Component.extend(FocusOnInsertMixin, WithNavToNearestAncestor, {
if (this.mode === 'edit') {
this.send('addRow');
}

this.set('validationMessages', {
path: '',
key: '',
maxVersions: '',
});
},

waitForKeyUp: task(function*() {
waitForKeyUp: task(function*(name, value) {
this.checkValidation(name, value);
while (true) {
let event = yield waitForEvent(document.body, 'keyup');
this.onEscape(event);
Expand Down Expand Up @@ -171,6 +182,32 @@ export default Component.extend(FocusOnInsertMixin, WithNavToNearestAncestor, {
return this.router.transitionTo(...arguments);
},

checkValidation(name, value) {
// because path and key are not on the model performing custom validations instead of cp-validations
if (name === 'path' || name === 'key') {
// no value indicates missing presence
!value
? set(this.validationMessages, name, `${name} can't be blank`)
: set(this.validationMessages, name, '');
}
if (name === 'maxVersions') {
// checking for value because value which is blank on first load. No keyup event has occurred and default is 10.
if (value) {
let number = Number(value);
this.model.set('maxVersions', number);
}
if (!this.model.validations.attrs.maxVersions.isValid) {
set(this.validationMessages, name, this.model.validations.attrs.maxVersions.message);
} else {
set(this.validationMessages, name, '');
}
}

let values = Object.values(this.validationMessages);

this.set('validationErrorCount', values.filter(Boolean).length);
},

onEscape(e) {
if (e.keyCode !== keys.ESC || this.mode !== 'show') {
return;
Expand Down Expand Up @@ -312,17 +349,14 @@ export default Component.extend(FocusOnInsertMixin, WithNavToNearestAncestor, {

createOrUpdateKey(type, event) {
event.preventDefault();
const MAXIMUM_VERSIONS = 9999999999999999;
let model = this.modelForData;
let secret = this.model;
// prevent from submitting if there's no key
let arraySecretKeys = Object.keys(model.secretData);
if (type === 'create' && isBlank(model.path || model.id)) {
this.flashMessages.danger('Please provide a path for the secret');
this.checkValidation('path', '');
return;
}
const maxVersions = secret.get('maxVersions');
if (MAXIMUM_VERSIONS < maxVersions) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note to future onlookers: this check was moved to secret-v2 model

this.flashMessages.danger('Max versions is too large');
if (arraySecretKeys.includes('')) {
this.checkValidation('key', '');
return;
}

Expand Down
10 changes: 9 additions & 1 deletion ui/app/models/secret-engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,20 @@ import Model, { attr } from '@ember-data/model';
import { computed } from '@ember/object';
import { fragment } from 'ember-data-model-fragments/attributes';
import fieldToAttrs, { expandAttributeMeta } from 'vault/utils/field-to-attrs';
import { validator, buildValidations } from 'ember-cp-validations';

//identity will be managed separately and the inclusion
//of the system backend is an implementation detail
const LIST_EXCLUDED_BACKENDS = ['system', 'identity'];

export default Model.extend({
const Validations = buildValidations({
path: validator('presence', {
presence: true,
hashishaw marked this conversation as resolved.
Show resolved Hide resolved
message: "Path can't be blank",
}),
});

export default Model.extend(Validations, {
path: attr('string'),
accessor: attr('string'),
name: attr('string'),
Expand Down
18 changes: 17 additions & 1 deletion ui/app/models/secret-v2.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,24 @@ import { alias } from '@ember/object/computed';
import { expandAttributeMeta } from 'vault/utils/field-to-attrs';
import KeyMixin from 'vault/mixins/key-mixin';
import lazyCapabilities, { apiPath } from 'vault/macros/lazy-capabilities';
import { validator, buildValidations } from 'ember-cp-validations';

export default Model.extend(KeyMixin, {
const Validations = buildValidations({
maxVersions: [
validator('number', {
allowString: false,
integer: true,
message: 'Maximum versions must be a number',
}),
validator('length', {
min: 1,
max: 16,
message: 'You cannot go over 16 characters',
}),
],
});

export default Model.extend(KeyMixin, Validations, {
failedServerRead: attr('boolean'),
engine: belongsTo('secret-engine', { async: false }),
engineId: attr('string'),
Expand Down
4 changes: 4 additions & 0 deletions ui/app/styles/core/forms.scss
Original file line number Diff line number Diff line change
Expand Up @@ -323,3 +323,7 @@ label {
fieldset.form-fieldset {
border: none;
}

.has-error-border {
border: 1px solid $red-500;
}
4 changes: 4 additions & 0 deletions ui/app/styles/core/message.scss
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@
&.padding-top {
padding-top: $size-8;
}

&.is-marginless {
margin-bottom: 0;
}
}

.has-text-highlight {
Expand Down
5 changes: 2 additions & 3 deletions ui/app/templates/components/mount-backend-form.hbs
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,9 @@
@mode="enable"
@noun={{if (eq mountType "auth") "Auth Method" "Secret Engine"}}
/>

<MessageError @model={{mountModel}} />
{{#if showEnable}}
<FormFieldGroups @model={{mountModel}} @onChange={{action "onTypeChange"}} @renderGroup="default" />
<FormFieldGroups @model={{mountModel}} @onChange={{action "onTypeChange"}} @renderGroup="default" @validationMessages={{validationMessages}} @onKeyUp={{action "onKeyUp"}} />
<FormFieldGroups @model={{mountModel}} @onChange={{action "onTypeChange"}} @renderGroup="Method Options" />
{{else}}
{{#each (array "generic" "cloud" "infra") as |category|}}
Expand Down Expand Up @@ -69,7 +68,7 @@
type="submit"
data-test-mount-submit="true"
class="button is-primary {{if mountBackend.isRunning "loading"}}"
disabled={{mountBackend.isRunning}}
disabled={{or mountBackend.isRunning validationError}}
>
{{#if (eq mountType "auth")}}
Enable Method
Expand Down
Loading