2023-04-10 15:36:59 +00:00
|
|
|
|
/**
|
|
|
|
|
* Copyright (c) HashiCorp, Inc.
|
|
|
|
|
* SPDX-License-Identifier: MPL-2.0
|
|
|
|
|
*/
|
|
|
|
|
|
2022-05-05 20:05:13 +00:00
|
|
|
|
/* eslint-disable qunit/require-expect */
|
2022-11-28 15:44:52 +00:00
|
|
|
|
import { currentURL, find, findAll, visit, click } from '@ember/test-helpers';
|
2019-03-13 00:04:16 +00:00
|
|
|
|
import { module, skip, test } from 'qunit';
|
|
|
|
|
import { setupApplicationTest } from 'ember-qunit';
|
2019-09-26 18:47:07 +00:00
|
|
|
|
import { setupMirage } from 'ember-cli-mirage/test-support';
|
2020-07-28 17:59:14 +00:00
|
|
|
|
import a11yAudit from 'nomad-ui/tests/helpers/a11y-audit';
|
2018-07-10 07:39:02 +00:00
|
|
|
|
import Tokens from 'nomad-ui/tests/pages/settings/tokens';
|
2018-07-11 19:42:43 +00:00
|
|
|
|
import Jobs from 'nomad-ui/tests/pages/jobs/list';
|
|
|
|
|
import JobDetail from 'nomad-ui/tests/pages/jobs/detail';
|
|
|
|
|
import ClientDetail from 'nomad-ui/tests/pages/clients/detail';
|
2021-04-01 18:21:30 +00:00
|
|
|
|
import Layout from 'nomad-ui/tests/pages/layout';
|
2022-05-05 20:05:13 +00:00
|
|
|
|
import percySnapshot from '@percy/ember';
|
2022-09-14 15:27:48 +00:00
|
|
|
|
import faker from 'nomad-ui/mirage/faker';
|
2022-11-28 15:44:52 +00:00
|
|
|
|
import moment from 'moment';
|
|
|
|
|
import { run } from '@ember/runloop';
|
2022-12-15 18:11:28 +00:00
|
|
|
|
import { allScenarios } from '../../mirage/scenarios/default';
|
2023-03-29 19:06:25 +00:00
|
|
|
|
import {
|
|
|
|
|
selectChoose,
|
|
|
|
|
clickTrigger,
|
|
|
|
|
} from 'ember-power-select/test-support/helpers';
|
2017-09-19 14:47:10 +00:00
|
|
|
|
|
|
|
|
|
let job;
|
|
|
|
|
let node;
|
2017-10-14 18:53:48 +00:00
|
|
|
|
let managementToken;
|
|
|
|
|
let clientToken;
|
2021-12-28 14:45:20 +00:00
|
|
|
|
module('Acceptance | tokens', function (hooks) {
|
2019-03-13 00:04:16 +00:00
|
|
|
|
setupApplicationTest(hooks);
|
2019-03-13 01:09:19 +00:00
|
|
|
|
setupMirage(hooks);
|
2019-03-13 00:04:16 +00:00
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
hooks.beforeEach(function () {
|
2020-04-03 18:31:19 +00:00
|
|
|
|
window.localStorage.clear();
|
|
|
|
|
window.sessionStorage.clear();
|
2022-09-14 15:27:48 +00:00
|
|
|
|
faker.seed(1);
|
2019-03-14 06:44:53 +00:00
|
|
|
|
|
2017-09-19 14:47:10 +00:00
|
|
|
|
server.create('agent');
|
|
|
|
|
node = server.create('node');
|
|
|
|
|
job = server.create('job');
|
2017-10-14 18:53:48 +00:00
|
|
|
|
managementToken = server.create('token');
|
|
|
|
|
clientToken = server.create('token');
|
2019-03-13 00:04:16 +00:00
|
|
|
|
});
|
2017-09-19 14:47:10 +00:00
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
test('it passes an accessibility audit', async function (assert) {
|
2021-12-28 19:30:38 +00:00
|
|
|
|
assert.expect(1);
|
|
|
|
|
|
2020-07-28 17:59:14 +00:00
|
|
|
|
await Tokens.visit();
|
2020-08-25 15:56:02 +00:00
|
|
|
|
await a11yAudit(assert);
|
2020-07-28 17:59:14 +00:00
|
|
|
|
});
|
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
test('the token form sets the token in local storage', async function (assert) {
|
2019-03-13 00:04:16 +00:00
|
|
|
|
const { secretId } = managementToken;
|
2017-09-19 14:47:10 +00:00
|
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
|
await Tokens.visit();
|
2021-12-28 16:08:12 +00:00
|
|
|
|
assert.equal(
|
|
|
|
|
window.localStorage.nomadTokenSecret,
|
|
|
|
|
null,
|
|
|
|
|
'No token secret set'
|
|
|
|
|
);
|
2023-02-02 21:29:04 +00:00
|
|
|
|
assert.equal(document.title, 'Authorization - Mirage - Nomad');
|
2017-09-19 14:47:10 +00:00
|
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
|
await Tokens.secret(secretId).submit();
|
2021-12-28 16:08:12 +00:00
|
|
|
|
assert.equal(
|
|
|
|
|
window.localStorage.nomadTokenSecret,
|
|
|
|
|
secretId,
|
|
|
|
|
'Token secret was set'
|
|
|
|
|
);
|
2017-09-19 14:47:10 +00:00
|
|
|
|
});
|
|
|
|
|
|
2019-03-13 00:04:16 +00:00
|
|
|
|
// TODO: unskip once store.unloadAll reliably waits for in-flight requests to settle
|
2021-12-28 14:45:20 +00:00
|
|
|
|
skip('the x-nomad-token header gets sent with requests once it is set', async function (assert) {
|
2019-03-13 00:04:16 +00:00
|
|
|
|
const { secretId } = managementToken;
|
2017-09-19 14:47:10 +00:00
|
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
|
await JobDetail.visit({ id: job.id });
|
|
|
|
|
await ClientDetail.visit({ id: node.id });
|
2017-09-19 14:47:10 +00:00
|
|
|
|
|
2021-12-28 16:08:12 +00:00
|
|
|
|
assert.ok(
|
|
|
|
|
server.pretender.handledRequests.length > 1,
|
|
|
|
|
'Requests have been made'
|
|
|
|
|
);
|
2017-09-19 14:47:10 +00:00
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
server.pretender.handledRequests.forEach((req) => {
|
2020-05-21 00:46:29 +00:00
|
|
|
|
assert.notOk(getHeader(req, 'x-nomad-token'), `No token for ${req.url}`);
|
2019-03-13 00:04:16 +00:00
|
|
|
|
});
|
2017-09-19 14:47:10 +00:00
|
|
|
|
|
2019-03-14 18:35:46 +00:00
|
|
|
|
const requestPosition = server.pretender.handledRequests.length;
|
2018-07-10 07:39:02 +00:00
|
|
|
|
|
2019-03-14 18:35:46 +00:00
|
|
|
|
await Tokens.visit();
|
2019-03-14 06:44:53 +00:00
|
|
|
|
await Tokens.secret(secretId).submit();
|
2017-09-19 14:47:10 +00:00
|
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
|
await JobDetail.visit({ id: job.id });
|
|
|
|
|
await ClientDetail.visit({ id: node.id });
|
2017-09-19 14:47:10 +00:00
|
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
|
const newRequests = server.pretender.handledRequests.slice(requestPosition);
|
|
|
|
|
assert.ok(newRequests.length > 1, 'New requests have been made');
|
2017-09-19 14:47:10 +00:00
|
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
|
// Cross-origin requests can't have a token
|
2021-12-28 14:45:20 +00:00
|
|
|
|
newRequests.forEach((req) => {
|
2021-12-28 16:08:12 +00:00
|
|
|
|
assert.equal(
|
|
|
|
|
getHeader(req, 'x-nomad-token'),
|
|
|
|
|
secretId,
|
|
|
|
|
`Token set for ${req.url}`
|
|
|
|
|
);
|
2017-10-14 18:53:48 +00:00
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
test('an error message is shown when authenticating a token fails', async function (assert) {
|
2019-03-13 00:04:16 +00:00
|
|
|
|
const { secretId } = managementToken;
|
|
|
|
|
const bogusSecret = 'this-is-not-the-secret';
|
|
|
|
|
assert.notEqual(
|
|
|
|
|
secretId,
|
|
|
|
|
bogusSecret,
|
|
|
|
|
'bogus secret is not somehow coincidentally equal to the real secret'
|
|
|
|
|
);
|
2017-10-14 18:53:48 +00:00
|
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
|
await Tokens.visit();
|
|
|
|
|
await Tokens.secret(bogusSecret).submit();
|
2017-10-14 18:53:48 +00:00
|
|
|
|
|
2021-12-28 16:08:12 +00:00
|
|
|
|
assert.equal(
|
|
|
|
|
window.localStorage.nomadTokenSecret,
|
|
|
|
|
null,
|
|
|
|
|
'Token secret is discarded on failure'
|
|
|
|
|
);
|
2019-03-13 00:04:16 +00:00
|
|
|
|
assert.ok(Tokens.errorMessage, 'Token error message is shown');
|
|
|
|
|
assert.notOk(Tokens.successMessage, 'Token success message is not shown');
|
|
|
|
|
assert.equal(Tokens.policies.length, 0, 'No token policies are shown');
|
2017-10-14 18:53:48 +00:00
|
|
|
|
});
|
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
test('a success message and a special management token message are shown when authenticating succeeds', async function (assert) {
|
2019-03-13 00:04:16 +00:00
|
|
|
|
const { secretId } = managementToken;
|
2017-10-14 18:53:48 +00:00
|
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
|
await Tokens.visit();
|
|
|
|
|
await Tokens.secret(secretId).submit();
|
2017-10-14 18:53:48 +00:00
|
|
|
|
|
2022-05-05 20:05:13 +00:00
|
|
|
|
await percySnapshot(assert);
|
|
|
|
|
|
2019-03-13 00:04:16 +00:00
|
|
|
|
assert.ok(Tokens.successMessage, 'Token success message is shown');
|
|
|
|
|
assert.notOk(Tokens.errorMessage, 'Token error message is not shown');
|
|
|
|
|
assert.ok(Tokens.managementMessage, 'Token management message is shown');
|
|
|
|
|
assert.equal(Tokens.policies.length, 0, 'No token policies are shown');
|
2017-10-14 18:53:48 +00:00
|
|
|
|
});
|
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
test('a success message and associated policies are shown when authenticating succeeds', async function (assert) {
|
2019-03-13 00:04:16 +00:00
|
|
|
|
const { secretId } = clientToken;
|
|
|
|
|
const policy = clientToken.policies.models[0];
|
|
|
|
|
policy.update('description', 'Make sure there is a description');
|
2017-10-14 18:53:48 +00:00
|
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
|
await Tokens.visit();
|
|
|
|
|
await Tokens.secret(secretId).submit();
|
2017-10-14 18:53:48 +00:00
|
|
|
|
|
2019-03-13 00:04:16 +00:00
|
|
|
|
assert.ok(Tokens.successMessage, 'Token success message is shown');
|
|
|
|
|
assert.notOk(Tokens.errorMessage, 'Token error message is not shown');
|
2021-12-28 16:08:12 +00:00
|
|
|
|
assert.notOk(
|
|
|
|
|
Tokens.managementMessage,
|
|
|
|
|
'Token management message is not shown'
|
|
|
|
|
);
|
2019-03-13 00:04:16 +00:00
|
|
|
|
assert.equal(
|
|
|
|
|
Tokens.policies.length,
|
|
|
|
|
clientToken.policies.length,
|
|
|
|
|
'Each policy associated with the token is listed'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
const policyElement = Tokens.policies.objectAt(0);
|
|
|
|
|
|
|
|
|
|
assert.equal(policyElement.name, policy.name, 'Policy Name');
|
2021-12-28 16:08:12 +00:00
|
|
|
|
assert.equal(
|
|
|
|
|
policyElement.description,
|
|
|
|
|
policy.description,
|
|
|
|
|
'Policy Description'
|
|
|
|
|
);
|
2019-03-13 00:04:16 +00:00
|
|
|
|
assert.equal(policyElement.rules, policy.rules, 'Policy Rules');
|
2017-09-19 14:47:10 +00:00
|
|
|
|
});
|
2017-10-18 23:42:08 +00:00
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
test('setting a token clears the store', async function (assert) {
|
2019-03-13 00:04:16 +00:00
|
|
|
|
const { secretId } = clientToken;
|
2017-10-18 23:42:08 +00:00
|
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
|
await Jobs.visit();
|
2017-10-18 23:42:08 +00:00
|
|
|
|
assert.ok(find('.job-row'), 'Jobs found');
|
|
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
|
await Tokens.visit();
|
|
|
|
|
await Tokens.secret(secretId).submit();
|
2017-10-18 23:42:08 +00:00
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
server.pretender.get('/v1/jobs', function () {
|
2017-10-18 23:42:08 +00:00
|
|
|
|
return [200, {}, '[]'];
|
|
|
|
|
});
|
|
|
|
|
|
2019-03-14 06:44:53 +00:00
|
|
|
|
await Jobs.visit();
|
2017-10-18 23:42:08 +00:00
|
|
|
|
|
2019-03-13 00:04:16 +00:00
|
|
|
|
// If jobs are lingering in the store, they would show up
|
|
|
|
|
assert.notOk(find('[data-test-job-row]'), 'No jobs found');
|
|
|
|
|
});
|
2017-10-19 02:04:19 +00:00
|
|
|
|
|
2022-11-28 15:44:52 +00:00
|
|
|
|
test('it handles expiring tokens', async function (assert) {
|
|
|
|
|
// Soon-expiring token
|
|
|
|
|
const expiringToken = server.create('token', {
|
|
|
|
|
name: "Time's a-tickin",
|
|
|
|
|
expirationTime: moment().add(1, 'm').toDate(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await Tokens.visit();
|
|
|
|
|
|
|
|
|
|
// Token with no TTL
|
|
|
|
|
await Tokens.secret(clientToken.secretId).submit();
|
|
|
|
|
assert
|
|
|
|
|
.dom('[data-test-token-expiry]')
|
|
|
|
|
.doesNotExist('No expiry shown for regular token');
|
|
|
|
|
|
|
|
|
|
await Tokens.clear();
|
|
|
|
|
|
|
|
|
|
// https://ember-concurrency.com/docs/testing-debugging/
|
|
|
|
|
setTimeout(() => run.cancelTimers(), 500);
|
|
|
|
|
|
|
|
|
|
// Token with TTL
|
|
|
|
|
await Tokens.secret(expiringToken.secretId).submit();
|
|
|
|
|
assert
|
|
|
|
|
.dom('[data-test-token-expiry]')
|
|
|
|
|
.exists('Expiry shown for TTL-having token');
|
|
|
|
|
|
|
|
|
|
// TTL Action
|
|
|
|
|
await Jobs.visit();
|
|
|
|
|
assert
|
2023-03-02 18:52:16 +00:00
|
|
|
|
.dom('.flash-message.alert-warning button')
|
2022-11-28 15:44:52 +00:00
|
|
|
|
.exists('A global alert exists and has a clickable button');
|
|
|
|
|
|
2023-03-02 18:52:16 +00:00
|
|
|
|
await click('.flash-message.alert-warning button');
|
2022-11-28 15:44:52 +00:00
|
|
|
|
assert.equal(
|
|
|
|
|
currentURL(),
|
|
|
|
|
'/settings/tokens',
|
|
|
|
|
'Redirected to tokens page on notification action'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('it handles expired tokens', async function (assert) {
|
|
|
|
|
const expiredToken = server.create('token', {
|
|
|
|
|
name: 'Well past due',
|
|
|
|
|
expirationTime: moment().add(-5, 'm').toDate(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// GC'd or non-existent token, from localStorage or otherwise
|
|
|
|
|
window.localStorage.nomadTokenSecret = expiredToken.secretId;
|
|
|
|
|
await Tokens.visit();
|
|
|
|
|
assert
|
|
|
|
|
.dom('[data-test-token-expired]')
|
|
|
|
|
.exists('Warning banner shown for expired token');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('it forces redirect on an expired token', async function (assert) {
|
|
|
|
|
const expiredToken = server.create('token', {
|
|
|
|
|
name: 'Well past due',
|
|
|
|
|
expirationTime: moment().add(-5, 'm').toDate(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
window.localStorage.nomadTokenSecret = expiredToken.secretId;
|
|
|
|
|
const expiredServerError = {
|
|
|
|
|
errors: [
|
|
|
|
|
{
|
|
|
|
|
detail: 'ACL token expired',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
server.pretender.get('/v1/jobs', function () {
|
|
|
|
|
return [500, {}, JSON.stringify(expiredServerError)];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await Jobs.visit();
|
|
|
|
|
assert.equal(
|
|
|
|
|
currentURL(),
|
|
|
|
|
'/settings/tokens',
|
|
|
|
|
'Redirected to tokens page due to an expired token'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('it forces redirect on a not-found token', async function (assert) {
|
|
|
|
|
const longDeadToken = server.create('token', {
|
|
|
|
|
name: 'dead and gone',
|
|
|
|
|
expirationTime: moment().add(-5, 'h').toDate(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
window.localStorage.nomadTokenSecret = longDeadToken.secretId;
|
|
|
|
|
const notFoundServerError = {
|
|
|
|
|
errors: [
|
|
|
|
|
{
|
|
|
|
|
detail: 'ACL token not found',
|
|
|
|
|
},
|
|
|
|
|
],
|
|
|
|
|
};
|
|
|
|
|
server.pretender.get('/v1/jobs', function () {
|
|
|
|
|
return [500, {}, JSON.stringify(notFoundServerError)];
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await Jobs.visit();
|
|
|
|
|
assert.equal(
|
|
|
|
|
currentURL(),
|
|
|
|
|
'/settings/tokens',
|
|
|
|
|
'Redirected to tokens page due to a token not being found'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('it notifies you when your token has 10 minutes remaining', async function (assert) {
|
|
|
|
|
let notificationRendered = assert.async();
|
|
|
|
|
let notificationNotRendered = assert.async();
|
|
|
|
|
window.localStorage.clear();
|
|
|
|
|
assert.equal(
|
|
|
|
|
window.localStorage.nomadTokenSecret,
|
|
|
|
|
null,
|
|
|
|
|
'No token secret set'
|
|
|
|
|
);
|
|
|
|
|
assert.timeout(6000);
|
|
|
|
|
const nearlyExpiringToken = server.create('token', {
|
|
|
|
|
name: 'Not quite dead yet',
|
|
|
|
|
expirationTime: moment().add(10, 'm').add(5, 's').toDate(),
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
await Tokens.visit();
|
|
|
|
|
|
|
|
|
|
// Ember Concurrency makes testing iterations convoluted: https://ember-concurrency.com/docs/testing-debugging/
|
|
|
|
|
// Waiting for half a second to validate that there's no warning;
|
|
|
|
|
// then a further 5 seconds to validate that there is a warning, and to explicitly cancelAllTimers(),
|
|
|
|
|
// short-circuiting our Ember Concurrency loop.
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
assert
|
2023-03-02 18:52:16 +00:00
|
|
|
|
.dom('.flash-message.alert-warning')
|
2022-11-28 15:44:52 +00:00
|
|
|
|
.doesNotExist('No notification yet for a token with 10m5s left');
|
|
|
|
|
notificationNotRendered();
|
|
|
|
|
setTimeout(async () => {
|
2022-11-30 19:33:07 +00:00
|
|
|
|
await percySnapshot(assert, {
|
|
|
|
|
percyCSS: '[data-test-expiration-timestamp] { display: none; }',
|
|
|
|
|
});
|
|
|
|
|
|
2022-11-28 15:44:52 +00:00
|
|
|
|
assert
|
2023-03-02 18:52:16 +00:00
|
|
|
|
.dom('.flash-message.alert-warning')
|
2022-11-28 15:44:52 +00:00
|
|
|
|
.exists('Notification is rendered at the 10m mark');
|
|
|
|
|
notificationRendered();
|
|
|
|
|
run.cancelTimers();
|
|
|
|
|
}, 5000);
|
|
|
|
|
}, 500);
|
|
|
|
|
await Tokens.secret(nearlyExpiringToken.secretId).submit();
|
|
|
|
|
});
|
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
test('when the ott query parameter is present upon application load it’s exchanged for a token', async function (assert) {
|
2021-04-01 18:21:30 +00:00
|
|
|
|
const { oneTimeSecret, secretId } = managementToken;
|
|
|
|
|
|
|
|
|
|
await JobDetail.visit({ id: job.id, ott: oneTimeSecret });
|
2021-04-13 16:56:59 +00:00
|
|
|
|
|
2021-12-28 16:08:12 +00:00
|
|
|
|
assert.notOk(
|
|
|
|
|
currentURL().includes(oneTimeSecret),
|
|
|
|
|
'OTT is cleared from the URL after loading'
|
|
|
|
|
);
|
2021-04-13 16:56:59 +00:00
|
|
|
|
|
2021-04-01 18:21:30 +00:00
|
|
|
|
await Tokens.visit();
|
|
|
|
|
|
2021-12-28 16:08:12 +00:00
|
|
|
|
assert.equal(
|
|
|
|
|
window.localStorage.nomadTokenSecret,
|
|
|
|
|
secretId,
|
|
|
|
|
'Token secret was set'
|
|
|
|
|
);
|
2021-04-01 18:21:30 +00:00
|
|
|
|
});
|
|
|
|
|
|
2022-11-28 15:44:52 +00:00
|
|
|
|
test('SSO Sign-in flow: Manager', async function (assert) {
|
|
|
|
|
server.create('auth-method', { name: 'vault' });
|
|
|
|
|
server.create('auth-method', { name: 'cognito' });
|
|
|
|
|
server.create('token', { name: 'Thelonious' });
|
|
|
|
|
|
|
|
|
|
await Tokens.visit();
|
|
|
|
|
assert.dom('[data-test-auth-method]').exists({ count: 2 });
|
|
|
|
|
await click('button[data-test-auth-method]');
|
|
|
|
|
assert.ok(currentURL().startsWith('/oidc-mock'));
|
|
|
|
|
let managerButton = [...findAll('button')].filter((btn) =>
|
|
|
|
|
btn.textContent.includes('Sign In as Manager')
|
|
|
|
|
)[0];
|
|
|
|
|
|
|
|
|
|
assert.dom(managerButton).exists();
|
|
|
|
|
await click(managerButton);
|
|
|
|
|
|
|
|
|
|
await percySnapshot(assert);
|
|
|
|
|
|
|
|
|
|
assert.ok(currentURL().startsWith('/settings/tokens'));
|
|
|
|
|
assert.dom('[data-test-token-name]').includesText('Token: Manager');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('SSO Sign-in flow: Regular User', async function (assert) {
|
|
|
|
|
server.create('auth-method', { name: 'vault' });
|
|
|
|
|
server.create('token', { name: 'Thelonious' });
|
|
|
|
|
|
|
|
|
|
await Tokens.visit();
|
|
|
|
|
assert.dom('[data-test-auth-method]').exists({ count: 1 });
|
|
|
|
|
await click('button[data-test-auth-method]');
|
|
|
|
|
assert.ok(currentURL().startsWith('/oidc-mock'));
|
|
|
|
|
let newTokenButton = [...findAll('button')].filter((btn) =>
|
|
|
|
|
btn.textContent.includes('Sign In as Thelonious')
|
|
|
|
|
)[0];
|
|
|
|
|
assert.dom(newTokenButton).exists();
|
|
|
|
|
await click(newTokenButton);
|
|
|
|
|
|
|
|
|
|
assert.ok(currentURL().startsWith('/settings/tokens'));
|
|
|
|
|
assert.dom('[data-test-token-name]').includesText('Token: Thelonious');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('It shows an error on failed SSO', async function (assert) {
|
|
|
|
|
server.create('auth-method', { name: 'vault' });
|
|
|
|
|
await visit('/settings/tokens?state=failure');
|
|
|
|
|
assert.ok(Tokens.ssoErrorMessage);
|
|
|
|
|
await Tokens.clearSSOError();
|
|
|
|
|
assert.equal(currentURL(), '/settings/tokens', 'State query param cleared');
|
|
|
|
|
assert.notOk(Tokens.ssoErrorMessage);
|
|
|
|
|
|
|
|
|
|
await click('button[data-test-auth-method]');
|
|
|
|
|
assert.ok(currentURL().startsWith('/oidc-mock'));
|
|
|
|
|
|
|
|
|
|
let failureButton = find('.button.error');
|
|
|
|
|
assert.dom(failureButton).exists();
|
|
|
|
|
await click(failureButton);
|
|
|
|
|
assert.equal(
|
|
|
|
|
currentURL(),
|
|
|
|
|
'/settings/tokens?state=failure',
|
|
|
|
|
'Redirected with failure state'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
await percySnapshot(assert);
|
|
|
|
|
assert.ok(Tokens.ssoErrorMessage);
|
|
|
|
|
});
|
|
|
|
|
|
2023-03-29 19:06:25 +00:00
|
|
|
|
test('JWT Sign-in flow: OIDC methods only', async function (assert) {
|
|
|
|
|
server.create('auth-method', { name: 'Vault', type: 'OIDC' });
|
|
|
|
|
server.create('auth-method', { name: 'Auth0', type: 'OIDC' });
|
|
|
|
|
await Tokens.visit();
|
|
|
|
|
assert
|
|
|
|
|
.dom('[data-test-auth-method]')
|
|
|
|
|
.exists({ count: 2 }, 'Both OIDC methods shown');
|
|
|
|
|
assert
|
|
|
|
|
.dom('label[for="token-input"]')
|
|
|
|
|
.hasText(
|
|
|
|
|
'Secret ID',
|
|
|
|
|
'Secret ID input shown without JWT info when no such method exists'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('JWT Sign-in flow: JWT method', async function (assert) {
|
|
|
|
|
server.create('auth-method', { name: 'Vault', type: 'OIDC' });
|
|
|
|
|
server.create('auth-method', { name: 'Auth0', type: 'OIDC' });
|
|
|
|
|
server.create('auth-method', { name: 'JWT-Local', type: 'JWT' });
|
|
|
|
|
await Tokens.visit();
|
|
|
|
|
assert
|
|
|
|
|
.dom('[data-test-auth-method]')
|
|
|
|
|
.exists(
|
|
|
|
|
{ count: 2 },
|
|
|
|
|
'The newly added JWT method does not add a 3rd Auth Method button'
|
|
|
|
|
);
|
|
|
|
|
assert
|
|
|
|
|
.dom('label[for="token-input"]')
|
|
|
|
|
.hasText('Secret ID or JWT', 'Secret ID input now shows JWT info');
|
|
|
|
|
|
|
|
|
|
// Expect to be signed in as a manager
|
|
|
|
|
await Tokens.secret(
|
|
|
|
|
'aaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.management'
|
|
|
|
|
).submit();
|
|
|
|
|
assert.ok(currentURL().startsWith('/settings/tokens'));
|
|
|
|
|
assert.dom('[data-test-token-name]').includesText('Token: Manager');
|
|
|
|
|
await Tokens.clear();
|
|
|
|
|
|
|
|
|
|
// Expect to be signed in as a client
|
|
|
|
|
await Tokens.secret(
|
|
|
|
|
'aaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.whateverlol'
|
|
|
|
|
).submit();
|
|
|
|
|
assert.ok(currentURL().startsWith('/settings/tokens'));
|
|
|
|
|
assert.dom('[data-test-token-name]').includesText(
|
|
|
|
|
`Token: ${
|
|
|
|
|
server.db.tokens.filter((token) => {
|
|
|
|
|
return token.type === 'client';
|
|
|
|
|
})[0].name
|
|
|
|
|
}`
|
|
|
|
|
);
|
|
|
|
|
await Tokens.clear();
|
|
|
|
|
|
|
|
|
|
// Expect to an error on bad JWT
|
|
|
|
|
await Tokens.secret(
|
|
|
|
|
'aaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.bad'
|
|
|
|
|
).submit();
|
|
|
|
|
assert.ok(currentURL().startsWith('/settings/tokens'));
|
|
|
|
|
assert.dom('[data-test-token-error]').exists();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('JWT Sign-in flow: JWT Method Selector, Single JWT', async function (assert) {
|
|
|
|
|
server.create('auth-method', { name: 'Vault', type: 'OIDC' });
|
|
|
|
|
server.create('auth-method', { name: 'Auth0', type: 'OIDC' });
|
|
|
|
|
server.create('auth-method', { name: 'JWT-Local', type: 'JWT' });
|
|
|
|
|
await Tokens.visit();
|
|
|
|
|
assert
|
|
|
|
|
.dom('[data-test-token-submit]')
|
|
|
|
|
.exists(
|
|
|
|
|
{ count: 1 },
|
|
|
|
|
'Submit token/JWT button exists with only a single JWT '
|
|
|
|
|
);
|
|
|
|
|
assert
|
|
|
|
|
.dom('[data-test-token-submit]')
|
|
|
|
|
.hasText(
|
|
|
|
|
'Sign in with secret',
|
|
|
|
|
'Submit token/JWT button has correct text with only a single JWT '
|
|
|
|
|
);
|
|
|
|
|
await Tokens.secret('very-short-secret');
|
|
|
|
|
assert
|
|
|
|
|
.dom('[data-test-token-submit]')
|
|
|
|
|
.hasText(
|
|
|
|
|
'Sign in with secret',
|
|
|
|
|
'A short secret still shows the "secret" verbiage on the button'
|
|
|
|
|
);
|
|
|
|
|
await Tokens.secret(
|
|
|
|
|
'aaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.whateverlol'
|
|
|
|
|
);
|
|
|
|
|
assert
|
|
|
|
|
.dom('[data-test-token-submit]')
|
|
|
|
|
.hasText(
|
|
|
|
|
'Sign in with JWT',
|
|
|
|
|
'A JWT-shaped secret will change button text to reflect JWT sign-in'
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert
|
|
|
|
|
.dom('[data-test-select-jwt]')
|
|
|
|
|
.doesNotExist('No JWT selector shown with only a single method');
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('JWT Sign-in flow: JWT Method Selector, Multiple JWT', async function (assert) {
|
|
|
|
|
server.create('auth-method', { name: 'Vault', type: 'OIDC' });
|
|
|
|
|
server.create('auth-method', { name: 'Auth0', type: 'OIDC' });
|
|
|
|
|
server.create('auth-method', {
|
|
|
|
|
name: 'JWT-Local',
|
|
|
|
|
type: 'JWT',
|
|
|
|
|
default: false,
|
|
|
|
|
});
|
|
|
|
|
server.create('auth-method', {
|
|
|
|
|
name: 'JWT-Regional',
|
|
|
|
|
type: 'JWT',
|
|
|
|
|
default: false,
|
|
|
|
|
});
|
|
|
|
|
server.create('auth-method', {
|
|
|
|
|
name: 'JWT-Global',
|
|
|
|
|
type: 'JWT',
|
|
|
|
|
default: true,
|
|
|
|
|
});
|
|
|
|
|
await Tokens.visit();
|
|
|
|
|
assert
|
|
|
|
|
.dom('[data-test-token-submit]')
|
|
|
|
|
.exists(
|
|
|
|
|
{ count: 1 },
|
|
|
|
|
'Submit token/JWT button exists with only a single JWT '
|
|
|
|
|
);
|
|
|
|
|
assert
|
|
|
|
|
.dom('[data-test-select-jwt]')
|
|
|
|
|
.doesNotExist('No JWT selector shown with an empty token/secret');
|
|
|
|
|
await Tokens.secret(
|
|
|
|
|
'aaaaaaaaaaaaaaaaaaaaaaaaa.aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa.whateverlol'
|
|
|
|
|
);
|
|
|
|
|
assert
|
|
|
|
|
.dom('[data-test-select-jwt]')
|
|
|
|
|
.exists({ count: 1 }, 'JWT selector shown with multiple JWT methods');
|
|
|
|
|
|
|
|
|
|
assert.equal(
|
|
|
|
|
currentURL(),
|
|
|
|
|
'/settings/tokens?jwtAuthMethod=JWT-Global',
|
|
|
|
|
'Default JWT method is selected'
|
|
|
|
|
);
|
|
|
|
|
await clickTrigger('[data-test-select-jwt]');
|
|
|
|
|
assert.dom('.dropdown-options').exists('Dropdown options are shown');
|
|
|
|
|
|
|
|
|
|
await selectChoose('[data-test-select-jwt]', 'JWT-Regional');
|
|
|
|
|
console.log(currentURL());
|
|
|
|
|
assert.equal(
|
|
|
|
|
currentURL(),
|
|
|
|
|
'/settings/tokens?jwtAuthMethod=JWT-Regional',
|
|
|
|
|
'Selected JWT method is shown'
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|
2021-12-28 14:45:20 +00:00
|
|
|
|
test('when the ott exchange fails an error is shown', async function (assert) {
|
2021-04-01 18:21:30 +00:00
|
|
|
|
await visit('/?ott=fake');
|
|
|
|
|
|
|
|
|
|
assert.ok(Layout.error.isPresent);
|
|
|
|
|
assert.equal(Layout.error.title, 'Token Exchange Error');
|
2021-12-28 16:08:12 +00:00
|
|
|
|
assert.equal(
|
|
|
|
|
Layout.error.message,
|
|
|
|
|
'Failed to exchange the one-time token.'
|
|
|
|
|
);
|
2021-04-01 18:21:30 +00:00
|
|
|
|
});
|
|
|
|
|
|
2022-12-15 18:11:28 +00:00
|
|
|
|
test('Tokens are shown on the policies index page', async function (assert) {
|
|
|
|
|
allScenarios.policiesTestCluster(server);
|
|
|
|
|
// Create an expired token
|
|
|
|
|
server.create('token', {
|
|
|
|
|
name: 'Expired Token',
|
|
|
|
|
id: 'just-expired',
|
|
|
|
|
policyIds: [server.db.policies[0].name],
|
|
|
|
|
expirationTime: new Date(new Date().getTime() - 10 * 60 * 1000), // 10 minutes ago
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
window.localStorage.nomadTokenSecret = server.db.tokens[0].secretId;
|
|
|
|
|
await visit('/policies');
|
|
|
|
|
assert.dom('[data-test-policy-token-count]').exists();
|
|
|
|
|
const expectedFirstPolicyTokens = server.db.tokens.filter((token) => {
|
|
|
|
|
return token.policyIds.includes(server.db.policies[0].name);
|
|
|
|
|
});
|
|
|
|
|
assert
|
|
|
|
|
.dom('[data-test-policy-total-tokens]')
|
|
|
|
|
.hasText(expectedFirstPolicyTokens.length.toString());
|
|
|
|
|
assert.dom('[data-test-policy-expired-tokens]').hasText('(1 expired)');
|
|
|
|
|
window.localStorage.nomadTokenSecret = null;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('Tokens are shown on a policy page', async function (assert) {
|
|
|
|
|
allScenarios.policiesTestCluster(server);
|
|
|
|
|
// Create an expired token
|
|
|
|
|
server.create('token', {
|
|
|
|
|
name: 'Expired Token',
|
|
|
|
|
id: 'just-expired',
|
|
|
|
|
policyIds: [server.db.policies[0].name],
|
|
|
|
|
expirationTime: new Date(new Date().getTime() - 10 * 60 * 1000), // 10 minutes ago
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
window.localStorage.nomadTokenSecret = server.db.tokens[0].secretId;
|
|
|
|
|
await visit('/policies');
|
|
|
|
|
|
|
|
|
|
await click('[data-test-policy-row]:first-child');
|
|
|
|
|
assert.equal(currentURL(), `/policies/${server.db.policies[0].name}`);
|
|
|
|
|
|
|
|
|
|
const expectedFirstPolicyTokens = server.db.tokens.filter((token) => {
|
|
|
|
|
return token.policyIds.includes(server.db.policies[0].name);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
assert
|
|
|
|
|
.dom('[data-test-policy-token-row]')
|
|
|
|
|
.exists(
|
|
|
|
|
{ count: expectedFirstPolicyTokens.length },
|
|
|
|
|
'Expected number of tokens are shown'
|
|
|
|
|
);
|
|
|
|
|
assert.dom('[data-test-token-expiration-time]').hasText('10 minutes ago');
|
|
|
|
|
|
|
|
|
|
window.localStorage.nomadTokenSecret = null;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('Tokens Deletion', async function (assert) {
|
|
|
|
|
allScenarios.policiesTestCluster(server);
|
|
|
|
|
// Create an expired token
|
|
|
|
|
server.create('token', {
|
|
|
|
|
name: 'Doomed Token',
|
|
|
|
|
id: 'enjoying-my-day-here',
|
|
|
|
|
policyIds: [server.db.policies[0].name],
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
window.localStorage.nomadTokenSecret = server.db.tokens[0].secretId;
|
|
|
|
|
await visit('/policies');
|
|
|
|
|
|
|
|
|
|
await click('[data-test-policy-row]:first-child');
|
|
|
|
|
assert.equal(currentURL(), `/policies/${server.db.policies[0].name}`);
|
|
|
|
|
|
|
|
|
|
assert
|
|
|
|
|
.dom('[data-test-policy-token-row]')
|
|
|
|
|
.exists({ count: 3 }, 'Expected number of tokens are shown');
|
|
|
|
|
|
|
|
|
|
const doomedTokenRow = [...findAll('[data-test-policy-token-row]')].find(
|
|
|
|
|
(a) => a.textContent.includes('Doomed Token')
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
assert.dom(doomedTokenRow).exists();
|
|
|
|
|
|
|
|
|
|
await click(doomedTokenRow.querySelector('button'));
|
|
|
|
|
assert
|
|
|
|
|
.dom(doomedTokenRow.querySelector('[data-test-confirm-button]'))
|
|
|
|
|
.exists();
|
|
|
|
|
await click(doomedTokenRow.querySelector('[data-test-confirm-button]'));
|
|
|
|
|
assert.dom('.flash-message.alert-success').exists();
|
|
|
|
|
assert
|
|
|
|
|
.dom('[data-test-policy-token-row]')
|
|
|
|
|
.exists({ count: 2 }, 'One fewer token after deletion');
|
|
|
|
|
await percySnapshot(assert);
|
|
|
|
|
window.localStorage.nomadTokenSecret = null;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
test('Test Token Creation', async function (assert) {
|
|
|
|
|
allScenarios.policiesTestCluster(server);
|
|
|
|
|
|
|
|
|
|
window.localStorage.nomadTokenSecret = server.db.tokens[0].secretId;
|
|
|
|
|
await visit('/policies');
|
|
|
|
|
|
|
|
|
|
await click('[data-test-policy-row]:first-child');
|
|
|
|
|
assert.equal(currentURL(), `/policies/${server.db.policies[0].name}`);
|
|
|
|
|
|
|
|
|
|
assert
|
|
|
|
|
.dom('[data-test-policy-token-row]')
|
|
|
|
|
.exists({ count: 2 }, 'Expected number of tokens are shown');
|
|
|
|
|
|
|
|
|
|
await click('[data-test-create-test-token]');
|
|
|
|
|
assert.dom('.flash-message.alert-success').exists();
|
|
|
|
|
assert
|
|
|
|
|
.dom('[data-test-policy-token-row]')
|
|
|
|
|
.exists({ count: 3 }, 'One more token after test token creation');
|
|
|
|
|
assert
|
|
|
|
|
.dom('[data-test-policy-token-row]:last-child [data-test-token-name]')
|
|
|
|
|
.hasText(`Example Token for ${server.db.policies[0].name}`);
|
|
|
|
|
await percySnapshot(assert);
|
|
|
|
|
window.localStorage.nomadTokenSecret = null;
|
|
|
|
|
});
|
|
|
|
|
|
2019-03-13 00:04:16 +00:00
|
|
|
|
function getHeader({ requestHeaders }, name) {
|
|
|
|
|
// Headers are case-insensitive, but object property look up is not
|
|
|
|
|
return (
|
2019-03-13 01:09:19 +00:00
|
|
|
|
requestHeaders[name] ||
|
|
|
|
|
requestHeaders[name.toLowerCase()] ||
|
|
|
|
|
requestHeaders[name.toUpperCase()]
|
2019-03-13 00:04:16 +00:00
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
});
|