/** * Copyright (c) HashiCorp, Inc. * SPDX-License-Identifier: BUSL-1.1 */ import { module, test } from 'qunit'; import { setupApplicationTest } from 'ember-qunit'; import { click, currentURL, visit, waitUntil, find, fillIn } from '@ember/test-helpers'; import { setupMirage } from 'ember-cli-mirage/test-support'; import { allSupportedAuthBackends, supportedAuthBackends } from 'vault/helpers/supported-auth-backends'; import VAULT_KEYS from 'vault/tests/helpers/vault-keys'; const AUTH_FORM = { method: '[data-test-select=auth-method]', token: '[data-test-token]', login: '[data-test-auth-submit]', }; const ENT_AUTH_METHODS = ['saml']; const { rootToken } = VAULT_KEYS; module('Acceptance | auth', function (hooks) { setupApplicationTest(hooks); setupMirage(hooks); test('auth query params', async function (assert) { const backends = supportedAuthBackends(); assert.expect(backends.length + 1); await visit('/vault/auth'); assert.strictEqual(currentURL(), '/vault/auth?with=token'); for (const backend of backends.reverse()) { await fillIn(AUTH_FORM.method, backend.type); assert.strictEqual( currentURL(), `/vault/auth?with=${backend.type}`, `has the correct URL for ${backend.type}` ); } }); test('it clears token when changing selected auth method', async function (assert) { await visit('/vault/auth'); await fillIn(AUTH_FORM.token, 'token'); await fillIn(AUTH_FORM.method, 'github'); await fillIn(AUTH_FORM.method, 'token'); assert.dom(AUTH_FORM.token).hasNoValue('it clears the token value when toggling methods'); }); module('it sends the right payload when authenticating', function (hooks) { hooks.beforeEach(function () { this.assertReq = () => {}; this.server.get('/auth/token/lookup-self', (schema, req) => { this.assertReq(req); req.passthrough(); }); this.server.post('/auth/:mount/login', (schema, req) => { // github only this.assertReq(req); req.passthrough(); }); this.server.post('/auth/:mount/oidc/auth_url', (schema, req) => { // For JWT and OIDC this.assertReq(req); req.passthrough(); }); this.server.post('/auth/:mount/login/:username', (schema, req) => { this.assertReq(req); req.passthrough(); }); this.server.put('/auth/:mount/sso_service_url', (schema, req) => { // SAML only (enterprise) this.assertReq(req); req.passthrough(); }); this.expected = { token: { url: '/v1/auth/token/lookup-self', payload: { 'X-Vault-Token': 'some-token', }, }, userpass: { url: '/v1/auth/custom-userpass/login/some-username', payload: { password: 'some-password', }, }, ldap: { url: '/v1/auth/custom-ldap/login/some-username', payload: { password: 'some-password', }, }, okta: { url: '/v1/auth/custom-okta/login/some-username', payload: { password: 'some-password', }, }, jwt: { url: '/v1/auth/custom-jwt/oidc/auth_url', payload: { redirect_uri: 'http://localhost:7357/ui/vault/auth/custom-jwt/oidc/callback', role: 'some-role', }, }, oidc: { url: '/v1/auth/custom-oidc/oidc/auth_url', payload: { redirect_uri: 'http://localhost:7357/ui/vault/auth/custom-oidc/oidc/callback', role: 'some-role', }, }, radius: { url: '/v1/auth/custom-radius/login/some-username', payload: { password: 'some-password', }, }, github: { url: '/v1/auth/custom-github/login', payload: { token: 'some-token', }, }, saml: { url: '/v1/auth/custom-saml/sso_service_url', payload: { role: 'some-role', }, }, }; }); for (const backend of allSupportedAuthBackends().reverse()) { test(`for ${backend.type} ${ ENT_AUTH_METHODS.includes(backend.type) ? '(enterprise)' : '' }`, async function (assert) { const { type } = backend; const expected = this.expected[type]; const isOidc = ['oidc', 'jwt'].includes(type); assert.expect(isOidc ? 6 : 2); this.assertReq = (req) => { const body = type === 'token' ? req.requestHeaders : JSON.parse(req.requestBody); if (isOidc && !body.role) { // OIDC and JWT auth form calls the endpoint every time the role or mount is updated. // if role is not provided, it means we haven't filled out the full info yet so don't // validate the payload until all data is provided // eslint-disable-next-line qunit/no-early-return return {}; } assert.strictEqual(req.url, expected.url, `${type} calls the correct URL`); Object.keys(expected.payload).forEach((expKey) => { assert.strictEqual( body[expKey], expected.payload[expKey], `${type} payload includes ${expKey} with expected value` ); }); }; await visit('/vault/auth'); await fillIn(AUTH_FORM.method, type); if (type !== 'token') { // set custom mount await click('[data-test-auth-form-options-toggle]'); await fillIn('[data-test-auth-form-mount-path]', `custom-${type}`); } backend.formAttributes.forEach(async (key) => { // fill in all form items, except JWT which is not rendered if (key === 'jwt') return; await fillIn(`[data-test-${key}]`, `some-${key}`); }); await click(AUTH_FORM.login); }); } }); test('it shows the push notification warning after submit', async function (assert) { assert.expect(1); this.server.get( '/auth/token/lookup-self', async () => { assert.ok( await waitUntil(() => find('[data-test-auth-message="push"]')), 'shows push notification message' ); return {}; }, { timing: 1000 } ); await visit('/vault/auth'); await fillIn(AUTH_FORM.method, 'token'); await click('[data-test-auth-submit]'); }); test('it does not call renew-self after successful login with non-renewable token', async function (assert) { this.server.post( '/auth/token/renew-self', () => new Error('should not call renew-self directly after logging in') ); await visit('/vault/auth'); await fillIn(AUTH_FORM.method, 'token'); await fillIn(AUTH_FORM.token, rootToken); await click('[data-test-auth-submit]'); assert.strictEqual(currentURL(), '/vault/dashboard'); }); });