diff --git a/changelog/27366.txt b/changelog/27366.txt new file mode 100644 index 0000000000..16b936e5df --- /dev/null +++ b/changelog/27366.txt @@ -0,0 +1,3 @@ +```release-note:bug +ui: Fix a bug where disabling TTL on the AWS credential form would still send TTL value +``` \ No newline at end of file diff --git a/ui/app/adapters/aws-credential.js b/ui/app/adapters/aws-credential.js index 948b91a6cb..4cba977295 100644 --- a/ui/app/adapters/aws-credential.js +++ b/ui/app/adapters/aws-credential.js @@ -16,7 +16,7 @@ export default ApplicationAdapter.extend({ if (roleType === 'iam_user') { method = 'GET'; } else { - if (ttl !== undefined) { + if (ttl) { data.ttl = ttl; } if (roleType === 'assumed_role' && roleArn) { diff --git a/ui/app/models/aws-credential.js b/ui/app/models/aws-credential.js index 3dccb192b2..7199ad75bf 100644 --- a/ui/app/models/aws-credential.js +++ b/ui/app/models/aws-credential.js @@ -49,6 +49,7 @@ export default Model.extend({ editType: 'ttl', defaultValue: '3600s', setDefault: true, + ttlOffValue: '', label: 'TTL', helpText: 'Specifies the TTL for the use of the STS token. Valid only when credential_type is assumed_role, federation_token, or session_token.', @@ -66,7 +67,7 @@ export default Model.extend({ iam_user: ['credentialType'], assumed_role: ['credentialType', 'ttl', 'roleArn'], federation_token: ['credentialType', 'ttl'], - session_token: ['ttl'], + session_token: ['credentialType', 'ttl'], }; if (this.accessKey || this.securityToken) { return expandAttributeMeta(this, DISPLAY_FIELDS.slice(0)); diff --git a/ui/app/templates/components/generate-credentials.hbs b/ui/app/templates/components/generate-credentials.hbs index 8ab1ca3f28..7b515e41f3 100644 --- a/ui/app/templates/components/generate-credentials.hbs +++ b/ui/app/templates/components/generate-credentials.hbs @@ -147,6 +147,7 @@ diff --git a/ui/lib/core/addon/components/form-field.js b/ui/lib/core/addon/components/form-field.js index a15d735f8a..59dba55784 100644 --- a/ui/lib/core/addon/components/form-field.js +++ b/ui/lib/core/addon/components/form-field.js @@ -158,7 +158,13 @@ export default class FormFieldComponent extends Component { @action setAndBroadcastTtl(value) { const alwaysSendValue = this.valuePath === 'expiry' || this.valuePath === 'safetyBuffer'; - const valueToSet = value.enabled === true || alwaysSendValue ? `${value.seconds}s` : 0; + const attrOptions = this.args.attr.options || {}; + let valueToSet = 0; + if (value.enabled || alwaysSendValue) { + valueToSet = `${value.seconds}s`; + } else if (Object.keys(attrOptions).includes('ttlOffValue')) { + valueToSet = attrOptions.ttlOffValue; + } this.setAndBroadcast(`${valueToSet}`); } @action diff --git a/ui/tests/acceptance/aws-test.js b/ui/tests/acceptance/aws-test.js index ee0b2bbfce..e415746e8d 100644 --- a/ui/tests/acceptance/aws-test.js +++ b/ui/tests/acceptance/aws-test.js @@ -3,16 +3,18 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { click, fillIn, findAll, currentURL, find, settled, waitUntil } from '@ember/test-helpers'; +import { click, fillIn, currentURL, find, settled, waitUntil } from '@ember/test-helpers'; import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; import { v4 as uuidv4 } from 'uuid'; import authPage from 'vault/tests/pages/auth'; import enablePage from 'vault/tests/pages/settings/mount-secret-backend'; +import { setupMirage } from 'ember-cli-mirage/test-support'; module('Acceptance | aws secret backend', function (hooks) { setupApplicationTest(hooks); + setupMirage(hooks); hooks.beforeEach(function () { this.uid = uuidv4(); @@ -20,9 +22,13 @@ module('Acceptance | aws secret backend', function (hooks) { }); test('aws backend', async function (assert) { - assert.expect(12); const path = `aws-${this.uid}`; const roleName = 'awsrole'; + this.server.post(`/${path}/creds/${roleName}`, (_, req) => { + const payload = JSON.parse(req.requestBody); + assert.deepEqual(payload, { role_arn: 'foobar' }, 'does not send TTL when unchecked'); + return {}; + }); await enablePage.enable('aws', path); await settled(); @@ -31,28 +37,25 @@ module('Acceptance | aws secret backend', function (hooks) { await click('[data-test-secret-backend-configure]'); assert.strictEqual(currentURL(), `/vault/settings/secrets/configure/${path}`); - assert.ok(findAll('[data-test-aws-root-creds-form]').length, 'renders the empty root creds form'); - assert.ok(findAll('[data-test-aws-link="root-creds"]').length, 'renders the root creds link'); - assert.ok(findAll('[data-test-aws-link="leases"]').length, 'renders the leases config link'); + assert.dom('[data-test-aws-root-creds-form]').exists(); + assert.dom('[data-test-aws-link="root-creds"]').exists(); + assert.dom('[data-test-aws-link="leases"]').exists(); await fillIn('[data-test-aws-input="accessKey"]', 'foo'); await fillIn('[data-test-aws-input="secretKey"]', 'bar'); await click('[data-test-aws-input="root-save"]'); - assert.ok( - find('[data-test-flash-message]').textContent.trim(), - `The backend configuration saved successfully!` - ); + assert + .dom('[data-test-flash-message]:last-of-type [data-test-flash-message-body]') + .includesText(`The backend configuration saved successfully!`); await click('[data-test-aws-link="leases"]'); await click('[data-test-aws-input="lease-save"]'); - - assert.ok( - find('[data-test-flash-message]').textContent.trim(), - `The backend configuration saved successfully!` - ); + assert + .dom('[data-test-flash-message]:last-of-type [data-test-flash-message-body]') + .includesText(`The backend configuration saved successfully!`); await click('[data-test-backend-view-link]'); @@ -60,10 +63,7 @@ module('Acceptance | aws secret backend', function (hooks) { await click('[data-test-secret-create]'); - assert.ok( - find('[data-test-secret-header]').textContent.includes('AWS Role'), - `aws: renders the create page` - ); + assert.dom('[data-test-secret-header]').includesText('AWS Role'); await fillIn('[data-test-input="name"]', roleName); @@ -78,7 +78,19 @@ module('Acceptance | aws secret backend', function (hooks) { await click(`[data-test-secret-breadcrumb="${path}"] a`); assert.strictEqual(currentURL(), `/vault/secrets/${path}/list`); - assert.ok(findAll(`[data-test-secret-link="${roleName}"]`).length, `aws: role shows in the list`); + assert.dom(`[data-test-secret-link="${roleName}"]`).exists(); + + // check that generates credentials flow is correct + await click(`[data-test-secret-link="${roleName}"]`); + assert.dom('h1').hasText('Generate AWS Credentials'); + assert.dom('[data-test-input="credentialType"]').hasValue('iam_user'); + await fillIn('[data-test-input="credentialType"]', 'assumed_role'); + await click('[data-test-ttl-toggle="TTL"]'); + assert.dom('[data-test-ttl-toggle="TTL"]').isNotChecked(); + await fillIn('[data-test-input="roleArn"]', 'foobar'); + await click('[data-test-secret-generate]'); + assert.dom('[data-test-warning]').exists('Shows access warning after generation'); + await click('[data-test-secret-generate-back]'); //and delete await click(`[data-test-secret-link="${roleName}"] [data-test-popup-menu-trigger]`); diff --git a/ui/tests/integration/components/form-field-test.js b/ui/tests/integration/components/form-field-test.js index fb7d25fae4..0d5916351b 100644 --- a/ui/tests/integration/components/form-field-test.js +++ b/ui/tests/integration/components/form-field-test.js @@ -127,14 +127,41 @@ module('Integration | Component | form field', function (hooks) { }); test('it renders: editType ttl', async function (assert) { - const [model, spy] = await setup.call(this, createAttr('foo', null, { editType: 'ttl' })); + const [model, spy] = await setup.call( + this, + createAttr('foo', null, { + editType: 'ttl', + helperTextDisabled: 'TTL is disabled', + helperTextEnabled: 'TTL is enabled', + }) + ); assert.ok(component.hasTTLPicker, 'renders the ttl-picker component'); + assert.dom('[data-test-ttl-form-subtext]').hasText('TTL is disabled'); + assert.dom('[data-test-ttl-toggle]').isNotChecked(); await component.fields.objectAt(0).toggleTtl(); await component.fields.objectAt(0).select('h').change(); await component.fields.objectAt(0).ttlTime('3'); const expectedSeconds = `${3 * 3600}s`; assert.strictEqual(model.get('foo'), expectedSeconds); assert.ok(spy.calledWith('foo', expectedSeconds), 'onChange called with correct args'); + await component.fields.objectAt(0).toggleTtl(); + assert.ok(spy.calledWith('foo', '0'), 'onChange called with 0 when toggle off'); + }); + + test('it renders: editType ttl with special settings', async function (assert) { + const [model, spy] = await setup.call( + this, + createAttr('foo', null, { + editType: 'ttl', + setDefault: '3600s', + ttlOffValue: '', + }) + ); + assert.ok(component.hasTTLPicker, 'renders the ttl-picker component'); + assert.dom('[data-test-ttl-toggle]').isChecked(); + await component.fields.objectAt(0).toggleTtl(); + assert.strictEqual(model.get('foo'), ''); + assert.ok(spy.calledWith('foo', ''), 'onChange called with correct args'); }); test('it renders: editType ttl without toggle', async function (assert) { diff --git a/ui/tests/unit/adapters/aws-credential-test.js b/ui/tests/unit/adapters/aws-credential-test.js index 527338900c..558c300e64 100644 --- a/ui/tests/unit/adapters/aws-credential-test.js +++ b/ui/tests/unit/adapters/aws-credential-test.js @@ -74,6 +74,11 @@ module('Unit | Adapter | aws credential', function (hooks) { [storeStub, type, makeSnapshot({ credentialType: 'assumed_role' })], 'POST', ], + [ + 'assumed_role type no arn, ttl empty', + [storeStub, type, makeSnapshot({ credentialType: 'assumed_role', ttl: '' })], + 'POST', + ], [ 'assumed_role type no arn', [storeStub, type, makeSnapshot({ credentialType: 'assumed_role', ttl: '3h' })],