open-vault/ui/tests/acceptance/oidc-config/clients-keys-test.js

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');
});
});