From 504334f8bb69a53bb17130bcdd20312512148d3e Mon Sep 17 00:00:00 2001 From: Vault Automation Date: Tue, 17 Feb 2026 22:28:47 -0500 Subject: [PATCH] UI: Add policy flyout to PKI (#12335) (#12373) * make router-lookup helper * add policyPaths arg to flyout and update route cache to map * update kv flyouts and test coverage * round out test coverage, rename method from get to lookup * alphabetize PATH_MAP * support other change events for inputSearch to allow copy/pasting items * update overview requests and improve ux for limited permissions * request each key permissions * add flyout to pki page header * update changelog Co-authored-by: claire bontempo <68122737+hellobontempo@users.noreply.github.com> --- changelog/_11798.txt | 2 +- ui/app/utils/constants/capabilities.ts | 71 +++++++++-------- ui/lib/core/addon/components/input-search.hbs | 2 +- ui/lib/core/addon/components/input-search.js | 3 +- .../addon/components/page/pki-key-list.hbs | 58 +++++++------- .../addon/components/page/pki-overview.hbs | 76 +++++++++++++------ .../pki/addon/components/pki-page-header.hbs | 1 + .../pki/addon/components/pki-page-header.ts | 32 ++++++-- ui/lib/pki/addon/routes/keys/index.js | 31 ++++++-- ui/lib/pki/addon/routes/overview.js | 42 ++++++++-- ui/lib/pki/addon/templates/keys/index.hbs | 3 +- ui/lib/pki/addon/templates/overview.hbs | 2 + ui/tests/acceptance/pki/pki-overview-test.js | 3 +- .../components/pki/page/pki-key-list-test.js | 32 ++++++-- .../components/pki/page/pki-overview-test.js | 56 ++++++++++++-- 15 files changed, 289 insertions(+), 125 deletions(-) diff --git a/changelog/_11798.txt b/changelog/_11798.txt index 93a22bbe39..98ad728407 100644 --- a/changelog/_11798.txt +++ b/changelog/_11798.txt @@ -1,3 +1,3 @@ ```release-note:feature -**UI Policy Generator (Enterprise)**: Adds policy generator flyout to KV V2 secrets engine prepopulated with relevant API requests for each page. +**UI Policy Generator (Enterprise)**: Adds policy generator flyout to KV V2 and PKI secrets engines prepopulated with relevant API requests for each page. ``` \ No newline at end of file diff --git a/ui/app/utils/constants/capabilities.ts b/ui/app/utils/constants/capabilities.ts index d17d38d834..01a8aed799 100644 --- a/ui/app/utils/constants/capabilities.ts +++ b/ui/app/utils/constants/capabilities.ts @@ -16,51 +16,56 @@ export const SUDO_PATHS = [ export const SUDO_PATH_PREFIXES = ['sys/leases/revoke-prefix', 'sys/leases/revoke-force']; export const PATH_MAP = { - customLogin: apiPath`sys/config/ui/login/default-auth/${'id'}`, - customMessages: apiPath`sys/config/ui/custom-messages/${'id'}`, - syncActivate: apiPath`sys/activation-flags/secrets-sync/activate`, - syncDestination: apiPath`sys/sync/destinations/${'type'}/${'name'}`, - syncSetAssociation: apiPath`sys/sync/destinations/${'type'}/${'name'}/associations/set`, - syncRemoveAssociation: apiPath`sys/sync/destinations/${'type'}/${'name'}/associations/remove`, - kvConfig: apiPath`${'path'}/config`, - kvMetadata: apiPath`${'backend'}/metadata/${'path'}`, authMethodConfig: apiPath`auth/${'path'}/config`, authMethodConfigAws: apiPath`auth/${'path'}/config/client`, authMethodDelete: apiPath`sys/auth/${'path'}`, - pkiRevoke: apiPath`${'backend'}/revoke`, + clientsActivityExport: apiPath`${'namespace'}/sys/internal/counters/activity/export`, + clientsConfig: apiPath`sys/internal/counters/config`, + customLogin: apiPath`sys/config/ui/login/default-auth/${'id'}`, + customMessages: apiPath`sys/config/ui/custom-messages/${'id'}`, + kmipCredentialsRevoke: apiPath`${'backend'}/scope/${'scope'}/role/${'role'}/credentials/revoke`, + kmipRole: apiPath`${'backend'}/scopes/${'scope'}/roles/${'name'}`, + kmipScope: apiPath`${'backend'}/scopes/${'name'}`, + kubernetesCreds: apiPath`${'backend'}/creds/${'name'}`, + kubernetesRole: apiPath`${'backend'}/role/${'name'}`, + kvConfig: apiPath`${'path'}/config`, + kvMetadata: apiPath`${'backend'}/metadata/${'path'}`, + ldapDynamicRole: apiPath`${'backend'}/role/${'name'}`, + ldapDynamicRoleCreds: apiPath`${'backend'}/creds/${'name'}`, + ldapLibrary: apiPath`${'backend'}/library/${'name'}`, + ldapLibraryCheckIn: apiPath`${'backend'}/library/${'name'}/check-in`, + ldapLibraryCheckOut: apiPath`${'backend'}/library/${'name'}/check-out`, + ldapRotateStaticRole: apiPath`${'backend'}/rotate-role/${'name'}`, + ldapStaticRole: apiPath`${'backend'}/static-role/${'name'}`, + ldapStaticRoleCreds: apiPath`${'backend'}/static-cred/${'name'}`, + pkiCertificates: apiPath`${'backend'}/certificates`, pkiConfigAcme: apiPath`${'backend'}/config/acme`, + pkiConfigAutoTidy: apiPath`${'backend'}/config/auto-tidy`, pkiConfigCluster: apiPath`${'backend'}/config/cluster`, pkiConfigCrl: apiPath`${'backend'}/config/crl`, pkiConfigUrls: apiPath`${'backend'}/config/urls`, - pkiIssuersImportBundle: apiPath`${'backend'}/issuers/import/bundle`, - pkiIssuersGenerateRoot: apiPath`${'backend'}/issuers/generate/root/${'type'}`, - pkiIssuersGenerateIntermediate: apiPath`${'backend'}/issuers/generate/intermediate/${'type'}`, - pkiIssuersCrossSign: apiPath`${'backend'}/issuers/cross-sign`, - pkiIssuer: apiPath`${'backend'}/issuer/${'issuerId'}`, - pkiIssuerSignIntermediate: apiPath`${'backend'}/issuer/${'issuerId'}/sign-intermediate`, - pkiRoot: apiPath`${'backend'}/root`, - pkiRootRotate: apiPath`${'backend'}/root/rotate/${'type'}`, pkiIntermediateCrossSign: apiPath`${'backend'}/intermediate/cross-sign`, + pkiIssue: apiPath`${'backend'}/issue/${'id'}`, + pkiIssuer: apiPath`${'backend'}/issuer/${'issuerId'}`, + pkiIssuersCrossSign: apiPath`${'backend'}/issuers/cross-sign`, + pkiIssuersGenerateIntermediate: apiPath`${'backend'}/issuers/generate/intermediate/${'type'}`, + pkiIssuersGenerateRoot: apiPath`${'backend'}/issuers/generate/root/${'type'}`, + pkiIssuerSignIntermediate: apiPath`${'backend'}/issuer/${'issuerId'}/sign-intermediate`, + pkiIssuersImportBundle: apiPath`${'backend'}/issuers/import/bundle`, pkiKey: apiPath`${'backend'}/key/${'keyId'}`, pkiKeysGenerate: apiPath`${'backend'}/keys/generate`, pkiKeysImport: apiPath`${'backend'}/keys/import`, + pkiRevoke: apiPath`${'backend'}/revoke`, pkiRole: apiPath`${'backend'}/roles/${'id'}`, - pkiIssue: apiPath`${'backend'}/issue/${'id'}`, + pkiRoles: apiPath`${'backend'}/roles`, + pkiRoot: apiPath`${'backend'}/root`, + pkiRootRotate: apiPath`${'backend'}/root/rotate/${'type'}`, pkiSign: apiPath`${'backend'}/sign/${'id'}`, pkiSignVerbatim: apiPath`${'backend'}/sign-verbatim/${'id'}`, - ldapStaticRole: apiPath`${'backend'}/static-role/${'name'}`, - ldapDynamicRole: apiPath`${'backend'}/role/${'name'}`, - ldapRotateStaticRole: apiPath`${'backend'}/rotate-role/${'name'}`, - ldapStaticRoleCreds: apiPath`${'backend'}/static-cred/${'name'}`, - ldapDynamicRoleCreds: apiPath`${'backend'}/creds/${'name'}`, - ldapLibrary: apiPath`${'backend'}/library/${'name'}`, - ldapLibraryCheckOut: apiPath`${'backend'}/library/${'name'}/check-out`, - ldapLibraryCheckIn: apiPath`${'backend'}/library/${'name'}/check-in`, - kubernetesRole: apiPath`${'backend'}/role/${'name'}`, - kubernetesCreds: apiPath`${'backend'}/creds/${'name'}`, - kmipScope: apiPath`${'backend'}/scopes/${'name'}`, - kmipRole: apiPath`${'backend'}/scopes/${'scope'}/roles/${'name'}`, - kmipCredentialsRevoke: apiPath`${'backend'}/scope/${'scope'}/role/${'role'}/credentials/revoke`, - clientsConfig: apiPath`sys/internal/counters/config`, - clientsActivityExport: apiPath`${'namespace'}/sys/internal/counters/activity/export`, + pkiTidy: apiPath`${'backend'}/tidy`, + pkiTidyStatus: apiPath`${'backend'}/tidy/status`, + syncActivate: apiPath`sys/activation-flags/secrets-sync/activate`, + syncDestination: apiPath`sys/sync/destinations/${'type'}/${'name'}`, + syncRemoveAssociation: apiPath`sys/sync/destinations/${'type'}/${'name'}/associations/remove`, + syncSetAssociation: apiPath`sys/sync/destinations/${'type'}/${'name'}/associations/set`, }; diff --git a/ui/lib/core/addon/components/input-search.hbs b/ui/lib/core/addon/components/input-search.hbs index da5e63120b..abf60f2e49 100644 --- a/ui/lib/core/addon/components/input-search.hbs +++ b/ui/lib/core/addon/components/input-search.hbs @@ -8,7 +8,7 @@ @type="text" @id={{@id}} @value={{this.searchInput}} - {{on "keyup" this.inputChanged}} + {{on (or @changeEvent "keyup") this.inputChanged}} placeholder={{@placeholder}} autocomplete="off" data-test-input-search={{@id}} diff --git a/ui/lib/core/addon/components/input-search.js b/ui/lib/core/addon/components/input-search.js index 2c4431ab9a..35a87c6006 100644 --- a/ui/lib/core/addon/components/input-search.js +++ b/ui/lib/core/addon/components/input-search.js @@ -9,13 +9,14 @@ import { tracked } from '@glimmer/tracking'; /** * @module InputSearch - * This component renders an input that fires a callback on "keyup" containing the input's value + * This component renders an input that fires a callback on "keyup" or the passed change event containing the input's value * * @example * * * @param {string} [id] - unique id for the input * @param {string} [initialValue] - initial search value, i.e. a secret path prefix, that pre-fills the input field + * @param {string} [changeEvent="keyup"] - the input change event for which to fire the onChange callback * @param {string} [placeholder] - placeholder text for the input * @param {string} [label] - label for the input * @param {string} [subtext] - displays below the label diff --git a/ui/lib/pki/addon/components/page/pki-key-list.hbs b/ui/lib/pki/addon/components/page/pki-key-list.hbs index 0c65a866c8..4d5109a9a8 100644 --- a/ui/lib/pki/addon/components/page/pki-key-list.hbs +++ b/ui/lib/pki/addon/components/page/pki-key-list.hbs @@ -44,34 +44,36 @@
- {{#if (or @canRead @canEdit)}} - - - {{#if @canRead}} - - Details - - {{/if}} - {{#if @canEdit}} - - Edit - - {{/if}} - - {{/if}} + {{#let (get @keyPermsById pkiKey.key_id) as |perms|}} + {{#if (or perms.canRead perms.canUpdate)}} + + + {{#if perms.canRead}} + + Details + + {{/if}} + {{#if perms.canUpdate}} + + Edit + + {{/if}} + + {{/if}} + {{/let}}
diff --git a/ui/lib/pki/addon/components/page/pki-overview.hbs b/ui/lib/pki/addon/components/page/pki-overview.hbs index 7b1de45e82..428300ea93 100644 --- a/ui/lib/pki/addon/components/page/pki-overview.hbs +++ b/ui/lib/pki/addon/components/page/pki-overview.hbs @@ -19,11 +19,11 @@ <:content> - {{format-number (if (eq @issuers 404) 0 @issuers.length)}} + {{format-number @issuers.length}} - {{#if (not-eq @roles 403)}} + {{#if @canListRoles}} <:content> - {{format-number (if (eq @roles 404) 0 @roles.length)}} + {{format-number @roles.length}} {{/if}} - + <:content>
- + {{#if @canListRoles}} + + {{else}} + + {{/if}} - + <:content>
- + {{#if @canListCertificates}} + + {{else}} + + {{/if}} <:actions> + {{#if @configRoute}} {{else}} diff --git a/ui/lib/pki/addon/components/pki-page-header.ts b/ui/lib/pki/addon/components/pki-page-header.ts index d55f06f5b4..4e6436c7fa 100644 --- a/ui/lib/pki/addon/components/pki-page-header.ts +++ b/ui/lib/pki/addon/components/pki-page-header.ts @@ -8,10 +8,12 @@ import { tracked } from '@glimmer/tracking'; import Component from '@glimmer/component'; import { task } from 'ember-concurrency'; -import type SecretsEngineResource from 'vault/resources/secrets/engine'; -import type RouterService from '@ember/routing/router-service'; -import type FlashMessageService from 'vault/services/flash-messages'; +import type { PATH_MAP } from 'vault/utils/constants/capabilities'; import type ApiService from 'vault/services/api'; +import type CapabilitiesService from 'vault/services/capabilities'; +import type FlashMessageService from 'vault/services/flash-messages'; +import type RouterService from '@ember/routing/router-service'; +import type SecretsEngineResource from 'vault/resources/secrets/engine'; /** * @module PkiPageHeader @@ -26,22 +28,40 @@ interface Args { backend: { id: string }; } +const ROUTE_PATH_MAP = { + 'vault.cluster.secrets.backend.pki.certificates.index': ['pkiCertificates'], + 'vault.cluster.secrets.backend.pki.roles.index': ['pkiRoles'], + 'vault.cluster.secrets.backend.pki.tidy.index': ['pkiTidy', 'pkiTidyStatus', 'pkiConfigAutoTidy'], +} satisfies Record; + export default class PkiPageHeader extends Component { @service('app-router') declare readonly router: RouterService; @service declare readonly api: ApiService; @service declare readonly flashMessages: FlashMessageService; + @service declare readonly capabilities: CapabilitiesService; @tracked engineToDisable = undefined; + get breadcrumbs() { return [ { label: 'Vault', route: 'vault', icon: 'vault', linkExternal: true }, { label: 'Secrets engines', route: 'secrets', linkExternal: true }, - { - label: this.args?.backend?.id, - }, + { label: this.args?.backend?.id }, ]; } + // PKI does not make capability requests for these routes + // so manually pass the relevant paths for each route. + get policyPaths() { + const backend = this.args?.backend?.id; + const { currentRouteName } = this.router; + const paths = ROUTE_PATH_MAP[currentRouteName as keyof typeof ROUTE_PATH_MAP]; + if (paths) { + return this.capabilities.pathsForList(paths, { backend }); + } + return null; + } + @task *disableEngine(engine: SecretsEngineResource) { const { engineType, id, path } = engine; diff --git a/ui/lib/pki/addon/routes/keys/index.js b/ui/lib/pki/addon/routes/keys/index.js index 6dca2961b6..b690ac86bd 100644 --- a/ui/lib/pki/addon/routes/keys/index.js +++ b/ui/lib/pki/addon/routes/keys/index.js @@ -22,21 +22,24 @@ export default class PkiKeysIndexRoute extends Route { }, }; - async fetchCapabilities(keyId) { + async fetchCapabilities(keys) { const { pathFor } = this.capabilities; const backend = this.secretMountPath.currentPath; + const keyPathsById = this.keyPathsById(backend, keys); const pathMap = { import: pathFor('pkiKeysImport', { backend }), generate: pathFor('pkiKeysGenerate', { backend }), - key: pathFor('pkiKey', { backend, keyId }), + ...keyPathsById, }; - const perms = await this.capabilities.fetch(Object.values(pathMap)); + const apiPaths = Object.values(pathMap); + const perms = await this.capabilities.fetch(apiPaths, { + routeForCache: 'vault.cluster.secrets.backend.pki.keys', + }); return { canImportKeys: perms[pathMap.import].canUpdate, canGenerateKeys: perms[pathMap.generate].canUpdate, - canRead: perms[pathMap.key].canRead, - canEdit: perms[pathMap.key].canUpdate, + keyPermsById: this.keyCapabilitiesById(keyPathsById, perms), }; } @@ -53,7 +56,7 @@ export default class PkiKeysIndexRoute extends Route { PkiListKeysListEnum.TRUE ); const keys = this.api.keyInfoToArray(response, 'key_id'); - const capabilities = await this.fetchCapabilities(keys[0].key_id); + const capabilities = await this.fetchCapabilities(keys); Object.assign(model, { ...capabilities, keys: paginate(keys, { page }) }); } catch (e) { if (e.response.status === 404) { @@ -82,4 +85,20 @@ export default class PkiKeysIndexRoute extends Route { controller.set('page', undefined); } } + + keyPathsById(backend, keys) { + // Construct API path for each key in the list + return Object.fromEntries( + keys.map(({ key_id: keyId }) => [keyId, this.capabilities.pathFor('pkiKey', { backend, keyId })]) + ); + } + + keyCapabilitiesById(keyPathsById, perms) { + // Iterate over key ids and return an object with Capabilities as their value + return Object.fromEntries( + Object.entries(keyPathsById) + .filter(([, apiPath]) => apiPath in perms) + .map(([keyId, apiPath]) => [keyId, perms[apiPath]]) + ); + } } diff --git a/ui/lib/pki/addon/routes/overview.js b/ui/lib/pki/addon/routes/overview.js index 2b9e8f1e4d..f0ddef9316 100644 --- a/ui/lib/pki/addon/routes/overview.js +++ b/ui/lib/pki/addon/routes/overview.js @@ -24,9 +24,9 @@ export const getCliMessage = (msg) => { @withConfig() export default class PkiOverviewRoute extends Route { - @service secretMountPath; - @service auth; @service api; + @service capabilities; + @service secretMountPath; async fetchAllCertificates() { try { @@ -36,7 +36,10 @@ export default class PkiOverviewRoute extends Route { ); return keys; } catch (e) { - return e.response.status; + const { status } = await this.api.parseError(e); + // If there was a permissions (403) or some other error + // swallow because this data is for rendering overview cards + return status === 404 ? [] : null; } } @@ -48,7 +51,10 @@ export default class PkiOverviewRoute extends Route { ); return keys; } catch (e) { - return e.response.status; + const { status } = await this.api.parseError(e); + // If there was a permissions (403) or some other error + // swallow because this data is for rendering overview cards + return status === 404 ? [] : null; } } @@ -60,17 +66,39 @@ export default class PkiOverviewRoute extends Route { ); return keys; } catch (e) { - return e.response.status; + const { status } = await this.api.parseError(e); + return status === 404 ? [] : null; } } + async fetchCapabilities() { + const { pathFor } = this.capabilities; + const backend = this.secretMountPath.currentPath; + // the issuers list endpoint is unauthenticated so we do not need to check capabilities for it + const pathMap = { + certificates: pathFor('pkiCertificates', { backend }), + roles: pathFor('pkiRoles', { backend }), + }; + const apiPaths = Object.values(pathMap); + const perms = await this.capabilities.fetch(apiPaths, { + routeForCache: 'vault.cluster.secrets.backend.pki.overview', + }); + return { + canListCertificates: perms[pathMap.certificates].canList, + canListRoles: perms[pathMap.roles].canList, + }; + } + async model() { + const { canListCertificates, canListRoles } = await this.fetchCapabilities(); return hash({ hasConfig: this.pkiMountHasConfig, engine: this.modelFor('application'), - roles: this.fetchAllRoles(), + roles: canListRoles ? this.fetchAllRoles() : null, issuers: this.fetchAllIssuers(), - certificates: this.fetchAllCertificates(), + certificates: canListCertificates ? this.fetchAllCertificates() : null, + canListCertificates, + canListRoles, }); } diff --git a/ui/lib/pki/addon/templates/keys/index.hbs b/ui/lib/pki/addon/templates/keys/index.hbs index d0bbbea5f4..4c91a19601 100644 --- a/ui/lib/pki/addon/templates/keys/index.hbs +++ b/ui/lib/pki/addon/templates/keys/index.hbs @@ -11,7 +11,6 @@ @backend={{this.model.parentModel.id}} @canImportKeys={{this.model.canImportKeys}} @canGenerateKeys={{this.model.canGenerateKeys}} - @canRead={{this.model.canRead}} - @canEdit={{this.model.canEdit}} + @keyPermsById={{this.model.keyPermsById}} @hasConfig={{this.model.hasConfig}} /> \ No newline at end of file diff --git a/ui/lib/pki/addon/templates/overview.hbs b/ui/lib/pki/addon/templates/overview.hbs index 4cd22aeb21..9f85b82009 100644 --- a/ui/lib/pki/addon/templates/overview.hbs +++ b/ui/lib/pki/addon/templates/overview.hbs @@ -13,6 +13,8 @@ @roles={{this.model.roles}} @certificates={{this.model.certificates}} @engine={{this.model.engine}} + @canListCertificates={{this.model.canListCertificates}} + @canListRoles={{this.model.canListRoles}} /> {{else}} diff --git a/ui/tests/acceptance/pki/pki-overview-test.js b/ui/tests/acceptance/pki/pki-overview-test.js index be103b3749..459acfa062 100644 --- a/ui/tests/acceptance/pki/pki-overview-test.js +++ b/ui/tests/acceptance/pki/pki-overview-test.js @@ -80,10 +80,11 @@ module('Acceptance | pki overview', function (hooks) { assert.dom(`${overviewCard.container('Roles')} p`).hasText('1'); }); - test('hides roles card if user does not have permissions', async function (assert) { + test('hides roles and certificates card if user does not have permissions', async function (assert) { await login(this.pkiIssuersList); await visit(`/vault/secrets-engines/${this.mountPath}/pki/overview`); assert.dom(overviewCard.title('Roles')).doesNotExist('Roles card does not exist'); + assert.dom(overviewCard.title('Certificates')).doesNotExist('Certificates card does not exist'); assert.dom(overviewCard.title('Issuers')).hasText('Issuers'); }); diff --git a/ui/tests/integration/components/pki/page/pki-key-list-test.js b/ui/tests/integration/components/pki/page/pki-key-list-test.js index b66a63f401..bfa3e91325 100644 --- a/ui/tests/integration/components/pki/page/pki-key-list-test.js +++ b/ui/tests/integration/components/pki/page/pki-key-list-test.js @@ -34,8 +34,26 @@ module('Integration | Component | pki key list page', function (hooks) { this.keys.meta = STANDARD_META; this.canImportKeys = true; this.canGenerateKeys = true; - this.canRead = true; - this.canEdit = true; + this.keyPermsById = { + '724862ff-6438-bad0-b598-77a6c7f4e934': { + canCreate: true, + canDelete: true, + canList: true, + canPatch: true, + canRead: true, + canSudo: true, + canUpdate: true, + }, + '9fdddf12-9ce3-0268-6b34-dc1553b00175': { + canCreate: true, + canDelete: true, + canList: true, + canPatch: true, + canRead: true, + canSudo: true, + canUpdate: true, + }, + }; this.renderComponent = () => render( @@ -45,8 +63,7 @@ module('Integration | Component | pki key list page', function (hooks) { @mountPoint="vault.cluster.secrets.backend.pki" @canImportKeys={{this.canImportKeys}} @canGenerateKeys={{this.canGenerateKeys}} - @canRead={{this.canRead}} - @canEdit={{this.canEdit}} + @keyPermsById={{this.keyPermsById}} />, `, { owner: this.engine } @@ -92,9 +109,10 @@ module('Integration | Component | pki key list page', function (hooks) { this.canImportKeys = false; this.canGenerateKeys = false; - this.canRead = false; - this.canEdit = false; - + this.keyPermsById = { + '724862ff-6438-bad0-b598-77a6c7f4e934': { canRead: false, canUpdate: false }, + '9fdddf12-9ce3-0268-6b34-dc1553b00175': { canRead: false, canUpdate: false }, + }; await this.renderComponent(); assert.dom(PKI_KEYS.importKey).doesNotExist('renders import action'); diff --git a/ui/tests/integration/components/pki/page/pki-overview-test.js b/ui/tests/integration/components/pki/page/pki-overview-test.js index cc63ea979b..46896fd6b4 100644 --- a/ui/tests/integration/components/pki/page/pki-overview-test.js +++ b/ui/tests/integration/components/pki/page/pki-overview-test.js @@ -24,10 +24,19 @@ module('Integration | Component | Page::PkiOverview', function (hooks) { this.roles = ['role-0', 'role-1', 'role-2']; this.certificates = ['22:2222:22222:2222', '33:3333:33333:3333']; this.engineId = 'pki'; + this.canListCertificates = true; + this.canListRoles = true; this.renderComponent = () => render( - hbs``, + hbs``, { owner: this.engine } ); }); @@ -39,6 +48,14 @@ module('Integration | Component | Page::PkiOverview', function (hooks) { .hasText( 'Issuers View issuers The total number of issuers in this PKI mount. Includes both root and intermediate certificates. 2' ); + + this.issuers = []; + await this.renderComponent(); + assert + .dom(overviewCard.container('Issuers')) + .hasText( + 'Issuers View issuers The total number of issuers in this PKI mount. Includes both root and intermediate certificates. 0' + ); }); test('shows the correct information on roles card', async function (assert) { @@ -48,7 +65,7 @@ module('Integration | Component | Page::PkiOverview', function (hooks) { .hasText( 'Roles View roles The total number of roles in this PKI mount that have been created to generate certificates. 3' ); - this.roles = 404; + this.roles = []; await this.renderComponent(); assert .dom(overviewCard.container('Roles')) @@ -57,17 +74,42 @@ module('Integration | Component | Page::PkiOverview', function (hooks) { ); }); - test('shows the input search fields for View Certificates card', async function (assert) { + test('shows the search select dropdown for View Certificates card', async function (assert) { + await this.renderComponent(); + assert.dom(overviewCard.title('View certificate')).hasText('View certificate'); + assert + .dom(overviewCard.description('View certificate')) + .hasText('Quickly view a certificate by looking up its serial number.'); + assert.dom(PKI_OVERVIEW.viewCertificateInput).exists(); + assert.dom(GENERAL.inputSearch('certificate')).doesNotExist('it does not render certificate input'); + assert.dom(PKI_OVERVIEW.viewCertificateButton).hasText('View'); + }); + + test('shows the search select dropdown for Issue Certificates card', async function (assert) { await this.renderComponent(); assert.dom(overviewCard.title('Issue certificate')).hasText('Issue certificate'); + assert + .dom(overviewCard.description('Issue certificate')) + .hasText('Begin issuing a certificate by choosing a role.'); assert.dom(PKI_OVERVIEW.issueCertificateInput).exists(); + assert.dom(GENERAL.inputSearch('role')).doesNotExist('it does not render role input'); assert.dom(PKI_OVERVIEW.issueCertificateButton).hasText('Issue'); }); - test('shows the input search fields for Issue Certificates card', async function (assert) { + test('it renders manual search inputs when no list permission', async function (assert) { + this.canListCertificates = false; + this.canListRoles = false; await this.renderComponent(); - assert.dom(overviewCard.title('View certificate')).hasText('View certificate'); - assert.dom(PKI_OVERVIEW.viewCertificateInput).exists(); - assert.dom(PKI_OVERVIEW.viewCertificateButton).hasText('View'); + assert.dom(overviewCard.container('Roles')).doesNotExist(); + assert + .dom(overviewCard.description('View certificate')) + .hasText('Quickly view a certificate by providing its serial number.'); + assert + .dom(overviewCard.description('Issue certificate')) + .hasText('Begin issuing a certificate by entering a role.'); + assert.dom(PKI_OVERVIEW.issueCertificateInput).doesNotExist('role search select does not render'); + assert.dom(GENERAL.inputSearch('role')).exists('it renders input instead of search select'); + assert.dom(PKI_OVERVIEW.viewCertificateInput).doesNotExist('certificate search select does not render'); + assert.dom(GENERAL.inputSearch('certificate')).exists('it renders input instead of search selects'); }); });