diff --git a/changelog/25646.txt b/changelog/25646.txt new file mode 100644 index 0000000000..d8c659a1dd --- /dev/null +++ b/changelog/25646.txt @@ -0,0 +1,3 @@ +```release-note:improvement +ui: Adds allowed_response_headers, plugin_version and user_lockout_config params to auth method configuration +``` \ No newline at end of file diff --git a/ui/app/components/auth-config-form/options.js b/ui/app/components/auth-config-form/options.js index da9e9b8f33..e665830469 100644 --- a/ui/app/components/auth-config-form/options.js +++ b/ui/app/components/auth-config-form/options.js @@ -30,12 +30,20 @@ export default AuthConfigComponent.extend({ waitFor(function* () { const data = this.model.config.serialize(); data.description = this.model.description; + data.user_lockout_config = {}; // token_type should not be tuneable for the token auth method. if (this.model.methodType === 'token') { delete data.token_type; } + this.model.userLockoutConfig.apiParams.forEach((attr) => { + if (Object.keys(data).includes(attr)) { + data.user_lockout_config[attr] = data[attr]; + delete data[attr]; + } + }); + try { yield this.model.tune(data); } catch (err) { diff --git a/ui/app/models/auth-method.js b/ui/app/models/auth-method.js index 32370df56c..459852ca70 100644 --- a/ui/app/models/auth-method.js +++ b/ui/app/models/auth-method.js @@ -68,6 +68,16 @@ export default class AuthMethodModel extends Model { return this.local ? 'local' : 'replicated'; } + userLockoutConfig = { + modelAttrs: [ + 'config.lockoutThreshold', + 'config.lockoutDuration', + 'config.lockoutCounterReset', + 'config.lockoutDisable', + ], + apiParams: ['lockout_threshold', 'lockout_duration', 'lockout_counter_reset', 'lockout_disable'], + }; + get tuneAttrs() { const { methodType } = this; let tuneAttrs; @@ -75,12 +85,12 @@ export default class AuthMethodModel extends Model { if (methodType === 'token') { tuneAttrs = [ 'description', - 'config.{listingVisibility,defaultLeaseTtl,maxLeaseTtl,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders}', + 'config.{listingVisibility,defaultLeaseTtl,maxLeaseTtl,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders,allowedResponseHeaders,pluginVersion,lockoutThreshold,lockoutDuration,lockoutCounterReset,lockoutDisable}', ]; } else { tuneAttrs = [ 'description', - 'config.{listingVisibility,defaultLeaseTtl,maxLeaseTtl,tokenType,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders}', + 'config.{listingVisibility,defaultLeaseTtl,maxLeaseTtl,tokenType,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders,allowedResponseHeaders,pluginVersion,lockoutThreshold,lockoutDuration,lockoutCounterReset,lockoutDisable}', ]; } return expandAttributeMeta(this, tuneAttrs); @@ -94,7 +104,7 @@ export default class AuthMethodModel extends Model { 'accessor', 'local', 'sealWrap', - 'config.{listingVisibility,defaultLeaseTtl,maxLeaseTtl,tokenType,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders}', + 'config.{listingVisibility,defaultLeaseTtl,maxLeaseTtl,tokenType,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders,allowedResponseHeaders,pluginVersion}', ]; } @@ -107,7 +117,7 @@ export default class AuthMethodModel extends Model { 'config.listingVisibility', 'local', 'sealWrap', - 'config.{defaultLeaseTtl,maxLeaseTtl,tokenType,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders}', + 'config.{defaultLeaseTtl,maxLeaseTtl,tokenType,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders,allowedResponseHeaders,pluginVersion}', ], }, ]; diff --git a/ui/app/models/mount-config.js b/ui/app/models/mount-config.js index f8d7d07714..d7b3665a70 100644 --- a/ui/app/models/mount-config.js +++ b/ui/app/models/mount-config.js @@ -54,7 +54,7 @@ export default class MountConfigModel extends Model { allowedResponseHeaders; @attr('string', { - label: 'Token Type', + label: 'Token type', helpText: 'The type of token that should be generated via this role. For `default-service` and `default-batch` service and batch tokens will be issued respectively, unless the auth method explicitly requests a different type.', possibleValues: ['default-service', 'default-batch', 'batch', 'service'], @@ -66,4 +66,42 @@ export default class MountConfigModel extends Model { editType: 'stringArray', }) allowedManagedKeys; + + @attr('string', { + label: 'Plugin version', + subText: + 'Specifies the semantic version of the plugin to use, e.g. "v1.0.0". If unspecified, the server will select any matching un-versioned plugin that may have been registered, the latest versioned plugin registered, or a built-in plugin in that order of precedence.', + }) + pluginVersion; + + // Auth mount userLockoutConfig params, added to user_lockout_config object in saveModel method + @attr('string', { + label: 'Lockout threshold', + subText: 'Specifies the number of failed login attempts after which the user is locked out, e.g. 15.', + }) + lockoutThreshold; + + @attr({ + label: 'Lockout duration', + helperTextEnabled: 'The duration for which a user will be locked out, e.g. "5s" or "30m".', + editType: 'ttl', + helperTextDisabled: 'No lockout duration configured.', + }) + lockoutDuration; + + @attr({ + label: 'Lockout counter reset', + helperTextEnabled: + 'The duration after which the lockout counter is reset with no failed login attempts, e.g. "5s" or "30m".', + editType: 'ttl', + helperTextDisabled: 'No reset duration configured.', + }) + lockoutCounterReset; + + @attr('boolean', { + label: 'Disable lockout for this mount', + subText: 'If checked, disables the user lockout feature for this mount.', + }) + lockoutDisable; + // end of user_lockout_config params } diff --git a/ui/app/templates/components/auth-config-form/options.hbs b/ui/app/templates/components/auth-config-form/options.hbs index eaef6e030d..56cd7cdc9d 100644 --- a/ui/app/templates/components/auth-config-form/options.hbs +++ b/ui/app/templates/components/auth-config-form/options.hbs @@ -7,8 +7,22 @@
+ {{#each this.model.tuneAttrs as |attr|}} - + {{#if (not (includes attr.name this.model.userLockoutConfig.modelAttrs))}} + + {{/if}} + {{/each}} + +
+ User lockout configuration + + Specifies the user lockout settings for this auth mount. + + {{#each this.model.tuneAttrs as |attr|}} + {{#if (includes attr.name this.model.userLockoutConfig.modelAttrs)}} + + {{/if}} {{/each}}
diff --git a/ui/tests/helpers/general-selectors.js b/ui/tests/helpers/general-selectors.js index 4a4641f410..ea736f61e0 100644 --- a/ui/tests/helpers/general-selectors.js +++ b/ui/tests/helpers/general-selectors.js @@ -44,8 +44,14 @@ export const SELECTORS = { infoRowLabel: (label) => `[data-test-row-label="${label}"]`, infoRowValue: (label) => `[data-test-value-div="${label}"]`, inputByAttr: (attr) => `[data-test-input="${attr}"]`, + selectByAttr: (attr) => `[data-test-select="${attr}"]`, fieldByAttr: (attr) => `[data-test-field="${attr}"]`, enableField: (attr) => `[data-test-enable-field="${attr}"] button`, + ttl: { + toggle: (attr) => `[data-test-toggle-label="${attr}"]`, + input: (attr) => `[data-test-ttl-value="${attr}"]`, + }, + validation: (attr) => `[data-test-field-validation=${attr}]`, validationWarning: (attr) => `[data-test-validation-warning=${attr}]`, messageError: '[data-test-message-error]', diff --git a/ui/tests/integration/components/auth-config-form/options-test.js b/ui/tests/integration/components/auth-config-form/options-test.js index f267dad0f1..9376516149 100644 --- a/ui/tests/integration/components/auth-config-form/options-test.js +++ b/ui/tests/integration/components/auth-config-form/options-test.js @@ -3,56 +3,73 @@ * SPDX-License-Identifier: BUSL-1.1 */ -import { resolve } from 'rsvp'; -import EmberObject from '@ember/object'; import { module, test } from 'qunit'; import { setupRenderingTest } from 'ember-qunit'; -import { render, settled } from '@ember/test-helpers'; +import { setupMirage } from 'ember-cli-mirage/test-support'; +import { click, fillIn, render } from '@ember/test-helpers'; import hbs from 'htmlbars-inline-precompile'; -import sinon from 'sinon'; -import { create } from 'ember-cli-page-object'; -import authConfigForm from 'vault/tests/pages/components/auth-config-form/options'; - -const component = create(authConfigForm); +import { SELECTORS } from 'vault/tests/helpers/general-selectors'; module('Integration | Component | auth-config-form options', function (hooks) { setupRenderingTest(hooks); + setupMirage(hooks); hooks.beforeEach(function () { this.owner.lookup('service:flash-messages').registerTypes(['success']); this.router = this.owner.lookup('service:router'); + this.store = this.owner.lookup('service:store'); + this.path = 'my-auth-method/'; + this.model = this.store.createRecord('auth-method', { path: this.path, type: 'approle' }); + this.model.set('config', this.store.createRecord('mount-config')); + }); + + test('it submits data correctly', async function (assert) { + assert.expect(2); this.router.reopen({ transitionTo() { return { followRedirects() { - return resolve(); + assert.ok('calls transitionTo on save'); }, }; }, - replaceWith() { - return resolve(); - }, }); - }); - test('it submits data correctly', async function (assert) { - assert.expect(1); - const model = EmberObject.create({ - tune() { - return resolve(); - }, - config: { - serialize() { - return {}; + this.server.post(`sys/mounts/auth/${this.path}/tune`, (schema, req) => { + const payload = JSON.parse(req.requestBody); + const expected = { + default_lease_ttl: '30s', + listing_visibility: 'unauth', + user_lockout_config: { + lockout_threshold: '7', + lockout_duration: '600s', + lockout_counter_reset: '5s', + lockout_disable: true, }, - }, + }; + assert.propEqual(payload, expected, 'payload contains tune parameters'); + return { payload }; }); - sinon.spy(model.config, 'serialize'); - this.set('model', model); await render(hbs``); - component.save(); - return settled().then(() => { - assert.strictEqual(model.config.serialize.callCount, 1, 'config serialize was called once'); - }); + + await click(SELECTORS.inputByAttr('config.listingVisibility')); + + await click(SELECTORS.ttl.toggle('Default Lease TTL')); + await fillIn(SELECTORS.ttl.input('Default Lease TTL'), '30'); + + await fillIn(SELECTORS.inputByAttr('config.lockoutThreshold'), '7'); + + await click(SELECTORS.ttl.toggle('Lockout duration')); + await fillIn(SELECTORS.ttl.input('Lockout duration'), '10'); + await fillIn( + `${SELECTORS.inputByAttr('config.lockoutDuration')} ${SELECTORS.selectByAttr('ttl-unit')}`, + 'm' + ); + await click(SELECTORS.ttl.toggle('Lockout counter reset')); + await fillIn(SELECTORS.ttl.input('Lockout counter reset'), '5'); + + await click(SELECTORS.inputByAttr('config.lockoutDisable')); + + await click('[data-test-save-config]'); }); }); diff --git a/ui/tests/pages/components/auth-config-form/options.js b/ui/tests/pages/components/auth-config-form/options.js deleted file mode 100644 index ecf77082d0..0000000000 --- a/ui/tests/pages/components/auth-config-form/options.js +++ /dev/null @@ -1,14 +0,0 @@ -/** - * Copyright (c) HashiCorp, Inc. - * SPDX-License-Identifier: BUSL-1.1 - */ - -import { clickable, fillable } from 'ember-cli-page-object'; - -import fields from '../form-field'; -export default { - ...fields, - ttlValue: fillable('[data-test-ttl-value]'), - ttlUnit: fillable('[data-test-ttl-value]'), - save: clickable('[data-test-save-config]'), -};