UI: Align auth method ttl with tune value (#26663)

* refactor findAll to use internal/ui/mounts when authenticated as well

* format ttl in details view

* include hours in format for easy comparison to CLI return

* Revert "include hours in format for easy comparison to CLI return"

This reverts commit 990aaf5d1e.

* add changelog

* revert adapter change

* add new adapter method instead of updating existing

* add test for ttl

* revert and use findAll again

* update mirage endpoints

* remove query obj

* Revert "update mirage endpoints"

This reverts commit f5fb333bf4.

* another one that snuck into a separate commit

* use adapterOption to manage endpoint logic

* add adapter tests

* Update changelog/26663.txt

Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com>

* add test that ttl inputs aren not checked

---------

Co-authored-by: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com>
This commit is contained in:
claire bontempo 2024-04-30 18:19:22 +01:00 committed by GitHub
parent 22b53ed099
commit 225cd9d19f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 134 additions and 30 deletions

3
changelog/26663.txt Normal file
View file

@ -0,0 +1,3 @@
```release-note:improvement
ui: Show computed values from `sys/internal/ui/mounts` endpoint for auth mount configuration view
```

View file

@ -21,20 +21,26 @@ export default ApplicationAdapter.extend({
findAll(store, type, sinceToken, snapshotRecordArray) {
const isUnauthenticated = snapshotRecordArray?.adapterOptions?.unauthenticated;
if (isUnauthenticated) {
// sys/internal/ui/mounts returns the actual value of the system TTL
// instead of '0' which just indicates the mount is using system defaults
const useMountsEndpoint = snapshotRecordArray?.adapterOptions?.useMountsEndpoint;
if (isUnauthenticated || useMountsEndpoint) {
const url = `/${this.urlPrefix()}/internal/ui/mounts`;
return this.ajax(url, 'GET', {
unauthenticated: true,
unauthenticated: isUnauthenticated,
})
.then((result) => {
return {
data: result.data.auth,
};
})
.catch(() => {
return {
data: {},
};
.catch((e) => {
if (isUnauthenticated) return { data: {} };
if (e instanceof AdapterError) {
set(e, 'policyPath', 'sys/internal/ui/mounts');
}
throw e;
});
}
return this.ajax(this.url(), 'GET').catch((e) => {

View file

@ -15,26 +15,28 @@ export default Route.extend({
model(params) {
const { path } = params;
return this.store.findAll('auth-method').then((modelArray) => {
const model = modelArray.find((m) => m.id === path);
if (!model) {
const error = new AdapterError();
set(error, 'httpStatus', 404);
throw error;
}
const supportManaged = supportedManagedAuthBackends();
if (!supportManaged.includes(model.methodType)) {
// do not fetch path-help for unmanaged auth types
model.set('paths', {
apiPath: model.apiPath,
paths: [],
return this.store
.findAll('auth-method', { adapterOptions: { useMountsEndpoint: true } })
.then((modelArray) => {
const model = modelArray.find((m) => m.id === path);
if (!model) {
const error = new AdapterError();
set(error, 'httpStatus', 404);
throw error;
}
const supportManaged = supportedManagedAuthBackends();
if (!supportManaged.includes(model.methodType)) {
// do not fetch path-help for unmanaged auth types
model.set('paths', {
apiPath: model.apiPath,
paths: [],
});
return model;
}
return this.pathHelp.getPaths(model.apiPath, path).then((paths) => {
model.set('paths', paths);
return model;
});
return model;
}
return this.pathHelp.getPaths(model.apiPath, path).then((paths) => {
model.set('paths', paths);
return model;
});
});
},
});

View file

@ -10,12 +10,14 @@
@alwaysRender={{not (is-empty-value (get @model attr.name))}}
@label={{or attr.options.label (to-label attr.name)}}
@value={{stringify (get @model attr.name)}}
@formatTtl={{eq attr.options.editType "ttl"}}
/>
{{else}}
<InfoTableRow
@alwaysRender={{not (is-empty-value (get @model attr.name))}}
@label={{or attr.options.label (to-label attr.name)}}
@value={{get @model attr.name}}
@formatTtl={{eq attr.options.editType "ttl"}}
/>
{{/if}}
{{/each}}

View file

@ -5,7 +5,7 @@
<PageHeader as |p|>
<p.top>
<Hds::Breadcrumb>
<Hds::Breadcrumb data-test-breadcrumbs>
<Hds::Breadcrumb::Item @text="Auth Methods" @route="vault.cluster.access.methods" />
<Hds::Breadcrumb::Item @text={{this.model.id}} @route="vault.cluster.access.method" @model={{this.model.id}} />
<Hds::Breadcrumb::Item @text="Configure" @current={{true}} />

View file

@ -3,11 +3,12 @@
* SPDX-License-Identifier: BUSL-1.1
*/
import { currentRouteName, settled } from '@ember/test-helpers';
import { click, currentRouteName, settled } from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { v4 as uuidv4 } from 'uuid';
import { GENERAL } from 'vault/tests/helpers/general-selectors';
import page from 'vault/tests/pages/settings/auth/enable';
import listPage from 'vault/tests/pages/access/methods';
import authPage from 'vault/tests/pages/auth';
@ -42,4 +43,26 @@ module('Acceptance | settings/auth/enable', function (hooks) {
await listPage.visit();
assert.ok(listPage.findLinkById(path), 'mount is present in the list');
});
test('it renders default config details', async function (assert) {
const path = `approle-config-${this.uid}`;
const type = 'approle';
await page.visit();
await page.enable(type, path);
// the config details is updated to query mount details from sys/internal/ui/mounts
// but we still want these forms to continue using sys/auth which returns 0 for default ttl values
// check tune form (right after enabling)
assert.dom(GENERAL.toggleInput('Default Lease TTL')).isNotChecked('default lease ttl is unset');
assert.dom(GENERAL.toggleInput('Max Lease TTL')).isNotChecked('max lease ttl is unset');
await click(GENERAL.breadcrumbAtIdx(1));
assert
.dom(GENERAL.infoRowValue('Default Lease TTL'))
.hasText('1 month 1 day', 'shows system default TTL');
assert.dom(GENERAL.infoRowValue('Max Lease TTL')).hasText('1 month 1 day', 'shows the proper max TTL');
// check edit form TTL values
await click('[data-test-configure-link]');
assert.dom(GENERAL.toggleInput('Default Lease TTL')).isNotChecked('default lease ttl is still unset');
assert.dom(GENERAL.toggleInput('Max Lease TTL')).isNotChecked('max lease ttl is still unset');
});
});

View file

@ -29,13 +29,14 @@ export const GENERAL = {
menuTrigger: '[data-test-popup-menu-trigger]',
listItem: '[data-test-list-item-link]',
// FORMS
checkboxByAttr: (attr: string) => `[data-test-checkbox="${attr}"]`,
enableField: (attr: string) => `[data-test-enable-field="${attr}"] button`,
fieldByAttr: (attr: string) => `[data-test-field="${attr}"]`,
infoRowLabel: (label: string) => `[data-test-row-label="${label}"]`,
infoRowValue: (label: string) => `[data-test-value-div="${label}"]`,
inputByAttr: (attr: string) => `[data-test-input="${attr}"]`,
selectByAttr: (attr: string) => `[data-test-select="${attr}"]`,
checkboxByAttr: (attr: string) => `[data-test-checkbox="${attr}"]`,
fieldByAttr: (attr: string) => `[data-test-field="${attr}"]`,
enableField: (attr: string) => `[data-test-enable-field="${attr}"] button`,
toggleInput: (attr: string) => `[data-test-toggle-input="${attr}"]`,
ttl: {
toggle: (attr: string) => `[data-test-toggle-label="${attr}"]`,
input: (attr: string) => `[data-test-ttl-value="${attr}"]`,

View file

@ -0,0 +1,67 @@
/**
* Copyright (c) HashiCorp, Inc.
* SPDX-License-Identifier: BUSL-1.1
*/
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
module('Unit | Adapter | auth method', function (hooks) {
setupTest(hooks);
setupMirage(hooks);
hooks.beforeEach(async function () {
this.store = this.owner.lookup('service:store');
this.mockResponse = {
data: {
auth: {
'approle/': {
accessor: 'auth_approle_43e5a627',
config: {
default_lease_ttl: 2764800,
force_no_cache: false,
listing_visibility: 'hidden',
max_lease_ttl: 2764800,
token_type: 'default-service',
},
uuid: '7a8bc146-76d0-3a9c-9feb-47a6713a85b3',
},
},
},
};
});
test('findAll makes request to correct endpoint with no adapterOptions', async function (assert) {
assert.expect(1);
this.server.get('sys/auth', () => {
assert.ok(true, 'request made to sys/auth when no options are passed to findAll');
return { data: this.mockResponse.data.auth };
});
await this.store.findAll('auth-method');
});
test('findAll makes request to correct endpoint when unauthenticated is true', async function (assert) {
assert.expect(1);
this.server.get('sys/internal/ui/mounts', () => {
assert.ok(true, 'request made to correct endpoint when unauthenticated');
return this.mockResponse;
});
await this.store.findAll('auth-method', { adapterOptions: { unauthenticated: true } });
});
test('findAll makes request to correct endpoint when useMountsEndpoint is true', async function (assert) {
assert.expect(1);
this.server.get('sys/internal/ui/mounts', () => {
assert.ok(true, 'request made to correct endpoint when useMountsEndpoint');
return this.mockResponse;
});
await this.store.findAll('auth-method', { adapterOptions: { useMountsEndpoint: true } });
});
});