319 lines
13 KiB
JavaScript
319 lines
13 KiB
JavaScript
/**
|
|
* Copyright (c) HashiCorp, Inc.
|
|
* SPDX-License-Identifier: MPL-2.0
|
|
*/
|
|
|
|
import { module, test } from 'qunit';
|
|
import { visit, click, fillIn, findAll, currentRouteName } from '@ember/test-helpers';
|
|
import { setupApplicationTest } from 'ember-qunit';
|
|
import { setupMirage } from 'ember-cli-mirage/test-support';
|
|
import ENV from 'vault/config/environment';
|
|
import authPage from 'vault/tests/pages/auth';
|
|
import logout from 'vault/tests/pages/logout';
|
|
import { create } from 'ember-cli-page-object';
|
|
import { clickTrigger, selectChoose } from 'ember-power-select/test-support/helpers';
|
|
import ss from 'vault/tests/pages/components/search-select';
|
|
import fm from 'vault/tests/pages/components/flash-message';
|
|
import {
|
|
OIDC_BASE_URL, // -> '/vault/access/oidc'
|
|
SELECTORS,
|
|
clearRecord,
|
|
overrideCapabilities,
|
|
overrideMirageResponse,
|
|
CLIENT_LIST_RESPONSE,
|
|
CLIENT_DATA_RESPONSE,
|
|
} from 'vault/tests/helpers/oidc-config';
|
|
const searchSelect = create(ss);
|
|
const flashMessage = create(fm);
|
|
|
|
// in congruency with backend verbiage 'applications' are referred to as 'clients'
|
|
// throughout the codebase and the term 'applications' only appears in the UI
|
|
|
|
module('Acceptance | oidc-config clients and keys', function (hooks) {
|
|
setupApplicationTest(hooks);
|
|
setupMirage(hooks);
|
|
|
|
hooks.before(function () {
|
|
ENV['ember-cli-mirage'].handler = 'oidcConfig';
|
|
});
|
|
|
|
hooks.beforeEach(async function () {
|
|
this.store = await this.owner.lookup('service:store');
|
|
return authPage.login();
|
|
});
|
|
|
|
hooks.afterEach(function () {
|
|
return logout.visit();
|
|
});
|
|
|
|
hooks.after(function () {
|
|
ENV['ember-cli-mirage'].handler = null;
|
|
});
|
|
|
|
test('it creates a key, signs a client and edits key access to only that client', async function (assert) {
|
|
assert.expect(21);
|
|
|
|
//* start with clean test state
|
|
await clearRecord(this.store, 'oidc/client', 'client-with-test-key');
|
|
await clearRecord(this.store, 'oidc/client', 'client-with-default-key');
|
|
await clearRecord(this.store, 'oidc/key', 'test-key');
|
|
|
|
// create client with default key
|
|
await visit(OIDC_BASE_URL + '/clients/create');
|
|
await fillIn('[data-test-input="name"]', 'client-with-default-key');
|
|
await click(SELECTORS.clientSaveButton);
|
|
|
|
// check reroutes from oidc index to clients index when client exists
|
|
await visit(OIDC_BASE_URL);
|
|
assert.strictEqual(
|
|
currentRouteName(),
|
|
'vault.cluster.access.oidc.clients.index',
|
|
'redirects to clients index route when clients exist'
|
|
);
|
|
assert.dom('[data-test-tab="clients"]').hasClass('active', 'clients tab is active');
|
|
assert
|
|
.dom('[data-test-oidc-client-linked-block]')
|
|
.hasTextContaining('client-with-default-key', 'displays linked block for client');
|
|
|
|
// navigate to keys
|
|
await click('[data-test-tab="keys"]');
|
|
assert.dom('[data-test-tab="keys"]').hasClass('active', 'keys tab is active');
|
|
assert.strictEqual(currentRouteName(), 'vault.cluster.access.oidc.keys.index');
|
|
assert
|
|
.dom('[data-test-oidc-key-linked-block="default"]')
|
|
.hasText('default', 'index page lists default key');
|
|
|
|
// navigate to default key details from pop-up menu
|
|
await click('[data-test-popup-menu-trigger]');
|
|
await click('[data-test-oidc-key-menu-link="details"]');
|
|
assert.dom(SELECTORS.keyDeleteButton).isDisabled('delete button is disabled for default key');
|
|
await click(SELECTORS.keyEditButton);
|
|
assert.strictEqual(
|
|
currentRouteName(),
|
|
'vault.cluster.access.oidc.keys.key.edit',
|
|
'navigates to edit from key details'
|
|
);
|
|
await click(SELECTORS.keyCancelButton);
|
|
assert.strictEqual(
|
|
currentRouteName(),
|
|
'vault.cluster.access.oidc.keys.key.details',
|
|
'key edit form navigates back to details on cancel'
|
|
);
|
|
await click(SELECTORS.keyClientsTab);
|
|
assert
|
|
.dom('[data-test-oidc-client-linked-block="client-with-default-key"]')
|
|
.exists('lists correct app with default');
|
|
|
|
// create a new key
|
|
await click('[data-test-breadcrumb-link="oidc-keys"]');
|
|
assert.strictEqual(
|
|
currentRouteName(),
|
|
'vault.cluster.access.oidc.keys.index',
|
|
'keys breadcrumb navigates back to list view'
|
|
);
|
|
await click('[data-test-oidc-key-create]');
|
|
assert.strictEqual(
|
|
currentRouteName(),
|
|
'vault.cluster.access.oidc.keys.create',
|
|
'navigates to key create form'
|
|
);
|
|
await fillIn('[data-test-input="name"]', 'test-key');
|
|
await click(SELECTORS.keySaveButton);
|
|
assert.strictEqual(
|
|
currentRouteName(),
|
|
'vault.cluster.access.oidc.keys.key.details',
|
|
'navigates to key details after save'
|
|
);
|
|
|
|
// create client with test-key
|
|
await visit(OIDC_BASE_URL + '/clients');
|
|
await click('[data-test-oidc-client-create]');
|
|
await fillIn('[data-test-input="name"]', 'client-with-test-key');
|
|
await click('[data-test-toggle-group="More options"]');
|
|
await click('[data-test-component="search-select"] [data-test-icon="trash"]');
|
|
await clickTrigger('#key');
|
|
await selectChoose('[data-test-component="search-select"]#key', 'test-key');
|
|
await click(SELECTORS.clientSaveButton);
|
|
|
|
// edit key and limit applications
|
|
await visit(OIDC_BASE_URL + '/keys');
|
|
await click('[data-test-oidc-key-linked-block="test-key"] [data-test-popup-menu-trigger]');
|
|
await click('[data-test-oidc-key-menu-link="edit"]');
|
|
assert.strictEqual(
|
|
currentRouteName(),
|
|
'vault.cluster.access.oidc.keys.key.edit',
|
|
'key linked block popup menu navigates to edit'
|
|
);
|
|
await click('[data-test-oidc-radio="limited"]');
|
|
await clickTrigger();
|
|
assert.strictEqual(searchSelect.options.length, 1, 'dropdown has only application that uses this key');
|
|
assert
|
|
.dom('.ember-power-select-option')
|
|
.hasTextContaining('client-with-test-key', 'dropdown renders correct application');
|
|
await searchSelect.options.objectAt(0).click();
|
|
await click(SELECTORS.keySaveButton);
|
|
assert.strictEqual(
|
|
flashMessage.latestMessage,
|
|
'Successfully updated the key test-key.',
|
|
'renders success flash upon key updating'
|
|
);
|
|
assert.strictEqual(
|
|
currentRouteName(),
|
|
'vault.cluster.access.oidc.keys.key.details',
|
|
'navigates back to details on update'
|
|
);
|
|
await click(SELECTORS.keyClientsTab);
|
|
assert
|
|
.dom('[data-test-oidc-client-linked-block="client-with-test-key"]')
|
|
.exists('lists client-with-test-key');
|
|
assert.strictEqual(findAll('[data-test-oidc-client-linked-block]').length, 1, 'it lists only one client');
|
|
|
|
// edit back to allow all
|
|
await click(SELECTORS.keyDetailsTab);
|
|
await click(SELECTORS.keyEditButton);
|
|
await click('[data-test-oidc-radio="allow-all"]');
|
|
await click(SELECTORS.keySaveButton);
|
|
await click(SELECTORS.keyClientsTab);
|
|
assert.notEqual(
|
|
findAll('[data-test-oidc-client-linked-block]').length,
|
|
1,
|
|
'more than one client appears in key applications tab'
|
|
);
|
|
|
|
//* clean up test state
|
|
await clearRecord(this.store, 'oidc/client', 'client-with-test-key');
|
|
await clearRecord(this.store, 'oidc/client', 'client-with-default-key');
|
|
await clearRecord(this.store, 'oidc/key', 'test-key');
|
|
});
|
|
|
|
test('it creates, rotates and deletes a key', async function (assert) {
|
|
assert.expect(10);
|
|
// mock client list so OIDC url does not redirect to landing page
|
|
this.server.get('/identity/oidc/client', () => overrideMirageResponse(null, CLIENT_LIST_RESPONSE));
|
|
this.server.post('/identity/oidc/key/test-key/rotate', (schema, req) => {
|
|
const json = JSON.parse(req.requestBody);
|
|
assert.strictEqual(json.verification_ttl, 86400, 'request made with correct args to accurate endpoint');
|
|
});
|
|
|
|
//* clear out test state
|
|
await clearRecord(this.store, 'oidc/key', 'test-key');
|
|
|
|
// create a new key
|
|
await visit(OIDC_BASE_URL + '/keys/create');
|
|
await fillIn('[data-test-input="name"]', 'test-key');
|
|
// toggle ttls to false, testing it sets correct default duration
|
|
await click('[data-test-input="rotationPeriod"]');
|
|
await click('[data-test-input="verificationTtl"]');
|
|
assert
|
|
.dom('[data-test-oidc-radio="limited"] input')
|
|
.isDisabled('limiting access radio button is disabled on create');
|
|
assert
|
|
.dom('[data-test-oidc-radio="limited"]')
|
|
.hasClass('is-disabled', 'limited radio button label has disabled class');
|
|
await click(SELECTORS.keySaveButton);
|
|
assert.strictEqual(
|
|
flashMessage.latestMessage,
|
|
'Successfully created the key test-key.',
|
|
'renders success flash upon key creation'
|
|
);
|
|
|
|
// assert default values in details view are correct
|
|
assert.dom('[data-test-value-div="Algorithm"]').hasText('RS256', 'defaults to RS526 algorithm');
|
|
assert
|
|
.dom('[data-test-value-div="Rotation period"]')
|
|
.hasText('1 day', 'when toggled off rotation period defaults to 1 day');
|
|
assert
|
|
.dom('[data-test-value-div="Verification TTL"]')
|
|
.hasText('1 day', 'when toggled off verification ttl defaults to 1 day');
|
|
|
|
// rotate key
|
|
await click(SELECTORS.keyDetailsTab);
|
|
await click(SELECTORS.keyRotateButton);
|
|
await click(SELECTORS.confirmActionButton);
|
|
assert.strictEqual(
|
|
flashMessage.latestMessage,
|
|
'Success: test-key connection was rotated.',
|
|
'renders success flash upon key rotation'
|
|
);
|
|
// delete
|
|
await click(SELECTORS.keyDeleteButton);
|
|
await click(SELECTORS.confirmActionButton);
|
|
assert.strictEqual(
|
|
flashMessage.latestMessage,
|
|
'Key deleted successfully',
|
|
'success flash message renders after deleting key'
|
|
);
|
|
assert.strictEqual(
|
|
currentRouteName(),
|
|
'vault.cluster.access.oidc.keys.index',
|
|
'navigates back to list view after delete'
|
|
);
|
|
});
|
|
|
|
test('it renders client details and providers', async function (assert) {
|
|
assert.expect(8);
|
|
this.server.get('/identity/oidc/client', () => overrideMirageResponse(null, CLIENT_LIST_RESPONSE));
|
|
this.server.get('/identity/oidc/client/test-app', () =>
|
|
overrideMirageResponse(null, CLIENT_DATA_RESPONSE)
|
|
);
|
|
await visit(OIDC_BASE_URL);
|
|
await click('[data-test-oidc-client-linked-block]');
|
|
assert.dom('[data-test-oidc-client-header]').hasText('test-app', 'renders application name as title');
|
|
assert.dom(SELECTORS.clientDetailsTab).hasClass('active', 'details tab is active');
|
|
assert.dom(SELECTORS.clientDeleteButton).exists('toolbar renders delete option');
|
|
assert.dom(SELECTORS.clientEditButton).exists('toolbar renders edit button');
|
|
assert.strictEqual(findAll('[data-test-component="info-table-row"]').length, 9, 'renders all info rows');
|
|
|
|
await click(SELECTORS.clientProvidersTab);
|
|
assert.strictEqual(
|
|
currentRouteName(),
|
|
'vault.cluster.access.oidc.clients.client.providers',
|
|
'navigates to client providers route'
|
|
);
|
|
assert.dom(SELECTORS.clientProvidersTab).hasClass('active', 'providers tab is active');
|
|
assert.dom('[data-test-oidc-provider-linked-block="default"]').exists('lists default provider');
|
|
});
|
|
|
|
test('it hides delete and edit client when no permission', async function (assert) {
|
|
assert.expect(5);
|
|
this.server.get('/identity/oidc/client', () => overrideMirageResponse(null, CLIENT_LIST_RESPONSE));
|
|
this.server.get('/identity/oidc/client/test-app', () =>
|
|
overrideMirageResponse(null, CLIENT_DATA_RESPONSE)
|
|
);
|
|
this.server.post('/sys/capabilities-self', () =>
|
|
overrideCapabilities(OIDC_BASE_URL + '/client/test-app', ['read'])
|
|
);
|
|
|
|
await visit(OIDC_BASE_URL);
|
|
await click('[data-test-oidc-client-linked-block]');
|
|
assert.dom('[data-test-oidc-client-header]').hasText('test-app', 'renders application name as title');
|
|
assert.dom(SELECTORS.clientDetailsTab).hasClass('active', 'details tab is active');
|
|
assert.dom(SELECTORS.clientDeleteButton).doesNotExist('delete option is hidden');
|
|
assert.dom(SELECTORS.clientEditButton).doesNotExist('edit button is hidden');
|
|
assert.strictEqual(findAll('[data-test-component="info-table-row"]').length, 9, 'renders all info rows');
|
|
});
|
|
|
|
test('it hides delete and edit key when no permission', async function (assert) {
|
|
assert.expect(4);
|
|
this.server.get('/identity/oidc/keys', () => overrideMirageResponse(null, { keys: ['test-key'] }));
|
|
this.server.get('/identity/oidc/key/test-key', () =>
|
|
overrideMirageResponse(null, {
|
|
algorithm: 'RS256',
|
|
allowed_client_ids: ['*'],
|
|
rotation_period: 86400,
|
|
verification_ttl: 86400,
|
|
})
|
|
);
|
|
this.server.post('/sys/capabilities-self', () =>
|
|
overrideCapabilities(OIDC_BASE_URL + '/key/test-key', ['read'])
|
|
);
|
|
|
|
await visit(OIDC_BASE_URL + '/keys');
|
|
await click('[data-test-oidc-key-linked-block]');
|
|
assert.dom(SELECTORS.keyDetailsTab).hasClass('active', 'details tab is active');
|
|
assert.dom(SELECTORS.keyDeleteButton).doesNotExist('delete option is hidden');
|
|
assert.dom(SELECTORS.keyEditButton).doesNotExist('edit button is hidden');
|
|
assert.strictEqual(findAll('[data-test-component="info-table-row"]').length, 4, 'renders all info rows');
|
|
});
|
|
});
|