From b00d986be7e1a431e5b79b0112eba5e57410b24e Mon Sep 17 00:00:00 2001 From: claire bontempo <68122737+hellobontempo@users.noreply.github.com> Date: Mon, 21 Apr 2025 11:28:22 -0700 Subject: [PATCH] UI: Assume default auth path if custom path is not provided (#30300) * add default path and update base tests * finish okta, oidc-jwt and saml tests * fix test name --- ui/app/components/auth/form/base.hbs | 2 + ui/app/components/auth/form/base.ts | 7 ++ .../components/auth/form/base-test.js | 101 +++++++++++++++++- .../components/auth/form/oidc-jwt-test.js | 24 ++++- .../components/auth/form/okta-test.js | 20 +++- .../components/auth/form/saml-test.js | 20 +++- .../components/auth/form/test-helper.js | 57 +++++++--- 7 files changed, 210 insertions(+), 21 deletions(-) diff --git a/ui/app/components/auth/form/base.hbs b/ui/app/components/auth/form/base.hbs index 34107cc303..382e785377 100644 --- a/ui/app/components/auth/form/base.hbs +++ b/ui/app/components/auth/form/base.hbs @@ -3,6 +3,8 @@ SPDX-License-Identifier: BUSL-1.1 ~}} +{{! Auth methods with standard login flows use this template. To implement any custom logic, this template should be copied into a new hbs file for that method. }} +
{{yield to="namespace"}} diff --git a/ui/app/components/auth/form/base.ts b/ui/app/components/auth/form/base.ts index f28cd0e945..7be8d6dec4 100644 --- a/ui/app/components/auth/form/base.ts +++ b/ui/app/components/auth/form/base.ts @@ -42,6 +42,13 @@ export default class AuthBase extends Component { data[key] = formData.get(key); } + // If path is not included in the submitted form data, + // set it as the auth type which is the default path Vault expects. + // The "token" auth method does not support custom login paths. + if (this.args.authType !== 'token' && !Object.keys(data).includes('path')) { + data['path'] = this.args.authType; + } + this.login.unlinked().perform(data); } diff --git a/ui/tests/integration/components/auth/form/base-test.js b/ui/tests/integration/components/auth/form/base-test.js index 9c626e2abc..8cb548e52d 100644 --- a/ui/tests/integration/components/auth/form/base-test.js +++ b/ui/tests/integration/components/auth/form/base-test.js @@ -28,7 +28,25 @@ module('Integration | Component | auth | form | base', function (hooks) { hooks.beforeEach(function () { this.authType = 'github'; this.expectedFields = ['token']; - this.renderComponent = () => { + this.expectedSubmit = { + default: { path: 'github', token: 'mysupersecuretoken' }, + custom: { path: 'custom-github', token: 'mysupersecuretoken' }, + }; + this.renderComponent = ({ yieldBlock = false } = {}) => { + if (yieldBlock) { + return render(hbs` + + <:advancedSettings> + + + + `); + } return render(hbs` { + this.expectedSubmit = { + default: { password: 'password', path: 'ldap', username: 'matilda' }, + custom: { password: 'password', path: 'custom-ldap', username: 'matilda' }, + }; + this.renderComponent = ({ yieldBlock = false } = {}) => { + if (yieldBlock) { + return render(hbs` + + <:advancedSettings> + + + + `); + } return render(hbs` { + this.expectedSubmit = { + default: { password: 'password', path: 'radius', username: 'matilda' }, + custom: { password: 'password', path: 'custom-radius', username: 'matilda' }, + }; + this.renderComponent = ({ yieldBlock = false } = {}) => { + if (yieldBlock) { + return render(hbs` + + <:advancedSettings> + + + + `); + } return render(hbs` { + this.expectedSubmit = { + default: { token: 'mytoken' }, + // token doesn't support custom paths, so just test yielding functionality + custom: { token: 'mytoken', yield: 'yield-token' }, + }; + this.renderComponent = ({ yieldBlock = false } = {}) => { + if (yieldBlock) { + return render(hbs` + + <:advancedSettings> + + + + `); + } return render(hbs` { + this.expectedSubmit = { + default: { password: 'password', path: 'userpass', username: 'matilda' }, + custom: { password: 'password', path: 'custom-userpass', username: 'matilda' }, + }; + this.renderComponent = ({ yieldBlock = false } = {}) => { + if (yieldBlock) { + return render(hbs` + + <:advancedSettings> + + + + `); + } return render(hbs` { + this.renderComponent = ({ yieldBlock = false } = {}) => { + if (yieldBlock) { + return render(hbs` + + <:advancedSettings> + + + + `); + } return render(hbs` { + this.expectedSubmit = { + default: { path: 'okta', username: 'matilda', password: 'password' }, + custom: { path: 'custom-okta', username: 'matilda', password: 'password' }, + }; + this.renderComponent = ({ yieldBlock = false } = {}) => { + if (yieldBlock) { + return render(hbs` + + <:advancedSettings> + + + + `); + } return render(hbs` { + this.expectedSubmit = { + default: { path: 'saml', role: 'some-dev' }, + custom: { path: 'custom-saml', role: 'some-dev' }, + }; + this.renderComponent = ({ yieldBlock = false } = {}) => { + if (yieldBlock) { + return render(hbs` + + <:advancedSettings> + + + + `); + } return render(hbs` { }); }); - test('it submits expected form data', async function (assert) { - await this.renderComponent(); - const { options } = AUTH_METHOD_MAP.find((m) => m.authType === this.authType); - const { loginData } = options; - - for (const [field, value] of Object.entries(loginData)) { - await fillIn(GENERAL.inputByAttr(field), value); - } - await click(AUTH_FORM.login); - const [actual] = this.authenticateStub.lastCall.args; - assert.propEqual(actual.data, loginData, 'auth service "authenticate" method is called with form data'); - }); - test('it fires onError callback', async function (assert) { this.authenticateStub.throws('permission denied'); await this.renderComponent(); @@ -58,4 +45,48 @@ export default (test) => { const [actual] = this.onSuccess.lastCall.args; assert.strictEqual(actual, 'success!', 'it calls onSuccess'); }); + + test('it submits form data with defaults', async function (assert) { + await this.renderComponent(); + const { options } = AUTH_METHOD_MAP.find((m) => m.authType === this.authType); + const { loginData } = options; + + for (const [field, value] of Object.entries(loginData)) { + await fillIn(GENERAL.inputByAttr(field), value); + } + await click(AUTH_FORM.login); + const [actual] = this.authenticateStub.lastCall.args; + assert.propEqual( + actual.data, + this.expectedSubmit.default, + 'auth service "authenticate" method is called with form data' + ); + }); + + // not for testing real-world submit, that happens in acceptance tests + // component here just yields <:advancedSettings> to test form submits data from yielded inputs + test('it submits form data from yielded inputs', async function (assert) { + await this.renderComponent({ yieldBlock: true }); + const { options } = AUTH_METHOD_MAP.find((m) => m.authType === this.authType); + const { loginData } = options; + + for (const [field, value] of Object.entries(loginData)) { + await fillIn(GENERAL.inputByAttr(field), value); + } + + if (this.authType === 'token') { + // token doesn't support custom paths, so just test yielding functionality + await fillIn(GENERAL.inputByAttr('yield'), `yield-${this.authType}`); + } else { + await fillIn(GENERAL.inputByAttr('path'), `custom-${this.authType}`); + } + + await click(AUTH_FORM.login); + const [actual] = this.authenticateStub.lastCall.args; + assert.propEqual( + actual.data, + this.expectedSubmit.custom, + 'auth service "authenticate" method is called with yielded form data' + ); + }); };