mirror of
https://github.com/hashicorp/vault.git
synced 2026-02-03 20:40:45 -05:00
UI: Revert api service use for requests to sys/internal/ui/mounts (#31094)
* revert api service use for sys/internal/ui/mounts * add changelog * replace this.api.sys.internalUiListEnabledVisibleMounts with ajax request to sys/internal/ui/mounts
This commit is contained in:
parent
71cc2df947
commit
b97bdc4cff
14 changed files with 68 additions and 28 deletions
3
changelog/31094.txt
Normal file
3
changelog/31094.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
```release-note:bug
|
||||
ui: Revert camelizing of parameters returned from `sys/internal/ui/mounts` so mount paths match serve value
|
||||
```
|
||||
|
|
@ -23,6 +23,10 @@ export default class AuthRoute extends ClusterRouteBase {
|
|||
@service store;
|
||||
@service version;
|
||||
|
||||
get adapter() {
|
||||
return this.store.adapterFor('application');
|
||||
}
|
||||
|
||||
beforeModel() {
|
||||
return super.beforeModel().then(() => {
|
||||
return this.version.fetchFeatures();
|
||||
|
|
@ -89,10 +93,9 @@ export default class AuthRoute extends ClusterRouteBase {
|
|||
}
|
||||
|
||||
async fetchLoginSettings() {
|
||||
const adapter = this.store.adapterFor('application');
|
||||
try {
|
||||
// TODO update with api service when api-client is updated
|
||||
const response = await adapter.ajax(
|
||||
const response = await this.adapter.ajax(
|
||||
'/v1/sys/internal/ui/default-auth-methods',
|
||||
'GET',
|
||||
this.api.buildHeaders({ token: '' })
|
||||
|
|
@ -114,11 +117,13 @@ export default class AuthRoute extends ClusterRouteBase {
|
|||
|
||||
async fetchMounts() {
|
||||
try {
|
||||
const resp = await this.api.sys.internalUiListEnabledVisibleMounts(
|
||||
const { data } = await this.adapter.ajax(
|
||||
'/v1/sys/internal/ui/mounts',
|
||||
'GET',
|
||||
this.api.buildHeaders({ token: '' })
|
||||
);
|
||||
// return a falsy value if the object is empty
|
||||
return isEmptyValue(resp.auth) ? null : resp.auth;
|
||||
return isEmptyValue(data.auth) ? null : data.auth;
|
||||
} catch {
|
||||
// catch error if there's a problem fetching mount data (i.e. invalid namespace)
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ export default class VaultClusterDashboardRoute extends Route.extend(ClusterRout
|
|||
|
||||
async model() {
|
||||
const clusterModel = this.modelFor('vault.cluster');
|
||||
const adapter = this.store.adapterFor('application');
|
||||
const hasChroot = clusterModel?.hasChrootNamespace;
|
||||
const replication =
|
||||
hasChroot || clusterModel.replicationRedacted
|
||||
|
|
@ -40,9 +41,10 @@ export default class VaultClusterDashboardRoute extends Route.extend(ClusterRout
|
|||
};
|
||||
const requests = [
|
||||
this.getVaultConfiguration(hasChroot),
|
||||
this.api.sys.internalUiListEnabledVisibleMounts().catch(() => ({})),
|
||||
adapter.ajax('/v1/sys/internal/ui/mounts', 'GET').catch(() => ({})),
|
||||
];
|
||||
const [vaultConfiguration, { secret }] = await Promise.all(requests);
|
||||
const [vaultConfiguration, { data }] = await Promise.all(requests);
|
||||
const secret = data.secret;
|
||||
const secretsEngines = this.api
|
||||
.responseObjectToArray(secret, 'path')
|
||||
.map((engine) => new SecretsEngineResource(engine));
|
||||
|
|
|
|||
|
|
@ -8,12 +8,16 @@ import { service } from '@ember/service';
|
|||
import SecretsEngineResource from 'vault/resources/secrets/engine';
|
||||
|
||||
import type ApiService from 'vault/services/api';
|
||||
import type Store from '@ember-data/store';
|
||||
|
||||
export default class SecretsBackends extends Route {
|
||||
@service declare readonly api: ApiService;
|
||||
@service declare readonly store: Store;
|
||||
|
||||
async model() {
|
||||
const { secret } = await this.api.sys.internalUiListEnabledVisibleMounts();
|
||||
const adapter = this.store.adapterFor('application');
|
||||
const { data } = await adapter.ajax('/v1/sys/internal/ui/mounts', 'GET');
|
||||
const secret = data.secret;
|
||||
return this.api.responseObjectToArray(secret, 'path').map((engine) => new SecretsEngineResource(engine));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import type RouterService from '@ember/routing/router-service';
|
|||
import type ApiService from 'vault/services/api';
|
||||
import type PaginationService from 'vault/services/pagination';
|
||||
import type FlashMessageService from 'vault/services/flash-messages';
|
||||
import type Store from '@ember-data/store';
|
||||
import type { SearchSelectOption } from 'vault/app-types';
|
||||
|
||||
interface Args {
|
||||
|
|
@ -26,6 +27,7 @@ export default class DestinationSyncPageComponent extends Component<Args> {
|
|||
@service declare readonly api: ApiService;
|
||||
@service declare readonly flashMessages: FlashMessageService;
|
||||
@service declare readonly pagination: PaginationService;
|
||||
@service declare readonly store: Store;
|
||||
|
||||
constructor(owner: unknown, args: Args) {
|
||||
super(owner, args);
|
||||
|
|
@ -48,9 +50,11 @@ export default class DestinationSyncPageComponent extends Component<Args> {
|
|||
|
||||
// unable to use built-in fetch functionality of SearchSelect since we need to filter by kv type
|
||||
async fetchMounts() {
|
||||
const adapter = this.store.adapterFor('application');
|
||||
const mounts = [];
|
||||
try {
|
||||
const { secret } = await this.api.sys.internalUiListEnabledVisibleMounts();
|
||||
const { data } = await adapter.ajax('/v1/sys/internal/ui/mounts', 'GET');
|
||||
const secret = data.secret;
|
||||
if (secret) {
|
||||
for (const path in secret) {
|
||||
const { type, options } = secret[path as keyof typeof secret];
|
||||
|
|
|
|||
|
|
@ -130,13 +130,15 @@ module('Acceptance | auth login form', function (hooks) {
|
|||
});
|
||||
|
||||
test('it renders preferred mount view if "with" query param is a mount path with listing_visibility="unauth"', async function (assert) {
|
||||
await visit('/vault/auth?with=my-oidc%2F');
|
||||
await visit('/vault/auth?with=my_oidc%2F');
|
||||
await waitFor(AUTH_FORM.tabBtn('oidc'));
|
||||
assert.dom(AUTH_FORM.authForm('oidc')).exists();
|
||||
assert.dom(AUTH_FORM.tabBtn('oidc')).exists();
|
||||
assert.dom(GENERAL.inputByAttr('role')).exists();
|
||||
assert.dom(GENERAL.inputByAttr('path')).hasAttribute('type', 'hidden');
|
||||
assert.dom(GENERAL.inputByAttr('path')).hasValue('my-oidc/');
|
||||
assert
|
||||
.dom(GENERAL.inputByAttr('path'))
|
||||
.hasValue('my_oidc/', 'mount path matches server value and is not camelized');
|
||||
assert.dom(GENERAL.button('Sign in with other methods')).exists('"Sign in with other methods" renders');
|
||||
|
||||
assert.dom(GENERAL.selectByAttr('auth type')).doesNotExist('dropdown does not render');
|
||||
|
|
@ -151,7 +153,7 @@ module('Acceptance | auth login form', function (hooks) {
|
|||
.dom(AUTH_FORM.tabBtn('oidc'))
|
||||
.hasAttribute('aria-selected', 'true', 'it selects tab matching query param');
|
||||
assert.dom(GENERAL.inputByAttr('path')).hasAttribute('type', 'hidden');
|
||||
assert.dom(GENERAL.inputByAttr('path')).hasValue('my-oidc/');
|
||||
assert.dom(GENERAL.inputByAttr('path')).hasValue('my_oidc/');
|
||||
assert.dom(GENERAL.button('Sign in with other methods')).exists('"Sign in with other methods" renders');
|
||||
assert.dom(GENERAL.backButton).doesNotExist();
|
||||
});
|
||||
|
|
@ -390,7 +392,7 @@ module('Acceptance | auth login form', function (hooks) {
|
|||
await visit('/vault/auth');
|
||||
|
||||
this.server.get('/sys/internal/ui/mounts', (_, req) => {
|
||||
assert.strictEqual(req.requestHeaders['x-vault-namespace'], 'admin', 'header contains namespace');
|
||||
assert.strictEqual(req.requestHeaders['X-Vault-Namespace'], 'admin', 'header contains namespace');
|
||||
req.passthrough();
|
||||
});
|
||||
await typeIn(GENERAL.inputByAttr('namespace'), 'admin');
|
||||
|
|
@ -23,8 +23,8 @@ module('Acceptance | Enterprise | auth form custom login settings', function (ho
|
|||
`write test-ns/sys/namespaces/child -force`,
|
||||
`write sys/config/ui/login/default-auth/root-rule backup_auth_types=token default_auth_type=okta disable_inheritance=false namespace_path=""`,
|
||||
`write sys/config/ui/login/default-auth/ns-rule default_auth_type=ldap disable_inheritance=true namespace_path=test-ns`,
|
||||
`write sys/auth/my-oidc type=oidc`,
|
||||
`write sys/auth/my-oidc/tune listing_visibility="unauth"`,
|
||||
`write sys/auth/my_oidc type=oidc`,
|
||||
`write sys/auth/my_oidc/tune listing_visibility="unauth"`,
|
||||
]);
|
||||
return await logout();
|
||||
});
|
||||
|
|
@ -37,7 +37,7 @@ module('Acceptance | Enterprise | auth form custom login settings', function (ho
|
|||
await runCmd([
|
||||
'delete sys/config/ui/login/default-auth/root-rule',
|
||||
'delete sys/config/ui/login/default-auth/ns-rule',
|
||||
'delete sys/auth/my-oidc',
|
||||
'delete sys/auth/my_oidc',
|
||||
'delete test-ns/sys/namespaces/child',
|
||||
'delete sys/namespaces/test-ns',
|
||||
]);
|
||||
|
|
@ -73,7 +73,7 @@ module('Acceptance | Enterprise | auth form custom login settings', function (ho
|
|||
});
|
||||
|
||||
test('it ignores login settings if query param references a visible mount path', async function (assert) {
|
||||
await visit('/vault/auth?with=my-oidc%2F');
|
||||
await visit('/vault/auth?with=my_oidc%2F');
|
||||
await waitFor(AUTH_FORM.tabBtn('oidc'));
|
||||
assert
|
||||
.dom(AUTH_FORM.tabBtn('oidc'))
|
||||
|
|
|
|||
|
|
@ -33,6 +33,24 @@ module('Acceptance | secret-engine list view', function (hooks) {
|
|||
return login();
|
||||
});
|
||||
|
||||
// the new API service camelizes response keys, so this tests is to assert that does NOT happen when we re-implement it
|
||||
test('it does not camelize the secret mount path', async function (assert) {
|
||||
await visit('/vault/secrets');
|
||||
await page.enableEngine();
|
||||
await click(MOUNT_BACKEND_FORM.mountType('aws'));
|
||||
await fillIn(GENERAL.inputByAttr('path'), 'aws_engine');
|
||||
await click(GENERAL.submitButton);
|
||||
await click(GENERAL.breadcrumbLink('Secrets'));
|
||||
assert.strictEqual(
|
||||
currentRouteName(),
|
||||
'vault.cluster.secrets.backends',
|
||||
'breadcrumb navigates to the list page'
|
||||
);
|
||||
assert.dom(SES.secretsBackendLink('aws_engine')).hasTextContaining('aws_engine/');
|
||||
// cleanup
|
||||
await runCmd(deleteEngineCmd('aws_engine'));
|
||||
});
|
||||
|
||||
test('after enabling an unsupported engine it takes you to list page', async function (assert) {
|
||||
await visit('/vault/secrets');
|
||||
await page.enableEngine();
|
||||
|
|
|
|||
|
|
@ -102,7 +102,9 @@ export const SYS_INTERNAL_UI_MOUNTS = {
|
|||
options: {},
|
||||
type: 'userpass',
|
||||
},
|
||||
'my-oidc/': {
|
||||
// there was a problem with the API service camel-casing mounts that were snake cased
|
||||
// so including a snake cased mount for testing
|
||||
'my_oidc/': {
|
||||
description: '',
|
||||
options: {},
|
||||
type: 'oidc',
|
||||
|
|
|
|||
|
|
@ -158,7 +158,7 @@ export const RESPONSE_STUBS = {
|
|||
},
|
||||
num_uses: 0,
|
||||
orphan: true,
|
||||
path: 'auth/my-oidc/oidc/callback',
|
||||
path: 'auth/my_oidc/oidc/callback',
|
||||
policies: ['default'],
|
||||
renewable: true,
|
||||
ttl: 2764799,
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ module('Integration | Component | auth | form template', function (hooks) {
|
|||
],
|
||||
oidc: [
|
||||
{
|
||||
path: 'my-oidc/',
|
||||
path: 'my_oidc/',
|
||||
description: '',
|
||||
options: {},
|
||||
type: 'oidc',
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ module('Integration | Component | auth | page | listing visibility', function (h
|
|||
module('with a direct link', function (hooks) {
|
||||
hooks.beforeEach(function () {
|
||||
// if path exists, the mount has listing_visibility="unauth"
|
||||
this.directLinkIsVisibleMount = { path: 'my-oidc/', type: 'oidc' };
|
||||
this.directLinkIsVisibleMount = { path: 'my_oidc/', type: 'oidc' };
|
||||
this.directLinkIsJustType = { type: 'okta' };
|
||||
});
|
||||
|
||||
|
|
@ -106,7 +106,7 @@ module('Integration | Component | auth | page | listing visibility', function (h
|
|||
assert.dom(AUTH_FORM.tabs).exists({ count: 1 }, 'only one tab renders');
|
||||
assert.dom(GENERAL.inputByAttr('role')).exists();
|
||||
assert.dom(GENERAL.inputByAttr('path')).hasAttribute('type', 'hidden');
|
||||
assert.dom(GENERAL.inputByAttr('path')).hasValue('my-oidc/');
|
||||
assert.dom(GENERAL.inputByAttr('path')).hasValue('my_oidc/');
|
||||
assert.dom(GENERAL.button('Sign in with other methods')).exists('"Sign in with other methods" renders');
|
||||
assert.dom(GENERAL.selectByAttr('auth type')).doesNotExist();
|
||||
assert.dom(AUTH_FORM.advancedSettings).doesNotExist();
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ module('Integration | Component | auth | page | ent login settings', function (h
|
|||
await this.renderComponent();
|
||||
assert.dom(AUTH_FORM.tabBtn('oidc')).hasText('OIDC', 'it renders default method');
|
||||
assert.dom(AUTH_FORM.tabs).exists({ count: 1 }, 'only one tab renders');
|
||||
this.assertPathInput(assert, { isHidden: true, value: 'my-oidc/' });
|
||||
this.assertPathInput(assert, { isHidden: true, value: 'my_oidc/' });
|
||||
await click(GENERAL.button('Sign in with other methods'));
|
||||
assert.dom(AUTH_FORM.tabs).exists({ count: 2 }, 'it renders 2 backup type tabs');
|
||||
assert
|
||||
|
|
@ -137,7 +137,7 @@ module('Integration | Component | auth | page | ent login settings', function (h
|
|||
assert.dom(AUTH_FORM.tabBtn('oidc')).hasText('OIDC', 'it renders default method');
|
||||
assert.dom(AUTH_FORM.tabs).exists({ count: 1 }, 'only one tab renders');
|
||||
assert.dom(AUTH_FORM.authForm('oidc')).exists();
|
||||
this.assertPathInput(assert, { isHidden: true, value: 'my-oidc/' });
|
||||
this.assertPathInput(assert, { isHidden: true, value: 'my_oidc/' });
|
||||
assert.dom(GENERAL.backButton).doesNotExist();
|
||||
assert.dom(GENERAL.button('Sign in with other methods')).doesNotExist();
|
||||
});
|
||||
|
|
@ -165,9 +165,9 @@ module('Integration | Component | auth | page | ent login settings', function (h
|
|||
});
|
||||
|
||||
test('(default+backups): it hides advanced settings for default with visible mount but it renders for backups', async function (assert) {
|
||||
this.visibleAuthMounts = { ...this.mountData('my-oidc/') };
|
||||
this.visibleAuthMounts = { ...this.mountData('my_oidc/') };
|
||||
await this.renderComponent();
|
||||
this.assertPathInput(assert, { isHidden: true, value: 'my-oidc/' });
|
||||
this.assertPathInput(assert, { isHidden: true, value: 'my_oidc/' });
|
||||
await click(GENERAL.button('Sign in with other methods'));
|
||||
assert.dom(AUTH_FORM.tabBtn('userpass')).hasAttribute('aria-selected', 'true');
|
||||
await this.assertPathInput(assert);
|
||||
|
|
@ -178,7 +178,7 @@ module('Integration | Component | auth | page | ent login settings', function (h
|
|||
test('(default+backups): it only renders advanced settings for method without mounts', async function (assert) {
|
||||
// default and only one backup method have visible mounts
|
||||
this.visibleAuthMounts = {
|
||||
...this.mountData('my-oidc/'),
|
||||
...this.mountData('my_oidc/'),
|
||||
...this.mountData('userpass/'),
|
||||
...this.mountData('userpass2/'),
|
||||
};
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ module('Integration | Component | auth | tabs', function (hooks) {
|
|||
],
|
||||
oidc: [
|
||||
{
|
||||
path: 'my-oidc/',
|
||||
path: 'my_oidc/',
|
||||
description: '',
|
||||
options: {},
|
||||
type: 'oidc',
|
||||
|
|
@ -98,7 +98,7 @@ module('Integration | Component | auth | tabs', function (hooks) {
|
|||
this.selectedAuthMethod = 'oidc';
|
||||
await this.renderComponent();
|
||||
assert.dom(GENERAL.inputByAttr('path')).hasAttribute('type', 'hidden');
|
||||
assert.dom(GENERAL.inputByAttr('path')).hasValue('my-oidc/');
|
||||
assert.dom(GENERAL.inputByAttr('path')).hasValue('my_oidc/');
|
||||
});
|
||||
|
||||
test('it calls handleTabClick with tab method type', async function (assert) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue