mirror of
https://github.com/optim-enterprises-bv/vault.git
synced 2025-10-30 02:02:43 +00:00
UI: Only add user lockout config for supported methods (#25867)
* update model so only supported methods add user_lockout_config params * update auth config form to only show user lockout config for supported methods * add changelog
This commit is contained in:
3
changelog/25867.txt
Normal file
3
changelog/25867.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
```release-note:bug
|
||||||
|
ui: remove user_lockout_config settings for unsupported methods
|
||||||
|
```
|
||||||
@@ -30,20 +30,22 @@ 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 = {};
|
|
||||||
|
if (this.model.supportsUserLockoutConfig) {
|
||||||
|
data.user_lockout_config = {};
|
||||||
|
this.model.userLockoutConfig.apiParams.forEach((attr) => {
|
||||||
|
if (Object.keys(data).includes(attr)) {
|
||||||
|
data.user_lockout_config[attr] = data[attr];
|
||||||
|
delete data[attr];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import { withModelValidations } from 'vault/decorators/model-validations';
|
|||||||
import { allMethods } from 'vault/helpers/mountable-auth-methods';
|
import { allMethods } from 'vault/helpers/mountable-auth-methods';
|
||||||
import lazyCapabilities from 'vault/macros/lazy-capabilities';
|
import lazyCapabilities from 'vault/macros/lazy-capabilities';
|
||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
|
import { camelize } from '@ember/string';
|
||||||
|
|
||||||
const validations = {
|
const validations = {
|
||||||
path: [
|
path: [
|
||||||
@@ -68,6 +69,10 @@ export default class AuthMethodModel extends Model {
|
|||||||
return this.local ? 'local' : 'replicated';
|
return this.local ? 'local' : 'replicated';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get supportsUserLockoutConfig() {
|
||||||
|
return ['approle', 'ldap', 'userpass'].includes(this.methodType);
|
||||||
|
}
|
||||||
|
|
||||||
userLockoutConfig = {
|
userLockoutConfig = {
|
||||||
modelAttrs: [
|
modelAttrs: [
|
||||||
'config.lockoutThreshold',
|
'config.lockoutThreshold',
|
||||||
@@ -79,21 +84,21 @@ export default class AuthMethodModel extends Model {
|
|||||||
};
|
};
|
||||||
|
|
||||||
get tuneAttrs() {
|
get tuneAttrs() {
|
||||||
const { methodType } = this;
|
// order here determines order tune fields render
|
||||||
let tuneAttrs;
|
const tuneAttrs = [
|
||||||
// token_type should not be tuneable for the token auth method
|
'listingVisibility',
|
||||||
if (methodType === 'token') {
|
'defaultLeaseTtl',
|
||||||
tuneAttrs = [
|
'maxLeaseTtl',
|
||||||
'description',
|
...(this.methodType === 'token' ? [] : ['tokenType']),
|
||||||
'config.{listingVisibility,defaultLeaseTtl,maxLeaseTtl,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders,allowedResponseHeaders,pluginVersion,lockoutThreshold,lockoutDuration,lockoutCounterReset,lockoutDisable}',
|
'auditNonHmacRequestKeys',
|
||||||
];
|
'auditNonHmacResponseKeys',
|
||||||
} else {
|
'passthroughRequestHeaders',
|
||||||
tuneAttrs = [
|
'allowedResponseHeaders',
|
||||||
'description',
|
'pluginVersion',
|
||||||
'config.{listingVisibility,defaultLeaseTtl,maxLeaseTtl,tokenType,auditNonHmacRequestKeys,auditNonHmacResponseKeys,passthroughRequestHeaders,allowedResponseHeaders,pluginVersion,lockoutThreshold,lockoutDuration,lockoutCounterReset,lockoutDisable}',
|
...(this.supportsUserLockoutConfig ? this.userLockoutConfig.apiParams.map((a) => camelize(a)) : []),
|
||||||
];
|
];
|
||||||
}
|
|
||||||
return expandAttributeMeta(this, tuneAttrs);
|
return expandAttributeMeta(this, ['description', `config.{${tuneAttrs.join(',')}}`]);
|
||||||
}
|
}
|
||||||
|
|
||||||
get formFields() {
|
get formFields() {
|
||||||
|
|||||||
@@ -14,16 +14,18 @@
|
|||||||
{{/if}}
|
{{/if}}
|
||||||
{{/each}}
|
{{/each}}
|
||||||
|
|
||||||
<hr class="has-top-margin-xl has-bottom-margin-l has-background-gray-200" />
|
{{#if this.model.supportsUserLockoutConfig}}
|
||||||
<Hds::Text::Display @tag="h2" @size="400" @weight="bold">User lockout configuration</Hds::Text::Display>
|
<hr class="has-top-margin-xl has-bottom-margin-l has-background-gray-200" />
|
||||||
<Hds::Text::Body @tag="p" @size="100" @color="faint" class="has-bottom-margin-m">
|
<Hds::Text::Display @tag="h2" @size="400" @weight="bold" data-test-user-lockout-section>User lockout configuration</Hds::Text::Display>
|
||||||
Specifies the user lockout settings for this auth mount.
|
<Hds::Text::Body @tag="p" @size="100" @color="faint" class="has-bottom-margin-m">
|
||||||
</Hds::Text::Body>
|
Specifies the user lockout settings for this auth mount.
|
||||||
{{#each this.model.tuneAttrs as |attr|}}
|
</Hds::Text::Body>
|
||||||
{{#if (includes attr.name this.model.userLockoutConfig.modelAttrs)}}
|
{{#each this.model.tuneAttrs as |attr|}}
|
||||||
<FormField @attr={{attr}} @model={{this.model}} />
|
{{#if (includes attr.name this.model.userLockoutConfig.modelAttrs)}}
|
||||||
{{/if}}
|
<FormField @attr={{attr}} @model={{this.model}} />
|
||||||
{{/each}}
|
{{/if}}
|
||||||
|
{{/each}}
|
||||||
|
{{/if}}
|
||||||
</div>
|
</div>
|
||||||
<div class="field is-grouped box is-fullwidth is-bottomless">
|
<div class="field is-grouped box is-fullwidth is-bottomless">
|
||||||
<Hds::Button
|
<Hds::Button
|
||||||
|
|||||||
@@ -9,6 +9,12 @@ import { setupMirage } from 'ember-cli-mirage/test-support';
|
|||||||
import { click, fillIn, render } from '@ember/test-helpers';
|
import { click, fillIn, render } from '@ember/test-helpers';
|
||||||
import hbs from 'htmlbars-inline-precompile';
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
import { SELECTORS } from 'vault/tests/helpers/general-selectors';
|
import { SELECTORS } from 'vault/tests/helpers/general-selectors';
|
||||||
|
import { methods } from 'vault/helpers/mountable-auth-methods';
|
||||||
|
|
||||||
|
const userLockoutSupported = ['approle', 'ldap', 'userpass'];
|
||||||
|
const userLockoutUnsupported = methods()
|
||||||
|
.map((m) => m.type)
|
||||||
|
.filter((m) => !userLockoutSupported.includes(m));
|
||||||
|
|
||||||
module('Integration | Component | auth-config-form options', function (hooks) {
|
module('Integration | Component | auth-config-form options', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
@@ -18,57 +24,177 @@ module('Integration | Component | auth-config-form options', function (hooks) {
|
|||||||
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.store = this.owner.lookup('service:store');
|
||||||
this.path = 'my-auth-method/';
|
this.createModel = (path, type) => {
|
||||||
this.model = this.store.createRecord('auth-method', { path: this.path, type: 'approle' });
|
this.model = this.store.createRecord('auth-method', { path, type });
|
||||||
this.model.set('config', this.store.createRecord('mount-config'));
|
this.model.set('config', this.store.createRecord('mount-config'));
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it submits data correctly', async function (assert) {
|
for (const type of userLockoutSupported) {
|
||||||
assert.expect(2);
|
test(`it submits data correctly for ${type} method (supports user_lockout_config)`, async function (assert) {
|
||||||
|
assert.expect(3);
|
||||||
|
const path = `my-${type}-auth/`;
|
||||||
|
this.createModel(path, type);
|
||||||
|
|
||||||
|
this.router.reopen({
|
||||||
|
transitionTo() {
|
||||||
|
return {
|
||||||
|
followRedirects() {
|
||||||
|
assert.ok(true, `saving ${type} calls transitionTo on save`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.server.post(`sys/mounts/auth/${path}/tune`, (schema, req) => {
|
||||||
|
const payload = JSON.parse(req.requestBody);
|
||||||
|
const expected = {
|
||||||
|
default_lease_ttl: '30s',
|
||||||
|
listing_visibility: 'unauth',
|
||||||
|
token_type: 'default-batch',
|
||||||
|
user_lockout_config: {
|
||||||
|
lockout_threshold: '7',
|
||||||
|
lockout_duration: '600s',
|
||||||
|
lockout_counter_reset: '5s',
|
||||||
|
lockout_disable: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
assert.propEqual(payload, expected, `${type} method payload contains tune parameters`);
|
||||||
|
return { payload };
|
||||||
|
});
|
||||||
|
await render(hbs`<AuthConfigForm::Options @model={{this.model}} />`);
|
||||||
|
|
||||||
|
assert.dom('[data-test-user-lockout-section]').hasText('User lockout configuration');
|
||||||
|
|
||||||
|
await click(SELECTORS.inputByAttr('config.listingVisibility'));
|
||||||
|
await fillIn(SELECTORS.inputByAttr('config.tokenType'), 'default-batch');
|
||||||
|
|
||||||
|
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]');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const type of userLockoutUnsupported) {
|
||||||
|
if (type === 'token') return; // separate test below because does not include tokenType field
|
||||||
|
|
||||||
|
test(`it submits data correctly for ${type} auth method`, async function (assert) {
|
||||||
|
assert.expect(7);
|
||||||
|
|
||||||
|
const path = `my-${type}-auth/`;
|
||||||
|
this.createModel(path, type);
|
||||||
|
|
||||||
|
this.router.reopen({
|
||||||
|
transitionTo() {
|
||||||
|
return {
|
||||||
|
followRedirects() {
|
||||||
|
assert.ok(true, `saving ${type} calls transitionTo on save`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
this.server.post(`sys/mounts/auth/${path}/tune`, (schema, req) => {
|
||||||
|
const payload = JSON.parse(req.requestBody);
|
||||||
|
const expected = {
|
||||||
|
default_lease_ttl: '30s',
|
||||||
|
listing_visibility: 'unauth',
|
||||||
|
token_type: 'default-batch',
|
||||||
|
};
|
||||||
|
assert.propEqual(payload, expected, `${type} method payload contains tune parameters`);
|
||||||
|
return { payload };
|
||||||
|
});
|
||||||
|
await render(hbs`<AuthConfigForm::Options @model={{this.model}} />`);
|
||||||
|
|
||||||
|
assert
|
||||||
|
.dom('[data-test-user-lockout-section]')
|
||||||
|
.doesNotExist(`${type} method does not render user lockout section`);
|
||||||
|
|
||||||
|
await click(SELECTORS.inputByAttr('config.listingVisibility'));
|
||||||
|
await fillIn(SELECTORS.inputByAttr('config.tokenType'), 'default-batch');
|
||||||
|
|
||||||
|
await click(SELECTORS.ttl.toggle('Default Lease TTL'));
|
||||||
|
await fillIn(SELECTORS.ttl.input('Default Lease TTL'), '30');
|
||||||
|
|
||||||
|
assert
|
||||||
|
.dom(SELECTORS.inputByAttr('config.lockoutThreshold'))
|
||||||
|
.doesNotExist(`${type} method does not render lockout threshold`);
|
||||||
|
assert
|
||||||
|
.dom(SELECTORS.ttl.toggle('Lockout duration'))
|
||||||
|
.doesNotExist(`${type} method does not render lockout duration `);
|
||||||
|
assert
|
||||||
|
.dom(SELECTORS.ttl.toggle('Lockout counter reset'))
|
||||||
|
.doesNotExist(`${type} method does not render lockout counter reset`);
|
||||||
|
assert
|
||||||
|
.dom(SELECTORS.inputByAttr('config.lockoutDisable'))
|
||||||
|
.doesNotExist(`${type} method does not render lockout disable`);
|
||||||
|
|
||||||
|
await click('[data-test-save-config]');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
test('it submits data correctly for token auth method', async function (assert) {
|
||||||
|
assert.expect(8);
|
||||||
|
const type = 'token';
|
||||||
|
const path = `my-${type}-auth/`;
|
||||||
|
this.createModel(path, type);
|
||||||
|
|
||||||
this.router.reopen({
|
this.router.reopen({
|
||||||
transitionTo() {
|
transitionTo() {
|
||||||
return {
|
return {
|
||||||
followRedirects() {
|
followRedirects() {
|
||||||
assert.ok('calls transitionTo on save');
|
assert.ok(true, `saving token calls transitionTo on save`);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
this.server.post(`sys/mounts/auth/${this.path}/tune`, (schema, req) => {
|
this.server.post(`sys/mounts/auth/${path}/tune`, (schema, req) => {
|
||||||
const payload = JSON.parse(req.requestBody);
|
const payload = JSON.parse(req.requestBody);
|
||||||
const expected = {
|
const expected = {
|
||||||
default_lease_ttl: '30s',
|
default_lease_ttl: '30s',
|
||||||
listing_visibility: 'unauth',
|
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');
|
assert.propEqual(payload, expected, `${type} method payload contains tune parameters`);
|
||||||
return { payload };
|
return { payload };
|
||||||
});
|
});
|
||||||
await render(hbs`<AuthConfigForm::Options @model={{this.model}} />`);
|
await render(hbs`<AuthConfigForm::Options @model={{this.model}} />`);
|
||||||
|
|
||||||
await click(SELECTORS.inputByAttr('config.listingVisibility'));
|
assert
|
||||||
|
.dom(SELECTORS.inputByAttr('config.tokenType'))
|
||||||
|
.doesNotExist('does not render tokenType for token auth method');
|
||||||
|
|
||||||
|
await click(SELECTORS.inputByAttr('config.listingVisibility'));
|
||||||
await click(SELECTORS.ttl.toggle('Default Lease TTL'));
|
await click(SELECTORS.ttl.toggle('Default Lease TTL'));
|
||||||
await fillIn(SELECTORS.ttl.input('Default Lease TTL'), '30');
|
await fillIn(SELECTORS.ttl.input('Default Lease TTL'), '30');
|
||||||
|
|
||||||
await fillIn(SELECTORS.inputByAttr('config.lockoutThreshold'), '7');
|
assert.dom('[data-test-user-lockout-section]').doesNotExist('token does not render user lockout section');
|
||||||
|
assert
|
||||||
await click(SELECTORS.ttl.toggle('Lockout duration'));
|
.dom(SELECTORS.inputByAttr('config.lockoutThreshold'))
|
||||||
await fillIn(SELECTORS.ttl.input('Lockout duration'), '10');
|
.doesNotExist('token method does not render lockout threshold');
|
||||||
await fillIn(
|
assert
|
||||||
`${SELECTORS.inputByAttr('config.lockoutDuration')} ${SELECTORS.selectByAttr('ttl-unit')}`,
|
.dom(SELECTORS.ttl.toggle('Lockout duration'))
|
||||||
'm'
|
.doesNotExist('token method does not render lockout duration ');
|
||||||
);
|
assert
|
||||||
await click(SELECTORS.ttl.toggle('Lockout counter reset'));
|
.dom(SELECTORS.ttl.toggle('Lockout counter reset'))
|
||||||
await fillIn(SELECTORS.ttl.input('Lockout counter reset'), '5');
|
.doesNotExist('token method does not render lockout counter reset');
|
||||||
|
assert
|
||||||
await click(SELECTORS.inputByAttr('config.lockoutDisable'));
|
.dom(SELECTORS.inputByAttr('config.lockoutDisable'))
|
||||||
|
.doesNotExist('token method does not render lockout disable');
|
||||||
|
|
||||||
await click('[data-test-save-config]');
|
await click('[data-test-save-config]');
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user