UI: Add missing auth config params (#25646)

* add allowed_response_headers and plugin_version to auth method config

* add user_lockout_config to auth tune

* add changelog;

* update test

* add test
This commit is contained in:
claire bontempo
2024-02-28 11:49:19 -08:00
committed by GitHub
parent c37978395f
commit 9f8ddae96f
8 changed files with 131 additions and 49 deletions

3
changelog/25646.txt Normal file
View File

@@ -0,0 +1,3 @@
```release-note:improvement
ui: Adds allowed_response_headers, plugin_version and user_lockout_config params to auth method configuration
```

View File

@@ -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) {

View File

@@ -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}',
],
},
];

View File

@@ -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
}

View File

@@ -7,8 +7,22 @@
<div class="box is-sideless is-fullwidth is-marginless">
<MessageError @model={{this.model}} @errorMessage={{this.model.errorMessage}} />
<NamespaceReminder @mode="save" @noun="Auth Method" />
{{#each this.model.tuneAttrs as |attr|}}
<FormField data-test-field @attr={{attr}} @model={{this.model}} />
{{#if (not (includes attr.name this.model.userLockoutConfig.modelAttrs))}}
<FormField data-test-field @attr={{attr}} @model={{this.model}} />
{{/if}}
{{/each}}
<hr class="has-top-margin-xl has-bottom-margin-l has-background-gray-200" />
<Hds::Text::Display @tag="h2" @size="400" @weight="bold">User lockout configuration</Hds::Text::Display>
<Hds::Text::Body @tag="p" @size="100" @color="faint" class="has-bottom-margin-m">
Specifies the user lockout settings for this auth mount.
</Hds::Text::Body>
{{#each this.model.tuneAttrs as |attr|}}
{{#if (includes attr.name this.model.userLockoutConfig.modelAttrs)}}
<FormField @attr={{attr}} @model={{this.model}} />
{{/if}}
{{/each}}
</div>
<div class="field is-grouped box is-fullwidth is-bottomless">

View File

@@ -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]',

View File

@@ -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`<AuthConfigForm::Options @model={{this.model}} />`);
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]');
});
});

View File

@@ -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]'),
};