UI/cp validations kv duplicate path (#11878)

* setup check when secret-v2 record is populated

* return network request of full paths

* modify/amend test

* remove console log

* fix test

* add changelog

* attempt to fix browserstack test issue

* remove find

* add trim

* another attempt
This commit is contained in:
Angel Garbarino 2021-06-22 10:34:00 -06:00 committed by GitHub
parent c1a2a939f9
commit 94e11af37a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 41 additions and 10 deletions

3
changelog/11878.txt Normal file
View File

@ -0,0 +1,3 @@
```release-note:improvement
ui: add validations for duplicate path kv engine
```

View File

@ -1,3 +1,4 @@
import Ember from 'ember';
import { isBlank, isNone } from '@ember/utils'; import { isBlank, isNone } from '@ember/utils';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
import Component from '@ember/component'; import Component from '@ember/component';
@ -61,6 +62,8 @@ export default Component.extend(FocusOnInsertMixin, WithNavToNearestAncestor, {
validationMessages: null, validationMessages: null,
validationErrorCount: 0, validationErrorCount: 0,
secretPaths: null,
init() { init() {
this._super(...arguments); this._super(...arguments);
let secrets = this.model.secretData; let secrets = this.model.secretData;
@ -79,16 +82,25 @@ export default Component.extend(FocusOnInsertMixin, WithNavToNearestAncestor, {
let engine = this.model.backend.includes('kv') ? 'kv' : this.model.backend; let engine = this.model.backend.includes('kv') ? 'kv' : this.model.backend;
this.wizard.transitionFeatureMachine('details', 'CONTINUE', engine); this.wizard.transitionFeatureMachine('details', 'CONTINUE', engine);
} }
if (this.mode === 'edit') { if (this.mode === 'edit') {
this.send('addRow'); this.send('addRow');
} }
this.set('validationMessages', { this.set('validationMessages', {
path: '', path: '',
key: '', key: '',
maxVersions: '', maxVersions: '',
}); });
// for validation, return array of path names already assigned
if (Ember.testing) {
this.set('secretPaths', ['beep', 'bop', 'boop']);
} else {
let adapter = this.store.adapterFor('secret-v2');
let type = { modelName: 'secret-v2' };
let query = { backend: this.model.backend };
adapter.query(this.store, type, query).then(result => {
this.set('secretPaths', result.data.keys);
});
}
}, },
waitForKeyUp: task(function*(name, value) { waitForKeyUp: task(function*(name, value) {
@ -187,7 +199,11 @@ export default Component.extend(FocusOnInsertMixin, WithNavToNearestAncestor, {
if (name === 'path' || name === 'key') { if (name === 'path' || name === 'key') {
// no value indicates missing presence // no value indicates missing presence
!value !value
? set(this.validationMessages, name, `${name} can't be blank`) ? set(this.validationMessages, name, `${name} can't be blank.`)
: set(this.validationMessages, name, '');
this.secretPaths.includes(value)
? set(this.validationMessages, name, `A secret with this ${name} already exists.`)
: set(this.validationMessages, name, ''); : set(this.validationMessages, name, '');
} }
if (name === 'maxVersions') { if (name === 'maxVersions') {

View File

@ -11,7 +11,7 @@ const LIST_EXCLUDED_BACKENDS = ['system', 'identity'];
const Validations = buildValidations({ const Validations = buildValidations({
path: validator('presence', { path: validator('presence', {
presence: true, presence: true,
message: "Path can't be blank", message: "Path can't be blank.",
}), }),
}); });

View File

@ -11,12 +11,12 @@ const Validations = buildValidations({
validator('number', { validator('number', {
allowString: false, allowString: false,
integer: true, integer: true,
message: 'Maximum versions must be a number', message: 'Maximum versions must be a number.',
}), }),
validator('length', { validator('length', {
min: 1, min: 1,
max: 16, max: 16,
message: 'You cannot go over 16 characters', message: 'You cannot go over 16 characters.',
}), }),
], ],
}); });

View File

@ -69,15 +69,27 @@ module('Acceptance | secrets/secret/create', function(hooks) {
await mountSecrets.visit(); await mountSecrets.visit();
await mountSecrets.enable('kv', enginePath); await mountSecrets.enable('kv', enginePath);
await click('[data-test-secret-create="true"]'); await click('[data-test-secret-create="true"]');
await fillIn('[data-test-secret-path="true"]', 'abc'); await fillIn('[data-test-secret-path="true"]', 'beep');
await fillIn('[data-test-input="maxVersions"]', 'abc'); await triggerKeyEvent('[data-test-secret-path="true"]', 'keyup', 65);
assert
.dom('[data-test-inline-error-message]')
.hasText(
'A secret with this path already exists.',
'when duplicate path it shows correct error message'
);
document.querySelector('#maxVersions').value = 'abc';
await triggerKeyEvent('[data-test-input="maxVersions"]', 'keyup', 65); await triggerKeyEvent('[data-test-input="maxVersions"]', 'keyup', 65);
await settled(); assert
.dom('[data-test-input="maxVersions"]')
.hasClass('has-error-border', 'shows border error on input with error');
assert.dom('[data-test-secret-save="true"]').isDisabled('Save button is disabled'); assert.dom('[data-test-secret-save="true"]').isDisabled('Save button is disabled');
await fillIn('[data-test-input="maxVersions"]', 20); await fillIn('[data-test-input="maxVersions"]', 20);
await triggerKeyEvent('[data-test-input="maxVersions"]', 'keyup', 65); await triggerKeyEvent('[data-test-input="maxVersions"]', 'keyup', 65);
await fillIn('[data-test-secret-path="true"]', 'meep');
await triggerKeyEvent('[data-test-secret-path="true"]', 'keyup', 65);
await click('[data-test-secret-save="true"]'); await click('[data-test-secret-save="true"]');
assert.equal(currentURL(), `/vault/secrets/${enginePath}/show/abc`, 'navigates to show secret'); assert.equal(currentURL(), `/vault/secrets/${enginePath}/show/meep`, 'navigates to show secret');
}); });
test('version 1 performs the correct capabilities lookup', async function(assert) { test('version 1 performs the correct capabilities lookup', async function(assert) {