From 94e11af37a3ae60449798f6c8d68ab5355e21e28 Mon Sep 17 00:00:00 2001 From: Angel Garbarino Date: Tue, 22 Jun 2021 10:34:00 -0600 Subject: [PATCH] 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 --- changelog/11878.txt | 3 +++ ui/app/components/secret-edit.js | 22 ++++++++++++++++--- ui/app/models/secret-engine.js | 2 +- ui/app/models/secret-v2.js | 4 ++-- .../secrets/backend/kv/secret-test.js | 20 +++++++++++++---- 5 files changed, 41 insertions(+), 10 deletions(-) create mode 100644 changelog/11878.txt diff --git a/changelog/11878.txt b/changelog/11878.txt new file mode 100644 index 000000000..62d28a327 --- /dev/null +++ b/changelog/11878.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: add validations for duplicate path kv engine +``` diff --git a/ui/app/components/secret-edit.js b/ui/app/components/secret-edit.js index 514776a64..a5b0b36d9 100644 --- a/ui/app/components/secret-edit.js +++ b/ui/app/components/secret-edit.js @@ -1,3 +1,4 @@ +import Ember from 'ember'; import { isBlank, isNone } from '@ember/utils'; import { inject as service } from '@ember/service'; import Component from '@ember/component'; @@ -61,6 +62,8 @@ export default Component.extend(FocusOnInsertMixin, WithNavToNearestAncestor, { validationMessages: null, validationErrorCount: 0, + secretPaths: null, + init() { this._super(...arguments); 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; this.wizard.transitionFeatureMachine('details', 'CONTINUE', engine); } - if (this.mode === 'edit') { this.send('addRow'); } - this.set('validationMessages', { path: '', key: '', 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) { @@ -187,7 +199,11 @@ export default Component.extend(FocusOnInsertMixin, WithNavToNearestAncestor, { if (name === 'path' || name === 'key') { // no value indicates missing presence !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, ''); } if (name === 'maxVersions') { diff --git a/ui/app/models/secret-engine.js b/ui/app/models/secret-engine.js index 21ded56d8..e1da82f77 100644 --- a/ui/app/models/secret-engine.js +++ b/ui/app/models/secret-engine.js @@ -11,7 +11,7 @@ const LIST_EXCLUDED_BACKENDS = ['system', 'identity']; const Validations = buildValidations({ path: validator('presence', { presence: true, - message: "Path can't be blank", + message: "Path can't be blank.", }), }); diff --git a/ui/app/models/secret-v2.js b/ui/app/models/secret-v2.js index 1060a8580..d6ed6c28d 100644 --- a/ui/app/models/secret-v2.js +++ b/ui/app/models/secret-v2.js @@ -11,12 +11,12 @@ const Validations = buildValidations({ validator('number', { allowString: false, integer: true, - message: 'Maximum versions must be a number', + message: 'Maximum versions must be a number.', }), validator('length', { min: 1, max: 16, - message: 'You cannot go over 16 characters', + message: 'You cannot go over 16 characters.', }), ], }); diff --git a/ui/tests/acceptance/secrets/backend/kv/secret-test.js b/ui/tests/acceptance/secrets/backend/kv/secret-test.js index 51782d165..16d42e188 100644 --- a/ui/tests/acceptance/secrets/backend/kv/secret-test.js +++ b/ui/tests/acceptance/secrets/backend/kv/secret-test.js @@ -69,15 +69,27 @@ module('Acceptance | secrets/secret/create', function(hooks) { await mountSecrets.visit(); await mountSecrets.enable('kv', enginePath); await click('[data-test-secret-create="true"]'); - await fillIn('[data-test-secret-path="true"]', 'abc'); - await fillIn('[data-test-input="maxVersions"]', 'abc'); + await fillIn('[data-test-secret-path="true"]', 'beep'); + 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 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'); await fillIn('[data-test-input="maxVersions"]', 20); 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"]'); - 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) {