From 1c0b2df8f14ebf22047b78a91af8ddcf466dddc9 Mon Sep 17 00:00:00 2001 From: Chelsea Shaw <82459713+hashishaw@users.noreply.github.com> Date: Mon, 21 Nov 2022 14:09:04 -0600 Subject: [PATCH] UI: PKI Read Role Details (#17985) --- ui/.eslintignore | 3 + ui/app/adapters/pki/role.js | 4 + ui/app/models/pki/role.js | 18 +++- .../components/pki-role-details-page.hbs | 98 +++++++++++++++++++ .../addon/components/pki-role-details-page.ts | 35 +++++++ ui/lib/pki/addon/routes/roles/index.js | 3 +- ui/lib/pki/addon/routes/roles/role/details.js | 12 ++- .../addon/templates/roles/role/details.hbs | 2 +- ui/lib/pki/package.json | 30 +++++- ui/tests/helpers/pki/page-role-details.js | 9 ++ .../components/pki/page-role-detail-test.js | 42 ++++++++ ui/tsconfig.json | 11 +++ 12 files changed, 258 insertions(+), 9 deletions(-) create mode 100644 ui/lib/pki/addon/components/pki-role-details-page.hbs create mode 100644 ui/lib/pki/addon/components/pki-role-details-page.ts create mode 100644 ui/tests/helpers/pki/page-role-details.js create mode 100644 ui/tests/integration/components/pki/page-role-detail-test.js diff --git a/ui/.eslintignore b/ui/.eslintignore index a292799de..c352da9c7 100644 --- a/ui/.eslintignore +++ b/ui/.eslintignore @@ -25,3 +25,6 @@ /package-lock.json.ember-try /yarn.lock.ember-try /tests/helpers/vault-keys.js + +# typescript declaration files +*.d.ts diff --git a/ui/app/adapters/pki/role.js b/ui/app/adapters/pki/role.js index 178faf74b..addf1708a 100644 --- a/ui/app/adapters/pki/role.js +++ b/ui/app/adapters/pki/role.js @@ -51,4 +51,8 @@ export default class PkiRoleAdapter extends ApplicationAdapter { query(store, type, query) { return this.fetchByQuery(store, query); } + + queryRecord(store, type, query) { + return this.fetchByQuery(store, query); + } } diff --git a/ui/app/models/pki/role.js b/ui/app/models/pki/role.js index 909fac8fd..9cfcfdf0a 100644 --- a/ui/app/models/pki/role.js +++ b/ui/app/models/pki/role.js @@ -29,6 +29,7 @@ export default class PkiRoleModel extends Model { @attr('string', { label: 'Issuer reference', + detailsLabel: 'Issuer', defaultValue: 'default', subText: `Specifies the issuer that will be used to create certificates with this role. To find this, run read -field=default pki_int/config/issuers in the console. By default, we will use the mounts default issuer.`, }) @@ -36,6 +37,7 @@ export default class PkiRoleModel extends Model { @attr({ label: 'Not valid after', + detailsLabel: 'Issued certificates expire after', subText: 'The time after which this certificate will no longer be valid. This can be a TTL (a range of time from now) or a specific date. If no TTL is set, the system uses "default" or the value of max_ttl, whichever is shorter. Alternatively, you can set the not_after date below.', editType: 'yield', @@ -44,6 +46,7 @@ export default class PkiRoleModel extends Model { @attr({ label: 'Backdate validity', + detailsLabel: 'Issued certificate backdating', helperTextEnabled: 'Also called the not_before_duration property. Allows certificates to be valid for a certain time period before now. This is useful to correct clock misalignment on various systems when setting up your CA.', editType: 'ttl', @@ -57,6 +60,7 @@ export default class PkiRoleModel extends Model { helperTextDisabled: 'The maximum Time-To-Live of certificates generated by this role. If not set, the system max lease TTL will be used.', editType: 'ttl', + defaultShown: 'System default', }) maxTtl; @@ -71,6 +75,7 @@ export default class PkiRoleModel extends Model { @attr('boolean', { label: 'Do not store certificates in storage backend', + detailsLabel: 'Store in storage backend', // template reverses value subText: 'This can improve performance when issuing large numbers of certificates. However, certificates issued in this way cannot be enumerated or revoked.', editType: 'boolean', @@ -80,6 +85,7 @@ export default class PkiRoleModel extends Model { @attr('boolean', { label: 'Basic constraints valid for non-CA', + detailsLabel: 'Add basic constraints', subText: 'Mark Basic Constraints valid when issuing non-CA certificates.', editType: 'boolean', }) @@ -231,16 +237,20 @@ export default class PkiRoleModel extends Model { defaultValue() { return ['DigitalSignature', 'KeyAgreement', 'KeyEncipherment']; }, + defaultShown: 'None', }) keyUsage; @attr('array', { - defaultValue() { - return []; - }, + defaultShown: 'None', }) extKeyUsage; + @attr('array', { + defaultShown: 'None', + }) + extKeyUsageOids; + @attr({ hideFormSection: true }) organization; @attr({ hideFormSection: true }) country; @attr({ hideFormSection: true }) locality; @@ -332,7 +342,7 @@ export default class PkiRoleModel extends Model { 'Key parameters': ['keyType', 'keyBits', 'signatureBits'], }, { - 'Key usage': ['keyUsage', 'extKeyUsage'], + 'Key usage': ['keyUsage', 'extKeyUsage', 'extKeyUsageOids'], }, { 'Policy identifiers': ['policyIdentifiers'] }, { diff --git a/ui/lib/pki/addon/components/pki-role-details-page.hbs b/ui/lib/pki/addon/components/pki-role-details-page.hbs new file mode 100644 index 000000000..853b923be --- /dev/null +++ b/ui/lib/pki/addon/components/pki-role-details-page.hbs @@ -0,0 +1,98 @@ + + + {{! TODO: This should be replaced with HDS::Breadcrumbs }} + + + +

+ + PKI Role + {{@role.name}} +

+
+
+ + + + Delete + +
+ Generate Certificate + Sign Certificate + Edit +
+
+{{#each @role.fieldGroups as |fg|}} + {{#each-in fg as |group fields|}} + {{#if (not-eq group "default")}} +

{{group}}

+ {{/if}} + {{#each fields as |attr|}} + {{#let (get @role attr.name) as |val|}} + {{#if (eq attr.name "issuerRef")}} + + {{val}} + + {{else if (includes attr.name this.arrayAttrs)}} + + {{#if (gt val.length 0)}} + {{#each val as |key|}} + {{key}}, + {{/each}} + {{else}} + None + {{/if}} + + {{else if (eq attr.name "noStore")}} + + {{else}} + + {{/if}} + {{/let}} + {{/each}} + {{/each-in}} +{{/each}} \ No newline at end of file diff --git a/ui/lib/pki/addon/components/pki-role-details-page.ts b/ui/lib/pki/addon/components/pki-role-details-page.ts new file mode 100644 index 000000000..45279ee6d --- /dev/null +++ b/ui/lib/pki/addon/components/pki-role-details-page.ts @@ -0,0 +1,35 @@ +import { action } from '@ember/object'; +import Component from '@glimmer/component'; + +// interface Attribute { +// name: string; +// options?: { +// label?: string; +// }; +// } + +// TODO: pull this in from route model once it's TS +interface Args { + role: { + backend: string; + id: string; + }; +} + +export default class DetailsPage extends Component { + get breadcrumbs() { + return [ + { label: this.args.role.backend || 'pki', path: 'overview' }, + { label: 'roles', path: 'roles.index' }, + { label: this.args.role.id }, + ]; + } + + get arrayAttrs() { + return ['keyUsage', 'extKeyUsage', 'extKeyUsageOids']; + } + + @action deleteRole() { + // TODO: delete role + } +} diff --git a/ui/lib/pki/addon/routes/roles/index.js b/ui/lib/pki/addon/routes/roles/index.js index ab4686ab6..a0b0df80a 100644 --- a/ui/lib/pki/addon/routes/roles/index.js +++ b/ui/lib/pki/addon/routes/roles/index.js @@ -7,7 +7,8 @@ export default class PkiRolesIndexRoute extends Route { @service pathHelp; beforeModel() { - // Must call this promise before the model hook otherwise it doesn't add OpenApi to record. + // Must call this promise before the model hook otherwise + // the model doesn't hydrate from OpenAPI correctly. return this.pathHelp.getNewModel('pki/role', 'pki'); } diff --git a/ui/lib/pki/addon/routes/roles/role/details.js b/ui/lib/pki/addon/routes/roles/role/details.js index eabe10072..8a9414d94 100644 --- a/ui/lib/pki/addon/routes/roles/role/details.js +++ b/ui/lib/pki/addon/routes/roles/role/details.js @@ -1,3 +1,11 @@ -import Route from '@ember/routing/route'; +import PkiRolesIndexRoute from '../index'; -export default class PkiRoleDetailsRoute extends Route {} +export default class RolesRoleDetailsRoute extends PkiRolesIndexRoute { + model() { + const { role } = this.paramsFor('roles/role'); + return this.store.queryRecord('pki/role', { + backend: this.secretMountPath.currentPath, + id: role, + }); + } +} diff --git a/ui/lib/pki/addon/templates/roles/role/details.hbs b/ui/lib/pki/addon/templates/roles/role/details.hbs index 42c9f3efa..8e79cd382 100644 --- a/ui/lib/pki/addon/templates/roles/role/details.hbs +++ b/ui/lib/pki/addon/templates/roles/role/details.hbs @@ -1 +1 @@ -roles.role.details \ No newline at end of file + \ No newline at end of file diff --git a/ui/lib/pki/package.json b/ui/lib/pki/package.json index df042c6b4..5f86b4715 100644 --- a/ui/lib/pki/package.json +++ b/ui/lib/pki/package.json @@ -7,7 +7,35 @@ "dependencies": { "ember-cli-babel": "*", "ember-cli-htmlbars": "*", - "ember-cli-typescript": "*" + "ember-cli-typescript": "*", + "@types/ember": "latest", + "@types/ember-data": "latest", + "@types/ember-data__adapter": "latest", + "@types/ember-data__model": "latest", + "@types/ember-data__serializer": "latest", + "@types/ember-data__store": "latest", + "@types/ember-qunit": "latest", + "@types/ember-resolver": "latest", + "@types/ember__application": "latest", + "@types/ember__array": "latest", + "@types/ember__component": "latest", + "@types/ember__controller": "latest", + "@types/ember__debug": "latest", + "@types/ember__destroyable": "latest", + "@types/ember__engine": "latest", + "@types/ember__error": "latest", + "@types/ember__object": "latest", + "@types/ember__polyfills": "latest", + "@types/ember__routing": "latest", + "@types/ember__runloop": "latest", + "@types/ember__service": "latest", + "@types/ember__string": "latest", + "@types/ember__template": "latest", + "@types/ember__test": "latest", + "@types/ember__test-helpers": "latest", + "@types/ember__utils": "latest", + "@types/qunit": "latest", + "@types/rsvp": "latest" }, "ember-addon": { "paths": [ diff --git a/ui/tests/helpers/pki/page-role-details.js b/ui/tests/helpers/pki/page-role-details.js new file mode 100644 index 000000000..c6c028183 --- /dev/null +++ b/ui/tests/helpers/pki/page-role-details.js @@ -0,0 +1,9 @@ +export const SELECTORS = { + breadcrumbContainer: '[data-test-breadcrumbs="role-details"]', + breadcrumbs: '[data-test-breadcrumbs="role-details"] li', + title: '[data-test-role-details-title]', + issuerLabel: '[data-test-row-label="Issuer"]', + noStoreValue: '[data-test-value-div="Store in storage backend"]', + keyUsageValue: '[data-test-value-div="Key usage"]', + extKeyUsageValue: '[data-test-value-div="Ext key usage"]', +}; diff --git a/ui/tests/integration/components/pki/page-role-detail-test.js b/ui/tests/integration/components/pki/page-role-detail-test.js new file mode 100644 index 000000000..893fb05b2 --- /dev/null +++ b/ui/tests/integration/components/pki/page-role-detail-test.js @@ -0,0 +1,42 @@ +import { module, test } from 'qunit'; +import { setupRenderingTest } from 'ember-qunit'; +import { render } from '@ember/test-helpers'; +import { hbs } from 'ember-cli-htmlbars'; +import { setupEngine } from 'ember-engines/test-support'; +import { SELECTORS } from 'vault/tests/helpers/pki/page-role-details'; + +module('Integration | Component | pki role details page', function (hooks) { + setupRenderingTest(hooks); + setupEngine(hooks, 'pki'); + + hooks.beforeEach(function () { + this.store = this.owner.lookup('service:store'); + this.model = this.store.createRecord('pki/role', { + name: 'Foobar', + noStore: false, + keyUsage: [], + extKeyUsage: ['bar', 'baz'], + }); + this.model.backend = 'pki'; + }); + + test('it should render the page component', async function (assert) { + assert.expect(7); + await render( + hbs` + + `, + { owner: this.engine } + ); + assert.dom(SELECTORS.breadcrumbContainer).exists({ count: 1 }, 'breadcrumb containers exist'); + assert.dom(SELECTORS.breadcrumbs).exists({ count: 4 }, 'Shows 4 breadcrumbs'); + assert.dom(SELECTORS.title).containsText('PKI Role Foobar', 'Title includes type and name of role'); + // Attribute-specific checks + assert.dom(SELECTORS.issuerLabel).hasText('Issuer', 'Label is'); + assert.dom(SELECTORS.keyUsageValue).hasText('None', 'Key usage shows none when array is empty'); + assert + .dom(SELECTORS.extKeyUsageValue) + .hasText('bar, baz,', 'Key usage shows comma-joined values when array has items'); + assert.dom(SELECTORS.noStoreValue).containsText('Yes', 'noStore shows opposite of what the value is'); + }); +}); diff --git a/ui/tsconfig.json b/ui/tsconfig.json index a24931101..e85cfbda6 100644 --- a/ui/tsconfig.json +++ b/ui/tsconfig.json @@ -1,6 +1,17 @@ { "extends": "@tsconfig/ember/tsconfig.json", "compilerOptions": { + "experimentalDecorators": true, + "allowJs": true, + "strict": true, + "noUncheckedIndexedAccess": true, + "noFallthroughCasesInSwitch": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "noPropertyAccessFromIndexSignature": true, + "noEmitOnError": true, + "skipLibCheck": true, // The combination of `baseUrl` with `paths` allows Ember's classic package // layout, which is not resolvable with the Node resolution algorithm, to // work with TypeScript.