mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-29 17:52:32 +00:00
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:
3
changelog/25646.txt
Normal file
3
changelog/25646.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
```release-note:improvement
|
||||||
|
ui: Adds allowed_response_headers, plugin_version and user_lockout_config params to auth method configuration
|
||||||
|
```
|
||||||
@@ -30,12 +30,20 @@ export default AuthConfigComponent.extend({
|
|||||||
waitFor(function* () {
|
waitFor(function* () {
|
||||||
const data = this.model.config.serialize();
|
const data = this.model.config.serialize();
|
||||||
data.description = this.model.description;
|
data.description = this.model.description;
|
||||||
|
data.user_lockout_config = {};
|
||||||
|
|
||||||
// token_type should not be tuneable for the token auth method.
|
// token_type should not be tuneable for the token auth method.
|
||||||
if (this.model.methodType === 'token') {
|
if (this.model.methodType === 'token') {
|
||||||
delete data.token_type;
|
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 {
|
try {
|
||||||
yield this.model.tune(data);
|
yield this.model.tune(data);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
|||||||
@@ -68,6 +68,16 @@ export default class AuthMethodModel extends Model {
|
|||||||
return this.local ? 'local' : 'replicated';
|
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() {
|
get tuneAttrs() {
|
||||||
const { methodType } = this;
|
const { methodType } = this;
|
||||||
let tuneAttrs;
|
let tuneAttrs;
|
||||||
@@ -75,12 +85,12 @@ export default class AuthMethodModel extends Model {
|
|||||||
if (methodType === 'token') {
|
if (methodType === 'token') {
|
||||||
tuneAttrs = [
|
tuneAttrs = [
|
||||||
'description',
|
'description',
|
||||||
'config.{listingVisibility,defaultLeaseTtl,maxLeaseTtl,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders}',
|
'config.{listingVisibility,defaultLeaseTtl,maxLeaseTtl,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders,allowedResponseHeaders,pluginVersion,lockoutThreshold,lockoutDuration,lockoutCounterReset,lockoutDisable}',
|
||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
tuneAttrs = [
|
tuneAttrs = [
|
||||||
'description',
|
'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);
|
return expandAttributeMeta(this, tuneAttrs);
|
||||||
@@ -94,7 +104,7 @@ export default class AuthMethodModel extends Model {
|
|||||||
'accessor',
|
'accessor',
|
||||||
'local',
|
'local',
|
||||||
'sealWrap',
|
'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',
|
'config.listingVisibility',
|
||||||
'local',
|
'local',
|
||||||
'sealWrap',
|
'sealWrap',
|
||||||
'config.{defaultLeaseTtl,maxLeaseTtl,tokenType,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders}',
|
'config.{defaultLeaseTtl,maxLeaseTtl,tokenType,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders,allowedResponseHeaders,pluginVersion}',
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export default class MountConfigModel extends Model {
|
|||||||
allowedResponseHeaders;
|
allowedResponseHeaders;
|
||||||
|
|
||||||
@attr('string', {
|
@attr('string', {
|
||||||
label: 'Token Type',
|
label: 'Token type',
|
||||||
helpText:
|
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.',
|
'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'],
|
possibleValues: ['default-service', 'default-batch', 'batch', 'service'],
|
||||||
@@ -66,4 +66,42 @@ export default class MountConfigModel extends Model {
|
|||||||
editType: 'stringArray',
|
editType: 'stringArray',
|
||||||
})
|
})
|
||||||
allowedManagedKeys;
|
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
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,8 +7,22 @@
|
|||||||
<div class="box is-sideless is-fullwidth is-marginless">
|
<div class="box is-sideless is-fullwidth is-marginless">
|
||||||
<MessageError @model={{this.model}} @errorMessage={{this.model.errorMessage}} />
|
<MessageError @model={{this.model}} @errorMessage={{this.model.errorMessage}} />
|
||||||
<NamespaceReminder @mode="save" @noun="Auth Method" />
|
<NamespaceReminder @mode="save" @noun="Auth Method" />
|
||||||
|
|
||||||
{{#each this.model.tuneAttrs as |attr|}}
|
{{#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}}
|
{{/each}}
|
||||||
</div>
|
</div>
|
||||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||||
|
|||||||
@@ -44,8 +44,14 @@ export const SELECTORS = {
|
|||||||
infoRowLabel: (label) => `[data-test-row-label="${label}"]`,
|
infoRowLabel: (label) => `[data-test-row-label="${label}"]`,
|
||||||
infoRowValue: (label) => `[data-test-value-div="${label}"]`,
|
infoRowValue: (label) => `[data-test-value-div="${label}"]`,
|
||||||
inputByAttr: (attr) => `[data-test-input="${attr}"]`,
|
inputByAttr: (attr) => `[data-test-input="${attr}"]`,
|
||||||
|
selectByAttr: (attr) => `[data-test-select="${attr}"]`,
|
||||||
fieldByAttr: (attr) => `[data-test-field="${attr}"]`,
|
fieldByAttr: (attr) => `[data-test-field="${attr}"]`,
|
||||||
enableField: (attr) => `[data-test-enable-field="${attr}"] button`,
|
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}]`,
|
validation: (attr) => `[data-test-field-validation=${attr}]`,
|
||||||
validationWarning: (attr) => `[data-test-validation-warning=${attr}]`,
|
validationWarning: (attr) => `[data-test-validation-warning=${attr}]`,
|
||||||
messageError: '[data-test-message-error]',
|
messageError: '[data-test-message-error]',
|
||||||
|
|||||||
@@ -3,56 +3,73 @@
|
|||||||
* SPDX-License-Identifier: BUSL-1.1
|
* SPDX-License-Identifier: BUSL-1.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { resolve } from 'rsvp';
|
|
||||||
import EmberObject from '@ember/object';
|
|
||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import { setupRenderingTest } from 'ember-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 hbs from 'htmlbars-inline-precompile';
|
||||||
import sinon from 'sinon';
|
import { SELECTORS } from 'vault/tests/helpers/general-selectors';
|
||||||
import { create } from 'ember-cli-page-object';
|
|
||||||
import authConfigForm from 'vault/tests/pages/components/auth-config-form/options';
|
|
||||||
|
|
||||||
const component = create(authConfigForm);
|
|
||||||
|
|
||||||
module('Integration | Component | auth-config-form options', function (hooks) {
|
module('Integration | Component | auth-config-form options', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
|
setupMirage(hooks);
|
||||||
|
|
||||||
hooks.beforeEach(function () {
|
hooks.beforeEach(function () {
|
||||||
this.owner.lookup('service:flash-messages').registerTypes(['success']);
|
this.owner.lookup('service:flash-messages').registerTypes(['success']);
|
||||||
this.router = this.owner.lookup('service:router');
|
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({
|
this.router.reopen({
|
||||||
transitionTo() {
|
transitionTo() {
|
||||||
return {
|
return {
|
||||||
followRedirects() {
|
followRedirects() {
|
||||||
return resolve();
|
assert.ok('calls transitionTo on save');
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
replaceWith() {
|
|
||||||
return resolve();
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
|
||||||
|
|
||||||
test('it submits data correctly', async function (assert) {
|
this.server.post(`sys/mounts/auth/${this.path}/tune`, (schema, req) => {
|
||||||
assert.expect(1);
|
const payload = JSON.parse(req.requestBody);
|
||||||
const model = EmberObject.create({
|
const expected = {
|
||||||
tune() {
|
default_lease_ttl: '30s',
|
||||||
return resolve();
|
listing_visibility: 'unauth',
|
||||||
},
|
user_lockout_config: {
|
||||||
config: {
|
lockout_threshold: '7',
|
||||||
serialize() {
|
lockout_duration: '600s',
|
||||||
return {};
|
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}} />`);
|
await render(hbs`<AuthConfigForm::Options @model={{this.model}} />`);
|
||||||
component.save();
|
|
||||||
return settled().then(() => {
|
await click(SELECTORS.inputByAttr('config.listingVisibility'));
|
||||||
assert.strictEqual(model.config.serialize.callCount, 1, 'config serialize was called once');
|
|
||||||
});
|
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]');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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]'),
|
|
||||||
};
|
|
||||||
Reference in New Issue
Block a user