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.