From 4f1de3f76cc6f6cf787248a663f09d90a387cb93 Mon Sep 17 00:00:00 2001 From: Chelsea Shaw Date: Thu, 3 Sep 2020 14:44:37 -0500 Subject: [PATCH] Ui/role transform: tests and edit page (#9887) * Set up acceptance tests for transform secrets engine * Update search-select to optionally disallow new items * role model transformations list does not allow new on search select * Add test for creating a transform role * Role edit extends TransformBase, roles list uses generic transform list item * Fix edit role not populating transformations * Role list item links to role show page correctly, and page has edit and delete buttons --- ui/app/helpers/options-for-backend.js | 3 +- ui/app/models/transform/role.js | 3 +- .../components/transform-create-form.hbs | 2 +- .../components/transform-role-edit.hbs | 9 +- .../secret-list/transform-list-item.hbs | 77 ++++++++++++++++ ui/lib/core/addon/components/search-select.js | 5 ++ .../addon/templates/components/form-field.hbs | 1 + .../acceptance/enterprise-transform-test.js | 90 +++++++++++++++++++ .../components/search-select-test.js | 14 +++ .../pages/secrets/backend/transform/roles.js | 12 +++ .../backend/transform/transformations.js | 14 +++ 11 files changed, 222 insertions(+), 8 deletions(-) create mode 100644 ui/app/templates/partials/secret-list/transform-list-item.hbs create mode 100644 ui/tests/acceptance/enterprise-transform-test.js create mode 100644 ui/tests/pages/secrets/backend/transform/roles.js create mode 100644 ui/tests/pages/secrets/backend/transform/transformations.js diff --git a/ui/app/helpers/options-for-backend.js b/ui/app/helpers/options-for-backend.js index 79575157e..e805f097c 100644 --- a/ui/app/helpers/options-for-backend.js +++ b/ui/app/helpers/options-for-backend.js @@ -58,7 +58,7 @@ const SECRET_BACKENDS = { transform: { displayName: 'Transformation', navigateTree: false, - listItemPartial: 'partials/secret-list/transform-transformation-item', + listItemPartial: 'partials/secret-list/transform-list-item', tabs: [ { name: 'transformations', @@ -67,6 +67,7 @@ const SECRET_BACKENDS = { item: 'transformation', create: 'Create transformation', editComponent: 'transformation-edit', + listItemPartial: 'partials/secret-list/transform-transformation-item', }, { name: 'role', diff --git a/ui/app/models/transform/role.js b/ui/app/models/transform/role.js index 6288fdbc9..0bf257c6f 100644 --- a/ui/app/models/transform/role.js +++ b/ui/app/models/transform/role.js @@ -22,13 +22,14 @@ const Model = DS.Model.extend({ readOnly: true, subText: 'The name for your role. This cannot be edited later.', }), - transformations: attr('string', { + transformations: attr('array', { editType: 'searchSelect', fallbackComponent: 'string-list', label: 'Transformations', models: ['transform'], subLabel: 'Transformations', subText: 'Select which transformations this role will have access to. It must already exist.', + onlyAllowExisting: true, }), attrs: computed('transformations', function() { diff --git a/ui/app/templates/components/transform-create-form.hbs b/ui/app/templates/components/transform-create-form.hbs index a35e16c68..241f67ffc 100644 --- a/ui/app/templates/components/transform-create-form.hbs +++ b/ui/app/templates/components/transform-create-form.hbs @@ -17,7 +17,7 @@ type="submit" disabled={{buttonDisabled}} class="button is-primary" - data-test-role-ssh-create=true + data-test-transform-create=true > {{#if (eq mode 'create')}} Create transformation diff --git a/ui/app/templates/components/transform-role-edit.hbs b/ui/app/templates/components/transform-role-edit.hbs index d4bd13ff5..a5ae6aec4 100644 --- a/ui/app/templates/components/transform-role-edit.hbs +++ b/ui/app/templates/components/transform-role-edit.hbs @@ -24,7 +24,6 @@ {{#if (eq mode "show")}} - {{!-- TODO: Ability to delete and edit role {{#if (or capabilities.canUpdate capabilities.canDelete)}}
{{/if}} @@ -39,14 +38,14 @@ {{/if}} {{#if capabilities.canUpdate }} Edit role - {{/if}} --}} + {{/if}} {{/if}} @@ -70,10 +69,10 @@ type="submit" disabled={{buttonDisabled}} class="button is-primary" - data-test-role-ssh-create=true + data-test-role-transform-create=true > {{#if (eq mode 'create')}} - Create transformation + Create role {{else if (eq mode 'edit')}} Save {{/if}} diff --git a/ui/app/templates/partials/secret-list/transform-list-item.hbs b/ui/app/templates/partials/secret-list/transform-list-item.hbs new file mode 100644 index 000000000..dd3951b32 --- /dev/null +++ b/ui/app/templates/partials/secret-list/transform-list-item.hbs @@ -0,0 +1,77 @@ +{{!-- TODO do not let click if !canRead --}} +{{#if (eq options.item "role")}} + {{#let (concat options.modelPrefix item.id) as |itemPath|}} + {{#linked-block + "vault.cluster.secrets.backend.show" + itemPath + class="list-item-row" + data-test-secret-link=itemPath + encode=true + queryParams=(secret-query-params backendModel.type) + }} +
+
+ + + {{if (eq item.id ' ') '(self)' (or item.keyWithoutParent item.id)}} + +
+
+ {{#if (or item.updatePath.canRead item.updatePath.canUpdate)}} + + + + {{/if}} +
+
+ {{/linked-block}} + {{/let}} +{{else}} +
+
+
+ + {{if (eq item.id ' ') '(self)' (or item.keyWithoutParent item.id)}} +
+
+
+{{/if}} diff --git a/ui/lib/core/addon/components/search-select.js b/ui/lib/core/addon/components/search-select.js index cdaa19658..cb5f01b68 100644 --- a/ui/lib/core/addon/components/search-select.js +++ b/ui/lib/core/addon/components/search-select.js @@ -22,6 +22,7 @@ import layout from '../templates/components/search-select'; * @param [subLabel] {String} - a smaller label below the main Label * @param fallbackComponent {String} - name of component to be rendered if the API call 403s * @param [backend] {String} - name of the backend if the query for options needs additional information (eg. secret backend) + * @param [disallowNewItems=false] {Boolean} - Controls whether or not the user can add a new item if none found * * @param options {Array} - *Advanced usage* - `options` can be passed directly from the outside to the * power-select component. If doing this, `models` should not also be passed as that will overwrite the @@ -44,6 +45,7 @@ export default Component.extend({ options: null, //all possible options shouldUseFallback: false, shouldRenderName: false, + disallowNewItems: false, init() { this._super(...arguments); this.set('selectedOptions', this.inputValue || []); @@ -149,6 +151,9 @@ export default Component.extend({ return !options.some(group => group.options.findBy('id', id)); } let existingOption = this.options && (this.options.findBy('id', id) || this.options.findBy('name', id)); + if (this.disallowNewItems && !existingOption) { + return false; + } return !existingOption; }, }, diff --git a/ui/lib/core/addon/templates/components/form-field.hbs b/ui/lib/core/addon/templates/components/form-field.hbs index 8ea8c1a63..a1b80ef87 100644 --- a/ui/lib/core/addon/templates/components/form-field.hbs +++ b/ui/lib/core/addon/templates/components/form-field.hbs @@ -79,6 +79,7 @@ @fallbackComponent={{attr.options.fallbackComponent}} @selectLimit={{attr.options.selectLimit}} @backend={{model.backend}} + @disallowNewItems={{attr.options.onlyAllowExisting}} />
{{else if (eq attr.options.editType "mountAccessor")}} diff --git a/ui/tests/acceptance/enterprise-transform-test.js b/ui/tests/acceptance/enterprise-transform-test.js new file mode 100644 index 000000000..4b3fbc28f --- /dev/null +++ b/ui/tests/acceptance/enterprise-transform-test.js @@ -0,0 +1,90 @@ +import { module, test } from 'qunit'; +import { setupApplicationTest } from 'ember-qunit'; +import { currentURL, click } from '@ember/test-helpers'; +import { create } from 'ember-cli-page-object'; +import { selectChoose, clickTrigger } from 'ember-power-select/test-support/helpers'; + +import authPage from 'vault/tests/pages/auth'; +import mountSecrets from 'vault/tests/pages/settings/mount-secret-backend'; +import transformationsPage from 'vault/tests/pages/secrets/backend/transform/transformations'; +import rolesPage from 'vault/tests/pages/secrets/backend/transform/roles'; +import searchSelect from 'vault/tests/pages/components/search-select'; + +const searchSelectComponent = create(searchSelect); + +const mount = async () => { + let path = `transform-${Date.now()}`; + await mountSecrets.enable('transform', path); + return path; +}; + +module('Acceptance | Enterprise | Transform secrets', function(hooks) { + setupApplicationTest(hooks); + + hooks.beforeEach(function() { + return authPage.login(); + }); + + test('it enables Transform secrets engine and shows tabs', async function(assert) { + let path = `transform-${Date.now()}`; + await mountSecrets.enable('transform', path); + assert.equal( + currentURL(), + `/vault/secrets/${path}/list`, + 'mounts and redirects to the transformations list page' + ); + assert.ok(transformationsPage.isEmpty, 'renders empty state'); + assert + .dom('.is-active[data-test-tab="Transformations"]') + .exists('Has Transformations tab which is active'); + assert.dom('[data-test-tab="Roles"]').exists('Has Roles tab'); + assert.dom('[data-test-tab="Templates"]').exists('Has Templates tab'); + assert.dom('[data-test-tab="Alphabets"]').exists('Has Alphabets tab'); + }); + + test('it can create a transformation and role', async function(assert) { + let path = await mount(); + const transformationName = 'foo'; + const roleName = 'foo-role'; + await transformationsPage.createLink(); + assert.equal(currentURL(), `/vault/secrets/${path}/create`, 'redirects to create transformation page'); + await transformationsPage.name(transformationName); + + assert.dom('[data-test-input="type"').hasValue('fpe', 'Has type FPE by default'); + assert.dom('[data-test-input="tweak_source"]').exists('Shows tweak source when FPE'); + await transformationsPage.type('masking'); + assert + .dom('[data-test-input="masking_character"]') + .exists('Shows masking character input when changed to masking type'); + assert.dom('[data-test-input="tweak_source"]').doesNotExist('Does not show tweak source when masking'); + await clickTrigger('#template'); + assert.equal(searchSelectComponent.options.length, 2, 'list shows two builtin options by default'); + await selectChoose('#template', '.ember-power-select-option', 0); + await transformationsPage.submit(); + assert.equal( + currentURL(), + `/vault/secrets/${path}/show/${transformationName}`, + 'redirects to show transformation page after submit' + ); + await click(`[data-test-secret-breadcrumb="${path}"]`); + assert.equal(currentURL(), `/vault/secrets/${path}/list`, 'Links back to list view from breadcrumb'); + await click('[data-test-tab="Roles"]'); + assert.equal(currentURL(), `/vault/secrets/${path}/list?tab=role`, 'links to role list page'); + await rolesPage.createLink(); + assert.equal( + currentURL(), + `/vault/secrets/${path}/create?itemType=role`, + 'redirects to create role page' + ); + await rolesPage.name(roleName); + await clickTrigger('#transformations'); + assert.equal(searchSelectComponent.options.length, 1, 'lists the transformation that was just created'); + await selectChoose('#transformations', '.ember-power-select-option', 0); + await rolesPage.submit(); + assert.equal( + currentURL(), + `/vault/secrets/${path}/show/role/${roleName}`, + 'redirects to show role page after submit' + ); + }); +}); diff --git a/ui/tests/integration/components/search-select-test.js b/ui/tests/integration/components/search-select-test.js index 64cfd42a3..beb2e2aef 100644 --- a/ui/tests/integration/components/search-select-test.js +++ b/ui/tests/integration/components/search-select-test.js @@ -89,6 +89,20 @@ module('Integration | Component | search select', function(hooks) { assert.equal(component.options.length, 1, 'list shows one option'); }); + test('it behaves correctly if new items not allowed', async function(assert) { + const models = ['identity/entity']; + this.set('models', models); + this.set('onChange', sinon.spy()); + await render(hbs`{{search-select label="foo" models=models onChange=onChange disallowNewItems=true}}`); + await clickTrigger(); + assert.equal(component.options.length, 3, 'shows all options'); + await typeInSearch('p'); + assert.equal(component.options.length, 1, 'list shows one option'); + assert.equal(component.options[0].text, 'No results found'); + await clickTrigger(); + assert.ok(this.onChange.notCalled, 'on change not called when empty state clicked'); + }); + test('it moves option from drop down to list when clicked', async function(assert) { const models = ['identity/entity']; this.set('models', models); diff --git a/ui/tests/pages/secrets/backend/transform/roles.js b/ui/tests/pages/secrets/backend/transform/roles.js new file mode 100644 index 000000000..fa89c7b4a --- /dev/null +++ b/ui/tests/pages/secrets/backend/transform/roles.js @@ -0,0 +1,12 @@ +import { create, clickable, fillable, visitable } from 'ember-cli-page-object'; +import ListView from 'vault/tests/pages/components/list-view'; + +export default create({ + ...ListView, + visit: visitable('/vault/secrets/:backend/list?tab=roles'), + visitCreate: visitable('/vault/secrets/:backend/create?itemType=role'), + createLink: clickable('[data-test-secret-create="true"]'), + name: fillable('[data-test-input="name"]'), + transformations: fillable('[data-test-input="transformations"'), + submit: clickable('[data-test-role-transform-create="true"]'), +}); diff --git a/ui/tests/pages/secrets/backend/transform/transformations.js b/ui/tests/pages/secrets/backend/transform/transformations.js new file mode 100644 index 000000000..22a20a75e --- /dev/null +++ b/ui/tests/pages/secrets/backend/transform/transformations.js @@ -0,0 +1,14 @@ +import { create, clickable, fillable, visitable } from 'ember-cli-page-object'; +import ListView from 'vault/tests/pages/components/list-view'; + +export default create({ + ...ListView, + visit: visitable('/vault/secrets/:backend/list'), + visitCreate: visitable('/vault/secrets/:backend/create'), + createLink: clickable('[data-test-secret-create="true"]'), + name: fillable('[data-test-input="name"]'), + submit: clickable('[data-test-transform-create]'), + type: fillable('[data-test-input="type"'), + tweakSource: fillable('[data-test-input="tweak_source"'), + maskingChar: fillable('[data-test-input="masking_character"'), +});