diff --git a/changelog/31094.txt b/changelog/31094.txt new file mode 100644 index 0000000000..a05e2ce8ff --- /dev/null +++ b/changelog/31094.txt @@ -0,0 +1,3 @@ +```release-note:bug +ui: Revert camelizing of parameters returned from `sys/internal/ui/mounts` so mount paths match serve value +``` \ No newline at end of file diff --git a/ui/app/routes/vault/cluster/auth.js b/ui/app/routes/vault/cluster/auth.js index 5094335d3d..be32ae9054 100644 --- a/ui/app/routes/vault/cluster/auth.js +++ b/ui/app/routes/vault/cluster/auth.js @@ -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; diff --git a/ui/app/routes/vault/cluster/dashboard.js b/ui/app/routes/vault/cluster/dashboard.js index fbaffb4614..b4b33386a8 100644 --- a/ui/app/routes/vault/cluster/dashboard.js +++ b/ui/app/routes/vault/cluster/dashboard.js @@ -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)); diff --git a/ui/app/routes/vault/cluster/secrets/backends.ts b/ui/app/routes/vault/cluster/secrets/backends.ts index e6b5f877a1..e98382fb41 100644 --- a/ui/app/routes/vault/cluster/secrets/backends.ts +++ b/ui/app/routes/vault/cluster/secrets/backends.ts @@ -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)); } } diff --git a/ui/lib/sync/addon/components/secrets/page/destinations/destination/sync.ts b/ui/lib/sync/addon/components/secrets/page/destinations/destination/sync.ts index be44167969..ed2310666e 100644 --- a/ui/lib/sync/addon/components/secrets/page/destinations/destination/sync.ts +++ b/ui/lib/sync/addon/components/secrets/page/destinations/destination/sync.ts @@ -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 { @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 { // 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]; diff --git a/ui/tests/acceptance/auth/auth-test.js b/ui/tests/acceptance/auth/auth-login-test.js similarity index 98% rename from ui/tests/acceptance/auth/auth-test.js rename to ui/tests/acceptance/auth/auth-login-test.js index 63152a8f65..a63eb6ad2a 100644 --- a/ui/tests/acceptance/auth/auth-test.js +++ b/ui/tests/acceptance/auth/auth-login-test.js @@ -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'); diff --git a/ui/tests/acceptance/auth/login-settings-test.js b/ui/tests/acceptance/auth/login-settings-test.js index 2d96d533b0..777939dc72 100644 --- a/ui/tests/acceptance/auth/login-settings-test.js +++ b/ui/tests/acceptance/auth/login-settings-test.js @@ -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')) diff --git a/ui/tests/acceptance/secret-engine-list-view-test.js b/ui/tests/acceptance/secret-engine-list-view-test.js index 2214fc6342..7b7a5ab41f 100644 --- a/ui/tests/acceptance/secret-engine-list-view-test.js +++ b/ui/tests/acceptance/secret-engine-list-view-test.js @@ -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(); diff --git a/ui/tests/helpers/auth/auth-helpers.ts b/ui/tests/helpers/auth/auth-helpers.ts index 188cfeba3b..13c6a75ef8 100644 --- a/ui/tests/helpers/auth/auth-helpers.ts +++ b/ui/tests/helpers/auth/auth-helpers.ts @@ -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', diff --git a/ui/tests/helpers/auth/response-stubs.ts b/ui/tests/helpers/auth/response-stubs.ts index f5be60d340..4a96da246f 100644 --- a/ui/tests/helpers/auth/response-stubs.ts +++ b/ui/tests/helpers/auth/response-stubs.ts @@ -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, diff --git a/ui/tests/integration/components/auth/form-template-test.js b/ui/tests/integration/components/auth/form-template-test.js index 5723bf7e8e..e1ce6caf32 100644 --- a/ui/tests/integration/components/auth/form-template-test.js +++ b/ui/tests/integration/components/auth/form-template-test.js @@ -110,7 +110,7 @@ module('Integration | Component | auth | form template', function (hooks) { ], oidc: [ { - path: 'my-oidc/', + path: 'my_oidc/', description: '', options: {}, type: 'oidc', diff --git a/ui/tests/integration/components/auth/page/listing-visibility-test.js b/ui/tests/integration/components/auth/page/listing-visibility-test.js index 0d9576a395..f3ea3b932f 100644 --- a/ui/tests/integration/components/auth/page/listing-visibility-test.js +++ b/ui/tests/integration/components/auth/page/listing-visibility-test.js @@ -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(); diff --git a/ui/tests/integration/components/auth/page/login-settings-test.js b/ui/tests/integration/components/auth/page/login-settings-test.js index 8a0c6c9ab6..d9b48042b8 100644 --- a/ui/tests/integration/components/auth/page/login-settings-test.js +++ b/ui/tests/integration/components/auth/page/login-settings-test.js @@ -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/'), }; diff --git a/ui/tests/integration/components/auth/tabs-test.js b/ui/tests/integration/components/auth/tabs-test.js index 40fc14c0a1..b1b75684bc 100644 --- a/ui/tests/integration/components/auth/tabs-test.js +++ b/ui/tests/integration/components/auth/tabs-test.js @@ -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) {