/** * Copyright (c) HashiCorp, Inc. * SPDX-License-Identifier: MPL-2.0 */ import { module, test } from 'qunit'; import { visit, currentURL, 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 } 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, ASSIGNMENT_LIST_RESPONSE, ASSIGNMENT_DATA_RESPONSE, } from 'vault/tests/helpers/oidc-config'; const searchSelect = create(ss); const flashMessage = create(fm); module('Acceptance | oidc-config clients and assignments', 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 renders only allow_all when no assignments are configured', async function (assert) { assert.expect(3); //* clear out test state await clearRecord(this.store, 'oidc/assignment', 'test-assignment'); await visit(OIDC_BASE_URL + '/assignments'); assert.strictEqual(currentURL(), '/vault/access/oidc/assignments'); assert.dom('[data-test-tab="assignments"]').hasClass('active', 'assignments tab is active'); assert .dom('[data-test-oidc-assignment-linked-block="allow_all"]') .hasClass('is-disabled', 'renders default allow all assignment and is disabled.'); }); test('it renders empty state when no clients are configured', async function (assert) { assert.expect(5); this.server.get('/identity/oidc/client', () => overrideMirageResponse(404)); await visit(OIDC_BASE_URL); assert.strictEqual(currentURL(), '/vault/access/oidc'); assert.dom('h1.title.is-3').hasText('OIDC Provider'); assert.dom(SELECTORS.oidcHeader).hasText( `Configure Vault to act as an OIDC identity provider, and offer Vault’s various authentication methods and source of identity to any client applications. Learn more Create your first app`, 'renders call to action header when no clients are configured' ); assert.dom('[data-test-oidc-landing]').exists('landing page renders when no clients are configured'); assert .dom(SELECTORS.oidcLandingImg) .hasAttribute('src', '/ui/images/oidc-landing.png', 'image renders image when no clients configured'); }); test('it creates an assignment inline, creates a client, updates client to limit access, deletes client', async function (assert) { assert.expect(22); //* clear out test state await clearRecord(this.store, 'oidc/client', 'test-app'); await clearRecord(this.store, 'oidc/client', 'my-webapp'); // created by oidc-provider-test await clearRecord(this.store, 'oidc/assignment', 'assignment-inline'); // create a client with allow all access await visit(OIDC_BASE_URL + '/clients/create'); assert.strictEqual( currentRouteName(), 'vault.cluster.access.oidc.clients.create', 'navigates to create form' ); await fillIn('[data-test-input="name"]', 'test-app'); await click('[data-test-toggle-group="More options"]'); // toggle ttls to false, testing it sets correct default duration await click('[data-test-input="idTokenTtl"]'); await click('[data-test-input="accessTokenTtl"]'); await click(SELECTORS.clientSaveButton); assert.strictEqual( flashMessage.latestMessage, 'Successfully created the application test-app.', 'renders success flash upon client creation' ); assert.strictEqual( currentRouteName(), 'vault.cluster.access.oidc.clients.client.details', 'navigates to client details view after save' ); // assert default values in details view are correct assert.dom('[data-test-value-div="Assignment"]').hasText('allow_all', 'client allows all assignments'); assert.dom('[data-test-value-div="Type"]').hasText('confidential', 'type defaults to confidential'); assert .dom('[data-test-value-div="Key"] a') .hasText('default', 'client uses default key and renders a link'); assert .dom('[data-test-value-div="Client ID"] [data-test-copy-button]') .exists('client ID exists and has copy button'); assert .dom('[data-test-value-div="Client Secret"] [data-test-copy-button]') .exists('client secret exists and has copy button'); assert .dom('[data-test-value-div="ID Token TTL"]') .hasText('1 day', 'ID token ttl toggled off sets default of 24h'); assert .dom('[data-test-value-div="Access Token TTL"]') .hasText('1 day', 'access token ttl toggled off sets default of 24h'); // edit client await click(SELECTORS.clientDetailsTab); await click(SELECTORS.clientEditButton); assert.strictEqual( currentRouteName(), 'vault.cluster.access.oidc.clients.client.edit', 'navigates to edit page from details' ); await fillIn('[data-test-input="redirectUris"] [data-test-string-list-input="0"]', 'some-url.com'); // limit access & create new assignment inline await click('[data-test-oidc-radio="limited"]'); await clickTrigger(); await fillIn('.ember-power-select-search input', 'assignment-inline'); await searchSelect.options.objectAt(0).click(); await click('[data-test-search-select="entities"] .ember-basic-dropdown-trigger'); await searchSelect.options.objectAt(0).click(); await click('[data-test-search-select="groups"] .ember-basic-dropdown-trigger'); await searchSelect.options.objectAt(0).click(); await click(SELECTORS.assignmentSaveButton); assert.strictEqual( flashMessage.latestMessage, 'Successfully created the assignment assignment-inline.', 'renders success flash upon assignment creating' ); await click(SELECTORS.clientSaveButton); assert.strictEqual( flashMessage.latestMessage, 'Successfully updated the application test-app.', 'renders success flash upon client updating' ); assert.strictEqual( currentRouteName(), 'vault.cluster.access.oidc.clients.client.details', 'navigates back to details on update' ); assert.dom('[data-test-value-div="Redirect URI"]').hasText('some-url.com', 'shows updated attribute'); assert .dom('[data-test-value-div="Assignment"]') .hasText('assignment-inline', 'updated to limited assignment'); // edit back to allow_all await click(SELECTORS.clientEditButton); assert.dom(SELECTORS.clientSaveButton).hasText('Update', 'form button renders correct text'); await click('[data-test-oidc-radio="allow-all"]'); await click(SELECTORS.clientSaveButton); assert .dom('[data-test-value-div="Assignment"]') .hasText('allow_all', 'client updated to allow all assignments'); // create another client await visit(OIDC_BASE_URL + '/clients/create'); await fillIn('[data-test-input="name"]', 'app-to-delete'); await click(SELECTORS.clientSaveButton); // immediately delete client, test transition await click(SELECTORS.clientDeleteButton); await click(SELECTORS.confirmActionButton); assert.strictEqual( flashMessage.latestMessage, 'Application deleted successfully', 'renders success flash upon deleting client' ); assert.strictEqual( currentRouteName(), 'vault.cluster.access.oidc.clients.index', 'navigates back to list view after delete' ); // delete last client await click('[data-test-oidc-client-linked-block]'); assert.strictEqual(currentRouteName(), 'vault.cluster.access.oidc.clients.client.details'); await click(SELECTORS.clientDeleteButton); await click(SELECTORS.confirmActionButton); assert.strictEqual( currentRouteName(), 'vault.cluster.access.oidc.index', 'redirects to call to action if only existing client is deleted' ); //* clean up test state await clearRecord(this.store, 'oidc/assignment', 'assignment-inline'); }); test('it creates, updates, and deletes an assignment', async function (assert) { assert.expect(14); await visit(OIDC_BASE_URL + '/assignments'); //* ensure clean test state await clearRecord(this.store, 'oidc/assignment', 'test-assignment'); // create a new assignment await click(SELECTORS.assignmentCreateButton); assert.strictEqual( currentRouteName(), 'vault.cluster.access.oidc.assignments.create', 'navigates to create form' ); assert.dom('[data-test-oidc-assignment-title]').hasText('Create Assignment', 'Form title renders'); await fillIn('[data-test-input="name"]', 'test-assignment'); await click('[data-test-component="search-select"]#entities .ember-basic-dropdown-trigger'); await click('.ember-power-select-option'); await click(SELECTORS.assignmentSaveButton); assert.strictEqual( flashMessage.latestMessage, 'Successfully created the assignment test-assignment.', 'renders success flash upon creating the assignment' ); assert.strictEqual( currentRouteName(), 'vault.cluster.access.oidc.assignments.assignment.details', 'navigates to the assignments detail view after save' ); // assert default values in assignment details view are correct assert.dom('[data-test-value-div="Name"]').hasText('test-assignment'); assert.dom('[data-test-value-div="Entities"]').hasText('test-entity', 'shows the entity name.'); // edit assignment await click(SELECTORS.assignmentEditButton); assert.strictEqual( currentRouteName(), 'vault.cluster.access.oidc.assignments.assignment.edit', 'navigates to the assignment edit page from details' ); assert.dom('[data-test-oidc-assignment-title]').hasText('Edit Assignment', 'Form title renders'); await click('[data-test-component="search-select"]#groups .ember-basic-dropdown-trigger'); await click('.ember-power-select-option'); assert.dom('[data-test-oidc-assignment-save]').hasText('Update'); await click(SELECTORS.assignmentSaveButton); assert.strictEqual( flashMessage.latestMessage, 'Successfully updated the assignment test-assignment.', 'renders success flash upon updating the assignment' ); assert.dom('[data-test-value-div="Entities"]').hasText('test-entity', 'it still shows the entity name.'); assert.dom('[data-test-value-div="Groups"]').hasText('test-group', 'shows updated group name id.'); // delete the assignment await click(SELECTORS.assignmentDeleteButton); await click(SELECTORS.confirmActionButton); assert.strictEqual( flashMessage.latestMessage, 'Assignment deleted successfully', 'renders success flash upon deleting assignment' ); assert.strictEqual( currentRouteName(), 'vault.cluster.access.oidc.assignments.index', 'navigates back to assignment list view after delete' ); }); test('it navigates to and from an assignment from the list view', async function (assert) { assert.expect(6); this.server.get('/identity/oidc/assignment', () => overrideMirageResponse(null, ASSIGNMENT_LIST_RESPONSE) ); this.server.get('/identity/oidc/assignment/test-assignment', () => overrideMirageResponse(null, ASSIGNMENT_DATA_RESPONSE) ); await visit(OIDC_BASE_URL + '/assignments'); assert .dom('[data-test-oidc-assignment-linked-block="test-assignment"]') .exists('displays linked block for test-assignment'); await click(SELECTORS.assignmentCreateButton); assert.strictEqual( currentRouteName(), 'vault.cluster.access.oidc.assignments.create', 'assignments index toolbar navigates to create form' ); await click(SELECTORS.assignmentCancelButton); assert.strictEqual( currentRouteName(), 'vault.cluster.access.oidc.assignments.index', 'create form navigates back to assignment index on cancel' ); await click('[data-test-popup-menu-trigger]'); await click('[data-test-oidc-assignment-menu-link="edit"]'); assert.strictEqual( currentRouteName(), 'vault.cluster.access.oidc.assignments.assignment.edit', 'linked block popup menu navigates to edit' ); await click(SELECTORS.assignmentCancelButton); assert.strictEqual( currentRouteName(), 'vault.cluster.access.oidc.assignments.assignment.details', 'edit form navigates back to assignment details on cancel' ); // navigate to details from index page await visit('/vault/access/oidc/assignments'); await click('[data-test-popup-menu-trigger]'); await click('[data-test-oidc-assignment-menu-link="details"]'); assert.strictEqual( currentRouteName(), 'vault.cluster.access.oidc.assignments.assignment.details', 'popup menu navigates to assignment details' ); }); test('it hides assignment delete and edit when no permission', async function (assert) { assert.expect(5); this.server.get('/identity/oidc/assignment', () => overrideMirageResponse(null, ASSIGNMENT_LIST_RESPONSE) ); this.server.get('/identity/oidc/assignment/test-assignment', () => overrideMirageResponse(null, ASSIGNMENT_DATA_RESPONSE) ); this.server.post('/sys/capabilities-self', () => overrideCapabilities(OIDC_BASE_URL + '/assignment/test-assignment', ['read']) ); await visit(OIDC_BASE_URL + '/assignments'); await click('[data-test-oidc-assignment-linked-block="test-assignment"]'); assert .dom('[data-test-oidc-assignment-title]') .hasText('test-assignment', 'renders assignment name as title'); assert.dom(SELECTORS.assignmentDetailsTab).hasClass('active', 'details tab is active'); assert.dom(SELECTORS.assignmentDeleteButton).doesNotExist('delete option is hidden'); assert.dom(SELECTORS.assignmentEditButton).doesNotExist('edit button is hidden'); assert.strictEqual( findAll('[data-test-component="info-table-row"]').length, 3, 'renders all assignment info rows' ); }); });