mirror of
https://github.com/hashicorp/vault.git
synced 2025-08-22 23:21:08 +02:00
* change entity_id to camel casing, remove "backends" key from stored auth data * fix tokenExpirationEpoch returning NaN, use authSuccess in auth service tests * camel case mfa_requirement references * refactor auth service * implement api service for token method * implement api service in standard auth methods * add lookupSelf request to persistAuthData method in auht service instead of calling in components * implement api service in oidc-jwt component * implement api service in okta component * implement api service in saml component * use api service for wrapped_token query param * remaining test updates, enterprise tests and stabilize auth helpers * upate renew() to use new persistAuthData method, add a test * revert as this will be addressed upstream * rename supported-login-methods to auth-form-helpers and delete old supported-auth-backends helper, update tests * cleanup normalize after testing mfa validation for each auth method * update type declarations, set displayName in each method component * stabilize redirect tests by waiting for login before asserting url * stabilize tests * modernize typescript syntax, move error const to util * use mirage instead of vault server to resolve test race conditions * fix file import
280 lines
9.3 KiB
JavaScript
280 lines
9.3 KiB
JavaScript
/**
|
|
* Copyright (c) HashiCorp, Inc.
|
|
* SPDX-License-Identifier: BUSL-1.1
|
|
*/
|
|
|
|
import { module, test } from 'qunit';
|
|
import { setupRenderingTest } from 'ember-qunit';
|
|
import { setupMirage } from 'ember-cli-mirage/test-support';
|
|
import { constraintId, setupTotpMfaResponse } from 'vault/tests/helpers/mfa/mfa-helpers';
|
|
import setupTestContext from './setup-test-context';
|
|
import { ERROR_JWT_LOGIN } from 'vault/utils/auth-form-helpers';
|
|
import { overrideResponse } from 'vault/tests/helpers/stubs';
|
|
import sinon from 'sinon';
|
|
import { triggerMessageEvent, windowStub } from 'vault/tests/helpers/oidc-window-stub';
|
|
import { AUTH_FORM } from 'vault/tests/helpers/auth/auth-form-selectors';
|
|
import { GENERAL } from 'vault/tests/helpers/general-selectors';
|
|
import { MFA_SELECTORS } from 'vault/tests/helpers/mfa/mfa-selectors';
|
|
import { fillInLoginFields } from 'vault/tests/helpers/auth/auth-helpers';
|
|
import { click, fillIn, waitFor } from '@ember/test-helpers';
|
|
|
|
const mfaTests = (test) => {
|
|
test('it displays mfa requirement for default paths', async function (assert) {
|
|
const loginKeys = Object.keys(this.loginData);
|
|
assert.expect(3 + loginKeys.length);
|
|
this.stubRequests();
|
|
await this.renderComponent();
|
|
|
|
// Fill in login form
|
|
await fillIn(AUTH_FORM.selectMethod, this.authType);
|
|
await fillInLoginFields(this.loginData);
|
|
|
|
if (this.authType === 'oidc') {
|
|
// fires "message" event which methods that rely on popup windows wait for
|
|
// pass mount path which is used to set :mount param in the callback url => /auth/:mount/oidc/callback
|
|
triggerMessageEvent(this.path);
|
|
}
|
|
|
|
await click(GENERAL.submitButton);
|
|
await waitFor(MFA_SELECTORS.mfaForm);
|
|
assert
|
|
.dom(MFA_SELECTORS.mfaForm)
|
|
.hasText(
|
|
'Back Multi-factor authentication is enabled for your account. Enter your authentication code to log in. TOTP passcode Verify'
|
|
);
|
|
await click(GENERAL.backButton);
|
|
assert.dom(AUTH_FORM.form).exists('clicking back returns to auth form');
|
|
assert.dom(AUTH_FORM.selectMethod).hasValue(this.authType, 'preserves method type on back');
|
|
for (const field of loginKeys) {
|
|
assert.dom(GENERAL.inputByAttr(field)).hasValue('', `${field} input clears on back`);
|
|
}
|
|
});
|
|
|
|
test('it displays mfa requirement for custom paths', async function (assert) {
|
|
this.path = `${this.authType}-custom`;
|
|
const loginKeys = Object.keys(this.loginData);
|
|
assert.expect(3 + loginKeys.length);
|
|
this.stubRequests();
|
|
await this.renderComponent();
|
|
|
|
// Fill in login form
|
|
await fillIn(AUTH_FORM.selectMethod, this.authType);
|
|
// Toggle more options to input a custom mount path
|
|
await fillInLoginFields({ ...this.loginData, path: this.path }, { toggleOptions: true });
|
|
|
|
if (this.authType === 'oidc') {
|
|
// fires "message" event which methods that rely on popup windows wait for
|
|
triggerMessageEvent(this.path);
|
|
}
|
|
|
|
await click(GENERAL.submitButton);
|
|
await waitFor(MFA_SELECTORS.mfaForm);
|
|
assert
|
|
.dom(MFA_SELECTORS.mfaForm)
|
|
.hasText(
|
|
'Back Multi-factor authentication is enabled for your account. Enter your authentication code to log in. TOTP passcode Verify'
|
|
);
|
|
await click(GENERAL.backButton);
|
|
assert.dom(AUTH_FORM.form).exists('clicking back returns to auth form');
|
|
assert.dom(AUTH_FORM.selectMethod).hasValue(this.authType, 'preserves method type on back');
|
|
for (const field of loginKeys) {
|
|
assert.dom(GENERAL.inputByAttr(field)).hasValue('', `${field} input clears on back`);
|
|
}
|
|
});
|
|
|
|
test('it submits mfa requirement for default paths', async function (assert) {
|
|
assert.expect(2);
|
|
this.stubRequests();
|
|
await this.renderComponent();
|
|
|
|
const expectedOtp = '12345';
|
|
this.server.post('/sys/mfa/validate', async (_, req) => {
|
|
const [actualOtp] = JSON.parse(req.requestBody).mfa_payload[constraintId];
|
|
assert.true(true, 'it makes request to mfa validate endpoint');
|
|
assert.strictEqual(actualOtp, expectedOtp, 'payload contains otp');
|
|
});
|
|
|
|
// Fill in login form
|
|
await fillIn(AUTH_FORM.selectMethod, this.authType);
|
|
await fillInLoginFields(this.loginData);
|
|
|
|
if (this.authType === 'oidc') {
|
|
// fires "message" event which methods that rely on popup windows wait for
|
|
triggerMessageEvent(this.path);
|
|
}
|
|
|
|
await click(GENERAL.submitButton);
|
|
await waitFor(MFA_SELECTORS.mfaForm);
|
|
await fillIn(MFA_SELECTORS.passcode(0), expectedOtp);
|
|
await click(MFA_SELECTORS.validate);
|
|
});
|
|
|
|
test('it submits mfa requirement for custom paths', async function (assert) {
|
|
assert.expect(2);
|
|
this.path = `${this.authType}-custom`;
|
|
this.stubRequests();
|
|
await this.renderComponent();
|
|
|
|
const expectedOtp = '12345';
|
|
this.server.post('/sys/mfa/validate', async (_, req) => {
|
|
const [actualOtp] = JSON.parse(req.requestBody).mfa_payload[constraintId];
|
|
assert.true(true, 'it makes request to mfa validate endpoint');
|
|
assert.strictEqual(actualOtp, expectedOtp, 'payload contains otp');
|
|
});
|
|
|
|
// Fill in login form
|
|
await fillIn(AUTH_FORM.selectMethod, this.authType);
|
|
// Toggle more options to input a custom mount path
|
|
await fillInLoginFields({ ...this.loginData, path: this.path }, { toggleOptions: true });
|
|
|
|
if (this.authType === 'oidc') {
|
|
triggerMessageEvent(this.path);
|
|
}
|
|
|
|
await click(GENERAL.submitButton);
|
|
await waitFor(MFA_SELECTORS.mfaForm);
|
|
await fillIn(MFA_SELECTORS.passcode(0), expectedOtp);
|
|
await click(MFA_SELECTORS.validate);
|
|
});
|
|
};
|
|
|
|
module('Integration | Component | auth | page | mfa', function (hooks) {
|
|
setupRenderingTest(hooks);
|
|
setupMirage(hooks);
|
|
|
|
hooks.beforeEach(async function () {
|
|
setupTestContext(this);
|
|
// additional setup for oidc-jwt component
|
|
this.routerStub = sinon.stub(this.owner.lookup('service:router'), 'urlFor').returns('123-example.com');
|
|
});
|
|
|
|
hooks.afterEach(function () {
|
|
this.routerStub.restore();
|
|
});
|
|
|
|
module('github', function (hooks) {
|
|
hooks.beforeEach(async function () {
|
|
this.authType = 'github';
|
|
this.loginData = { token: 'mysupersecuretoken' };
|
|
this.path = this.authType;
|
|
this.stubRequests = () => {
|
|
this.server.post(`/auth/${this.path}/login`, () => setupTotpMfaResponse(this.path));
|
|
};
|
|
});
|
|
|
|
mfaTests(test);
|
|
});
|
|
|
|
module('jwt', function (hooks) {
|
|
hooks.beforeEach(async function () {
|
|
this.authType = 'jwt';
|
|
this.loginData = { role: 'some-dev', jwt: 'jwttoken' };
|
|
this.path = this.authType;
|
|
|
|
this.stubRequests = () => {
|
|
this.server.post('/auth/:path/oidc/auth_url', () =>
|
|
overrideResponse(400, { errors: [ERROR_JWT_LOGIN] })
|
|
);
|
|
this.server.post(`/auth/${this.path}/login`, () => setupTotpMfaResponse(this.path));
|
|
};
|
|
});
|
|
|
|
mfaTests(test);
|
|
});
|
|
|
|
module('oidc', function (hooks) {
|
|
hooks.beforeEach(async function () {
|
|
this.authType = 'oidc';
|
|
this.loginData = { role: 'some-dev' };
|
|
this.path = this.authType;
|
|
// Requests are stubbed in the order they are hit
|
|
this.stubRequests = () => {
|
|
this.server.post(`/auth/${this.path}/oidc/auth_url`, () => {
|
|
return { data: { auth_url: 'http://dev-foo-bar.com' } };
|
|
});
|
|
this.server.get(`/auth/${this.path}/oidc/callback`, () => setupTotpMfaResponse(this.path));
|
|
};
|
|
|
|
this.windowStub = windowStub();
|
|
});
|
|
|
|
hooks.afterEach(function () {
|
|
this.windowStub.restore();
|
|
});
|
|
|
|
mfaTests(test);
|
|
});
|
|
|
|
module('username and password methods', function (hooks) {
|
|
hooks.beforeEach(async function () {
|
|
this.loginData = { username: 'matilda', password: 'password' };
|
|
this.stubRequests = () => {
|
|
this.server.post(`/auth/${this.path}/login/matilda`, () => setupTotpMfaResponse(this.path));
|
|
};
|
|
});
|
|
|
|
module('ldap', function (hooks) {
|
|
hooks.beforeEach(async function () {
|
|
this.authType = 'ldap';
|
|
this.path = this.authType;
|
|
});
|
|
|
|
mfaTests(test);
|
|
});
|
|
|
|
module('okta', function (hooks) {
|
|
hooks.beforeEach(async function () {
|
|
this.authType = 'okta';
|
|
this.path = this.authType;
|
|
});
|
|
|
|
mfaTests(test);
|
|
});
|
|
|
|
module('radius', function (hooks) {
|
|
hooks.beforeEach(async function () {
|
|
this.authType = 'radius';
|
|
this.path = this.authType;
|
|
});
|
|
|
|
mfaTests(test);
|
|
});
|
|
|
|
module('userpass', function (hooks) {
|
|
hooks.beforeEach(async function () {
|
|
this.authType = 'userpass';
|
|
this.path = this.authType;
|
|
});
|
|
|
|
mfaTests(test);
|
|
});
|
|
});
|
|
|
|
// ENTERPRISE METHODS
|
|
module('saml', function (hooks) {
|
|
hooks.beforeEach(async function () {
|
|
this.version.type = 'enterprise';
|
|
this.authType = 'saml';
|
|
this.path = this.authType;
|
|
this.loginData = { role: 'some-dev' };
|
|
// Requests are stubbed in the order they are hit
|
|
this.stubRequests = () => {
|
|
this.server.post(`/auth/${this.path}/sso_service_url`, () => ({
|
|
data: {
|
|
sso_service_url: 'test/fake/sso/route',
|
|
token_poll_id: '1234',
|
|
},
|
|
}));
|
|
this.server.post(`/auth/${this.path}/token`, () => setupTotpMfaResponse(this.authType));
|
|
};
|
|
this.windowStub = windowStub();
|
|
});
|
|
|
|
hooks.afterEach(function () {
|
|
this.windowStub.restore();
|
|
});
|
|
|
|
mfaTests(test);
|
|
});
|
|
});
|